• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Quest System

Status
Not open for further replies.
Level 11
Joined
Dec 19, 2012
Messages
411
Quest System

Description :
This a an advance quest system that is not relying on default warcraft 3 quest system. It uses game text as main display of quest details. Currently it is still in developing stage.

Main Goals :
The main goals of the system
  • To create quests within a short time
  • Support all the basic functions of a quest
  • Enable user to customize their quest dynamically
  • To support more features which could be dynamically enabled/disabled depend on user needs


Features :
These are the features currently supported by the Quest System. All of these features could be dynamically enable or disable depend on your need
  • Enable creation of custom quest category
  • Enable creation of quest item (usually used with custom content/custom inventory system)
  • Enable dynamically enable/disable a quest depend on how your map event goes
  • Enable dynamically assign different types of events to each of the quest, either receive/return
  • Enable registration event for specific when receiving or returning a quest
  • Enable user to generate randomize quest
  • Enable user to create own custom quest content
  • Enable user to create own custom quest reward
  • Enable user to create own custom quest condition
  • Enable user to define and categorize their quest
  • Enable user to define quest receive/update/return sound
  • Enable user to set a quest as repeatable quest


Requirements :
Quest system requires several requirement which will be listed here :

Main Code :
The main codes, which included Configuration, API and main system code.

Close

Configuration

API

Main System Code




Here is the configuration that the user could configure toward the QuestSystem, more configuration would be added as the system develop.


JASS:
/**************************************************************************************************/
/*****************************************CONFIGURABLE*********************************************/
/**************************************************************************************************/
globals
    /***********************************************************************************************
    Below constants are NON-CONFIGURABLE, it is listed here for user to know what type of quest
    events exist in the system
    ***********************************************************************************************/
    constant integer EVENT_QUEST_SELECT                    = 1
    constant integer EVENT_QUEST_NEAR                    = 2
endglobals

globals
    /***********************************************************************************************
        - EVENT_QUEST_SELECT (receive/return quest via select the unit)
            NOTE : This event requires ability with 2 levels
            
        - EVENT_QUEST_NEAR (receive/return quest via near the unit)
    ***********************************************************************************************/
    
    //Quest default receive and return event
    private constant integer EVENT_QUEST_DEFAULT = EVENT_QUEST_SELECT
endglobals

globals
    //Dummy unit id (selectable unit)
    private constant integer DUMMY_ID                    = 'h000'

    //Dummy unit id for PICK_UP quest type, as replacement of creating item directly
    private constant integer QUEST_ITEM_ID                = 'h002'

    //Dummy hero id for fast viewing quest info
    private constant integer QUEST_INFO_ID                 = 'H001'

    //Maximum number of quests that allowed to be taken by player (regarless if enabled QE QuestCateogory or not)
    private constant integer MAX_QUEST_PER_PLAYER        = 7

    //Receive effect path for quest
    private constant string  EFFECT_PATH_RECEIVE        = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"

    //Return effect path for quest
    private constant string  EFFECT_PATH_RETURN            = "Objects\\RandomObject\\RandomObject.mdl"
    
    //Effect path for talk content
    private constant string  EFFECT_PATH_TALK            = "Objects\\RandomObject\\RandomObject.mdl"

    //Effect path for goto content
    private constant string  EFFECT_PATH_GOTO            = "Objects\\RandomObject\\RandomObject.mdl"

    //The initial scale for QUEST_ITEM_ID unit
    private constant real     QUEST_ITEM_SCALE            = 1.00

    //Maximum range for the player to near the NPC before he could be able to talk to NPC and accept/return the quest(s)
    private constant real     MAX_SELECT_RANGE            = 200.

    //Minimum range for the player to near the NPC before he could automatically accept/reeturn the quest(s)
    private constant real     NEAR_RANGE                    = 200.

    //Every NEAR_TIMER_TIMEOUT, a check will be performed to see if player near and NPC
    //High value may lead to overhead timer processes
    //Low value may lead to "delay" accept/return
    private constant real     NEAR_TIMER_TIMEOUT            = 0.2

    //Minimum range for the player unit to be able to "pick up" the item(actually its a unit)
    //High value - may lead to unusual/unexpected/unwanted situation like unit too far from the item and picked it up
    //It is recommended not to change the value unless you know what you're doing
    //recomended value : 150 (default pick up range defined in gameplay constant)
    private constant real     PICK_UP_RANGE                = 150.

    //Coorperate with PICK_UP_RANGE
    //High value -  may lead to unusual/unexpected/unwanted situation like unit is within pick range but not pick up immediately
    //Low value - improve accuraccy for "picking up the item", but may lead to overhead timer processes
    //recomended value : 0.1
    private constant real    MIN_TIME_OUT                = 0.1

    //neutral player which doesn't use in your map
    private constant player NEUTRAL                        = Player(15)

    /*
    color list for enhancing your visual when viewing the quest info, modify the color to suit your own needs if needed

    A text picture for layout for easier understand which text will get which type of color :

    QUEST_NAME_STRING            quest name
    -----------QUEST_BASIC DESRIPCTION HERE-----------------
    -----------QUEST_BASIC DESRIPCTION HERE-----------------
    QUEST_REQUIREMENT_STRING
    - QUEST_BASIC CONTAIN GOES HERE
    - QUEST_BASIC CONTAIN GOES HERE

    Example output (based on default) :

    Quest Name : Your quest
    You quest description locates here

    Quest Requirement :
    - Kill 2 unit type (0/2)
    - Kill 2 unit type 2 (0/2)

    So, here is the list that describe which text will get which color :
    quest name - white color (user may specific color by adding color code)
    QUEST_NAME_STRING, QUEST_REQUIREMENT_STRING - QUEST_COLOR_GENERAL
    QUEST_BASIC DESRIPCTION HERE - QUEST_COLOR_DESCRIPTION
    QUEST_BASIC CONTAIN GOES HERE - 2 type of colors : For incomplete quest requirement - QUEST_COLOR_INCOMPLETE
                                                       For coplete quest requirement - QUEST_COLOR_COMPLETE
    */

    constant string QUEST_COLOR_GENERAL                    = "|cff4da6ff" //moderate blue (77, 166, 255)
    constant string QUEST_COLOR_DESCRIPTION                = "|cff48d1cc" //medium turquoise (72, 209, 204)
    constant string QUEST_COLOR_INCOMPLETE                = "|cffff6666" //moderate red (255, 102, 102)
    constant string QUEST_COLOR_COMPLETE                = "|cff66ff99" //light green (102, 255, 153)
    constant string QUEST_DEFAULT_COLOR                    = "|cffffffff" //white, default color to display quest title at dialog if
                                                                       //quest title have no color code

    //color code for highlight dialog's hotkey
    constant string DIALOG_HOTKEY_HIGHLIGHT                = "|cffffff00"

    //Message that will display if a player trying to accept more quest with max quest list registered
    private constant string QUEST_LIST_FULL_MESSAGE        = QUEST_COLOR_GENERAL + "Your quest list is fulled."

    //integer array used as hotkey for the dialog, input an ASCII character (or integer if you know what to do)
    integer array DIALOG_HOTKEY
endglobals

//Init function for user to configure
private function InitEx takes nothing returns nothing
    //Configure your own dialog button hotkey here (index starts with 0 and end with MAX_QUEST_PER_PLAYER)
    set DIALOG_HOTKEY[0] = '1'
    set DIALOG_HOTKEY[1] = '2'
    set DIALOG_HOTKEY[2] = '3'
    set DIALOG_HOTKEY[3] = '4'
    set DIALOG_HOTKEY[4] = '5'
    set DIALOG_HOTKEY[5] = '6'
    set DIALOG_HOTKEY[6] = '7'
    set DIALOG_HOTKEY[7] = 512 //ESC hotkey
endfunction

//A function that adjusts text display's position of y, leave it if you unsure what it is
//Currently not in use
private function GetTextDisplayY takes real NoOfLines returns real
    return 0.54 - NoOfLines*0.04
endfunction

//A function that adjusts text display's position of x, leave it if you unsure what it is
private constant function GetTextDisplayX takes nothing returns real
    return 0.25
endfunction


Here is the list of API, these API are basic APIs to construct a basic quest


JASS:
        //used by PICK_UP content
        struct RectSet extends array
            static method create takes nothing returns thistype
        //    -create a new rect set
    
            method addRect takes rect r returns nothing
        //    -add a rect to the rect set
        //    -item will spawn randomly within the area of the rect, each rect will only spawn one item
        //
        //    -limitation : the number of defined rect must be >= (number of item to pick up)
        endstruct
    
        /*******************************************************************************************/
    
        struct Quest extends array
            readonly QuestEvent         event
            readonly QuestCondition     condition
            readonly QuestContent       content
            readonly QuestReward        reward
    
            static method create takes nothing returns thistype
        //    -create a new quest instance
    
            method operator title= takes string questTitle returns nothing
        //    -assign quest title
    
            method operator description= takes string questDescription returns nothing
        //    -assign quest description
    
            method createEvent takes nothing returns nothing
        //  -create new event
        //  -a quest can have multiple events
        //  -syntax for setting event is (assuming (q) is Quest instance) :
        //   q.event.(QuestEvent methods)
    
            method createCondition takes nothing returns nothing
        //  -create new condition
        //  -a quest can have multiple conditions
        //  -syntax for setting condition is (assuming (q) is Quest instance) :
        //   q.condition.(QuestCondition methods)
    
            method createContent takes nothing returns nothing
        //  -create new content
        //  -a quest can have multiple contents
        //  -syntax for setting content is (assuming (q) is Quest instance) :
        //   q.content.(QuestContent methods)
    
            method createReward takes nothing returns nothing
        //  -create new reward
        //  -a quest can have multiple rewards
        //  -syntax for setting reward is (assuming (q) is Quest instance) :
        //   q.reward.(QuestReward methods)
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestEvent extends array
            method operator receiveUnit= takes unit receiveU returns nothing
        //  -assign quest receive unit
        
            method operator returnUnit= takes unit returnU returns nothing
        //  -assign quest return unit
    
            method operator receiveReturnUnit= takes unit rrUnit returns nothing
        //  -assign quest receive and return unit to (rrUnit)
        //  -use this method instead if both receive/return unit are same
        //  -using both above methods works too, but 1 more line needed
    
            method operator abilityId= takes integer aId returns nothing
        //  -assign receive ability id
        //  -ability MUST contains 2 level : level  1 for receving, level 2 for returning
        //  -only used by   _SELECT
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestCondition extends array
            method operator level= takes integer rqLevel returns nothing
        //    -assign lv condition
    
            method operator addQuest= takes Quest qId returns nothing
        //    -assign quest condition (example quest1 needed to be completed before quest2 could be received)
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestContent extends array
            method registerKill takes integer unitType, integer noToKill returns nothing
        //    -assign killing requirement
        //    -request a player to kill a (noToKill) of specific unit type (unitType)
        //
        //    -uId : unit-type id
        //    -noToKill : number to kill
    
            method registerKillObtain takes integer unitType, string itemName, integer noToObtain, real chance returns nothing
        //    -assign kill and obtain item requirement, item is not directly created
        //    -request a player to kill specific unit type (unitType) and obtain (noToObtain) of (itemName) with the drop chance
        //     of (chance)
        //
        //    -uId : unit-type id
        //    -itemName : the item's name player need to obtain
        //    -noToObtain : number of "item" to obtain
        //    -chance : chance to obtain the "item", in probability format ( 0 - 1.00 )
    
            method registerKillObtainItem takes integer unitType, integer iId, integer noToObtain, real chance returns nothing
        //    -assign kill and obtain item requirement, quest item is directly created
        //    -request a player to kill specific unit type (unitType) and pick up (noToObtain) specific item type (iId) with the
        //     drop chance of (chance)
        //
        //    -uId : unit-type id
        //    -iId : item's id
        //    -noToObtain : number of item to obtain
        //    -chance : chance to obtain the item, in probability format ( 0 - 1.00 )
    
            method registerTalk takes unit u, integer aId, string description, string exDescription returns nothing
        //    -assign talk requirement
        //    -request a player to talk to a specific unit (u) by selecting unit (u) and cast (aId)
        //    -uses event : EVENT_QUEST_SELECT
        //
        //    -u : unit to talk
        //    -aId : ability id (so that player can click the ability and complete requirement, only 1 lv is needed)
        //    -description : Description that will be displayed in quest info
        //    -exDescription : Will display at player's screen after player completed talk
    
            method registerTalk2 takes unit u, string description, string exDescription returns nothing
        //    -assign talk requirement
        //    -request a player to talk to a specific unit (u) with the talk effect (effectPath) attached on the unit
        //    -uses event : EVENT_QUEST_NEAR
        //
        //    -u : unit to talk
        //    -description : Description that will be displayed in quest info
        //    -exDescription : Will display at player's screen after player completed talk
        
        
            method registerPickUp takes RectSet rs, integer itemType, integer noToPick, string description returns nothing
        //    -assign pick up requirement
        //    -request a player to pick up a number(noToPick) of specific item (itemType) which will spawn within a set of rect (rs)
        //     (non-repeatable spawn for each rect, max spawn is one)
        //    -Note : noToPick must be >= number of rects defined in rs
        //
        //    -rs : RectSet which defined a set of rect
        //    -itemType : item-type id
        //    -noToPick : number of item to pick
        //    -description : Description that will be displayed in quest info
    
            method registerGoto takes rect r, string desciption, string completeDescription returns nothing
        //    -assign go to requirement
        //    -request a player to go to a specific place (rect) to complete the requirement
        //
        //    -r : rect which player need to enter
        //    -desciption : The requirement description shown in quest info
        //                  may put something like : "Investigate this area and report back to me"
        //    -completeDescription : will display the completeDescription out once player went to rect r
        //                  may put something like : "Completed investigation, report it back"
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestReward extends array
    
            method operator gold= takes integer amount returns nothing
        //    -setting gold reward amount
    
            method operator lumber= takes integer amount returns nothing
        //    -setting lumber reward amount
    
            method operator experience= takes integer amount returns nothing
        //    -setting experience reward amount
    
            method addItem takes integer itemId, integer amount returns nothing
        //  -reward (amount) of item type (itemId)
        endstruct

        /*******************************************************************************************/
    
            function QuestAddUnit takes unit u returns nothing
        //  -add unit (u) into group which enables (u) to accept/return quest
        //  -one player can only add 1 unit to group (not more than 1)
        //  -unit can be non-hero type
        //  -KNOWN ISSUE:
        //      1. Over high level may cause quest init function exceed op-limit
        //      2. (save-load only) Currently doesn't support saved quest state, which means if loaded a completed quest
        //          that is required to unlock next quest, receive effect will not be displayed properly.
    
            function IsQuestReceivedByPlayer takes player p, Quest qId returns boolean
        //  - return true if quest (qId) is received by player (p)
    
            function IsQuestBeenCompleted takes player p, Quest qId returns boolean
        //  - return true if quest (qId) been completed by player (p)
        //  - been completed means once quest been completed for once, this will always
        //    return true


Here is the main system code which all the quest generating, processing are done through here.

JASS:
library QuestSystem initializer Init /*
*====================================Quest System by DD_LegionTN====================================*
*                                                                                               
*    System Description :                                                                       
*        A fully customized quest system that using game text as main display for quests
*                                                                                               
*===================================================================================================*
*
*    Version
*        v1.0.0
*
*===================================================================================================*
*
*    Main Goals :
*        - To create quests within a short time
*        - Support all the basic functions of a quest
*        - Enable user to customize their quest dynamically
*        - To support more features which could be dynamically enabled/disabled depend on user needs
*
*===================================================================================================*
*
*    These are the optional features currently supported by the Quest System. All of these features
*    can be dynamically enabled or disabled depend on your need
*
*    Features :
*       - Enable creation of custom quest category
*       - Enable creation of quest item (usually used with custom content/custom inventory system)
*        - Enable dynamically enable/disable a quest depend on how your map event goes
*        - Enable dynamically assign different types of events to each of the quest, either receive/return
*       - Enable registration event for specific when receiving or returning a quest
*        - Enable user to generate randomize quest
*        - Enable user to create own custom quest content
*        - Enable user to create own custom quest reward
*        - Enable user to create own custom quest condition
*        - Enable user to define and categorize their quest
*       - Enable user to define quest receive/update/return sound
*        - Enable user to set a quest as repeatable quest
*
*===================================================================================================*
*
*    These are the addition content added toward the original base QuestContent, and can also be enabled
*    or disabled depend on your need
*
*    Addition Content :
*        - "GroupKill" (Enable a group of difference unit-id to be register under the same killing requirement)
*        - "CustomContent" (Enable user to create own custom quest content)
*
*===================================================================================================*
*                                                                                           
*    Requirement :                                                                               
*        */requires /*                                                                           
*
*       */Dialog /*
*           - To ease the creation of dialog
*           - Link : https://www.hiveworkshop.com/threads/snippet-dialog-wrapper.240003/
*
*        */MultidimensionalArray /*                                                                               
*            - Allows better safty, better syntax and also to easier to manage data storage
*           - Link : https://www.hiveworkshop.com/threads/snippet-multidimensional-array.289785/
*
*       */TimerUtils /*
*           - To ease the creation and data attachment for timer
*           - Link : http://www.wc3c.net/showthread.php?t=101322
*
*       */UnitDex /*
*           - To enable the usage of array for unit related data storage
*           - Link : https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*
*        */optional QuestExtension /*
*            - Extension of Quest System to enable more features
*              (QuestExtension should have version 1.0.0)
*
*===================================================================================================*
*
*   Import Instructions :
*       1. Copy all the required libraries into your map.
*       2. Copy the main code "QuestSystem" into your map
*       3. (Optional) Copy 'h000' (DUMMY_ID), 'h002' (QUEST_DUMMY_ID) and 'H001' (QUEST_INFO_ID)
*       4. Configure DUMMY_ID, QUEST_DUMMY_ID and QUEST_INFO_ID (rest configuration are optional)
*
*       If you would like to have any add-ons, continue the step, else you're done and may look into
*       Basic Examples (Example 1 to Example 5) for example usage
*
*       5. Copy "QuestExtension"
*       6. Choose the add-ons you would like to add, and copy to your map (Alternative way, copy
*          all of the add-ons to your map, and disable which you don't need)
*
*       You're done, and may look into Basic Examples (which doesn't required add-ons), then to
*       Advance Examples (Depends on what you have for optional add-ons) for example usage.
*
*===================================================================================================*/
//! novjass
    API :
        //used by PICK_UP content
        struct RectSet extends array
            static method create takes nothing returns thistype
        //    -create a new rect set
    
            method addRect takes rect r returns nothing
        //    -add a rect to the rect set
        //    -item will spawn randomly within the area of the rect, each rect will only spawn one item
        //
        //    -limitation : the number of defined rect must be >= (number of item to pick up)
        endstruct
    
        /*******************************************************************************************/
    
        struct Quest extends array
            readonly QuestEvent         event
            readonly QuestCondition     condition
            readonly QuestContent       content
            readonly QuestReward        reward
    
            static method create takes nothing returns thistype
        //    -create a new quest instance
    
            method operator title= takes string questTitle returns nothing
        //    -assign quest title
    
            method operator description= takes string questDescription returns nothing
        //    -assign quest description
    
            method createEvent takes nothing returns nothing
        //  -create new event
        //  -a quest can have multiple events
        //  -syntax for setting event is (assuming (q) is Quest instance) :
        //   q.event.(QuestEvent methods)
    
            method createCondition takes nothing returns nothing
        //  -create new condition
        //  -a quest can have multiple conditions
        //  -syntax for setting condition is (assuming (q) is Quest instance) :
        //   q.condition.(QuestCondition methods)
    
            method createContent takes nothing returns nothing
        //  -create new content
        //  -a quest can have multiple contents
        //  -syntax for setting content is (assuming (q) is Quest instance) :
        //   q.content.(QuestContent methods)
    
            method createReward takes nothing returns nothing
        //  -create new reward
        //  -a quest can have multiple rewards
        //  -syntax for setting reward is (assuming (q) is Quest instance) :
        //   q.reward.(QuestReward methods)
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestEvent extends array
            method operator receiveUnit= takes unit receiveU returns nothing
        //  -assign quest receive unit
        
            method operator returnUnit= takes unit returnU returns nothing
        //  -assign quest return unit
    
            method operator receiveReturnUnit= takes unit rrUnit returns nothing
        //  -assign quest receive and return unit to (rrUnit)
        //  -use this method instead if both receive/return unit are same
        //  -using both above methods works too, but 1 more line needed
    
            method operator abilityId= takes integer aId returns nothing
        //  -assign receive ability id
        //  -ability MUST contains 2 level : level  1 for receving, level 2 for returning
        //  -only used by   _SELECT
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestCondition extends array
            method operator level= takes integer rqLevel returns nothing
        //    -assign lv condition
    
            method operator addQuest= takes Quest qId returns nothing
        //    -assign quest condition (example quest1 needed to be completed before quest2 could be received)
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestContent extends array
            method registerKill takes integer unitType, integer noToKill returns nothing
        //    -assign killing requirement
        //    -request a player to kill a (noToKill) of specific unit type (unitType)
        //
        //    -uId : unit-type id
        //    -noToKill : number to kill
    
            method registerKillObtain takes integer unitType, string itemName, integer noToObtain, real chance returns nothing
        //    -assign kill and obtain item requirement, item is not directly created
        //    -request a player to kill specific unit type (unitType) and obtain (noToObtain) of (itemName) with the drop chance
        //     of (chance)
        //
        //    -uId : unit-type id
        //    -itemName : the item's name player need to obtain
        //    -noToObtain : number of "item" to obtain
        //    -chance : chance to obtain the "item", in probability format ( 0 - 1.00 )
    
            method registerKillObtainItem takes integer unitType, integer iId, integer noToObtain, real chance returns nothing
        //    -assign kill and obtain item requirement, quest item is directly created
        //    -request a player to kill specific unit type (unitType) and pick up (noToObtain) specific item type (iId) with the
        //     drop chance of (chance)
        //
        //    -uId : unit-type id
        //    -iId : item's id
        //    -noToObtain : number of item to obtain
        //    -chance : chance to obtain the item, in probability format ( 0 - 1.00 )
    
            method registerTalk takes unit u, integer aId, string description, string exDescription returns nothing
        //    -assign talk requirement
        //    -request a player to talk to a specific unit (u) by selecting unit (u) and cast (aId)
        //    -uses event : EVENT_QUEST_SELECT
        //
        //    -u : unit to talk
        //    -aId : ability id (so that player can click the ability and complete requirement, only 1 lv is needed)
        //    -description : Description that will be displayed in quest info
        //    -exDescription : Will display at player's screen after player completed talk
    
            method registerTalk2 takes unit u, string description, string exDescription returns nothing
        //    -assign talk requirement
        //    -request a player to talk to a specific unit (u) with the talk effect (effectPath) attached on the unit
        //    -uses event : EVENT_QUEST_NEAR
        //
        //    -u : unit to talk
        //    -description : Description that will be displayed in quest info
        //    -exDescription : Will display at player's screen after player completed talk
        
        
            method registerPickUp takes RectSet rs, integer itemType, integer noToPick, string description returns nothing
        //    -assign pick up requirement
        //    -request a player to pick up a number(noToPick) of specific item (itemType) which will spawn within a set of rect (rs)
        //     (non-repeatable spawn for each rect, max spawn is one)
        //    -Note : noToPick must be >= number of rects defined in rs
        //
        //    -rs : RectSet which defined a set of rect
        //    -itemType : item-type id
        //    -noToPick : number of item to pick
        //    -description : Description that will be displayed in quest info
    
            method registerGoto takes rect r, string desciption, string completeDescription returns nothing
        //    -assign go to requirement
        //    -request a player to go to a specific place (rect) to complete the requirement
        //
        //    -r : rect which player need to enter
        //    -desciption : The requirement description shown in quest info
        //                  may put something like : "Investigate this area and report back to me"
        //    -completeDescription : will display the completeDescription out once player went to rect r
        //                  may put something like : "Completed investigation, report it back"
        endstruct
    
        /*******************************************************************************************/
    
        struct QuestReward extends array
    
            method operator gold= takes integer amount returns nothing
        //    -setting gold reward amount
    
            method operator lumber= takes integer amount returns nothing
        //    -setting lumber reward amount
    
            method operator experience= takes integer amount returns nothing
        //    -setting experience reward amount
    
            method addItem takes integer itemId, integer amount returns nothing
        //  -reward (amount) of item type (itemId)
        endstruct

        /*******************************************************************************************/
    
            function QuestAddUnit takes unit u returns nothing
        //  -add unit (u) into group which enables (u) to accept/return quest
        //  -one player can only add 1 unit to group (not more than 1)
        //  -unit can be non-hero type
        //  -KNOWN ISSUE:
        //      1. Over high level may cause quest init function exceed op-limit
        //      2. (save-load only) Currently doesn't support saved quest state, which means if loaded a completed quest
        //          that is required to unlock next quest, receive effect will not be displayed properly.
    
            function IsQuestReceivedByPlayer takes player p, Quest qId returns boolean
        //  - return true if quest (qId) is received by player (p)
    
            function IsQuestBeenCompleted takes player p, Quest qId returns boolean
        //  - return true if quest (qId) been completed by player (p)
        //  - been completed means once quest been completed for once, this will always
        //    return true
//! endnovjass





/**************************************************************************************************/
/*****************************************CONFIGURABLE*********************************************/
/**************************************************************************************************/
globals
    /***********************************************************************************************
    Below constants are NON-CONFIGURABLE, it is listed here for user to know what type of quest
    events exist in the system
    ***********************************************************************************************/
    constant integer EVENT_QUEST_SELECT                    = 1
    constant integer EVENT_QUEST_NEAR                    = 2
endglobals

globals
    /***********************************************************************************************
        - EVENT_QUEST_SELECT (receive/return quest via select the unit)
            NOTE : This event requires ability with 2 levels
            
        - EVENT_QUEST_NEAR (receive/return quest via near the unit)
    ***********************************************************************************************/
    
    //Quest default receive and return event
    private constant integer EVENT_QUEST_DEFAULT = EVENT_QUEST_SELECT
endglobals

globals
    //Dummy unit id (selectable unit)
    private constant integer DUMMY_ID                    = 'h000'

    //Dummy unit id for PICK_UP quest type, as replacement of creating item directly
    private constant integer QUEST_ITEM_ID                = 'h002'

    //Dummy hero id for fast viewing quest info
    private constant integer QUEST_INFO_ID                 = 'H001'

    //Maximum number of quests that allowed to be taken by player (regarless if enabled QE QuestCateogory or not)
    private constant integer MAX_QUEST_PER_PLAYER        = 7

    //Receive effect path for quest
    private constant string  EFFECT_PATH_RECEIVE        = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"

    //Return effect path for quest
    private constant string  EFFECT_PATH_RETURN            = "Objects\\RandomObject\\RandomObject.mdl"
    
    //Effect path for talk content
    private constant string  EFFECT_PATH_TALK            = "Objects\\RandomObject\\RandomObject.mdl"

    //Effect path for goto content
    private constant string  EFFECT_PATH_GOTO            = "Objects\\RandomObject\\RandomObject.mdl"

    //The initial scale for QUEST_ITEM_ID unit
    private constant real     QUEST_ITEM_SCALE            = 1.00

    //Maximum range for the player to near the NPC before he could be able to talk to NPC and accept/return the quest(s)
    private constant real     MAX_SELECT_RANGE            = 200.

    //Minimum range for the player to near the NPC before he could automatically accept/reeturn the quest(s)
    private constant real     NEAR_RANGE                    = 200.

    //Every NEAR_TIMER_TIMEOUT, a check will be performed to see if player near and NPC
    //High value may lead to overhead timer processes
    //Low value may lead to "delay" accept/return
    private constant real     NEAR_TIMER_TIMEOUT            = 0.2

    //Minimum range for the player unit to be able to "pick up" the item(actually its a unit)
    //High value - may lead to unusual/unexpected/unwanted situation like unit too far from the item and picked it up
    //It is recommended not to change the value unless you know what you're doing
    //recomended value : 150 (default pick up range defined in gameplay constant)
    private constant real     PICK_UP_RANGE                = 150.

    //Coorperate with PICK_UP_RANGE
    //High value -  may lead to unusual/unexpected/unwanted situation like unit is within pick range but not pick up immediately
    //Low value - improve accuraccy for "picking up the item", but may lead to overhead timer processes
    //recomended value : 0.1
    private constant real    MIN_TIME_OUT                = 0.1

    //neutral player which doesn't use in your map
    private constant player NEUTRAL                        = Player(15)

    /*
    color list for enhancing your visual when viewing the quest info, modify the color to suit your own needs if needed

    A text picture for layout for easier understand which text will get which type of color :

    QUEST_NAME_STRING            quest name
    -----------QUEST_BASIC DESRIPCTION HERE-----------------
    -----------QUEST_BASIC DESRIPCTION HERE-----------------
    QUEST_REQUIREMENT_STRING
    - QUEST_BASIC CONTAIN GOES HERE
    - QUEST_BASIC CONTAIN GOES HERE

    Example output (based on default) :

    Quest Name : Your quest
    You quest description locates here

    Quest Requirement :
    - Kill 2 unit type (0/2)
    - Kill 2 unit type 2 (0/2)

    So, here is the list that describe which text will get which color :
    quest name - white color (user may specific color by adding color code)
    QUEST_NAME_STRING, QUEST_REQUIREMENT_STRING - QUEST_COLOR_GENERAL
    QUEST_BASIC DESRIPCTION HERE - QUEST_COLOR_DESCRIPTION
    QUEST_BASIC CONTAIN GOES HERE - 2 type of colors : For incomplete quest requirement - QUEST_COLOR_INCOMPLETE
                                                       For coplete quest requirement - QUEST_COLOR_COMPLETE
    */

    constant string QUEST_COLOR_GENERAL                    = "|cff4da6ff" //moderate blue (77, 166, 255)
    constant string QUEST_COLOR_DESCRIPTION                = "|cff48d1cc" //medium turquoise (72, 209, 204)
    constant string QUEST_COLOR_INCOMPLETE                = "|cffff6666" //moderate red (255, 102, 102)
    constant string QUEST_COLOR_COMPLETE                = "|cff66ff99" //light green (102, 255, 153)
    constant string QUEST_DEFAULT_COLOR                    = "|cffffffff" //white, default color to display quest title at dialog if
                                                                       //quest title have no color code

    //color code for highlight dialog's hotkey
    constant string DIALOG_HOTKEY_HIGHLIGHT                = "|cffffff00"

    //Message that will display if a player trying to accept more quest with max quest list registered
    private constant string QUEST_LIST_FULL_MESSAGE        = QUEST_COLOR_GENERAL + "Your quest list is fulled."

    //integer array used as hotkey for the dialog, input an ASCII character (or integer if you know what to do)
    integer array DIALOG_HOTKEY
endglobals

//Init function for user to configure
private function InitEx takes nothing returns nothing
    //Configure your own dialog button hotkey here (index starts with 0 and end with MAX_QUEST_PER_PLAYER)
    set DIALOG_HOTKEY[0] = '1'
    set DIALOG_HOTKEY[1] = '2'
    set DIALOG_HOTKEY[2] = '3'
    set DIALOG_HOTKEY[3] = '4'
    set DIALOG_HOTKEY[4] = '5'
    set DIALOG_HOTKEY[5] = '6'
    set DIALOG_HOTKEY[6] = '7'
    set DIALOG_HOTKEY[7] = 512 //ESC hotkey
endfunction

//A function that adjusts text display's position of y, leave it if you unsure what it is
//Currently not in use
private function GetTextDisplayY takes real NoOfLines returns real
    return 0.54 - NoOfLines*0.04
endfunction

//A function that adjusts text display's position of x, leave it if you unsure what it is
private constant function GetTextDisplayX takes nothing returns real
    return 0.25
endfunction
/**************************************************************************************************/
/********************************BELOW ARE NON-CONFIGURABLE****************************************/
/**************************************************************************************************/





/**************************************************************************************************/
//globals init private CONSTANT or ARRAY_CONSTANT which use internally by QuestSystem only
globals
    //constant used by setting quest ability level : 2 = quest ready to be completed
    private constant integer COMPLETE                    = 2

    //facing angle of ITEM_DUMMY
    private constant real     QUEST_ITEM_ANGLE            = 278.

    //Player's hero
    unit array HERO

    //Player's quest dummy hero (will auto create after assigned a unit to variable HERO)
    unit array QUEST_INFO

    //used by GetTriggerQuestId()
    private Quest TriggerQuestId                        = 0

    //used byGetTriggerPlayerId()
    private integer TriggerPlayerId                     = 0
endglobals
/**************************************************************************************************/





/**************************************************************************************************/
    private keyword QuestEvent
    private keyword QuestCondition
    private keyword QuestContent
    private keyword QuestReward
    private keyword Internal
    private keyword enterRegTrigger
/**************************************************************************************************/





/**************************************************************************************************/
    //External Globals
    //! runtextmacro optional QEQuestCategory__Globals()
    //! runtextmacro optional QEQuestSound__Globals()

    //External Structs
    //! runtextmacro optional QEGroupKill__UnitIdSet()
    //! runtextmacro optional QEQuestCategory__QuestCategory()
    //! runtextmacro optional QEQuestSound__QuestSound()
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct for defining set of rect for PICK_UP content type
        */
    
        Internal API - RectSet :
    
            struct RectSet extends array
                method setRectList takes nothing returns nothing
                    - get list of stored rect
                    - use rectList to access rect
                    - last array position is set to null for exit looping purpose
                
                method getRegisteredAmount takes nothing returns integer
                    - return registered amount of rect
    */
    struct RectSet extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            //------------------------------------------------------------
        
        
            //---------------------Ordinary Globals-----------------------
            private             integer             listCount
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  rect    array       rectList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    list[a] = b
                
                    a : listCount (starts  from 0)
                    b : store rect
                */
            private             Rect1D              list
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes nothing returns thistype
            local thistype this = instanceCount + 1
            set instanceCount   = this
        
            set .list           = Rect1D.create()
            set .listCount      = 0
        
            return this
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method addRect takes rect rec returns nothing
            set .list[.listCount] = rec
        
            set .listCount = .listCount + 1
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getRegisteredAmount takes nothing returns integer
            return .listCount
        endmethod
    
        method setRectList takes nothing returns nothing
            local integer   i   = 0
        
            loop
                exitwhen i == .listCount
            
                set rectList[i] = .list[i]
                set i           = i + 1
            endloop
        
            set rectList[i]     = null
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to Item, which aims to provide :
                1. Quest item data assign, included quest id, subset and player
        */
    
        Internal API - Item :
    
            private struct Item extends array
                static method assignData takes item it, Quest qId, integer subset, player user returns nothing
                    - assign (qId), (subset) data to (it), which owned by (user)
            
                static method setData takes item it returns nothing
                    - set all the data stored in (it)
                    - enable the access of getting quest id, subset and owner
                
                static method clearData takes item it returns nothing
                    - clear all data related to (it)
                
                static method getQuestId takes nothing returns Quest
                    - return quest id stored in item
            
                static method getSubset takes nothing returns integer
                    - return subset stored in item
            
                static method getOwner takes nothing returns player
                    - return owner of item
    */
    private module ItemInit
        private static method onInit takes nothing returns nothing
            set itemData = Array2D.create()
        endmethod
    endmodule

    private struct Item extends array
        //==========================Globals===============================
            //---------------------Ordinary Globals-----------------------
            private     static  integer             questId
            private     static  integer             subset
            private     static  player              owner
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
            private     static  Array2D             itemData
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        static method assignData takes item it, Quest qId, integer subset, player user returns nothing
            local Array1D a1d = itemData[GetHandleId(it)]
        
            set a1d[1].integer = qId
            set a1d[2].integer = subset
            set a1d[3].player  = user
        endmethod
    
        static method clearData takes item it returns nothing
            call itemData[GetHandleId(it)].flush()
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        static method setData takes item it returns nothing
            local Array1D a1d = itemData[GetHandleId(it)]
        
            set thistype.questId    = a1d[1].integer
            set thistype.subset     = a1d[2].integer
            set thistype.owner      = a1d[3].player
        endmethod
    
        static method getQuestId takes nothing returns Quest
            return thistype.questId
        endmethod
    
        static method getSubset takes nothing returns integer
            return thistype.subset
        endmethod
    
        static method getOwner takes nothing returns player
            return thistype.owner
        endmethod
        //================================================================
        implement ItemInit
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to Timer, which aims to provide :
                1. Running specified code once unit target to another unit
                   after a given period of time
              
                Uses GetUnitId(u) as struct index
        */
    
        Internal API - Timer :
    
            private struct Timer extends array
                method start takes unit targetUnit, real speed, real time, code c returns nothing
                    - start new instance for this unit who ordered to (targetUnit)
                    - run (c) once (time) is reached
                
                method restart takes real time, code c returns nothing
                    - restart the timer with (time) running (c)
                
                method terminate takes nothing returns nothing
                    - terminate the running timer for this unit
                    - remove unit from group as well

                method getTargetUnit takes nothing returns unit
                    - return targeted unit

                method getTargetUnitX takes nothing returns real
                    - return targeted unit x-axis

                method getTargetUnitY takes nothing returns real
                    - return targeted unit y-axis

                method getOrderedUnitMoveSpeed takes nothing returns real
                    - return ordered unit move speed
                
                static method isUnitInGroup takes unit u returns boolean
                    - return true if unit is in ordered group
    */
    private struct Timer extends array
        //==========================Globals===============================
            //---------------------Ordinary Globals-----------------------
            private             timer               tim
        
            private             unit                targetUnit
        
            private             real                targetUnitX
            private             real                targetUnitY
            private             real                orderedUnitSpeed
        
            private     static  group               orderedGroup        = CreateGroup()
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method start takes unit target, real speed, real time, code c returns nothing
            set .targetUnitX                = GetUnitX(target)
            set .targetUnitY                = GetUnitY(target)
            set .orderedUnitSpeed            = speed
            set .tim                        = NewTimerEx(this)

            set .targetUnit                    = target

            call GroupAddUnit(thistype.orderedGroup, GetUnitById(this))
            call TimerStart(.tim, time, false, c)
        endmethod

        method restart takes real time, code c returns nothing
            call TimerStart(.tim, time, false, c)
        endmethod
    
        method terminate takes nothing returns nothing
            call GroupRemoveUnit(thistype.orderedGroup, GetUnitById(this))
            call ReleaseTimer(.tim)
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getTargetUnit takes nothing returns unit
            return .targetUnit
        endmethod

        method getTargetUnitX takes nothing returns real
            return .targetUnitX
        endmethod

        method getTargetUnitY takes nothing returns real
            return .targetUnitY
        endmethod

        method getOrderedUnitMoveSpeed takes nothing returns real
            return .orderedUnitSpeed
        endmethod
    
        static method isUnitInGroup takes unit u returns boolean
            return IsUnitInGroup(u, thistype.orderedGroup)
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to region data storage, which aims to provide :
                1. Store quest id and content subset to specific rect
        */
    
        Internal API - Region :
    
            private struct Region extends array
                static method add takes region reg, Quest qId, integer subset returns nothing
                    - store (qId) and (subset) to region (reg)
                
                static method getQuestId takes region reg returns Quest
                    - return stored quest id from (reg)
        
                static method getSubset takes region reg returns integer
                    - return stored subset from (reg)
    */
    private module RegionInit
        private static method onInit takes nothing returns nothing
            set regTable = Integer2D.create()
        endmethod
    endmodule

    private struct Region extends array
        //==========================Globals===============================
            //----------------------Struct Globals------------------------
            private     static  Integer2D           regTable
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        static method add takes region reg, Quest qId, integer subset returns nothing
            local Integer1D i1d = regTable[GetHandleId(reg)]
        
            set i1d[0]          = qId
            set i1d[1]          = subset
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        static method getQuestId takes region reg returns Quest
            return regTable[GetHandleId(reg)][0]
        endmethod
    
        static method getSubset takes region reg returns integer
            return regTable[GetHandleId(reg)][1]
        endmethod
        //================================================================
        implement RegionInit
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to dialog creation, which aims to provide :
                1. funtionality which required to display quest list
        */
    
        Internal API - DialogEx :
    
            private struct DialogEx extends array
                static method create takes nothing returns thistype
            
                method operator title= takes string text returns nothing
                    - set dialog title name to (text)
            
                method addButton takes string text, integer hotkey, Quest qId returns nothing
                    - add a button to dialog with (text) of (hotkey) which links to (qId)
            
                method display takes player p returns nothing
                    - display dislog to player (p)
            
                method registerClickEvent takes boolexpr b returns nothing
                    - register click event which attached (b)
            
                method clear takes nothing returns nothing
                    - clear dialog
                
                method getClickedButtonQuestId takes nothing returns Quest
                    - return quest id of clicked dialog button
            
                static method getPlayer takes nothing returns player
                    - return play who clicked the dialog button
    */
    private module DialogExInit
        private static method onInit takes nothing returns nothing
            set buttonTable = Integer1D.create()
        endmethod
    endmodule

    private struct DialogEx extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount   = 0
            //------------------------------------------------------------
        
        
            //---------------------Ordinary Globals-----------------------
            private             integer             buttonCount
            private             integer             currentPage
        
            private     static  integer             NEXT_PAGE       = -1
            private     static  integer             PREV_PAGE       = -2
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
            private             Dialog              log
            private     static  Integer1D           buttonTable
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=======================Creation Method==========================
        static method create takes nothing returns thistype
            local thistype this = instanceCount + 1
            set instanceCount   = this
        
            set .log            = Dialog.create()
            set .buttonCount    = 0
            set .currentPage    = 1
        
            return this
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method operator title= takes string text returns nothing
            set .log.title = text
        endmethod
    
        method addButton takes string text, integer hotkey, Quest qId returns nothing
            set buttonTable[GetHandleId(.log.addButton(text, hotkey))] = qId
            set .buttonCount = .buttonCount + 1
        endmethod
    
        method display takes player p returns nothing
            call .log.display(p, true)
        endmethod
    
        method registerClickEvent takes boolexpr b returns nothing
            call .log.registerClickEvent(b)
        endmethod
    
        method clear takes nothing returns nothing
            call .log.clear()
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getClickedButtonQuestId takes nothing returns Quest
            return buttonTable[GetHandleId(Dialog.getClickedButton())]
        endmethod
    
        static method getPlayer takes nothing returns player
            return Dialog.getPlayer()
        endmethod
        //================================================================
        implement DialogExInit
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct aims to support struct EffectUnit, by creating effect path node for each
            quest receiving/returning unit
        */
    
        Internal API - EffectNode :
    
            private struct EffectNode extends array
                static method create takes string path, integer pathType returns thistype
                    - create a new EffectNode
                    - (path) is the effect path
                    - (pathType) is effect path type, either RECEIVE or RETURN
                
                method destroy takes nothing returns nothing
            
                method linkNext takes thistype node returns nothing
                    - link the next instance of this instance to (node)
            
                method linkPrev takes thistype node returns nothing
                    - link the prev instance of this instance to (node)
                
                method getNext takes nothing returns thistype
                    - return the next linked node
            
                method getPrev takes nothing returns thistype
                    - return the prev linked node
                
                method getPath takes nothing returns string
                    - return the effect path
            
                method getType takes nothing returns integer
                    - return the effect path type
    */
    private struct EffectNode extends array
        //==========================Globals===============================
            //----------------------Public Globals------------------------
            readonly    static  integer             RECEIVE         = 0
            readonly    static  integer             RETURN          = 1
            //------------------------------------------------------------
        
        
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
                
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
        

            //---------------------Ordinary Globals-----------------------
            private             string              path
            private             integer             pathType
        
            private             thistype            next
            private             thistype            prev
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes string effectPath, integer pathType returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount       = recycleAmount - 1
                set this                = recycle[recycleAmount]
            else
                set this                = instanceCount + 1
                set instanceCount       = this
            endif
        
            set .next                   = 0
            set .prev                   = 0
            set .path                   = effectPath
            set .pathType               = pathType
        
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set .next                   = 0
            set .prev                   = 0
        
            set recycle[recycleAmount]  = this
            set recycleAmount           = recycleAmount + 1
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
            method linkNext takes thistype node returns nothing
                set .next = node
            endmethod
        
            method linkPrev takes thistype node returns nothing
                set .prev = node
            endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
            method getNext takes nothing returns thistype
                return .next
            endmethod
        
            method getPrev takes nothing returns thistype
                return .prev
            endmethod
        
            method getPath takes nothing returns string
                return .path
            endmethod
        
            method getType takes nothing returns integer
                return .pathType
            endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    private struct EffectUnit extends array
    /*
        /*
            Struct aims to provide independent instance for each unit, having seperated singly linked
            list for each quest receiving/returning unit.
        
        
            Struct uses GetUnitId() as struct index
        
            playerId : Seperate linked list of each unit to each player
        */
    
        Internal API - EffectUnit :
    
            private struct EffectUnit extends array
                method increase takes integer playerId, string path, boolean isReceive returns EffectNode
                    - create a new EffectNode which stores (path)
                    - (isReceive) : true if receive effect path, else return effect path
                    - return newly created effect node
                
                method decrease takes integer playerId, EffectNode en returns EffectNode
                    - destroy a (en) from this unit
                    - return the next available EffectNode
                    - return 0 if no more available EffectNode
                
                method getReceiveHead takes integer playerId return EffectNode
                    - return first of the receive EffectNode assigned to this unit
            
                method getReturnHead takes integer playerId returns EffectNode
                    - return first of the return EffectNode assigned to this unit
            
                method getReceiveHeadPath takes integer playerId returns string
                    - return first of the receive effect path assigned to this unit
            
                method getReturnHeadPath takes integer playerId returns string
                    - return first of the return effect path assigned to this unit
    
    */
        //==========================Globals===============================
            //--------------------Ordinary Globals------------------------
            private     static      integer         RECEIVE_HEAD        = 1
            private     static      integer         RECEIVE_LAST        = 2
            private     static      integer         RETURN_HEAD         = 3
            private     static      integer         RETURN_LAST         = 4
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
            private                 Integer2D       node
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method increase takes integer playerId, string path, boolean isReceive returns EffectNode
            local Integer1D i1d
            local integer i = 0
        
            local EffectNode tail
            local EffectNode en
            local integer headType
            local integer tailType
        
            if .node == 0 then
                set .node = Integer2D.create()
            endif
        
            set i1d = node[playerId]
        
            if isReceive then
                set headType = RECEIVE_HEAD
                set tailType = RECEIVE_LAST
                set en = EffectNode.create(path, EffectNode.RECEIVE)
            else
                set headType = RETURN_HEAD
                set tailType = RETURN_LAST
                set en = EffectNode.create(path, EffectNode.RETURN)
            endif
        
            set tail = i1d[tailType]
            
            if i1d[headType] == 0 then
                set i1d[headType] = en
            else
                call tail.linkNext(en)
            endif
        
            call en.linkPrev(tail)
            set i1d[tailType] = en
        
            return en
        endmethod
    
        method decrease takes integer playerId, EffectNode en returns EffectNode
            local EffectNode nextEn
        
            local EffectNode startEn
            local EffectNode endEn
        
            local EffectNode anotherStartEn
        
            local Integer1D i1d       = node[playerId]
        
            local integer headType
            local integer tailType
        
            local boolean receiveType = en.getType() == EffectNode.RECEIVE
            local boolean returnType  = en.getType() == EffectNode.RETURN
        
            local integer i = 0
        
            if receiveType then
                set headType = RECEIVE_HEAD
                set tailType = RECEIVE_LAST
            
                set anotherStartEn = i1d[RETURN_HEAD]
            elseif returnType then
                set headType = RETURN_HEAD
                set tailType = RETURN_LAST
            
                set anotherStartEn = i1d[RECEIVE_HEAD]
            endif
        
            set startEn = i1d[headType]
            set endEn = i1d[tailType]
        
            //case 1 : if en to be removed is head node
            if en == startEn then
                set nextEn = startEn.getNext()
                set i1d[headType] = nextEn
            
                //addition checking : if it is return type
                if returnType then
                    //if return head node is empty
                    if i1d[headType] == 0 then
                        set nextEn = anotherStartEn
                    endif
                //if return type of head node is not empty
                elseif anotherStartEn != 0 then
                    set nextEn = anotherStartEn
                endif
            //case 2 : if en to be removed is last node
            elseif en == endEn then
                set nextEn = endEn.getPrev()
                set i1d[tailType] = nextEn
                call nextEn.linkNext(0)
            
                if receiveType and anotherStartEn != 0 then
                    set nextEn = anotherStartEn
                endif
            //case 3 : en to be removed is in between list
            else
                call en.getPrev().linkNext(en.getNext())
                call en.getNext().linkPrev(en.getPrev())
                set nextEn = en.getNext()
            endif
        
            call en.destroy()
            return nextEn
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getReceiveHead takes integer playerId returns EffectNode
            return node[playerId][RECEIVE_HEAD]
        endmethod
    
        method getReturnHead takes integer playerId returns EffectNode
            return node[playerId][RETURN_HEAD]
        endmethod
    
        method getReceiveHeadPath takes integer playerId returns string
            return EffectNode(node[playerId][RECEIVE_HEAD]).getPath()
        endmethod
    
        method getReturnHeadPath takes integer playerId returns string
            return EffectNode(node[playerId][RETURN_HEAD]).getPath()
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to effect creation, which aims to provide :
                1. attach/remove effect to specific unit/point
            
            Struct uses player id as struct index
        */
    
        Internal API - Effect :
    
            private struct Effect extends array
                method applyUnit takes unit u, string effectPath, boolean isReceive returns EffectNode
                    - attach effect with path of (effectPath) to (u
                    - (isReceive) : true for receive quest event, false for return quest event
                    - return created EffectNode associated with this unit (acts like handle id of effect)
            
                method removeUnit takes unit u, EffectNode oldEn returns nothing
                    - remove effect of (oldEn) attached to (u)
                
                method applyRect takes rect r, string effectPath returns nothing
                    - attach effect with path of (effectPath) to (r)
                
                method removeRect takes rect r returns nothing
                    - remove effect attached to (r)
    */
    private struct Effect extends array
        //==========================Globals===============================----------------------------------------------------
            //----------------------Struct Globals------------------------
            /*
                effectTable[a].type
            
                a : GetUnitId()
            
                type :  effect  - effect handle
                        boolean - true if return effect path applied, else false
                        integer - currently EffectNode apply to unit
            */
            private             Array1D             effectTable
        
            /*
                rectEffectTable[a].type
            
                a : handle id of rect
            
                type :  effect - effect handle
                        integer - effect count
            */
            private             Array1D             rectEffectTable
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=======================Internal Methods=========================
        private method applyEffect takes unit u, integer unitId, string effectPath, boolean destroyPrev returns nothing
            if destroyPrev then
                call DestroyEffect(effectTable[unitId].effect)
            endif
            
            if GetLocalPlayer() != Player(this) then
                set effectPath = ""
            endif
            
            set effectTable[unitId].effect = AddSpecialEffectTarget(effectPath, u, "overhead")
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method applyUnit takes unit u, string effectPath, boolean isReceive returns EffectNode
            local integer unitId = GetUnitId(u)
            local EffectUnit eu  = unitId
            local EffectNode en
        
            set en = eu.increase(this, effectPath, isReceive)
        
            if effectTable == 0 then
                set effectTable = Array1D.create()
            endif
        
            if effectTable[unitId].effect == null then
                call .applyEffect(u, unitId, effectPath, false)
            
                set effectTable[unitId].boolean = not isReceive
                set effectTable[unitId].integer = en
            elseif not effectTable[unitId].boolean and not isReceive then
                call .applyEffect(u, unitId, eu.getReturnHeadPath(this), true)
            
                set effectTable[unitId].boolean = true
                set effectTable[unitId].integer = en
            endif
        
            return en
        endmethod
    
        method removeUnit takes unit u, EffectNode oldEn returns nothing
            local integer unitId    = GetUnitId(u)
            local EffectUnit eu     = unitId
            local EffectNode newEn  = eu.decrease(this, oldEn)
            local EffectNode currentEn
        
            if newEn == 0 then
                call DestroyEffect(effectTable[unitId].effect)
                call effectTable.remove(unitId)
            else
                set currentEn = effectTable[unitId].integer
            
                //if newEn not belongs to either receive/return head,
                //simply ignore the operations
                if newEn == eu.getReceiveHead(this) then
                    if newEn.getPath() != currentEn.getPath() then
                        call .applyEffect(u, unitId, eu.getReceiveHeadPath(this), true)
                    endif
                
                    set effectTable[unitId].boolean = false
                elseif newEn == eu.getReturnHead(this) then
                    if newEn.getPath() != currentEn.getPath() then
                        call .applyEffect(u, unitId, eu.getReturnHeadPath(this), true)
                    endif
                endif
            
                //if the node to be removed == current applying en
                //then apply the newly using en as current applying en
                if oldEn == currentEn then
                    set effectTable[unitId].integer = newEn
                endif
            endif
        endmethod
    
        method applyRect takes rect r, string effectPath returns nothing
            local integer rectHandle = GetHandleId(r)
            local integer count
        
            if rectEffectTable == 0 then
                set rectEffectTable = Array1D.create()
            endif
        
            set count = rectEffectTable[rectHandle].integer
        
            if count > 0 then
                set rectEffectTable[rectHandle].integer = count + 1
            else
                if GetLocalPlayer() != Player(this) then
                    set effectPath = ""
                endif
            
                set rectEffectTable[rectHandle].effect = AddSpecialEffect(effectPath, GetRectCenterX(r), GetRectCenterY(r))
                set rectEffectTable[rectHandle].integer = 1
            endif
        endmethod
    
        method removeRect takes rect r returns nothing
            local integer rectHandle = GetHandleId(r)
            local integer count = rectEffectTable[rectHandle].integer
        
            if count > 1 then
                set rectEffectTable[rectHandle].integer = count - 1
            else
                call DestroyEffect(rectEffectTable[rectHandle].effect)
                call rectEffectTable.remove(rectHandle)
            endif
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to linking between quest, which aims to provide :
                1. Linkage from required quest to requering quest (for checking available quest once player completed quest)
        */
    
        Internal API - LinkedQuest :
    
            private struct LinkedQuest extends array
                static method add takes Quest requiredQuest, Quest toLink returns nothing
                    - link (requiredQuest) to (toLink)
            
                static method setLinkedQuestList takes Quest requiredQuest returns nothing
                    - get list of linked quest
                    - use questList to access quest id
                    - last array position is set to 0 for exit looping purpose
    */
    private struct LinkedQuest extends array
        //==========================Globals===============================
            //---------------------Ordinary Globals-----------------------
            private             integer             count
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  Quest   array       questList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    lvList[a] = b
                
                    a : lvListCount
                    b : store quest id
                */
            private             Integer1D           list
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        static method add takes Quest requiredQuest, Quest toLink returns nothing
            local thistype this     = thistype(requiredQuest)
        
            if .list == 0 then
                set .list           = Integer1D.create()
                set .count          = 0
            endif
        
            set .list[.count]       = toLink
            set .count              = .count + 1
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        static method setLinkedQuestList takes Quest requiredQuest returns nothing
            local thistype this     = thistype(requiredQuest)
            local integer i         = 0
        
            if .list != 0 then
                loop
                    set questList[i]    = list[i]
                    set i               = i + 1
                    exitwhen i == .count
                endloop
            endif
        
            set questList[i]            = 0
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to level, which aims to provide :
                1. Linkage for level to quest id (for checking available quest once player lv up)
        */
    
        Internal API - Level :
    
            private struct Level extends array
                method linkQuest takes Quest qId returns nothing
                    - link (qId) to this level instance
                    - uses level as struct index
            
                method getQuestId takes integer index returns Quest
                    - return quest instance at (index)
                    - (index) starts from 0
                    - uses level as struct index
                
                method getMaxQuestCount takes nothing returns integer
                    - return registered quest amount associated to this level
                    - uses level as struct index
    */
    private struct Level extends array
        //==========================Globals===============================
            //---------------------Ordinary Globals-----------------------
            private             integer             lvListCount
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    lvList[a] = b
                
                    a : lvListCount
                    b : store quest id
                */
            private             Integer1D           lvList
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method linkQuest takes Quest qId returns nothing
            if .lvList == 0 then
                set .lvList             = Integer1D.create()
                set .lvListCount        = 0
            endif
        
            set .lvList[.lvListCount]   = qId
            set .lvListCount            = .lvListCount + 1
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getQuestId takes integer index returns Quest
            return lvList[index]
        endmethod
    
        method getMaxQuestCount takes nothing returns integer
            return .lvListCount
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
            /*
                Struct related to ability, which aims to provide :
                    1. Quest instance retrieve from ability (receive/return)
                    2. Ability id retrieve from Quest
                    3. Ability links to subset (for TALK/TALK_2 content)
            */
        
        Internal API - Ability :
    
            private struct Ability extends array
                static method linkQuest takes integer aId, Quest qId returns nothing
                    - link ability (aId) to (qId)
                
                static method talkLink takes integer aId, Quest qId, integer subset returns nothing
                    - link talk ability (aId) to (qId) and (subset)
                
                static method getQuestId takes integer aId returns Quest
                    - return linked quest id from (aId)
                
                static method getTalkQuestId takes integer aId returns Quest
                    - return talk linked quest id from (aId)
                
                static method getTalkSubset takes integer aId returns integer
                    - return linked subset from (aId)
                    - for ability related TALK/TALK_2 content type usage
                
                static method get takes Quest qId returns integer
                    - return linked ability id from (qId)
    */
    private module AbilityInit
        private static method onInit takes nothing returns nothing
            set linkList    = Integer1D.create()
            set talkList    = Integer2D.create()
        endmethod
    endmodule

    private struct Ability extends array
        //==========================Globals===============================
            //----------------------Struct Globals------------------------
                /*
                    list[a] = b
                
                    a : ability id
                    b : store quest id
                
                        AND
                    
                    a : quest id
                    b : store ability id
                */
            private     static  Integer1D           linkList
        
                /*
                    talkList[a][b] = c
                
                    a : ability id
                
                
                    b : 0
                    c : store quest id
                
                    b : 1
                    c : store subset
                */
            private     static  Integer2D           talkList
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        static method linkQuest takes integer aId, Quest qId returns nothing
            set linkList[aId] = qId
            set linkList[qId] = aId
        endmethod
    
        static method talkLink takes integer aId, Quest qId, integer subset returns nothing
            set talkList[aId][0] = qId
            set talkList[aId][1] = subset
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        static method getQuestId takes integer aId returns Quest
            return linkList[aId]
        endmethod
    
        static method getTalkQuestId takes integer aId returns Quest
            return talkList[aId][0]
        endmethod
    
        static method getTalkSubset takes integer aId returns integer
            return talkList[aId][1]
        endmethod
    
        static method get takes Quest qId returns integer
            return linkList[qId]
        endmethod
        //================================================================
        implement AbilityInit
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to unit, which aims to provide :
                1. Quest instance retrieve from unit (receive/return quest)
                2. Quest instance retrieve from monster
                3. Quest unit (item) data assign, included quest id, subset and player
        */
    
        Internal API - Unit :
    
            private struct Unit extends array
                method addReceivableQuest takes Quest qId returns nothing
                    - register this unit to have receivable (qId)
                    - uses GetUnitId(u) as struct index
            
                method addReturnableQuest takes Quest qId returns nothing
                    - register this unit to have returnable (qId)
                    - uses GetUnitId(u) as struct index
                
                static method monsterLinkQuest takes integer uId, Quest qId returns nothing
                    - link unit type (unitType) with (qId)
                
                method assignData takes Quest qId, integer subset, player user returns nothing
                    - assign (qId), (subset) to this unit
                    - ownership of the unit belongs to (user)
                    - uses GetUnitId(u) as struct index
                
                method getReceivableQuest takes integer index returns Quest
                    - return receivable quest at (index) for this unit
                    - (index) starts from 0
            
                method getReturnableQuest takes integer index returns Quest
                    - return returnable quest at (index) for this unit
                    - (index) starts from 0
            
                /*function below is currently not in use*/
                static method setMonsterQuestList takes integer unitType returns nothing
                    - get list of linked monster quest
                    - use linkQuestList to access quest id
                    - last array position is set to 0 for exit looping purpose
                
                method getMaxReceivableQuest takes nothing returns integer
                    - return maximum receivable quest for this unit
                
                method getMaxReturnableQuest takes nothing returns integer
                    - return maximum returnable quest for this unit
                
                method getQuestId takes nothing returns Quest
                    - return stored quest id for this unit
                    - use to retrieve stored data once assignData() is called
            
                method getSubset takes nothing returns integer
                    - return stored subset for this unit
                    - use to retrieve stored data once assignData() is called
            
                method getOwner takes nothing returns player
                    - return ownership of this unit
                    - use to retrieve stored data once assignData() is called
                
                method clear takes nothing returns nothing
                    - clear data assigned to this unit
                    - for clearing assignData() data
    */
    private module UnitInit
        private static method onInit takes nothing returns nothing
            set monsterQuestList = Integer2D.create()
        endmethod
    endmodule

    private struct Unit extends array
        //==========================Globals===============================
            //---------------------Ordinary Globals-----------------------
            private             integer             receiveListCount
            private             integer             returnListCount
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  Quest   array       linkQuestList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    receiveList/returnList[a] = b
                
                    a : receiveListCount/returnListCount (starts from 0)
                    b : store quest id
                */
            private             Integer1D           receiveList
            private             Integer1D           returnList
        
                /*
                    unitData[a].type = b
                
                    a : 1
                    b : quest id    (type integer)
                
                    a : 2
                    b : subset      (type integer)
                
                    a : 3
                    b : user        (type player)
                */
            private             Array1D             unitData
        
                /*
                    monsterQuestList[a][b] = c
                
                    a : unit type id
                    b : linkCount (starts from 0)
                    c : store quest id
                */
            private     static  Integer2D           monsterQuestList
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method addReceivableQuest takes Quest qId returns nothing
            if .receiveList == 0 then
                set .receiveList = Integer1D.create()
                set .receiveListCount = 0
            endif
        
            set .receiveList[.receiveListCount] = qId
            set .receiveListCount = .receiveListCount + 1
        endmethod
    
        method addReturnableQuest takes Quest qId returns nothing
            if .returnList == 0 then
                set .returnList = Integer1D.create()
                set .returnListCount = 0
            endif
        
            set .returnList[.returnListCount] = qId
            set .returnListCount = .returnListCount + 1
        endmethod
    
        static method monsterLinkQuest takes integer unitType, Quest qId returns nothing
            local Integer1D i1d
            local integer i
        
            if monsterQuestList == 0 then
                set monsterQuestList = Integer2D.create()
            endif
        
            set i1d = monsterQuestList[unitType]
            set i = i1d[0] + 1
            set i1d[0] = i
            set i1d[i] = qId
        endmethod
    
        method assignData takes Quest qId, integer subset, player user returns nothing
            if .unitData == 0 then
                set .unitData = Array1D.create()
            endif
        
            set .unitData[1].integer = qId
            set .unitData[2].integer = subset
            set .unitData[3].player  = user
        endmethod
    
        method clear takes nothing returns nothing
            call .unitData.flush()
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getReceivableQuest takes integer index returns Quest
            return .receiveList[index]
        endmethod
    
        method getReturnableQuest takes integer index returns Quest
            return .returnList[index]
        endmethod
    
        /*static method setMonsterQuestList takes integer unitType returns nothing
            local Integer1D i1d         = monsterQuestList[unitType]
            local integer i             = 0
            local integer max           = i1d[0]
        
            loop
                set linkQuestList[i]    = i1d[i+1]
                set i                   = i + 1
                exitwhen i == max
            endloop
        
            set linkQuestList[i]             = 0
        endmethod*/
    
        method getMaxReceivableQuest takes nothing returns integer
            return .receiveListCount
        endmethod
                
        method getMaxReturnableQuest takes nothing returns integer
            return .returnListCount
        endmethod
    
        method getQuestId takes nothing returns Quest
            return .unitData[1].integer
        endmethod
    
        method getSubset takes nothing returns integer
            return .unitData[2].integer
        endmethod
    
        method getOwner takes nothing returns player
            return .unitData[3].player
        endmethod
        //================================================================
        implement UnitInit
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct aims to support struct PlayerQuestList, by creating node for each
            quest for each player
        */
    
        Internal API - QuestListNode :
    
            private struct QuestListNode extends array
                method create takes nothing returns thistype
            
                method destroy takes nothing returns nothing
                
                method link takes thistype node returns nothing
                    - link this instance with (node)
                
                method operator questId= takes Quest qId returns nothing
                    - assign (qId) to this instance
                
                method getNext takes nothing returns thistype
                    - obtain next node linked to this instance
            
                method getQuestId takes nothing returns Quest
                    - obtain assigned quest id from this instance
    */
    private struct QuestListNode extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
                
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
        

            //---------------------Ordinary Globals-----------------------
            private             Quest               q
            private             thistype            next
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes nothing returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount       = recycleAmount - 1
                set this                = recycle[recycleAmount]
            else
                set this                = instanceCount + 1
                set instanceCount       = this
            endif
        
            set .next                   = 0
        
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set .next                   = 0
        
            set recycle[recycleAmount]  = this
            set recycleAmount           = recycleAmount + 1
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
            method link takes thistype node returns nothing
                set .next = node
            endmethod
        
            method operator questId= takes Quest qId returns nothing
                set .q = qId
            endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
            method getNext takes nothing returns thistype
                return .next
            endmethod
        
            method getQuestId takes nothing returns Quest
                return .q
            endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to player quest list, which aims to provide :
                1. Quest instance list received by each player
            
            Struct index uses (player id + 1) as struct index of 0 is invalid
        */
    
        Internal API - PlayerQuestList :
    
            private struct PlayerQuestList extends array
                method add takes Quest qId returns nothing
                    - register received quest (qId) for player p
                    - uses GetPlayerId(p) as struct index
            
                method remove takes Quest qId returns nothing
                    - unregister received quest (qId) from player p
                    - uses GetPlayerId(p) as struct index
                
                method received takes Quest qId returns boolean
                    - check if player p received quest (qId)
                
                method setQuestList takes nothing returns nothing
                    - get list of quest from this player
                    - use questList to access quest id
                    - last array position is set to 0 for exit looping purpose
                
                method fulled takes nothing returns boolean
                    - return true if player's quest list is fulled
                
                method isEmpty takes nothing returns boolean
                    - return true if player's quest list is empty
    */
    //! runtextmacro optional QEQuestCategory__PlayerQuestListInit()

    private struct PlayerQuestList extends array
        //==========================Globals===============================
            //---------------------Ordinary Globals-----------------------
            private             integer             questCount
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  Quest   array       questList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
            private             QuestListNode       head
            private             QuestListNode       last
            //------------------------------------------------------------
        
        
            //--------------------External Globals------------------------
            //! runtextmacro optional QEQuestCategory__PlayerQuestList__Globals()
            //------------------------------------------------------------
        //================================================================
        
        
        //=================Data Manipulation Methods======================
        method add takes Quest qId returns nothing
            local QuestListNode node = QuestListNode.create()
            
            if last == 0 then
                set .head       = node
                set .questCount = 0
            else
                call .last.link(node)
            endif
            
            set node.questId    = qId
            set .last           = node
            set .questCount     = .questCount + 1
        endmethod
            
        method remove takes Quest qId returns nothing
            local QuestListNode node = .head
            local QuestListNode temp
            
            //if first node is the quest qId we want to remove
            if node.getQuestId() == qId then
                if .head == .last then
                    set .head = 0
                    set .last = 0
                else
                    set .head = node.getNext()
                endif
                
                call node.destroy()
            else
                loop
                    set temp = node.getNext()
                    exitwhen temp == 0
                    
                    //if next quest id is the quest id we want to remove
                    if temp.getQuestId() == qId then
                        if .last == temp then
                            call .last.destroy()
                            call node.link(0)
                            set .last = node
                        else
                            call node.link(temp.getNext())
                            call temp.destroy()
                        endif
                    
                        exitwhen true
                    endif
                
                    set node = temp
                endloop
            endif
        
            set .questCount = .questCount - 1
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method received takes Quest qId returns boolean
            local QuestListNode node = .head
        
            loop
                if node.getQuestId() == qId then
                    return true
                endif
            
                set node = node.getNext()
                exitwhen node == 0
            endloop
        
            return false
        endmethod
        
        method setQuestList takes nothing returns nothing
            local QuestListNode node    = .head
            local integer       i       = 0
        
            loop
                set questList[i]        = node.getQuestId()
            
                set node                = node.getNext()
                set i                   = i + 1
                exitwhen node == 0
            endloop
        
            set questList[i]            = 0
        endmethod
    
        method fulled takes nothing returns boolean
            return .questCount > MAX_QUEST_PER_PLAYER
        endmethod
    
        method isEmpty takes nothing returns boolean
            return .questCount == 0
        endmethod
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QEQuestCategory__PlayerQuestList__Member()
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct related to player quest data, which aims to provide :
                1. Quest data required to process and update
                2. Quest status for each quests
            
            Struct index uses (player id + 1) : struct index of 0 is invalid
        */
    
        Internal API - PlayerQuestData :
    
            private struct PlayerQuestData extends array
                method init takes Quest qId, player user returns nothing
                    - init neessasary data for (qId)
                    - Example PICK_UP need to get initialized for creating items
                
                method setQuestStatus takes Quest qId, integer whichStatus returns nothing
                    - set (qId) status to (whichStatus)
                    - (whichStatus) uses globals defined in this struct, included :
                        1. UNTAKEN
                        2. IN_PROGRSS
                        3. FULLFILL
                        4. COMPLETED
                    
                method revertQuestStatus takes Quest qId returns nothing
                    - revert the quest status from FULLFILL to IN_PROGRESS
                
                method showFullfillEffect takes Quest qId, Effect e, boolean showCompleteMsg returns nothing
                    - apply effect to return unit of (qId) to player id of (e)
                    - play sound (if enabled QEQuestSound)
                    - if (showCompleteMsg) is true, display message that quest of (qId) is complete
                
                method isSubsetCompleted takes Quest qId, integer subset returns boolean
                    - return true if (subset) of (qId) is completed
                
                method getQuestStatus takes Quest qId returns integer
                    - return the status of (qId)
                
                method beenCompleted takes Quest qId returns boolean
                    - return true if quest been completed once by player
                
                method isRequiredQuestCompleted takes Quest qId returns boolean
                    - return true if all prior required quests are in
                      completed status (or been completed status)
                  
                method isQuestReceivable takes Quest qId returns boolean
                    - return true if quest is receivable
                
                method getFullDetails takes Quest qId, integer structIndex returns string
                    - return full details of (qId) for this player
                    - (structIndex) for accessing PlayerQuestData members
                    - details included :
                        1. quest title
                        2. quest description
                        3. quest contents
                        4. player quest progress
                    
                method createQuestItem takes real x, real y, Quest qId, integer subset, player user returns unit
                    - create quest item at (x,y) which only visible and pickable by player (user)
                    - quest item will stores (qId),(subset) and owner (which is user) associated to it
                    - return created quest item
                
                method removeQuestItem takes unit u, Quest qId returns nothing
                    - remove and clear all quest item related to (qId) for unit (u)
                
                method applyUnitEffect takes Quest qId, integer subset, unit toApply, string effectPath, boolean isReceive, Effect e returns nothing
                    - apply effect to (toApply) of path (effectPath) to player (e)
                    - (qId) is the effect stored in the quest
                    - (subset) is the effect stored in the (qId) of subset, set to -1 if
                      it is receive/return unit apply
                    - (isReceive) : true for receiving effect, else false
                
                method removeUnitEffect takes Quest qId, integer subset, unit toRemove, Effect e returns nothing
                    - remove effect from (toRemove) of player (e)
                    - (qId) is the effect stored in the quest
                    - (subset) is the effect stored in the (qId) of subset, set to -1 if
                      it is receive/return unit remove
                  
                method clear takes Quest qId returns nothing
                    - clear all stored data of (qId) associated to the player
                
                /*KILL*/
                method updateKillAmount takes Quest qId, integer subset returns nothing
                    - update killing amount of this (subset)
                    - (subset) starts from 1
                    - usable for content type : KILL
                
                method getKillAmount takes Quest qId, integer subset returns integer
                    - return killed amount
                    - (subset) starts from 1
                    - usable for content type : KILL
                
                /*TALK/TALK_2*/
                method updateTalk takes Quest qId, integer subset returns nothing
                    - set unit as talked for this (subset)
                    - (subset) starts from 1
                    - usable for content type : TALK, TALK_2
                
                method isUnitTalked takes Quest qId, integer subset returns boolean
                    - return true if unit talked
                    - (subset) starts from 1
                    - usable for content type : TALK, TALK_2
                
                /*PICK_UP/KILL_OBTAIN_ITEM*/
                method updatePickAmount takes Quest qId, integer subset returns nothing
                    - update pick amount of this (subset)
                    - (subset) starts from 1
                    - usable for content type : PICK_UP, KILL_OBTAIN_ITEM
                
                method decreasePickAmount takes Quest qId, integer subset returns nothing
                    - decrease pick amount of this (subset) by 1
                    - (subset) starts from 1
                    - usable for content type : PICK_UP, KILL_OBTAIN_ITEM
                
                method getPickAmount takes Quest qId, integer subset returns integer
                    - return picked amount
                    - (subset) starts from 1
                    - usable for content type : PICK_UP, KILL_OBTAIN_ITEM
                
                /*KILL_OBTAIN*/
                method updateObtainAmount takes Quest qId, integer subset returns nothing
                    - update obtained amount of this (subset)
                    - (subset) starts from 1
                    - usable for content type : KILL_OBTAIN
            
                method getObtainAmount takes Quest qId, integer subset returns integer
                    - return obtained amount
                    - (subset) starts from 1
                    - usable for content type : KILL_OBTAIN
                
                /*KILL_OBTAIN_ITEM*/
                method updateDropAmount takes Quest qId, integer subset returns nothing
                    - update drop amount of this (subset)
                    - (subset) starts from 1
                    - usable for content type : KILL_OBTAIN_ITEM
                
                method getDropAmount takes Quest qId, integer subset returns integer
                    - return drop amount
                    - (subset) starts from 1
                    - usable for content type : KILL_OBTAIN_ITEM
                
                /*GOTO*/
                method updateGoto takes Quest qId, integer subset returns nothing
                    - set target rect as went for this (subset)
                    - (subset) starts from 1
                    - usable for content type : GOTO
                
                method isPlaceWent takes Quest qId, integer subset returns boolean
                    - return true if place went
                    - (subset) starts from 1
                    - usable for content type : GOTO
                
                /*Addition Type : GROUP_KILL (If enabled QEGroupKill)*/
                method updateGroupKill takes Quest qId, integer subset returns nothing
                    - update group kill amount by 1
                    - (subset) starts from 1
                    - usable for content type : GROUP_KILL
                
                method getGroupKillAmount takes Quest qId, integer subset returns integer
                    - return killed amount
                    - (subset) starts from 1
                    - usable for content type : GROUP_KILL
                
                /*Addition Type : CUSTOM_CONTENT (If enabled QECustomContent*/
                method updateCustomContent takes Quest qId, integer subset returns nothing
                    - update group kill amount by 1
                    - (subset) starts from 1
                    - usable for content type : GROUP_KILL
    */
    private module PlayerQuestDataInit
        private static method onInit takes nothing returns nothing
            local thistype this = 1
        
            loop
                set .data = Array3D.create()
            
                exitwhen integer(this) == bj_MAX_PLAYERS
                set this = this + 1
            endloop
        endmethod
    endmodule

    private struct PlayerQuestData extends array
        //==========================Globals===============================
            //----------------------Public Globals------------------------
            readonly    static  integer             UNTAKEN         = 0
            readonly    static  integer             IN_PROGRESS     = 1
            readonly    static  integer             FULLFILL        = 2
            readonly    static  integer             COMPLETED       = 3
            //------------------------------------------------------------
            
            
            //----------------------Struct Globals------------------------
                /*
                    storing quest data
                    most data will be cleared once quest is completed by player
                
                    data[a][b][c] = quest progress data
                
                
                    a : 0
                    b : quest id
                    c : 0 - quest progress          (type integer) /*will be cleared to UNTAKEN status*/
                        1 - completed subset        (type integer) /*will be cleared to 0*/
                        2 - disabled                (type boolean) (used by QEDynamicEnable) /*will not be cleared*/
                        3 - been completed          (type boolean) /*will not be cleared*/
                        4 - stored EffectNode       (type integer) /*will be cleared*/
                    
                    
                    /*all data below will be cleared*/
                    a : quest id
                    b : subset (starts from 0)
                    c : depends on content type, which listed below :
                
                    /*ALL types*/
                    c : 0 - true if subset completed(type boolean)
                    c : 2 - stored EffectNode       (type integer)
                
                    /*KILL*/
                    c : 0 - kill    amount          (type integer)
                
                    /*TALK/TALK_2*/
                    c : 1 - true if unit talked     (type boolean)
                
                    /*PICK_UP*/
                    c : 0 - pick    amount          (type integer)
                
                    /*KILL_OBTAIN*/
                    c : 0 - obtain  amount          (type integer)
                
                    /*KILL_OBTAIN_ITEM*/
                    c : 0 - pick    amount          (type integer)
                        1 - dropped amount          (type integer)
                    
                    /*GOTO*/
                    c : 1 - true if went            (type boolean)
                
                    /*GROUP_KILL (if enabled QEGroupKill)*/
                    c : 0 - kill    amount          (type integer)
                
                    /*CUSTOM_CONTENT (if enabled QECustomContent)*/
                    c : 0 - update  aomunt          (type integer)
                */
            private             Array3D             data
            //------------------------------------------------------------
        //================================================================
    
    
    
        //=======================Priority Methods=========================
        //Priority methods are the methods which required by either internal
        //methods or external callers. Any methods met such conditions will
        //be placed here.
        static method createQuestItem takes real x, real y, Quest qId, integer subset, player user returns unit
            local integer transparency = 255
            local real scale = QUEST_ITEM_SCALE
        
            set bj_lastCreatedUnit = CreateUnit(NEUTRAL, QUEST_ITEM_ID, x, y, QUEST_ITEM_ANGLE)
        
            if GetLocalPlayer() != user then
                set transparency = 0
                set scale = 0.001
            endif
        
            call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, transparency)    //prevent other player to see it
            call SetUnitScale(bj_lastCreatedUnit, scale, scale, scale)                  //prevent other player left click it
            call Unit(GetUnitId(bj_lastCreatedUnit)).assignData(qId, subset, user)
        
            return bj_lastCreatedUnit
        endmethod
    
        method applyUnitEffect takes Quest qId, integer subset, unit toApply, string effectPath, boolean isReceive, Effect e returns nothing
            if subset == -1 then
                set data[0][qId][4].integer = e.applyUnit(toApply, effectPath, isReceive)
            else
                set data[qId][subset][2].integer = e.applyUnit(toApply, effectPath, isReceive)
            endif
        endmethod
        //================================================================
    
    
    
        //=======================Internal Methods=========================
        private static method initPickUpItems takes Quest qId, integer subset, player user returns nothing
            local integer       i           = 0
            local integer       maxRect     = QuestContent.getPickUpRectSet().getRegisteredAmount() - 1
            local integer       max         = QuestContent.getPickUpRequiredAmount()
            local integer       rand
            local rect          rec
        
            call QuestContent.getPickUpRectSet().setRectList()
        
            loop
                exitwhen i == max
            
                set rand                = GetRandomInt(0, maxRect)
                set rec                 = RectSet.rectList[rand]
            
                call thistype.createQuestItem(GetRandomReal(GetRectMinX(rec), GetRectMaxX(rec)), GetRandomReal(GetRectMinY(rec), GetRectMaxY(rec)), qId, subset, user)
            
                if rand != maxRect then
                    set RectSet.rectList[rand] = RectSet.rectList[maxRect]
                endif
            
                set maxRect = maxRect - 1
                set i = i + 1
            endloop
        
            set rec = null
        endmethod
    
        private method getCompletedSubset takes Array1D temp returns integer
            return temp[1].integer
        endmethod
    
        private method setSubsetIncomplete takes Array1D temp, Array1D a1d returns nothing
            local integer completedSubset = temp[1].integer - 1
        
            set temp[1].integer         = completedSubset   //temp : data[0][qId]
            set a1d[0].boolean          = false             //a1d  : data[qId][subset]
        endmethod
    
        private method setSubsetComplete takes Array1D temp, Array1D a1d returns nothing
            local integer completedSubset = temp[1].integer + 1
        
            set temp[1].integer         = completedSubset   //temp : data[0][qId]
            set a1d[0].boolean          = true              //a1d  : data[qId][subset]
        endmethod
    
        //boolean b :   true for quest progressing
        //              false for quest reverting
        private method update takes Array1D a1d, Quest qId, integer subset, boolean b returns nothing
            local Array1D temp          = data[0][qId]
            local QuestContent content  = qId.getContentIndex()
            local integer contentType   = content.initContent(subset)
        
            /*---------------------------------------------------------------------------*/
            if contentType == QuestContent.KILL then
            /**///Revert is impossible to involve kill content type
            /**/if .getKillAmount(qId, subset) == QuestContent.getKillRequiredAmount() then
            /**/    call .setSubsetComplete(temp, a1d)
            /**/endif
            /*---------------------------------------------------------------------------*/
            elseif  contentType == QuestContent.TALK or contentType == QuestContent.TALK_2 or /*
                */  contentType == QuestContent.GOTO then
            /**///Revert is impossible to involve talk/talk2/goto content type
            /**/    call .setSubsetComplete(temp, a1d)
            /*---------------------------------------------------------------------------*/
            elseif  contentType == QuestContent.PICK_UP then
            /**/if b then
            /**/    if .getPickAmount(qId, subset) == QuestContent.getPickUpRequiredAmount() then
            /**/        call .setSubsetComplete(temp, a1d)
            /**/    endif
            /**///Checking if subset if completed
            /**/elseif data[qId][subset][0].boolean then
            /**/    call .setSubsetIncomplete(temp, a1d)
            /**/endif
            /*---------------------------------------------------------------------------*/
            elseif  contentType == QuestContent.KILL_OBTAIN then
            /**///Revert is impossible to involve kill obtain content type
            /**/if .getObtainAmount(qId, subset) == QuestContent.getKillObtainRequiredAmount() then
            /**/    call .setSubsetComplete(temp, a1d)
            /**/endif
            /*---------------------------------------------------------------------------*/
            elseif  contentType == QuestContent.KILL_OBTAIN_ITEM then
            /**/if b then
            /**/    if .getPickAmount(qId, subset) == QuestContent.getKillObtainItemRequiredAmount() then
            /**/        call .setSubsetComplete(temp, a1d)
            /**/    endif
            /**///Checking if subset if completed
            /**/elseif data[qId][subset][0].boolean then
            /**/    call .setSubsetIncomplete(temp, a1d)
            /**/endif
            /*---------------------------------------------------------------------------*/
            //! runtextmacro optional QEGroupKill__PlayerQuestData__Update()
            //! runtextmacro optional QECustomContent__PlayerQuestData__Update()
            endif
        
            if b then
                if .getCompletedSubset(temp) == content.getRegisteredAmount() then
                    set temp[0].integer = thistype.FULLFILL
                endif
            else
                if .getQuestStatus(qId) == thistype.FULLFILL then
                    set temp[0].integer = thistype.IN_PROGRESS
                endif
            endif
        endmethod
    
        //increase integer by 1
        private method updateInteger takes Quest qId, integer subset, integer index returns nothing
            local Array1D a1d   = data[qId][subset]
            local integer i     = a1d[index].integer + 1
            set a1d[index].integer  = i
        
            call .update(a1d, qId, subset, true)
        endmethod
    
        //set boolean from false to true
        private method updateBoolean takes Quest qId, integer subset, integer index returns nothing
            local Array1D a1d   = data[qId][subset]
            set a1d[index].boolean  = true
            
            call .update(a1d, qId, subset, true)
        endmethod
    
        //decrease integer by 1
        private method decreaseInteger takes Quest qId, integer subset, integer index returns nothing
            local Array1D a1d   = data[qId][subset]
            local integer i     = a1d[index].integer - 1
            set a1d[index].integer  = i
            
            call .update(a1d, qId, subset, false)
        endmethod
        //================================================================
    
    
    
        //=================Data Manipulation Methods======================
            //--------------------KILL Content Type-----------------------
            method updateKillAmount takes Quest qId, integer subset returns nothing
                call .updateInteger(qId, subset, 0)
            endmethod
            //------------------------------------------------------------
        
            //----------------TALK/TALK_2 Content Type--------------------
            method updateTalk takes Quest qId, integer subset returns nothing
                call .updateBoolean(qId, subset, 1)
            endmethod
            //------------------------------------------------------------
        
            //------------------PICK_UP Content Type----------------------
            method updatePickAmount takes Quest qId, integer subset returns nothing
                call .updateInteger(qId, subset, 0)
            endmethod
        
            method decreasePickAmount takes Quest qId, integer subset returns nothing
                call .decreaseInteger(qId, subset, 0)
            endmethod
            //------------------------------------------------------------
        
            //----------------KILL_OBTAIN Content Type--------------------
            method updateObtainAmount takes Quest qId, integer subset returns nothing
                call .updateInteger(qId, subset, 0)
            endmethod
            //------------------------------------------------------------
        
            //--------------KILL_OBTAIN_ITEM Content Type-----------------
            method updateDropAmount takes Quest qId, integer subset returns nothing
                call .updateInteger(qId, subset, 1)
            endmethod
            //------------------------------------------------------------
        
            //--------------------GOTO Content Type-----------------------
            method updateGoto takes Quest qId, integer subset returns nothing
                call .updateBoolean(qId, subset, 1)
            endmethod
            //------------------------------------------------------------
        
            //--------------------------Misc------------------------------
            method setQuestStatus takes Quest qId, integer whichStatus returns nothing
                local Array1D a1d = data[0][qId]
                set a1d[0].integer = whichStatus
            
                if whichStatus == thistype.COMPLETED then
                    set a1d[3].boolean = true
                endif
            endmethod
        
            method removeUnitEffect takes Quest qId, integer subset, unit toRemove, Effect e returns nothing
                local Array1D a1d
            
                if subset == -1 then
                    set a1d = data[0][qId]
                
                    call e.removeUnit(toRemove, a1d[4].integer)
                
                    call a1d.remove(4)
                else
                    set a1d = data[qId][subset]
                
                    call e.removeUnit(toRemove, a1d[2].integer)
                
                    call a1d.remove(2)
                endif
            endmethod
        
            method revertQuestStatus takes Quest qId returns nothing
                call .setQuestStatus(qId, thistype.IN_PROGRESS)
            
                call .removeUnitEffect(qId, -1, qId.getEventIndex().getReturnUnit(), Effect(this-1))
            endmethod
        
            method showFullfillEffect takes Quest qId, Effect e, boolean showCompleteMsg returns nothing
                local player user = Player(e)
            
                //apply effect to return unit
                static if LIBRARY_QEDynamicEffectPath then
                    if qId.getReturnPath() != "" then
                        call .applyUnitEffect(qId, -1, qId.getEventIndex().getReturnUnit(), qId.getReturnPath(), false, e)
                    else
                        call .applyUnitEffect(qId, -1, qId.getEventIndex().getReturnUnit(), EFFECT_PATH_RETURN, false, e)
                    endif
                else
                    call .applyUnitEffect(qId, -1, qId.getEventIndex().getReturnUnit(), EFFECT_PATH_RETURN, false, e)
                endif
      
                //if sound enabled, play sound
                //! runtextmacro optional QEQuestSound__UpdateSound()
            
                if showCompleteMsg then
                    call DisplayTextToPlayer(user, 0, 0, QUEST_COLOR_GENERAL + "Quest - |r" + qId.getTitle() + QUEST_COLOR_GENERAL + " has been completed")
                endif
            
                set user = null
            endmethod
        
            method removeQuestItem takes unit u, Quest qId returns nothing
                local item it
                local integer i = 0
            
                loop
                    set it = UnitItemInSlot(u, i)
            
                    call Item.setData(it)
                
                    if Item.getQuestId() == qId then
                        call Item.clearData(it)
                        call RemoveItem(it)
                    endif
                
                    set i = i + 1
                    exitwhen i == 5
                endloop
            
                set it = null
            endmethod
        
            method clear takes Quest qId returns nothing
                local Array1D a1d = data[0][qId]
            
                //clear quest status(index 0) and quest completed subset(index 1)
                set a1d[0].integer = thistype.UNTAKEN
                set a1d[1].integer = 0
            
                //clear all subset contents
                call data[qId].flush()
            endmethod
            //------------------------------------------------------------
        //================================================================
    
    
    
        //===================Content Init Method==========================
            method init takes Quest qId, player user returns nothing
                local QuestContent  content     = qId.getContentIndex()
                local integer       subset      = 0
                local Effect        e           = GetPlayerId(user)
                local integer       max         = content.getRegisteredAmount()
                local integer       contentType
            
                loop
                    set contentType = content.getContentType(subset)
                
                    if contentType == QuestContent.TALK or contentType == QuestContent.TALK_2 then
                        call content.initContent(subset)
                        call .applyUnitEffect(qId, subset, QuestContent.getTalkUnit(), EFFECT_PATH_TALK, false, e)
                    elseif contentType == QuestContent.GOTO then
                        call content.initContent(subset)
                        call e.applyRect(QuestContent.getGotoRect(), EFFECT_PATH_GOTO)
                    elseif contentType == QuestContent.PICK_UP then
                        call content.initContent(subset)
                        call thistype.initPickUpItems(qId, subset, user)
                    endif
                
                    set subset = subset + 1
                    exitwhen subset == max
                endloop
            endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
            //--------------------KILL Content Type-----------------------
            method getKillAmount takes Quest qId, integer subset returns integer
                return data[qId][subset][0].integer
            endmethod
            //------------------------------------------------------------
        
            //----------------TALK/TALK_2 Content Type--------------------
            method isUnitTalked takes Quest qId, integer subset returns boolean
                return data[qId][subset][0].boolean
            endmethod
            //------------------------------------------------------------
        
            //------------------PICK_UP Content Type----------------------
            method getPickAmount takes Quest qId, integer subset returns integer
                return .getKillAmount(qId, subset)
            endmethod
            //------------------------------------------------------------
        
            //----------------KILL_OBTAIN Content Type--------------------
            method getObtainAmount takes Quest qId, integer subset returns integer
                return .getKillAmount(qId, subset)
            endmethod
            //------------------------------------------------------------
        
            //--------------KILL_OBTAIN_ITEM Content Type-----------------
            method getDropAmount takes Quest qId, integer subset returns integer
                return data[qId][subset][1].integer
            endmethod
            //------------------------------------------------------------
        
            //--------------------GOTO Content Type-----------------------
            method isPlaceWent takes Quest qId, integer subset returns boolean
                return .isUnitTalked(qId, subset)
            endmethod
            //------------------------------------------------------------
        
            //--------------------------Misc------------------------------
            method isSubsetCompleted takes Quest qId, integer subset returns boolean
                return data[qId][subset][0].boolean
            endmethod
    
            method getQuestStatus takes Quest qId returns integer
                return data[0][qId][0].integer
            endmethod
        
            method beenCompleted takes Quest qId returns boolean
                return data[0][qId][3].boolean
            endmethod
        
            method isRequiredQuestCompleted takes Quest qId returns boolean
                local integer   i = 0
                local Quest     questId
            
                call qId.getConditionIndex().setRequiredQuestList()
            
                loop
                    set questId = QuestCondition.questList[i]
                    exitwhen questId == 0
                
                    if not .beenCompleted(questId) then
                        return false
                    endif
                
                    set i = i + 1
                endloop
            
                return true
            endmethod
        
            method isQuestReceivable takes Quest qId returns boolean
                local boolean b = true
              
                static if LIBRARY_QECustomCondition then
                    local QuestCondition cond
                    local integer index = 0
                endif
            
                //First check if quest is enabled for the player (optional)
                //! runtextmacro optional QEDynamicEnable__PlayerQuestData__Check()
            
                //Quest is enabled for player, assign the conditions defined in QuestCondition
                if b then
                    set b =   .getQuestStatus(qId) == thistype.UNTAKEN and /*
                                */      GetHeroLevel(HERO[this-1]) >= qId.getConditionIndex().getRequiredLevel() and /*
                                */      .isRequiredQuestCompleted(qId)
                endif
            
                //Second check if quest been completed for the player (if not enabled QERepeatableQuest)
                //If enabled, then it will check whether it is a repeatable quest
                if b then
                    static if not LIBRARY_QERepeatableQuest then
                        //quest untaken, check if been completed (set to false if been completed)
                        if .beenCompleted(qId) then
                            set b = false
                        endif
                    endif
                
                    //! runtextmacro optional QERepeatableQuest__PlayerQuestData__Check()
                endif
            
                //Last check if there are custom condition defined by user (optional) and then
                //return the result
                static if not LIBRARY_QECustomCondition then
                    return b
                endif
            
                //! runtextmacro optional QECustomCondition__PlayerQuestData__Check()
            endmethod
        
            method getFullDetails takes Quest qId, integer structIndex returns string
                local QuestContent      content         = qId.getContentIndex()
                local PlayerQuestData   userQuestData   = structIndex
            
                local integer subset                    = 0
                local string s              //storing overall quest details string
                local string s2             //for storing quest complete/incomeplete color
                local integer contentType
            
                local integer i
                local integer i2
            
                set s = QUEST_COLOR_GENERAL + "Quest Name : |r" + qId.getTitle() + "\n"
                set s = s + QUEST_COLOR_DESCRIPTION + qId.getDescription() + "\n\n"
                set s = s + QUEST_COLOR_GENERAL + "Quest Requirement :|r"
            
                loop
                    exitwhen subset == content.getRegisteredAmount()
                    set contentType = content.initContent(subset)
                
                    /*---------------------------------------------------------------------------*/
                    if contentType == QuestContent.KILL then
                    /**/set i   = .getKillAmount(qId, subset)
                    /**/set i2  = QuestContent.getKillRequiredAmount()
                    /**/
                    /**/if i == i2 then
                    /**/    set s2 = QUEST_COLOR_COMPLETE
                    /**/else
                    /**/    set s2 = QUEST_COLOR_INCOMPLETE
                    /**/endif
                    /**/
                    /**/set s = s + s2 + "\n- Kill " + I2S(i2) + " |r" + GetObjectName(QuestContent.getKillRequiredUnitType()) + s2 + " (" + I2S(i) + "/" + I2S(i2) + ")"
                    /*---------------------------------------------------------------------------*/
                    elseif  contentType == QuestContent.TALK or contentType == QuestContent.TALK_2 then
                    /**/if .isUnitTalked(qId, subset) then
                    /**/    set s2 = QUEST_COLOR_COMPLETE
                    /**/else
                    /**/    set s2 = QUEST_COLOR_INCOMPLETE
                    /**/endif
                    /**/
                    /**/set s =  s + s2 + "\n- " + QuestContent.getTalkDescription()
                    /*---------------------------------------------------------------------------*/
                    elseif  contentType == QuestContent.PICK_UP then
                    /**/set i   = .getPickAmount(qId, subset)
                    /**/set i2  = QuestContent.getPickUpRequiredAmount()
                    /**/
                    /**/if i == i2 then
                    /**/    set s2 = QUEST_COLOR_COMPLETE
                    /**/else
                    /**/    set s2 = QUEST_COLOR_INCOMPLETE
                    /**/endif
                    /**/
                    /**/set s = s + s2 + "\n- " + QuestContent.getPickUpDescription() + s2 + " (" + I2S(i) + "/" + I2S(i2) + ")"
                    /*---------------------------------------------------------------------------*/
                    elseif  contentType == QuestContent.KILL_OBTAIN then
                    /**/set i   = .getObtainAmount(qId, subset)
                    /**/set i2  = QuestContent.getKillObtainRequiredAmount()
                    /**/
                    /**/if i == i2 then
                    /**/    set s2 = QUEST_COLOR_COMPLETE
                    /**/else
                    /**/    set s2 = QUEST_COLOR_INCOMPLETE
                    /**/endif
                    /**/
                    /**/set s = s + s2 + "\n- Kill |r" + GetObjectName(QuestContent.getKillObtainRequiredUnitType()) + s2 + " and obtain " + I2S(i2) + " |r" + QuestContent.getKillObtainItemName() + s2 + " (" + I2S(i) + "/" + I2S(i2) + ")"
                    /*---------------------------------------------------------------------------*/
                    elseif  contentType == QuestContent.KILL_OBTAIN_ITEM then
                    /**/set i   = .getPickAmount(qId, subset)
                    /**/set i2  = QuestContent.getKillObtainItemRequiredAmount()
                    /**/
                    /**/if i == i2 then
                    /**/    set s2 = QUEST_COLOR_COMPLETE
                    /**/else
                    /**/    set s2 = QUEST_COLOR_INCOMPLETE
                    /**/endif
                    /**/
                    /**/set s = s + s2 + "\n- Kill " + GetObjectName(QuestContent.getKillObtainItemRequiredUnitType()) + s2 + " and pick up " + I2S(i2) + " |r" + GetObjectName(QuestContent.getKillObtainItemItemTypeId()) + s2 + " (" + I2S(i) + "/" + I2S(i2) + ")"
                    /*---------------------------------------------------------------------------*/
                    elseif  contentType == QuestContent.GOTO then
                    /**/if .isPlaceWent(qId, subset) then
                    /**/    set s2 = QUEST_COLOR_COMPLETE
                    /**/else
                    /**/    set s2 = QUEST_COLOR_INCOMPLETE
                    /**/endif
                    /**/
                    /**/set s =  s + s2 + "\n- " + QuestContent.getGotoDescription()
                    /*---------------------------------------------------------------------------*/
                    //! runtextmacro optional QEGroupKill__PlayerQuestData__ViewDetails()
                    //! runtextmacro optional QECustomContent__PlayerQuestData__ViewDetails()
                    endif
        
                    set subset = subset + 1
                endloop
            
                return s
            endmethod
            //------------------------------------------------------------
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QEDynamicEnable__PlayerQuestData__Member()
        //! runtextmacro optional QEGroupKill__PlayerQuestData__Member()
        //! runtextmacro optional QECustomContent__PlayerQuestData__Member()
        //================================================================
        implement PlayerQuestDataInit
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
/***************************End Of Quest Internal Data Storage*************************************/
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        Internal API - QuestReward :
    
            struct QuestReward extends array
                            
                method getGold takes nothing returns integer
                    - return the amount of gold to be rewarded
                
                method getLumber takes nothing returns integer
                    - return the amount of lumber to be rewarded
                
                method getExperience takes nothing returns integer
                    - return the amount of experience to be rewarded
                
                method setItemList takes nothing returns nothing
                    - get list of rewarded Item and its amount
                    - use itemList to access item type id
                    - use itemAmountList to access item reward amount
                    - last array position (for itemList and itemAmountList)
                      is set to 0 for exit looping purpose
    */
    struct QuestReward extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
    
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
        
        
            //---------------------Ordinary Globals-----------------------
            private             integer             g   //gold
            private             integer             l   //lumber
            private             integer             e   //experience
        
            private             integer             itemCount
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  integer array       itemList
                        static  integer array       itemAmountList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    storing item reward list
                
                    itemList[a] = b
                
                    a : itemCount
                    b : even number : store item type id (starts from 0)
                        odd  number : store reward amount (starts from 1)
                */
            private             Integer1D           itemListTb
            //------------------------------------------------------------
        
        
            //--------------------External Globals------------------------
            //! runtextmacro optional QECustomReward__QuestReward__Globals()
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes nothing returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount       = recycleAmount - 1
                set this                = recycle[recycleAmount]
            else
                set this                = instanceCount + 1
                set instanceCount       = this
            endif
        
            set .g                      = 0
            set .l                      = 0
            set .e                      = 0
            set .itemCount              = 0
        
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set recycle[recycleAmount]  = this
            set recycleAmount           = recycleAmount + 1
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method operator gold= takes integer amount returns nothing
            set .g              = amount
        endmethod
    
        method operator lumber= takes integer amount returns nothing
            set .l              = amount
        endmethod
    
        method operator experience= takes integer amount returns nothing
            set .e              = amount
        endmethod
    
        method addItem takes integer itemType, integer amount returns nothing
            local integer i     = .itemCount*2
        
            if itemListTb == 0 then
                set .itemListTb = Item1D.create()
                set .itemCount  = 0
            endif
        
            set .itemListTb[i]  = itemType
            set .itemListTb[i+1]= amount
        
            set .itemCount      = .itemCount + 1
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getGold takes nothing returns integer
            return .g
        endmethod
    
        method getLumber takes nothing returns integer
            return .l
        endmethod
    
        method getExperience takes nothing returns integer
            return .e
        endmethod
    
        method setItemList takes nothing returns nothing
            local integer i             = 0
        
            loop
                exitwhen i == .itemCount
                set itemList[i]         = itemListTb[i*2]
                set itemAmountList[i]   = itemListTb[i*2+1]
                set i                   = i + 1
            endloop
        
            set itemList[i]             = 0
            set itemAmountList[i]       = 0
        endmethod
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QECustomReward__QuestReward__Member()
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        Internal API - QuestContent :
    
            struct QuestContent extends array
                readonly    static  integer             KILL
                readonly    static  integer             TALK
                readonly    static  integer             TALK_2
                readonly    static  integer             PICK_UP
                readonly    static  integer             KILL_OBTAIN
                readonly    static  integer             KILL_OBTAIN_ITEM
                readonly    static  integer             GOTO
        
                method initContent takes integer index returns integer
                    - init all data in (index) fully
                    - return content type at (index)
                    - after init, you're free to use accessor methods for
                      the returned content type
                    - (index) starts from 0
            
                method initContentCond takes boolean b, integer index returns integer
                    - init all data in (index) fully if (b) is true
                    - return content type at (index)
                    - return 0 if (b) is false
                    - after init, you're free to use accessor methods for
                      the returned content type
                    - (index) starts from 0
                
                method getRegisteredAmount takes nothing returns integer
                    - return the number of registered content
                
                method getContentType takes integer index returns integer
                    - return content type at (index)
                    - (index) starts from 0
                
                /*=================KILL Content type==================*/
                static method getKillRequiredUnitType takes nothing returns integer
                    - return unit type id of required to be killed
        
                static method getKillRequiredAmount takes nothing returns integer
                    - return required kill amount for unit type
                
                /*=============TALK/TALK_2 Content Type===============*/
                static method getTalkUnit takes nothing returns unit
                    - return unit required to talk to
        
                static method getTalkAbilityId takes nothing returns integer
                    - return talk ability id
        
                static method getTalkDescription takes nothing returns string
                    - return talk description shown in QuestInfo
        
                static method getTalkExDescription takes nothing returns string
                    - return talk talk content shown after talked to a unit
                
                /*==============PICK_UP Content Type==================*/
                static method getPickUpRectSet takes nothing returns RectSet
                    - return registered RectSet instance
        
                static method getPickUpItemTypeId takes nothing returns integer
                    - return required pick up item type id
        
                static method getPickUpRequiredAmount takes nothing returns integer
                    - return required pick up amount
        
                static method getPickUpDescription takes nothing returns string
                    - return pick up description shown in QuestInfo
        
                /*=============KILL_OBTAIN Content Type===============*/
                static method getKillObtainRequiredUnitType takes nothing returns integer
                    - return kill obtain required killing unit type id
        
                static method getKillObtainItemName takes nothing returns string
                    - return kill obtain required item name
        
                static method getKillObtainRequiredAmount takes nothing returns integer
                    - return kill obtain required obtain amount
        
                static method getKillObtainChance takes nothing returns real
                    - return kill obtain chance of obtain (virtual) item
        
                /*==========KILL_OBTAIN_ITEM Content Type=============*/
                static method getKillObtainItemRequiredUnitType takes nothing returns integer
                    - return kill obtain item required killing unit type id
        
                static method getKillObtainItemItemTypeId takes nothing returns string
                    - return kill obtain item required item type id
        
                static method getKillObtainItemRequiredAmount takes nothing returns integer
                    - return kill obtain item required obtain amount
        
                static method getKillObtainItemChance takes nothing returns real
                    - return kill obtain item chance of obtain item
                
                /*==================GOTO Content Type==================*/
                static method geteGotoRect takes nothing returns rect
                    - return required goto rect
        
                static method getGotoDescription takes nothing returns string
                    - return goto description shown in QuestInfo
        
                static method getGotoCompleteDescription takes nothing returns string
                    - return goto complete description shown after reaching rect
    */
    struct QuestContent extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
    
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
        
        
            //---------------------Ordinary Globals-----------------------
            private             integer             subset
        
            //globals below are for accessor methods usage
            private     static  integer             int1
            private     static  integer             int2
            private     static  integer             int3
            private     static  unit                u1
            private     static  string              str1
            private     static  string              str2
            private     static  real                r1
            private     static  rect                rec1
            private     static  RectSet             rs1
            //------------------------------------------------------------
        
        
            //-------------------Content Type Globals---------------------
            readonly    static  integer             KILL                = 1
            readonly    static  integer             TALK                = 2
            readonly    static  integer             TALK_2              = 3
            readonly    static  integer             PICK_UP             = 4
            readonly    static  integer             KILL_OBTAIN         = 5
            readonly    static  integer             KILL_OBTAIN_ITEM    = 6
            readonly    static  integer             GOTO                = 7
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
            private             Quest               questId
                /*
                    data store all of the registered content
                
                    data[a][b] = c
                
                    a : subset (for each registered content will increase subset by 1) (starts from 0)
                    b : content's data index, depend on type of content
                    c : data to store
                */
            private             Array2D             data
            //------------------------------------------------------------
        
        
            //--------------------External Globals------------------------
            //! runtextmacro optional QEGroupKill__QuestContent__Globals()
            //! runtextmacro optional QECustomContent__QuestContent__Globals()
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes Quest qId returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount       = recycleAmount - 1
                set this                = recycle[recycleAmount]
            else
                set this                = instanceCount + 1
                set instanceCount       = this
            endif
        
            set .questId                = qId
            set .subset                 = 0
            set .data                   = Array2D.create()
        
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set recycle[recycleAmount]  = this
            set recycleAmount           = recycleAmount + 1
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method registerKill takes integer unitType, integer noToKill returns nothing
            local Array1D a1d       = data[.subset]
            call Unit.monsterLinkQuest(unitType, questId)
        
            set a1d[0].integer      = KILL
            set a1d[1].integer      = unitType
            set a1d[2].integer      = noToKill
        
            set .subset             = .subset + 1
        endmethod
    
        method registerTalk takes unit u, integer aId, string description, string exDescription returns nothing
            local Array1D a1d       = data[.subset]
        
            set a1d[0].integer      = TALK
            set a1d[1].unit         = u
            set a1d[2].integer      = aId
            set a1d[3].string       = description
            set a1d[4].string       = exDescription
        
            call Ability.talkLink(aId, questId, .subset)
            set .subset             = .subset + 1
        endmethod
    
        method registerTalk2 takes unit u, string description, string exDescription returns nothing
            local Array1D a1d       = data[.subset]
        
            set a1d[0].integer      = TALK_2
            set a1d[1].unit         = u
            set a1d[2].string       = description
            set a1d[3].string       = exDescription
        
            set .subset             = .subset + 1
        endmethod
    
        method registerPickUp takes RectSet rs, integer iId, integer noToPick, string description returns nothing
            local Array1D a1d       = data[.subset]
        
            set a1d[0].integer      = PICK_UP
            set a1d[1].integer      = rs
            set a1d[2].integer      = iId
            set a1d[3].integer      = noToPick
            set a1d[4].string       = description
        
            set .subset             = .subset + 1
        endmethod
    
        method registerKillObtain takes integer unitType, string itemName, integer noToObtain, real chance returns nothing
            local Array1D a1d       = data[.subset]
            call Unit.monsterLinkQuest(unitType, questId)
        
            set a1d[0].integer      = KILL_OBTAIN
            set a1d[1].integer      = unitType
            set a1d[2].string       = itemName
            set a1d[3].integer      = noToObtain
            set a1d[4].real         = chance
        
            set .subset             = .subset + 1
        endmethod
    
        method registerKillObtainItem takes integer unitType, integer itemType, integer noToObtain, real chance returns nothing
            local Array1D a1d       = data[.subset]
            call Unit.monsterLinkQuest(unitType, questId)
        
            set a1d[0].integer      = KILL_OBTAIN_ITEM
            set a1d[1].integer      = unitType
            set a1d[2].integer      = itemType
            set a1d[3].integer      = noToObtain
            set a1d[4].real         = chance
        
            set .subset             = .subset + 1
        endmethod
    
        method registerGoto takes rect rt, string description, string completeDescription returns nothing
            local region reg        = CreateRegion()
            local Array1D a1d       = data[.subset]
        
            set a1d[0].integer      = GOTO
            set a1d[1].rect         = rt
            set a1d[2].string       = description
            set a1d[3].string       = completeDescription
        
            call Region.add(reg, questId, .subset)
            call RegionAddRect(reg, rt)
            call TriggerRegisterEnterRegion(enterRegTrigger, reg, null)
        
            set .subset             = .subset + 1
            set reg                 = null
        endmethod
        //================================================================
    
    
        //===================Content Init Method==========================
            method initContent takes integer index returns integer
                local Array1D a1d           = data[index]
                local integer contentType   = a1d[0].integer
            
                if contentType == thistype.KILL then
                    set int1                = a1d[1].integer
                    set int2                = a1d[2].integer
                elseif contentType == thistype.TALK then
                    set u1                  = a1d[1].unit
                    set int1                = a1d[2].integer
                    set str1                = a1d[3].string
                    set str2                = a1d[4].string
                elseif contentType == thistype.TALK_2 then
                    set u1                  = a1d[1].unit
                    set str1                = a1d[2].string
                    set str2                = a1d[3].string
                elseif contentType == thistype.PICK_UP then
                    set rs1                 = a1d[1].integer
                    set int1                = a1d[2].integer
                    set int2                = a1d[3].integer
                    set str1                = a1d[4].string
                elseif contentType == thistype.KILL_OBTAIN then
                    set int1                = a1d[1].integer
                    set str1                = a1d[2].string
                    set int2                = a1d[3].integer
                    set r1                  = a1d[4].real
                elseif contentType == thistype.KILL_OBTAIN_ITEM then
                    set int1                = a1d[1].integer
                    set int2                = a1d[2].integer
                    set int3                = a1d[3].integer
                    set r1                  = a1d[4].real
                elseif contentType == thistype.GOTO then
                    set rec1                = a1d[1].rect
                    set str1                = a1d[2].string
                    set str2                = a1d[3].string
                //! runtextmacro optional QEGroupKill__QuestContent__Init()
                //! runtextmacro optional QECustomContent__QuestContent__Init()
                endif
            
                return contentType
            endmethod
        
            method initContentCond takes boolean b, integer index returns integer
                if b then
                    return .initContent(index)
                endif
            
                return 0
            endmethod
        //================================================================
    
    
        //======================Accessor Methods==========================
            method getRegisteredAmount takes nothing returns integer
                return .subset
            endmethod
        
            method getContentType takes integer index returns integer
                return data[index][0].integer
            endmethod
        
            //--------------------KILL Content Type-----------------------
            static method getKillRequiredUnitType takes nothing returns integer
                return int1
            endmethod
        
            static method getKillRequiredAmount takes nothing returns integer
                return int2
            endmethod
            //------------------------------------------------------------
        
            //----------------TALK/TALK_2 Content Type--------------------
            static method getTalkUnit takes nothing returns unit
                return u1
            endmethod
        
            static method getTalkAbilityId takes nothing returns integer
                return int1
            endmethod
        
            static method getTalkDescription takes nothing returns string
                return str1
            endmethod
        
            static method getTalkExDescription takes nothing returns string
                return str2
            endmethod
            //------------------------------------------------------------
        
            //------------------PICK_UP Content Type----------------------
            static method getPickUpRectSet takes nothing returns RectSet
                return rs1
            endmethod
        
            static method getPickUpItemTypeId takes nothing returns integer
                return int1
            endmethod
        
            static method getPickUpRequiredAmount takes nothing returns integer
                return int2
            endmethod
        
            static method getPickUpDescription takes nothing returns string
                return str1
            endmethod
            //------------------------------------------------------------
        
            //----------------KILL_OBTAIN Content Type--------------------
            static method getKillObtainRequiredUnitType takes nothing returns integer
                return int1
            endmethod
        
            static method getKillObtainItemName takes nothing returns string
                return str1
            endmethod
        
            static method getKillObtainRequiredAmount takes nothing returns integer
                return int2
            endmethod
        
            static method getKillObtainChance takes nothing returns real
                return r1
            endmethod
            //------------------------------------------------------------
        
            //--------------KILL_OBTAIN_ITEM Content Type-----------------
            static method getKillObtainItemRequiredUnitType takes nothing returns integer
                return int1
            endmethod
        
            static method getKillObtainItemItemTypeId takes nothing returns integer
                return int2
            endmethod
        
            static method getKillObtainItemRequiredAmount takes nothing returns integer
                return int3
            endmethod
        
            static method getKillObtainItemChance takes nothing returns real
                return r1
            endmethod
            //------------------------------------------------------------
        
            //--------------------GOTO Content Type-----------------------
            static method getGotoRect takes nothing returns rect
                return rec1
            endmethod
        
            static method getGotoDescription takes nothing returns string
                return str1
            endmethod
        
            static method getGotoCompleteDescription takes nothing returns string
                return str2
            endmethod
            //------------------------------------------------------------
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QEGroupKill__QuestContent__Member()
        //! runtextmacro optional QECustomContent__QuestContent__Member()
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        Internal API - QuestCondition :
    
            struct QuestCondition extends array
                            
                method getRequiredLevel takes nothing returns integer
                    - retrive required level
                
                method setRequiredQuestList takes nothing returns nothing
                    - get list of required Quest instance
                    - use questList to access
                    - last array position is set to 0 for exit looping purpose
    */
    struct QuestCondition extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
    
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
        
        
            //---------------------Ordinary Globals-----------------------
            private             integer             lv
            private             integer             rqQuestCount
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  integer array       questList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    rqQuest stores the prior required quest in order to
                    receive this quest.
                
                    rqQuest[a] = b
                
                    a : rqQuestCount
                    b : store required quest id
                */
            private             Integer1D           rqQuest
        
            private             Quest               questId
            //------------------------------------------------------------
        
        
            //--------------------External Globals------------------------
            //! runtextmacro optional QECustomCondition__QuestCondition__Globals()
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes Quest qId returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount       = recycleAmount - 1
                set this                = recycle[recycleAmount]
            else
                set this                = instanceCount + 1
                set instanceCount       = this
            endif
        
            set .lv                     = 0
            set .questId                = qId
        
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set recycle[recycleAmount]  = this
            set recycleAmount           = recycleAmount + 1
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method operator level= takes integer rqLevel returns nothing
            set .lv                     = rqLevel
        
            call Level(.lv).linkQuest(questId)
        endmethod
    
        method operator addQuest= takes Quest qId returns nothing
            if .rqQuest == 0 then
                set .rqQuest            = Integer1D.create()
                set .rqQuestCount       = 0
            endif
        
            set .rqQuest[.rqQuestCount] = qId
            set .rqQuestCount           = .rqQuestCount + 1
            call LinkedQuest.add(qId, questId)
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getRequiredLevel takes nothing returns integer
            return .lv
        endmethod
    
        method setRequiredQuestList takes nothing returns nothing
            local integer i         = 0
        
            loop
                exitwhen i == .rqQuestCount
            
                set questList[i]    = .rqQuest[i]
                set i               = i + 1
            endloop
        
            set questList[i]        = 0
        endmethod
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QECustomCondition__QuestCondition__Member()
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        Internal API - QuestEvent :
    
            struct QuestEvent extends array
                            
                method getReceiveUnit takes nothing returns unit
                    - retrive receive unit
                
                method getReturnUnit takes nothing returns unit
                    - retrieve return unit
    */
    struct QuestEvent extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
    
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
    
    
            //---------------------Ordinary Globals-----------------------
            private             integer             recEvent    //receive event
            private             integer             retEvent    //return event
        
            private             unit                rcUnit  //receive unit
            private             unit                rtUnit  //return unit
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
            private             Quest               questId
            //------------------------------------------------------------
        
        
            //--------------------External Globals------------------------
            //! runtextmacro optional QEDynamicEvent__QuestEvent__Globals()
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes Quest qId returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount           = recycleAmount - 1
                set this                    = recycle[recycleAmount]
            else
                set this                    = instanceCount + 1
                set instanceCount           = this
            endif
        
            set .questId                    = qId
            set .recEvent                   = EVENT_QUEST_DEFAULT
            set .retEvent                   = EVENT_QUEST_DEFAULT
        
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set recycle[recycleAmount]      = this
            set recycleAmount               = recycleAmount + 1
        endmethod
        //================================================================
    
    
    
        //=======================Internal Methods=========================
        private method setReceiveUnit takes unit receiveU, integer unitIndexId returns nothing
            set .rcUnit = receiveU
            call Unit(unitIndexId).addReceivableQuest(questId)
        endmethod
    
        private method setReturnUnit takes unit returnU, integer unitIndexId returns nothing
            set .rtUnit = returnU
            call Unit(unitIndexId).addReturnableQuest(questId)
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method operator receiveUnit= takes unit receiveU returns nothing
            call .setReceiveUnit(receiveU, GetUnitId(receiveU))
        endmethod
    
        method operator returnUnit= takes unit returnU returns nothing
            call .setReturnUnit(returnU, GetUnitId(returnU))
        endmethod
    
        method operator receiveReturnUnit= takes unit rrUnit returns nothing
            local integer unitIndexId = GetUnitId(rrUnit)
        
            call .setReceiveUnit(rrUnit, unitIndexId)
            call .setReturnUnit(rrUnit, unitIndexId)
        endmethod
    
        method operator abilityId= takes integer aId returns nothing
            call Ability.linkQuest(aId, questId)
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        method getReceiveUnit takes nothing returns unit
            return .rcUnit
        endmethod
    
        method getReturnUnit takes nothing returns unit
            return .rtUnit
        endmethod
    
        method getReceiveEvent takes nothing returns integer
            return .recEvent
        endmethod
    
        method getReturnEvent takes nothing returns integer
            return .retEvent
        endmethod
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QEDynamicEvent__QuestEvent__Member()
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        Internal API - Quest :
    
            struct Quest extends array
                integer array       eventList
                integer array       conditionList
                integer array       contentList
                integer array       rewardList
                          
                static method getQuestCount takes nothing returns integer
                    - return registered quest count
                
                method getTitle takes nothing returns string
                    - retrieve title
            
                method getDescription takes nothing returns string
                    - retrieve description
            
                method getEventIndex takes nothing returns QuestEvent
                    - return choosen event index
            
                method getConditionIndex takes nothing returns QuestCondition
                    - return choosen condition index
            
                method getContentIndex takes nothing returns QuestContent
                    - return choosen content index
            
                method getRewardIndex takes nothing returns QuestReward
                    - return choosen reward index
                
                /*
                    NOT IN USE AT THE MOMENT
                
                method setEventList takes nothing returns nothing
                    - get list of QuestEvent instance
                    - use eventList to access
                    - last array position is set to 0 for exit looping purpose
            
                method setConditionList takes nothing returns nothing
                    - get list of QuestCondition instance
                    - use conditionList to access
                    - last array position is set to 0 for exit looping purpose
            
                method setContentList takes nothing returns nothing
                    - get list of QuestContent instance
                    - use contentList to access
                    - last array position is set to 0 for exit looping purpose
            
                method setRewardList takes nothing returns nothing
                    - get list of QuestReward instance
                    - use rewardList to access
                    - last array position is set to 0 for exit looping purpose
                */
    */
    struct Quest extends array
        //==========================Globals===============================
            //---------------------Creation Globals-----------------------
            private     static  thistype            instanceCount = 0
            private        static    thistype    array    recycle
    
            private        static    integer                recycleAmount = 0
            //------------------------------------------------------------
    
    
            //---------------------Ordinary Globals-----------------------
            private             string              name
            private             string              detail
        
            private             integer             eventCount
            private             integer             conditionCount
            private             integer             contentCount
            private             integer             rewardCount
        
            private             integer             choosenEvent
            private             integer             choosenCondition
            private             integer             choosenContent
            private             integer             choosenReward
            //------------------------------------------------------------
        
        
            //----------------------Public Globals------------------------
                        static  integer array       eventList
                        static  integer array       conditionList
                        static  integer array       contentList
                        static  integer array       rewardList
            //------------------------------------------------------------
        
        
            //----------------------Struct Globals------------------------
                /*
                    list[a][b] = c
                
                    a : struct type id (QuestEvent, QuestCondition, QuestConten and QuestReward)
                    b : eventCount/conditionCount/contentCount/rewardCount (starts from 0)
                    c : store corresponding struct type id
                */
            private             Integer2D           list
        
            readonly            QuestEvent          event
            readonly            QuestCondition      condition
            readonly            QuestContent        content
            readonly            QuestReward         reward
            //------------------------------------------------------------
        
        
            //--------------------External Globals------------------------
            //! runtextmacro optional QEDynamicEffectPath__Quest__Globals()
            //! runtextmacro optional QEQuestCateogry__Quest__Globals()
            //! runtextmacro optional QEQuestSound__Quest__Globals()
            //! runtextmacro optional QEReceiveReturnEvent__Quest__Globals()
            //! runtextmacro optional QERepeatableQuest__Quest__Globals()
            //------------------------------------------------------------
        //================================================================
    
    
    
        //====================Creation/Destroy Methods====================
        static method create takes nothing returns thistype
            local thistype this
        
            if recycleAmount > 0 then
                set recycleAmount       = recycleAmount - 1
                set this                = recycle[recycleAmount]
            else
                set this                = instanceCount + 1
                set instanceCount       = this
            endif
        
            set .eventCount             = 0
            set .conditionCount         = 0
            set .contentCount           = 0
            set .rewardCount            = 0
        
            set .choosenEvent           = 0
            set .choosenCondition       = 0
            set .choosenContent         = 0
            set .choosenReward          = 0
        
            static if LIBRARY_QEDynamicEffectPath then
        
            set .receivePath            = EFFECT_PATH_RECEIVE
            set .returnPath             = EFFECT_PATH_RETURN
        
            endif
        
            set .list                   = Integer2D.create()
            return this
        endmethod
        //================================================================
    
    
    
        //=====================Data Assign Methods========================
        method operator title= takes string questTitle returns nothing
            set .name = questTitle
        endmethod
    
        method operator description= takes string questDescription returns nothing
            set .detail = questDescription
        endmethod
        //================================================================
    
    
    
        //========Event/Condition/Content/Reward Creation Methods=========
        method createEvent takes nothing returns nothing
            set .event = QuestEvent.create(this)
        
            if .choosenEvent == 0 then
                set .choosenEvent = .event
            endif
        
            set list[QuestEvent.typeid][.eventCount] = .event
            set .eventCount = .eventCount + 1
        endmethod
    
        method createCondition takes nothing returns nothing
            set .condition = QuestCondition.create(this)
        
            if .choosenCondition == 0 then
                set .choosenCondition = .condition
            endif
        
            set list[QuestCondition.typeid][.conditionCount] = .condition
            set .conditionCount = .conditionCount + 1
        endmethod
    
        method createContent takes nothing returns nothing
            set .content = QuestContent.create(this)
        
            if .choosenContent == 0 then
                set .choosenContent = .content
            endif
        
            set list[QuestContent.typeid][.contentCount] = .content
            set .contentCount = .contentCount + 1
        endmethod
    
        method createReward takes nothing returns nothing
            set .reward = QuestReward.create()
        
            if .choosenReward == 0 then
                set .choosenReward = .reward
            endif
        
            set list[QuestReward.typeid][.rewardCount] = .reward
            set .rewardCount = .rewardCount + 1
        endmethod
        //================================================================
    
    
    
        //======================Accessor Methods==========================
        static method getQuestCount takes nothing returns integer
            return thistype.instanceCount
        endmethod
    
        method getTitle takes nothing returns string
            return .name
        endmethod
    
        method getDescription takes nothing returns string
            return .detail
        endmethod
    
        method getEventIndex takes nothing returns QuestEvent
            return .choosenEvent
        endmethod
    
        method getConditionIndex takes nothing returns QuestCondition
            return .choosenCondition
        endmethod
    
        method getContentIndex takes nothing returns QuestContent
            return .choosenContent
        endmethod
    
        method getRewardIndex takes nothing returns QuestReward
            return .choosenReward
        endmethod
    
        /*
            NOT IN USE AT THE MOMENT
        method setEventList takes nothing returns nothing
            local Integer1D i1d         = list[QuestEvent.typeid]
            local integer   i           = 0
        
            loop
                exitwhen i == .eventCount
                set eventList[i]        = i1d[i]
                set i                   = i + 1
            endloop
        
            set eventList[i]            = 0
        endmethod
    
        method setConditionList takes nothing returns nothing
            local Integer1D i1d         = list[QuestCondition.typeid]
            local integer   i           = 0
        
            loop
                exitwhen i == .conditionCount
                set conditionList[i]    = i1d[i]
                set i                   = i + 1
            endloop
        
            set conditionList[i]        = 0
        endmethod
    
        method setContentList takes nothing returns nothing
            local Integer1D i1d         = list[QuestContent.typeid]
            local integer   i           = 0
        
            loop
                exitwhen i == .contentCount
                set contentList[i]      = i1d[i]
                set i                   = i + 1
            endloop
        
            set contentList[i]          = 0
        endmethod
    
        method setRewardList takes nothing returns nothing
            local Integer1D i1d         = list[QuestReward.typeid]
            local integer   i           = 0
        
            loop
                exitwhen i == .rewardCount
                set rewardList[i]       = i1d[i]
                set i                   = i + 1
            endloop
        
            set rewardList[i]           = 0
        endmethod
        */
        //================================================================
    
    
    
        //======================External Methods==========================
        //! runtextmacro optional QEDynamicEffectPath__Quest__Member()
        //! runtextmacro optional QEQuestCateogry__Quest__Member()
        //! runtextmacro optional QEQuestSound__Quest__Member()
        //! runtextmacro optional QEReceiveReturnEvent__Quest__Member()
        //! runtextmacro optional QERandomizeQuest__Quest__Member()
        //! runtextmacro optional QERepeatableQuest__Quest__Member()
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    /*
        /*
            Struct for reducing duplicate code and provide addition supports funtions
        */
    
        Internal API - Internal :
    
            private struct Internal extends array
                static method clearTextMessagesForPlayer takes player user returns nothing
                    - clear text massages for player (user)
              
                static method distanceBetweenCoordinates takes real x, real y, real x2, real y2 returns real
                    - return the distance between (x,y) and (x2,y2)
                
                static method isUnitSlotFull takes unit u returns boolean
                    - return true if unit slot is fulled
                
                static method talkUpdate takes Quest qId, player user, integer subset, PlayerQuestData userQuestData returns nothing
                    - set talk/TALK_2 content in (qId) with subset of (subset) as talked for (user)
                    - content init is not needed as prior caller function initialized talk/TALK_2 content
                
                static acceptQuest takes Quest qId, player user, PlayerQuestData userQuestData returns nothing
                    - register quest (qId) to (user)
                
                static returnQuest takes Quest qId, player user, PlayerQuestData userQuestData returns nothing
                    - return quest (qId) and remove (qId) from (user) quest list
                
                static method checkQuest takes player user, PlayerQuestData userQuestData, unit npc, real npcX, real npcY, integer eventType returns nothing
                    - check if there present receivable/returnable quest for player (user)
                    - (npc) is the unit to check for quest, (npcX) and (npcY) is the coordinates of (npc)
                    - (eventType) is the type of event, included :
                        1. EVENT_QUEST_SELECT
                        2. EVENT_QUEST_NEAR
            
                static method itemDropped takes item it, Quest qId, integer subset, player owner, real itemX, real itemY returns nothing
                    - create item unit at (itemX,itemY) with store info of (qId), (subset) and (owner).
                    - (it) data will be cleared and removed
    */
    private struct Internal extends array
        //===========================Misc=================================
        static method clearTextMessagesForPlayer takes player user returns nothing
            if GetLocalPlayer() == user then
                call ClearTextMessages()
            endif
        endmethod

        static method distanceBetweenCoordinates takes real x, real y, real x2, real y2 returns real
            local real dx = x2 - x
            local real dy = y2 - y
            return SquareRoot(dx * dx + dy * dy)
        endmethod

        //Method for color correction for a string (in case a string contains 1 or more color code)
        static method colorCorrection takes string s, string color returns string
            local integer loopInt = 0
            local integer endInt = StringLength(s)
            local string new = ""
            local string substr

            loop
                set substr = SubString(s, loopInt+1, loopInt+2)
                if SubString(s, loopInt, loopInt+1) == "|" and (substr == "R" or substr == "r" ) then
                    set new = new + color
                    set loopInt = loopInt + 1
                else
                    set new = new + SubString(s, loopInt, loopInt+1)
                endif

                set loopInt = loopInt + 1
                exitwhen loopInt >= endInt
            endloop

            return new + color
        endmethod
    
        static method isUnitSlotFull takes unit u returns boolean
            local integer i = 0
        
            loop
                if UnitItemInSlot(u, i) == null then
                    return false
                endif
                set i = i + 1
                exitwhen i == bj_MAX_INVENTORY
            endloop
        
            return true
        endmethod
    
        static method talkUpdate takes Quest qId, player user, integer subset, PlayerQuestData userQuestData returns nothing
            local Effect e = GetPlayerId(user)
        
            call thistype.clearTextMessagesForPlayer(user)
        
            call DisplayTextToPlayer(user, 0, 0.2, QuestContent.getTalkExDescription() + "\n")
            call userQuestData.updateTalk(qId, subset)
            call userQuestData.removeUnitEffect(qId, subset, QuestContent.getTalkUnit(), e)
        
            if userQuestData.getQuestStatus(qId) == PlayerQuestData.FULLFILL then
                call userQuestData.showFullfillEffect(qId, e, true)
            endif
        endmethod
    
        //functions that filter out unwanted units where trigger will run when selecting a unit
        static method onSelectFilter takes nothing returns boolean
            return GetUnitTypeId(GetFilterUnit()) != DUMMY_ID
        endmethod
        //================================================================
    
    
    
        //====================Check Available Quest=======================
        //check available quest once player completed a quest
        static method checkAvailableQuest takes Quest qId, PlayerQuestData userQuestData returns nothing
            local integer   index   = 0
            local Effect    e       = userQuestData - 1
        
            //! runtextmacro optional QERepeatableQuest__PlayerQuestData__AssignEffect()
        
            call LinkedQuest.setLinkedQuestList(qId)
        
            loop
                set qId = LinkedQuest.questList[index]
                exitwhen qId == 0
            
                if userQuestData.isQuestReceivable(qId) then
                    static if LIBRARY_QEDynamicEffectPath then
                        if qId.getReceivePath() != "" then
                            call userQuestData.applyUnitEffect(qId, -1, qId.getEventIndex().getReceiveUnit(), qId.getReceivePath(), true, e)
                        else
                            call userQuestData.applyUnitEffect(qId, -1, qId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, e)
                        endif
                    else
                        call userQuestData.applyUnitEffect(qId, -1, qId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, e)
                    endif
                
                endif
            
                set index = index + 1
            endloop
        endmethod
        //================================================================
    
    
    
        //==================Accepting/Returning Quest=====================
        static method acceptQuest takes Quest qId, player user, PlayerQuestData userQuestData returns nothing
            //! runtextmacro optional QEQuestCategory__acceptQuest__Check()
        
            call userQuestData.removeUnitEffect(qId, -1, qId.getEventIndex().getReceiveUnit(), Effect(userQuestData-1))
            call PlayerQuestList(userQuestData).add(qId)
            call userQuestData.setQuestStatus(qId, PlayerQuestData.IN_PROGRESS)
            call userQuestData.init(qId, user)
            call DisplayTextToPlayer(user, 0, 0, QUEST_COLOR_GENERAL + "Quest - |r" + qId.getTitle() + QUEST_COLOR_GENERAL + " has been registered into your quest list.")
        
            //! runtextmacro optional QEReceiveReturnEvent__OnReceiveEvent()
            //! runtextmacro optional QEQuestSound__ReceiveSound()
        endmethod
    
        static method returnQuest takes Quest qId, player user, integer userPId, PlayerQuestData userQuestData returns nothing
            local QuestContent  content = qId.getContentIndex()
            local QuestReward   reward  = qId.getRewardIndex()
            local string        s       = QUEST_COLOR_GENERAL + "Quest - |r" + qId.getTitle() + QUEST_COLOR_GENERAL + " has been fully completed."
            local integer       i       = 0
            local integer       i2
        
            if reward != 0 then
                set s = s + "\n\nRewards :"
            
                if reward.getGold() != 0 then
                    call SetPlayerState(user, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(user, PLAYER_STATE_RESOURCE_GOLD) + reward.getGold())
                    set s = s + "\n- " + I2S(reward.getGold()) + " Gold"
                endif

                if reward.getLumber() != 0 then
                    //Reward lumber
                    call SetPlayerState(user, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(user, PLAYER_STATE_RESOURCE_LUMBER) + reward.getLumber())
                    set s = s + "\n- " + I2S(reward.getLumber()) + " Lumber"
                endif

                if reward.getExperience() != 0 then
                    set s = s + "\n- " + I2S(reward.getExperience()) + " Experience"
                    call AddHeroXP(HERO[userPId], reward.getExperience(), true)
                endif

                call reward.setItemList()
            
                loop
                    exitwhen QuestReward.itemList[i] == 0
                    set i2 = 0
                
                    loop
                        exitwhen i2 == QuestReward.itemAmountList[i]
                        set bj_lastCreatedItem = CreateItem(QuestReward.itemList[i], 0, 0)
                        call UnitAddItem(HERO[userPId], bj_lastCreatedItem)
                        set i2 = i2 + 1
                    endloop
                
                    set s = s + "\n- " + QUEST_COLOR_GENERAL
                
                    if QuestReward.itemAmountList[i] > 1 then
                        set s = s + I2S(QuestReward.itemAmountList[i]) + " "
                    endif
                
                    set s = s + "|r" + GetObjectName(QuestReward.itemList[i])
                
                    set i = i + 1
                endloop
            
                //! runtextmacro optional QECustomReward__PlayerQuestData__Reward()
            endif
        
            call userQuestData.removeUnitEffect(qId, -1, qId.getEventIndex().getReturnUnit(), Effect(userQuestData-1))
        
            call PlayerQuestList(userQuestData).remove(qId)
            call userQuestData.setQuestStatus(qId, PlayerQuestData.COMPLETED)
            call userQuestData.removeQuestItem(HERO[userPId], qId)
            call userQuestData.clear(qId)
        
            //! runtextmacro optional QEQuestCategory__returnQuest__Action()
            //! runtextmacro optional QEQuestSound__ReturnSound()
            //! runtextmacro optional QEReceiveReturnEvent__OnReturnEvent()
        
            call thistype.checkAvailableQuest(qId, userQuestData)
            call thistype.clearTextMessagesForPlayer(user)
        
            call DisplayTextToPlayer(user, 0, 0, s)
        endmethod
        //================================================================
    
    
    
        //=============Checking Receivable/Returnable Quest===============
        static method checkQuest takes player user, PlayerQuestData userQuestData, unit npc, real npcX, real npcY, integer eventType returns nothing
            local Unit              npcId               = GetUnitId(npc)
            local integer           maxReceivableQuest  = npcId.getMaxReceivableQuest()
            local integer           maxReturnableQuest  = npcId.getMaxReturnableQuest()
            local integer           index               = 0
        
            local Quest             questId
            local QuestContent      content
            local integer           contentType
            local integer           i
            local boolean           receivable
            local boolean           returnable
    
            set bj_lastCreatedUnit          = null
        
            loop
            /**/exitwhen (index >= maxReceivableQuest) and (index >= maxReturnableQuest)
            /**/
            /**/set questId     = npcId.getReceivableQuest(index)
            /**/
            /**/if questId != 0 then
            /**/    set receivable  = (index < maxReceivableQuest) and (userQuestData.isQuestReceivable(questId)) and (questId.getEventIndex().getReceiveUnit() == npc)
            /**/
            /**/    if receivable then
            /**/        if eventType == EVENT_QUEST_SELECT and questId.getEventIndex().getReceiveEvent() == EVENT_QUEST_SELECT then
            /**/            if bj_lastCreatedUnit == null then
            /**/                set bj_lastCreatedUnit = CreateUnit(user, DUMMY_ID, npcX, npcY, 0.)
            /**/                call SelectUnitForPlayerSingle(bj_lastCreatedUnit, user)
            /**/            endif
            /**/
            /**/            call UnitAddAbility(bj_lastCreatedUnit, Ability.get(questId))
            /**/        elseif eventType == EVENT_QUEST_NEAR and questId.getEventIndex().getReceiveEvent() == EVENT_QUEST_NEAR then
            /**/            if not PlayerQuestList(userQuestData).fulled() then
            /**/                call thistype.acceptQuest(questId, user, userQuestData)
            /**/            else
            /**/                call Internal.clearTextMessagesForPlayer(user)
            /**/                call DisplayTextToPlayer(user, 0, 0, QUEST_LIST_FULL_MESSAGE)
            /**/            endif
            /**/        endif
            /**/    endif
            /**/endif
            /**/
            /**/set questId     = npcId.getReturnableQuest(index)
            /**/
            /**/if questId != 0 then
            /**/    set returnable  = (index < maxReturnableQuest) and (userQuestData.getQuestStatus(questId) == PlayerQuestData.FULLFILL)
            /**/
            /**/    if returnable then
            /**/        if eventType == EVENT_QUEST_SELECT and questId.getEventIndex().getReturnEvent() == EVENT_QUEST_SELECT then
            /**/            if bj_lastCreatedUnit == null then
            /**/                set bj_lastCreatedUnit = CreateUnit(user, DUMMY_ID, npcX, npcY, 0.)
            /**/                call SelectUnitForPlayerSingle(bj_lastCreatedUnit, user)
            /**/            endif
            /**/
            /**/            set i = Ability.get(questId)
            /**/            call UnitAddAbility(bj_lastCreatedUnit, i)
            /**/            call SetUnitAbilityLevel(bj_lastCreatedUnit, i, COMPLETE)
            /**/        elseif eventType == EVENT_QUEST_NEAR and questId.getEventIndex().getReturnEvent() == EVENT_QUEST_NEAR then
            /**/            call thistype.returnQuest(questId, user, GetPlayerId(user), userQuestData)
            /**/        endif
            /**/    endif
            /**/endif
            /**/
            /**/set index = index + 1
            endloop
        
            //Talk quest
            call PlayerQuestList(GetPlayerId(user)+1).setQuestList()
            set index = 0
        
            loop
            /**/set questId     = PlayerQuestList.questList[index]
            /**/exitwhen PlayerQuestList.questList[index] == 0
            /**/set content     = PlayerQuestList.questList[index].getContentIndex()
            /**/set i           = 0
            /**/
            /**/loop
            /**/    set contentType = content.getContentType(i)
            /**/    exitwhen contentType == 0
            /**/
            /**/    if contentType == QuestContent.TALK or contentType == QuestContent.TALK_2 then
            /**/        call content.initContent(i)
            /**/   
            /**/        if npc == QuestContent.getTalkUnit() and not userQuestData.isUnitTalked(questId, i) then
            /**/            if contentType == QuestContent.TALK then
            /**/                if eventType == EVENT_QUEST_SELECT then
            /**/                    if bj_lastCreatedUnit == null then
            /**/                        set bj_lastCreatedUnit = CreateUnit(user, DUMMY_ID, npcX, npcY, 0.)
            /**/                        call SelectUnitForPlayerSingle(bj_lastCreatedUnit, user)
            /**/                    endif
            /**/
            /**/                    call UnitAddAbility(bj_lastCreatedUnit, QuestContent.getTalkAbilityId())
            /**/                endif
            /**/            else
            /**/                call thistype.talkUpdate(questId, user, i, userQuestData)
            /**/            endif
            /**/        endif
            /**/    endif
            /**/
            /**/    set i = i + 1
            /**/endloop
            /**/
            /**/set index = index + 1
            endloop
        endmethod
        //================================================================
    
    
    
        //======================Unit Dropping Item========================
        static method itemDropped takes item it, Quest qId, integer subset, player owner, real itemX, real itemY returns nothing
            local integer           contentType     = qId.getContentIndex().getContentType(subset)
            local PlayerQuestData   userQuestData   = GetPlayerId(owner) + 1
            //! runtextmacro optional QEQuestItem__OnDrop2__LocalVar()
        
            set bj_lastCreatedUnit = PlayerQuestData.createQuestItem(itemX, itemY, qId, subset, owner)
        
            if contentType == QuestContent.PICK_UP or contentType == QuestContent.KILL_OBTAIN_ITEM then
                //put in condition is because quest type of "CUSTOM_CONTENT" shouldn't be reverted
                //automatically
                if userQuestData.getQuestStatus(qId) == PlayerQuestData.FULLFILL then
                    call userQuestData.revertQuestStatus(qId)
                endif
            
                call userQuestData.decreasePickAmount(qId, subset)
            endif
        
            //! runtextmacro optional QEQuestItem__OnDrop2__AddInstance()
        
            call Item.clearData(it)
            call RemoveItem(it)
        endmethod
        //================================================================
    endstruct
/**************************************************************************************************/





/**************************************************************************************************/
    //Placed here cause it needed PlayerQuestData and Internal
    //! runtextmacro optional QECustomContent__CustomContent()
    //! runtextmacro optional QEQuestItem__QuestItem()
/**************************************************************************************************/





/**************************************************************************************************/
/***************************************End Of Structs*********************************************/
/**************************************************************************************************/

    globals
        //Globals used by OnNear()
        private trigger nearTrigger = CreateTrigger()
        private group nearGroup = CreateGroup()
    
        //Globals used by OnSelect()
        private trigger selectTrigger = CreateTrigger()
    
        //Globals used by OnAccept()
        private trigger acceptTrigger = CreateTrigger()
    
        //Globals used by OnDeselect()
        private trigger deselectTrigger = CreateTrigger()
    
        //Globals used by OnDeath()
        private trigger deathTrigger = CreateTrigger()
    
        //Globals used by OnDrop()
        //"OnDrop" is just a prefix which reffering to OnDrop function
        private trigger dropTrigger = CreateTrigger()
        private item onDropItem
    
        //Globals used by OnUse()
        private trigger useTrigger = CreateTrigger()
        private boolean ItemOnUse = false
    
        //Globals used by OnPick()
        private trigger pickTrigger = CreateTrigger()
    
        //Globals used by OnLevel()
        private trigger levelTrigger = CreateTrigger()
    
        //Globals used by OnOrder()
        private trigger orderTrigger = CreateTrigger()
    
        //Globals used by OnEnterReg()
        private trigger enterRegTrigger = CreateTrigger()
    endglobals
    //==============================================================================================



    //==============================================================================================
    /*
        OnClick function :
    */
    private function OnClick takes nothing returns boolean
        local player            user            = DialogEx.getPlayer()
        local integer           structIndex     = GetPlayerId(user) + 1
    
        local Quest             questId         = DialogEx(structIndex).getClickedButtonQuestId()
        local string            s

        //Set the number of lines that the quest have so that quest can display at proper place
        //Default number of line (assuming description and requirement take 1 line only) : 5
        if questId != 0 then
            set s = PlayerQuestData(structIndex).getFullDetails(questId, structIndex)
            //
            call DisplayTextToPlayer(user, GetTextDisplayX(), 0, s)
        endif

        set user = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        DisplayQuestList function (get replced by new one if enabled QEQuestCategory) :
    */
    static if not LIBRARY_QEQuestCategory then

    private function DisplayQuestList takes player user, integer userPId returns nothing
        local PlayerQuestList   userQuestList   = userPId + 1
        local DialogEx          log             = userPId + 1
        local integer           i               = 0
    
        local Quest             questId
        local string            s
    
        call Internal.clearTextMessagesForPlayer(user)
    
        if userQuestList.isEmpty() then
            call DisplayTextToPlayer(user, 0, 0, QUEST_COLOR_GENERAL + "Currently you don't have any registered quest inside your list")
        else
            call log.clear()
            set log.title = "Quest List"
            call userQuestList.setQuestList()

            loop
                set questId = PlayerQuestList.questList[i]
                exitwhen questId == 0
            
                set s = DIALOG_HOTKEY_HIGHLIGHT + I2S(i+1) + " |r"

                //check if quest name is colored. If not, add QUEST_DEFAULT_COLOR
                if SubString(questId.getTitle(), 0, 1) != "|" then
                    set s = s + QUEST_DEFAULT_COLOR
                endif

                set s = s + questId.getTitle()

                if PlayerQuestData(userQuestList).getQuestStatus(questId) == PlayerQuestData.FULLFILL then
                    set s = s + " (Completed)|r"
                endif

                call log.addButton(s, DIALOG_HOTKEY[i], questId)
                set i = i + 1
            endloop
        
            call log.addButton("Cancel", DIALOG_HOTKEY[MAX_QUEST_PER_PLAYER], 0)
            call log.display(user)
        endif
    endfunction

    else

    //! runtextmacro optional QuestCategoryDisplayTitle()

    endif
    //==============================================================================================



    //==============================================================================================
    /*
        OnAccept function :
    */
    private function OnAccept takes nothing returns boolean
        local unit              triggerUnit         = GetTriggerUnit()
    
        local PlayerQuestData   userQuestData
        local PlayerQuestList   userQuestList
        local Quest             questId
        local player            user                = GetTriggerPlayer()
        local integer           userPId             = GetPlayerId(user)
        local integer           subset
        local integer           abilityId

        if GetUnitTypeId(triggerUnit) == DUMMY_ID then
            set abilityId                           = GetSpellAbilityId()
            set questId                             = Ability.getQuestId(abilityId)
            set userQuestData                       = userPId + 1
        
        
            //for receiving/returning quest
            if questId != 0 then
            /**/call SelectUnitForPlayerSingle(HERO[userPId], user)
            /**/set userQuestList                   = userPId + 1
            /**/
            /**/if GetUnitAbilityLevel(triggerUnit, abilityId) == PlayerQuestData.FULLFILL then
            /**/    call Internal.returnQuest(questId, user, userPId, userQuestData)
            /**/elseif not userQuestList.fulled() then
            /**/    call Internal.acceptQuest(questId, user, userQuestData)
            /**/else
            /**/    call Internal.clearTextMessagesForPlayer(user)
            /**/    call DisplayTextToPlayer(user, 0, 0, QUEST_LIST_FULL_MESSAGE)
            /**/endif
            endif
        
            set questId                         = Ability.getTalkQuestId(abilityId)
        
            //for talk content type
            if questId != 0 then
            /**/call SelectUnitForPlayerSingle(HERO[userPId], user)
            /**/set subset = Ability.getTalkSubset(abilityId)
            /**/
            /**/call questId.getContentIndex().initContent(subset)
            /**/call Internal.talkUpdate(questId, user, subset, userQuestData)
            endif
        //! runtextmacro optional QEQuestCategory__OnAccept__Display()
        endif
    
        set user = null
        set triggerUnit = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnSelect function :
    */
    private function OnSelect takes nothing returns boolean
        local unit              selectedUnit        = GetTriggerUnit()
        local player            user                = GetTriggerPlayer()
        local integer           userPId             = GetPlayerId(user)
        local real              selectedUnitX       = GetUnitX(selectedUnit)
        local real              selectedUnitY       = GetUnitY(selectedUnit)
    
        if selectedUnit == QUEST_INFO[userPId] then
            call SetUnitX(selectedUnit, GetUnitX(HERO[userPId]))
            call SetUnitY(selectedUnit, GetUnitY(HERO[userPId]))
        
            static if not LIBRARY_QEQuestCategory then
                call SelectUnitForPlayerSingle(HERO[userPId], user)
                call DisplayQuestList(user, userPId)
            endif
        elseif GetUnitTypeId(selectedUnit) == QUEST_ITEM_ID and Unit(GetUnitId(selectedUnit)).getOwner() != user then
            call ClearSelectionForPlayer(user)
        elseif selectedUnit != HERO[userPId] and Internal.distanceBetweenCoordinates(GetUnitX(HERO[userPId]), GetUnitY(HERO[userPId]), selectedUnitX, selectedUnitY) <= MAX_SELECT_RANGE then
            call Internal.checkQuest(user, userPId+1, selectedUnit, selectedUnitX, selectedUnitY, EVENT_QUEST_SELECT)
        endif
    
        set selectedUnit = null
        set user = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnDeselect function :
    */
    private function OnDeselect takes nothing returns boolean
        local unit deselectedUnit = GetTriggerUnit()
    
        if GetUnitTypeId(deselectedUnit) == DUMMY_ID then
            call RemoveUnit(deselectedUnit)
        endif
    
        set deselectedUnit = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnNear function :
    */
    private function OnNear takes nothing returns boolean
        local integer           userPId             = 0
        local unit              pickedUnit
    
        loop
            exitwhen userPId == bj_MAX_PLAYERS
        
            if HERO[userPId] != null then
                call GroupEnumUnitsInRange(nearGroup, GetUnitX(HERO[userPId]), GetUnitY(HERO[userPId]), NEAR_RANGE, null)
                set pickedUnit = null
            
                loop
                    set pickedUnit = FirstOfGroup(nearGroup)
                    exitwhen pickedUnit == null
                
                    if  pickedUnit != HERO[userPId] and GetPlayerController(GetOwningPlayer(pickedUnit)) != MAP_CONTROL_USER then
                        call Internal.checkQuest(Player(userPId), userPId+1, pickedUnit, 0, 0, EVENT_QUEST_NEAR)
                    endif
                
                    call GroupRemoveUnit(nearGroup, pickedUnit)
                endloop
            endif
        
            set userPId = userPId + 1
        endloop
    
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnDeath function :
    */
    private function OnDeath takes nothing returns boolean
        local unit              diedUnit        = GetTriggerUnit()
        local integer           diedUnitType    = GetUnitTypeId(diedUnit)
        local player            user            = GetOwningPlayer(GetKillingUnit())
        local integer           userPId         = GetPlayerId(user)
        local string            s                = ""    //string for storing any updated content
        local string            s2                = ""    //string for storing any completed quest
        local integer           i               = 0
    
        local PlayerQuestData   userQuestData   = userPId + 1
        local Quest             questId
        local QuestContent      content
        local integer           contentType
        local integer           subset
        local integer           maxSubset
        local integer           curAmount
        local integer           maxAmount

        call PlayerQuestList(userQuestData).setQuestList()
    
        loop
            set questId = PlayerQuestList.questList[i]
            exitwhen questId == 0
        
            set content = questId.getContentIndex()
            set subset = 0
            set maxSubset = content.getRegisteredAmount()
        
            loop
                exitwhen subset == maxSubset
                set contentType = content.getContentType(subset)
            
                if not userQuestData.isSubsetCompleted(questId, subset) and content.initContentCond(contentType != QuestContent.PICK_UP and contentType != QuestContent.GOTO and contentType != QuestContent.TALK and contentType != QuestContent.TALK_2, subset) != 0 then
                /**/if contentType == QuestContent.KILL then
                /**/    if QuestContent.getKillRequiredUnitType() == diedUnitType then
                /**/        call userQuestData.updateKillAmount(questId, subset)
                /**/
                /**/        set s = s + questId.getTitle() + QUEST_COLOR_GENERAL + " : |r" + GetUnitName(diedUnit)
                /**/        set curAmount = userQuestData.getKillAmount(questId, subset)
                /**/        set maxAmount = QuestContent.getKillRequiredAmount()
                /**/
                /**/        if userQuestData.isSubsetCompleted(questId, subset) then
                /**/            set s = s + QUEST_COLOR_COMPLETE
                /**/        else
                /**/            set s = s + QUEST_COLOR_INCOMPLETE
                /**/        endif
                /**/
                /**/        set s = s + " (" + I2S(curAmount) + "/" + I2S(maxAmount) + ")|r\n"
                /**/
                /**/        if userQuestData.getQuestStatus(questId) == PlayerQuestData.FULLFILL then
                /**/            call userQuestData.showFullfillEffect(questId, userPId, false)
                /**/
                /**/            set s2 = s2 + QUEST_COLOR_GENERAL + "Quest - |r" + questId.getTitle() + QUEST_COLOR_GENERAL + " has been completed|r\n"
                /**/        endif
                /**/    endif
                /**/elseif contentType == QuestContent.KILL_OBTAIN then
                /**/    if QuestContent.getKillObtainRequiredUnitType() == diedUnitType and GetRandomReal(0., 1.) <= QuestContent.getKillObtainChance() then
                /**/        call userQuestData.updateObtainAmount(questId, subset)
                /**/   
                /**/        set s = s + questId.getTitle() + QUEST_COLOR_GENERAL + " : Obtained |r" + QuestContent.getKillObtainItemName()
                /**/        set curAmount = userQuestData.getObtainAmount(questId, subset)
                /**/        set maxAmount = QuestContent.getKillObtainRequiredAmount()
                /**/
                /**/        if userQuestData.isSubsetCompleted(questId, subset) then
                /**/            set s = s + QUEST_COLOR_COMPLETE
                /**/        else
                /**/            set s = s + QUEST_COLOR_INCOMPLETE
                /**/        endif
                /**/
                /**/        set s = s + " (" + I2S(curAmount) + "/" + I2S(maxAmount) + ")|r\n"
                /**/
                /**/        if userQuestData.getQuestStatus(questId) == PlayerQuestData.FULLFILL then
                /**/            call userQuestData.showFullfillEffect(questId, userPId, false)
                /**/
                /**/            set s2 = s2 + QUEST_COLOR_GENERAL + "Quest - |r" + questId.getTitle() + QUEST_COLOR_GENERAL + " has been completed|r\n"
                /**/        endif
                /**/    endif
                /**/elseif contentType == QuestContent.KILL_OBTAIN_ITEM then
                /**/    if QuestContent.getKillObtainItemRequiredUnitType() == diedUnitType and GetRandomReal(0., 1.) <= QuestContent.getKillObtainItemChance() then
                /**/        call userQuestData.updateDropAmount(questId, subset)
                /**/   
                /**/        call PlayerQuestData.createQuestItem(GetUnitX(diedUnit), GetUnitY(diedUnit), questId, subset, user)
                /**/    endif
                /**///! runtextmacro optional QEGroupKill__OnDeath__Update()
                /**/endif
                endif
            
                set subset = subset + 1
            endloop
        
            set i = i + 1
        endloop
    
        if s != "" then
            call Internal.clearTextMessagesForPlayer(user)
            call DisplayTextToPlayer(user, 0, 0, s + "\n" + s2)
        endif

        set diedUnit    = null
        set user        = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnDrop and OnDrop2 functions :
    
            OnDrop2 exists due to GetItemX/Y will return item's previous position value, hence a
            0 second timer is required
    */

    private function OnDrop2 takes nothing returns nothing
        local Quest             questId         = Item.getQuestId()
    
        if questId != 0 and not IsItemOwned(onDropItem) then
            call Internal.itemDropped(onDropItem, questId, Item.getSubset(), Item.getOwner(), GetItemX(onDropItem), GetItemY(onDropItem))
        endif
    endfunction

    private function OnDrop takes nothing returns boolean
        if not ItemOnUse then
            set onDropItem = GetManipulatedItem()
            call Item.setData(onDropItem)
        
            if Item.getQuestId() != 0 then
                call TimerStart(NewTimer(), 0., false, function OnDrop2)
            endif
        endif
    
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnUse and ResetOnUse functions :
    
            Exists due to when using a quest item, it will trigger OnDrop, which is not intended
            behaviour.
    */

    private function ResetOnUse takes nothing returns nothing
        set ItemOnUse = false
    endfunction

    private function OnUse takes nothing returns boolean
        set ItemOnUse = true
        call TimerStart(NewTimer(), 0, false, function ResetOnUse)
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnPick functions :
            For detecting if a player passes quest item to another player
    */
    private function OnPick takes nothing returns boolean
        local item it = GetManipulatedItem()
        local player owner
    
        local Quest questId
        local integer userPId
    
        call Item.setData(it)
    
        set questId             = Item.getQuestId()
        set owner               = Item.getOwner()
    
        if questId != 0 and owner != GetTriggerPlayer() then
            set userPId = GetPlayerId(owner)
            call Internal.itemDropped(it, questId, Item.getSubset(), owner, GetUnitX(HERO[userPId]), GetUnitY(HERO[userPId]))
        endif
    
        set it = null
        set owner = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        PickUp (unit), OnPickTimeout and OnOrder functions :
    */
    private function PickUp takes unit orderedUnit, unit targetUnit returns nothing
        local player            user            = GetOwningPlayer(orderedUnit)
        local integer           userPId         = GetPlayerId(user)
    
        local PlayerQuestData   userQuestData   = userPId + 1
        local Unit              itemDummy       = GetUnitId(targetUnit)
        local Quest             questId
        local QuestContent      content
        local integer           contentType
        local integer           subset
        local integer           pickedAmount
        local integer           requirePickAmount
        local integer           itemTypeId
        local string s
        local string s2
        //! runtextmacro optional QEQuestItem__PickUp__LocalVar()
    
        if not Internal.isUnitSlotFull(orderedUnit) then
            set questId     = itemDummy.getQuestId()
            set subset      = itemDummy.getSubset()
            set content     = questId.getContentIndex()
            set contentType = content.initContent(subset)
        
            if contentType == QuestContent.PICK_UP then
            /**/call userQuestData.updatePickAmount(questId, subset)
            /**/
            /**/set pickedAmount        = userQuestData.getPickAmount(questId, subset)
            /**/set requirePickAmount   = QuestContent.getPickUpRequiredAmount()
            /**/set itemTypeId          = QuestContent.getPickUpItemTypeId()
            /**/set s                   = questId.getTitle() + QUEST_COLOR_GENERAL + " : |r" + GetObjectName(itemTypeId)
            /**/
            /**/if userQuestData.isSubsetCompleted(questId, subset) then
            /**/    set s = s + QUEST_COLOR_COMPLETE
            /**/else
            /**/    set s = s + QUEST_COLOR_INCOMPLETE
            /**/endif
            /**/
            /**/set s = s + " (" + I2S(pickedAmount) + "/" + I2S(requirePickAmount) + ")"
            /**/
            /**/call Internal.clearTextMessagesForPlayer(user)
            /**/
            /**/if userQuestData.getQuestStatus(questId) == PlayerQuestData.FULLFILL then
            /**/    call userQuestData.showFullfillEffect(questId, userPId, false)
            /**/
            /**/    set s = s + "\n" + QUEST_COLOR_GENERAL + "Quest - |r" + questId.getTitle() + QUEST_COLOR_GENERAL + " has been completed|r"
            /**/endif
            /**/
            /**/call DisplayTextToPlayer(user, 0, 0, s)
            elseif contentType == QuestContent.KILL_OBTAIN_ITEM then
            /**/call userQuestData.updatePickAmount(questId, subset)
            /**/
            /**/set pickedAmount        = userQuestData.getPickAmount(questId, subset)
            /**/set requirePickAmount   = QuestContent.getKillObtainItemRequiredAmount()
            /**/set itemTypeId          = QuestContent.getKillObtainItemItemTypeId()
            /**/set s                   = questId.getTitle() + QUEST_COLOR_GENERAL + " : |r" + GetObjectName(itemTypeId)
            /**/
            /**/if userQuestData.isSubsetCompleted(questId, subset) then
            /**/    set s = s + QUEST_COLOR_COMPLETE
            /**/else
            /**/    set s = s + QUEST_COLOR_INCOMPLETE
            /**/endif
            /**/
            /**/set s = s + " (" + I2S(pickedAmount) + "/" + I2S(requirePickAmount) + ")"
            /**/
            /**/call Internal.clearTextMessagesForPlayer(user)
            /**/
            /**/if userQuestData.getQuestStatus(questId) == PlayerQuestData.FULLFILL then
            /**/    call userQuestData.showFullfillEffect(questId, userPId, false)
            /**/
            /**/    set s = s + "\n" + QUEST_COLOR_GENERAL + "Quest - |r" + questId.getTitle() + QUEST_COLOR_GENERAL + " has been completed|r"
            /**/endif
            /**/
            /**/call DisplayTextToPlayer(user, 0, 0, s)
            //! runtextmacro optional QEQuestItem__PickUp__AdditionType()
            endif
        
            call itemDummy.clear()
            call RemoveUnit(targetUnit)
            set bj_lastCreatedItem = CreateItem(itemTypeId, 0, 0)
            call UnitAddItem(orderedUnit, bj_lastCreatedItem)
            call Item.assignData(bj_lastCreatedItem, questId, subset, user)
            //! runtextmacro optional QEQuestItem__PickUp__AddInstance()
        else
            call DisplayTextToPlayer(user, 0, 0, QUEST_COLOR_GENERAL + "Your inventory is fulled.|r")
        endif
    
        set user = null
    endfunction

    private function OnPickTimeout takes nothing returns nothing
        local Timer t = GetTimerData(GetExpiredTimer())
        local unit orderedUnit = GetUnitById(t)
        local real distance = Internal.distanceBetweenCoordinates(GetUnitX(orderedUnit), GetUnitY(orderedUnit), t.getTargetUnitX(), t.getTargetUnitY())
        local real time = (distance-PICK_UP_RANGE)/t.getOrderedUnitMoveSpeed()
    
        if distance <= PICK_UP_RANGE or time <= MIN_TIME_OUT then
            call DisableTrigger(orderTrigger)
            call IssueImmediateOrder(orderedUnit, "stop")
            call EnableTrigger(orderTrigger)

            call PickUp(orderedUnit, t.getTargetUnit())

            call t.terminate()
        else
            call t.restart(time, function OnPickTimeout)
        endif

        set orderedUnit = null
    endfunction

    private function OnOrder takes nothing returns boolean
        local unit orderedUnit  = GetTriggerUnit()
        local unit targetUnit   = GetOrderTargetUnit()
    
        local Timer t           = GetUnitId(orderedUnit)
        local real distance
        local real time
        local real x
        local real y
        local real speed
    
        if GetUnitTypeId(targetUnit) == QUEST_ITEM_ID then
            set x = GetUnitX(targetUnit)
            set y = GetUnitY(targetUnit)
        
            call DisableTrigger(orderTrigger)
            call IssuePointOrder(orderedUnit, "move", x, y)
            call EnableTrigger(orderTrigger)
        
            if not Timer.isUnitInGroup(orderedUnit) then
                set distance = Internal.distanceBetweenCoordinates(GetUnitX(orderedUnit), GetUnitY(orderedUnit), x, y)
                set speed    = GetUnitMoveSpeed(orderedUnit)
            
                if distance > PICK_UP_RANGE then
                    set time = (distance-PICK_UP_RANGE)/speed
                else
                    set time = 0.
                endif
            
                call Timer(GetUnitId(orderedUnit)).start(targetUnit, speed, time, function OnPickTimeout)
            endif
        elseif Timer.isUnitInGroup(orderedUnit) then
            call t.terminate()
        endif
    
        set orderedUnit = null
        set targetUnit  = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnEnterReg function :
    */
    private function OnEnterReg takes nothing returns boolean
        local region            enteredReg      = GetTriggeringRegion()
        local player            user            = GetOwningPlayer(GetTriggerUnit())
        local integer           i               = GetPlayerId(user) + 1
    
        local PlayerQuestData   userQuestData   = PlayerQuestData(i)
        local Quest             questId         = Region.getQuestId(enteredReg)
        local integer           subset
    
        if questId != 0 then
            if PlayerQuestList(i).received(questId) then
                set subset = Region.getSubset(enteredReg)
            
                if not userQuestData.isPlaceWent(questId, subset) then
                    call questId.getContentIndex().initContent(subset)
                    call Effect(i-1).removeRect(QuestContent.getGotoRect())
                    call userQuestData.updateGoto(questId, subset)
                    call Internal.clearTextMessagesForPlayer(user)
                    call DisplayTextToPlayer(user, 0, 0, questId.getTitle() + QUEST_COLOR_GENERAL + " : " + QuestContent.getGotoCompleteDescription())
                
                    if userQuestData.getQuestStatus(questId) == PlayerQuestData.FULLFILL then
                        call userQuestData.showFullfillEffect(questId, i-1, true)
                    endif
                endif
            endif
        endif
    
        set enteredReg  = null
        set user        = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    /*
        OnLevel function :
    */
    private function OnLevel takes nothing returns boolean
        local unit              leveledUnit     = GetTriggerUnit()
        local player            user            = GetOwningPlayer(leveledUnit)
        local integer           userPId         = GetPlayerId(user)
        local integer           index           = 0
    
        local PlayerQuestData   userQuestData
        local Quest             questId
        local Level             lv

    
        if leveledUnit == HERO[userPId] then
            set lv = GetHeroLevel(HERO[userPId])
            set userQuestData = userPId + 1
        
            loop
                exitwhen index == lv.getMaxQuestCount()
                set questId = lv.getQuestId(index)
            
                if userQuestData.isQuestReceivable(questId) then
                    static if LIBRARY_QEDynamicEffectPath then
                        if questId.getReceivePath() != "" then
                            call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), questId.getReceivePath(), true, Effect(userPId))
                        else
                            call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, Effect(userPId))
                        endif
                    else
                        call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, Effect(userPId))
                    endif
                endif
            
                set index = index + 1
            endloop
        endif
    
        set leveledUnit = null
        set user = null
        return false
    endfunction
    //==============================================================================================



    //==============================================================================================
    //Init functions
    private function InitQuests takes integer userPId returns nothing
        local Quest             questId
        local Level             lv              = GetHeroLevel(HERO[userPId])
        local Effect            e               = userPId
    
        local PlayerQuestData   userQuestData   = userPId + 1
        local integer           index
    
        if integer(lv) > 1 then
            set userQuestData = userPId + 1
        
            loop
                exitwhen lv == 1
            
                set index = 0
                loop
                    exitwhen index == lv.getMaxQuestCount()
                    set questId = lv.getQuestId(index)
                
                    if userQuestData.isQuestReceivable(questId) then
                        static if LIBRARY_QEDynamicEffectPath then
                            if questId.getReceivePath() != "" then
                                call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), questId.getReceivePath(), true, e)
                            else
                                call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, e)
                            endif
                        else
                            call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, e)
                        endif
                    endif
                
                    set index = index + 1
                endloop
            
                set lv = lv - 1
            endloop
        endif
    
        set questId = 1
    
        loop
            exitwhen integer(questId) > Quest.getQuestCount()
        
            if questId.getConditionIndex() == 0 then
                static if LIBRARY_QEDynamicEffectPath then
                    if questId.getReceivePath() != "" then
                        call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), questId.getReceivePath(), true, e)
                    else
                        call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, e)
                    endif
                else
                    call userQuestData.applyUnitEffect(questId, -1, questId.getEventIndex().getReceiveUnit(), EFFECT_PATH_RECEIVE, true, e)
                endif
            endif
        
            set questId = questId + 1
        endloop
    endfunction
    //==============================================================================================



    //==============================================================================================
    //API
    function QuestAddUnit takes unit u returns nothing
        local player  user    = GetOwningPlayer(u)
        local integer userPId = GetPlayerId(user)
    
        //debug call ThrowWarning(HERO[userPId] != null, "QuestSystem", "", "QuestAddUnit", 0, "Player("+I2S(userPId)+") already have registered hero!")
        set HERO[userPId] = u
        set QUEST_INFO[userPId] = CreateUnit(user, QUEST_INFO_ID, GetUnitX(HERO[userPId]), GetUnitY(HERO[userPId]), 0)
    
        static if LIBRARY_QEQuestCategory then
            call QuestCategory.setUp(QUEST_INFO[userPId])
        endif
    
        call InitQuests(userPId)
        set user = null
    endfunction

    function IsQuestReceivedByPlayer takes player p, Quest qId returns boolean
        return PlayerQuestList(GetPlayerId(p)+1).received(qId)
    endfunction

    function IsQuestBeenCompleted takes player p, Quest qId returns boolean
        return PlayerQuestData(GetPlayerId(p)+1).beenCompleted(qId)
    endfunction

    //GetTriggerQuestId() and GetTriggerPlayerId() is only used by some extension, which nessasary
    //to pass in quest id and player id for user to use
    function GetTriggerQuestId takes nothing returns Quest
        return TriggerQuestId
    endfunction

    function GetTriggerPlayerId takes nothing returns integer
        return TriggerPlayerId
    endfunction

    //! runtextmacro optional QECustomCondition_ReceiveEffect()
    //==============================================================================================



    //==============================================================================================
    private function Init takes nothing returns nothing
        local integer i             = 0
        local player p

        call TriggerRegisterTimerEvent(nearTrigger, NEAR_TIMER_TIMEOUT, true)
    
        loop
            set p = Player(i)

            call TriggerRegisterPlayerUnitEvent(selectTrigger   , p, EVENT_PLAYER_UNIT_SELECTED           , Filter(function Internal.onSelectFilter))
            call TriggerRegisterPlayerUnitEvent(acceptTrigger   , p, EVENT_PLAYER_UNIT_SPELL_EFFECT       , null)
            call TriggerRegisterPlayerUnitEvent(deselectTrigger , p, EVENT_PLAYER_UNIT_DESELECTED         , null)
            call TriggerRegisterPlayerUnitEvent(deathTrigger    , p, EVENT_PLAYER_UNIT_DEATH              , null)
            call TriggerRegisterPlayerUnitEvent(dropTrigger     , p, EVENT_PLAYER_UNIT_DROP_ITEM          , null)
            call TriggerRegisterPlayerUnitEvent(useTrigger      , p, EVENT_PLAYER_UNIT_USE_ITEM           , null)
            call TriggerRegisterPlayerUnitEvent(pickTrigger     , p, EVENT_PLAYER_UNIT_PICKUP_ITEM        , null)
            call TriggerRegisterPlayerUnitEvent(levelTrigger    , p, EVENT_PLAYER_HERO_LEVEL              , null)
            call TriggerRegisterPlayerUnitEvent(orderTrigger    , p, EVENT_PLAYER_UNIT_ISSUED_ORDER       , null)
            call TriggerRegisterPlayerUnitEvent(orderTrigger    , p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER , null)
            call TriggerRegisterPlayerUnitEvent(orderTrigger    , p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)

            //Init dialog
            call DialogEx.create().registerClickEvent(Condition(function OnClick))

            set i                   = i + 1
        
            exitwhen i == bj_MAX_PLAYERS
        endloop

        loop
            set p = Player(i)

            call TriggerRegisterPlayerUnitEvent(deathTrigger    , p, EVENT_PLAYER_UNIT_DEATH                , null)

            set i = i + 1
            exitwhen i == bj_MAX_PLAYER_SLOTS
        endloop

        call TriggerAddCondition(selectTrigger  , Condition(function OnSelect)     )
        call TriggerAddCondition(nearTrigger    , Condition(function OnNear)       )
        call TriggerAddCondition(acceptTrigger  , Condition(function OnAccept)     )
        call TriggerAddCondition(deselectTrigger, Condition(function OnDeselect)   )
        call TriggerAddCondition(deathTrigger   , Condition(function OnDeath)      )
        call TriggerAddCondition(dropTrigger    , Condition(function OnDrop)       )
        call TriggerAddCondition(useTrigger     , Condition(function OnUse)        )
        call TriggerAddCondition(pickTrigger    , Condition(function OnPick)       )
        call TriggerAddCondition(levelTrigger   , Condition(function OnLevel)      )
        call TriggerAddCondition(enterRegTrigger, Condition(function OnEnterReg)   )
        call TriggerAddCondition(orderTrigger   , Condition(function OnOrder)      )
        call InitEx()

        static if not LIBRARY_QEDynamicEvent then
            if EVENT_QUEST_DEFAULT == EVENT_QUEST_SELECT then
                call DestroyTrigger(nearTrigger)
            endif
        endif
    
        set p = null
    endfunction
    //==============================================================================================
endlibrary


Optional Codes :

Optional codes are the code that provide addition features toward the basic quest system. Currently, there are total 13 configurable optional triggers.

Closed

Main Optional Code


JASS:
library QuestExtension requires /*
*====================================Quest System Extension========================================*
*                                                                                               
*    System Description :                                                                       
*        Quest system extension that extends the ability of original Quest System, enable more
*        features to be available apart from the basic quest features.
*                                                                                               
*===================================================================================================*
*
*    Version
*        v1.0.0
*===================================================================================================*
*    Configuration :
*
*        For information hiding reason, I've decided to split out each part into a single trigger thread
*        To enable/disable a specific feature, simply enable/disable the trigger correspond to the feature.
*        The trigger name will be same as the library name as listed below
*
*        Details for each features are written in each of the corresponding trigger thread
*
*    1.    */optional QECustomCondition    /*
*
*    2.    */optional QECustomContent        /*
*
*    3    */optional QECustomReward        /*
*
*    4.    */optional QEDynamicEffectPath    /*
*
*    5.    */optional QEDynamicEnable        /*
*
*    6.    */optional QEDynamicEvent        /*
*
*    7.    */optional QEGroupKill            /*
*
*    8.    */optional QEQuestCategory        /*
*
*    9.    */optional QEQuestItem            /*
*
*    10.    */optional QEReceiveReturnEvent    /*
*
*    11.    */optional QERanomdizeQuest        /*
*
*    12.    */optional QERepeatableQuest    /*
*
*   13. */optional QEQuestSound         /*
*
*===================================================================================================*/
endlibrary

Because It is too long to post all optional triggers at here, download the test map for more details info.


Change Log :

General changes :
  • Restructuring of whole Quest System
    • Reconstruction for more intuitive API
    • Code readability and flexibility improvement
    • Code optimization
  • Changes to quest requirements.
  • Effects from now on will be applied for only one time for each units and rects.
    • Return effect will have priority over receive effect
    • For quest with dynamic effects, effects are stored in order and destroy in order
  • From now on supports load function (loaded unit which then added into the QuestSystem will be able to receive quest properly)
  • From now on able to support custom inventory system (using optional feature QE QuestItem)
  • Temporary removed debug operations, it will be added back later
System internal changes (QuestSystem):
  • From now on all methods will be non-static methods (excluded Quest.create() method)
    • Quest :
      • Added method createEvent takes nothing returns nothing
      • Added method createCondition takes nothing returns nothing
      • Added method createContent takes nothing returns nothing
      • Added method createReward takes nothing returns nothing
      • Renamed and modified method isReceived takes player p returns boolean to function IsQuestReceivedByPlayer takes player p, Quest qId returns boolean
      • Renamed and modified method isCompleted takes player p returns boolean to function IsQuestBeenCompleted takes player p, Quest qId returns boolean
    • QuestEvent :
      • Added method operator receiveUnit= takes unit receiveU returns nothing
      • Added method operator returnUnit= takes unit returnU returns nothing
      • Renamed static method operator questAbilityId= takes integer i returns nothing to method operator abilityId= takes integer aId returns nothing
    • QuestCondition :
      • Renamed static method operator addLevel= takes integer levelRequired returns nothing to method operator level= takes integer rqLevel returns nothing
    • QuestContent :
      • Renamed static method registerKillUnit takes integer unitType, integer noToKill returns nothing to method registerKill takes integer unitType, integer noToKill returns nothing
      • static method registerTalk takes unit u, integer aId, string extraDescription, string extraDescription2, string effectPath returns nothing changed to method registerTalk takes unit u, integer aId, string description, string exDescription returns nothing
      • registerTalk2 has similar changes as registerTalk
      • registerPickUp's parameter : extraDescription, has been changed to description, which will be displayed in Quest Info
      • registerGoto's parameter : effectPath, has been removed
      • ----------------------------------------------------------------------
      • description : Description will be displayed in Quest Info
      • exDescription : Description will be displayed once completed this content
    • QuestReward :
      • Renamed static method item takes integer itemId, integer amount returns nothing to method addItem takes integer itemId, integer amount returns nothing
  • Now user will no longer be self configure HERO, instead replaced by function QuestAddUnit takes unit u returns nothing
System Internal Changes (QuestExtension) :
  • Removed "QE DynamicUnitAssign"
    • QE CustomCondition :
      • Removed method assignReceiveEffectPath takes player p returns nothing and replace with function AssignReceiveEffect takes Quest qId, player p returns nothing
      • Removed method removeReceiveEffectPath takes player p returns nothing and replace with function RemoveReceiveEffect takes Quest qId, player p returns nothing
    • QE CustomReward :
      • method addCustomText takes string rewardText returns nothing now will be limited to be registered by once
    • QE DynamicEffectPath :
      • Renamed static method setEffectPathReceive takes string s, string attachPoint returns nothing to method operator effectPathReceive= takes string s returns nothing
      • Renamed static method setEffectPathReturn takes string s, string attachPoint returns nothing to method operator effectPathReturn= takes string s returns nothing
    • QE DynamicEnable :
      • Changes method enable takes player p returns nothing to method enable takes nothing returns nothing
      • Changes method enable takes player p returns nothing to method enable takes nothing returns nothing
      • Added method enableEx takes player user returns nothing
      • Added method disableEx takes player user reeturns nothing
    • QE DynamicEvent :
      • Renamed static method operator registerReceiveEvent= takes integer questEvent returns nothing to method operator receiveEvent= takes integer questEvent returns nothing
      • Renamed static method operator registerReturnEvent= takes integer questEvent returns nothing to method operator returnEvent= takes integer questEvent returns nothing
    • QE GroupKill :
      • UnitIdSet : Renamed method addUnitType takes integer uId returns nothing to method add takes integer unitTypeId returns nothing
    • QE QuestCategory :
      • Changed create method in QuestCategory struct from static method register takes integer categoryId, integer aId returns nothing to static method register takes integer categoryId, integer abilityId, integer max, string name returns nothing
      • In addition to the 2 parameters, (max) is the number of quests allowed to register into this category; (name) is the name of the category
      • Removed global constants MAX_QUEST_PER_CATEGORY
      • Category registration no longer be limited in QE QuestCategory
    • QE QuestItem :
      • Renamed static method createItem takes integer itemId, real x, real y, player p, QuestVirtual qv returns unit to static method create takes integer itemId, real x, real y, player p, CustomContent cc returns thistype
      • Renamed and modified from static method unitAddItem takes unit toAdd, unit it returns nothing to method addToUnit takes unit toAdd returns boolean
      • Renamed and modified from static method removeItem takes nothing returns nothing to method remove takes nothing returns nothing
      • Removed static method removeUnit takes unit u returns nothing
      • Added method destroy takes nothing returns nothing
      • Added static method destroyEx takes item toDestroy returns nothing
      • Added static method destroyRelated takes Quest qId returns nothing
      • Added static method createEmpty takes player p, CustomContent cc returns thistype
      • Added method setXY takes real x, real y returns nothing
      • Added static method selectRelated takes Quest qId returns Integer1D
      • Added method isOwned takes nothing returns boolean
      • Added method getOwner takes nothing returns player
      • Added method get takes nothing returns item
      • Now optionally requires QEQuestVirtual (or new name, QECustomContent)
    • QE QuestSound :
      • Added method enableSound takes nothing returns nothing
      • Added method setSoundEx takes integer soundType, Sound s returns nothing
    • QE ReceiveReturnEvent :
      • Renamed static method registerEx takes code c, Quest questId returns nothing to method registerOnReceive takes code c returns nothing and method registerOnReturn takes code c returns nothing for both struct ReceiveEvent and struct ReturnEvent
      • Removed both structs and add the API to struct Quest
    • QE RandomizeQuest :
      • Removed method randomizeEvent takes nothing returns nothing
      • Removed method randomizeCondition takes nothing returns nothing
      • Removed method randomizeContent takes nothing returns nothing
      • Removed method randomizeReward takes nothing returns nothing
      • Renamed method operator questEventList= takes integer whichList returns nothing to method operator eventInstance= takes integer instance returns nothing
      • Renamed method operator questConditionList= takes integer whichList returns nothing to method operator conditionInstance= takes integer instance returns nothing
      • Renamed method operator questContentList= takes integer whichList returns nothing to method operator contentInstance= takes integer instance returns nothing
      • Renamed method operator questRewardList= takes integer whichList returns nothing to method operator rewardInstance= takes integer instance returns nothing
      • Renamed method randomizeFull takes nothing returns nothing to method randomize takes nothing returns nothing
    • QE RepeatableQuest :
      • Modified and renamed static method setRepeatable takes code c returns nothing to method operator repeatable= takes boolean b returns nothing
    • QE VirtualContent :
      • Renamed to QE CustomContent
      • Renamed method registerVirtualContent takes string description, string updateDescription, integer updateTimes returns CustomContent to method registerCustomContent takes string description, string updateDescription, integer updateTimes returns CustomContent
      • Renamed struct QuestVirtual to struct CustomContent
      • Renamed method isComplete takes integer playerId returns boolean to method isCompleted takes integer playerId returns boolean
      • Remodified method update takes integer playerId, boolean displayUpdateDescription, boolean displayUpdateTimes returns nothing to method update takes integer playerId, integer updateNumber, boolean displayUpdateDescription, boolean displayUpdateTimes returns nothing
      • Removed method getQuestContentId takes nothing returns QuestContent
Globals Constants changes :
  • Removed constant MAX_CONTENT_PER_QUEST
  • Several constants has been renamed
Bug Fixes :
  • Fixed found bugs while restructuring Quest System


Below change logs are prior to v1.0.0 :


System internal changes (Quest Extension) :
  • Quest : Added "QuestSound" to configure quest receive/update/return sound
    Reason : -


System internal changes (Quest System) :
  • Quest : Added method method isCompleted takes player p returns boolean
    Description : Enable user to check if a quest is completed for player p
System internal changes (Quest Extension) :
  • Quest : Added "RepeatableQuest" to configure whether a quest is repeatable or not
    Reason : -
    --------------------------------------------------------------------
  • Quest : Added "QuestCategory" which allows user to define own quest category
    Reason : -
    --------------------------------------------------------------------
  • Quest Condition : Added "CustomCondition" as an addition condition type which enables user to define their custom condition type for certain quests.
    Reason : -
    --------------------------------------------------------------------
  • QE QuestItem : Added method static method removeItem takes item it returns nothing and static method removeUnit takes unit u returns nothing
    Description : Same function as the native function RemoveItem and RemoveUnit, but clear internal data stored inside the item/unit.
Constant changes :
  • Added constant string QUEST_LIST_FULL_MESSAGE
    Description : Message that will display if a player tries to receive more quest than he allowed to.


System internal changes (QuestSystem):
  • Added "QUEST_EVENT_NEAR" event type
    Reason : -
System internal changes (QuestExtension):
  • Quest Reward : Added "CustomReward" reward type
    Reason : -
API Changes:
  • QuestContent - Added API : static method registerTalk2 takes unit u, string extraDescription, string extraDescription2, string effectPath
    Description : The difference between registerTalk is that this uses QUEST_EVENT_NEAR, while registerTalk uses QUEST_EVENT_SELECT


System internal changes (QuestSystem):
  • Optimized player's quest data storage
    Reasion :-
System internal changes (QuestExtension):
  • Quest Content : Added "VirtualContent" content type
    Reason : -
    --------------------------------------------------------------------
  • Quest Content : Removed "KillObtainItemUse" content type
    Reason : Function can be replaced by "VirtualContent"
    --------------------------------------------------------------------
  • Added struct QuestItem
    Description : "VirtualContent" may have "Quest Item" creation, this struct provides that the "Quest Item" could be clear properly after the completion of a quest
    --------------------------------------------------------------------
  • Added struct ReceiveEvent and struct ReturnEvent
    Description : A struct which enables user to perform custom actions if a quest is accepted or if a quest is returned back
API Changes :
  • Quest : Added method isReceived takes player p returns boolean
    Description : Enable user to check if a player received this Quest
Bug fixes:
  • Any quest contents that hold the parameter string description will now have color display properly
    Reason : Previously doing "text " + "|cffff0000color text|r" + " other text" will not have color display properly


Bug Fixes :
  • Fixed complied errors
  • Fixed some minor text errors
  • Fixed a bug where quest system failed to init quest effect when player hero is created while initialization


General changes :
  • System now have built in debug checking, which optionally requires ErrorMessage
  • Improved the examples documentation
System internal changes (QuestSystem):
  • Moved few APIs from QuestSystem to QuestExtension which included :
    For struct Quest :
    • static method setEffectPathReceive takes string s, string attachPoint returns nothing
    • static method setEffectPathReturn takes string s, string attachPoint returns nothing
    • method operator questEventList= takes integer whichList returns nothing
    • method operator questConditionList= takes integer whichList returns nothing
    • method operator questContentList= takes integer whichList returns nothing
    • method operator questRewardList= takes integer whichList returns nothing

    For struct QuestEvent :
    • static method operator registerReceiveEvent= takes integer i returns nothing
    • static method operator registerReturnEvent= takes integer i returns nothing
    • static method operator receiveFrom= takes unit u returns nothing
    • static method operator returnTo= takes unit u returns nothing

    For struct QuestEvent, struct QuestCondition, struct QuestContent, struct QuestReward:
    • static method new takes Quest q returns nothing
    Reason : Addition features of QuestSystem should be moved to the QuestExtension library.
  • Added API : static method operator receiveReturnUnit= takes unit u returns nothing
    Reason : For replacement of missing basic API
System internal changes (QuestExtension):
  • Split all of the addition features each into a single trigger library thread
    Reason : Due to some unsolvable issues and information hiding problems, thus made such decision.
    --------------------------------------------------------------------
  • Quest : Added API :
    • method enable takes player whichPlayer returns nothing
    • method disable takes player whichPlayer returns nothing
    Reason : So that users could dynamically enable and disable quest at anytime depend on how their map event goes.
    --------------------------------------------------------------------
  • Quest : Added API :
    • method randomizeEvent takes nothing returns nothing
    • method randomizeCondition takes nothing returns nothing
    • method randomizeContent takes nothing returns nothing
    • method randomizeReward takes nothing returns nothing
    • method randomizeFull takes nothing returns nothing
    Reason : So that users could randomize quest more easily by just calling these methods.
    --------------------------------------------------------------------
  • Quest Content : Added "KillObtainItemUse" content type
    Reason : -


General Changes :
  • Added "QuestExtension" library as features extension toward the original "QuestSystem" library
    • Which included :
    1. QuestContent : GroupKill
System internal changes:
  • QuestContent : Added Goto type content
    Reason : -
  • QuestReward : Added Item type as reward
    Reason : -
Bug Fixes:
  • Fixed a bug that player could failed to return quest if both of the accepting unit and returning unit are not the same unit.


General changes :
  • Hugely increases the system overall performance
System internal changes :
  • All API methods are changed from non-static to static
    Reason : Methods no longer requires struct reference since system mechanism changes hugely.
    --------------------------------------------------------------------
  • API of struct constructor changed from create to newwhich included QuestEvent, QuestCondition, QuestContent and QuestReward
    Reason : No longer requires return of the corresponding struct instance, replaced with a more appropriate name.
    --------------------------------------------------------------------
  • Added Kill & Obtain type contents
    Reason : -
Constants Changes :
  • Added constant string QUEST_DEFAULT_COLOR
    Description : Default color to display quest title at dialog if quest title have no color code present

Bug fixes :
  • Fixed a bug that quest system failed to check the hero's level requirement
  • Fixed a bug that quest dialog failed to display the default quest color (white) properly starts from 2nd quest
  • Fixed a bug that talk effect display to all player instead of local player only


System internal changes :
  • Added PickUp content
    Reason : -
    --------------------------------------------------------------------
  • Modified that for each update of quest content will additionally display their corresponding quest name
    Reason : So that player would not confused if 2 quests have more then 2 same content get updated simultaneously.
    --------------------------------------------------------------------
  • Removed API : method operator difficulty= takes string s returns nothing
    Reason : Can easily be done though by adding the difficulty into the quest description
    --------------------------------------------------------------------
  • Removed API : method operator descriptionLines= takes integer lines returns nothing
    Reason : Integrated into the system itself
Bug Fixes :
  • Fixed a bug that causes Quest Info failed to create properly when 2 or more HERO init simultaneously
  • Fixed a bug that causes Quest Info's titles failed to display properly for Player 2 and above
  • Fixed a bug that causes Killing Quest failed to properly increasing kill count when 2 or more quests having the same unit type to kill


System internal changes :
  • HERO will be changed to be manually defined by user at any function instead of limiting it in function Init()
    Reason : So that user can assign the loaded hero (if player loaded a hero) to the variable at anytime instead of only bounded to assign hero when map is in init
    --------------------------------------------------------------------
  • add dummy hero as replacement of "-quest" command
    Reason : A more convenient way to view quest details
Bug fixes :
  • Fixed a bug that causes "Talk" requirement malfunction
  • Fixed a bug that randomized quest failed to properly recognize the receive unit



  • System initially uploaded



Credits :

  • ADG for MultidimensionalArray
  • TriggerHappy for UnitDex
  • PurgeandFire for Dialog
  • Vexorian for TimerUtils
  • Magtheridon96 for SoundTools
  • Emm-A- for a more convenient way to view Quest Info
  • edo494 for "GroupKill" content type

 

Attachments

  • QuestSystem v1.0.0.w3x
    259.3 KB · Views: 72
Last edited:
Level 11
Joined
Dec 19, 2012
Messages
411
Author's Words :
Uploaded this quest system and trying to receive some feedback, currently it is still in progress, more features will be implemented in future.

Condition Types :
Have 2 Conditions Types, with 1 addition type :

- Level condition
- Quest condition (requires to complete Quest1 before could receive Quest2)

Additional Feature :
- Customize your quest condition using "CustomCondition"


Contents Types :
Have 6 base contents types, with 2 addition types :

- Kill (number) of (unit type)
- Talk to (unit)
- Pick up (number) of (item)
- Kill (unit type) and obtain (number) (item)
- Kill (unit type) and pick up (number) (item)
- Go to a (place)

Additional Features :
- Kill (number) of (grouped unit type)
- Customize your own quest content using "VirtualContent"


How to view quest details :
To view the quest info, Press F2 (Quest Info)

In Progress update :

Current have no upcoming update


Requires Feedback :

Quest system may contains bug, hence I would like to have people report found bug, and feel free to give any suggestion regarding quest system.

 
Last edited:
Level 14
Joined
Jul 1, 2008
Messages
1,314
Hey legion,

I think creating a quest system is a very good idea, as making quests is quite tedious without some kind of standards ..

I think, that your system needs more visible features though.
Personally, I would prefer the quests tab over your system, but if one would agree to use your system, then, still, I think it is not nice having to access the system via "-quest".
Maybe you could use some dummy hero button to access a dialog field or something.
Also, some notice should flash up once a player has achieved some parts of a quest.
Also it would be nice to be able to have sub-quests or more than one requirement per quest.
Afterall, this is just my opinion :)

Are you planning on adding more quest types?
Maybe, add "go to some rect/region(s)" and "collect x of some unit or item"?

Another question, why exactly dont you use the blizzard quest tab?
 
Level 11
Joined
Dec 19, 2012
Messages
411
Hey legion,

I think creating a quest system is a very good idea, as making quests is quite tedious without some kind of standards ..
Thanks :)

I think, that your system needs more visible features though.
It will have more features, currently i'm creating the most basic things first before coming up additional features.

What i planned so far would be :
- adding the API which enables auto ping for quest location
- adding categorized quest to Main Quest, Optional Quest, and perhaps even let user define the category themselves.
- adding "priority" option toward quest requirements (enable to control quest requirements's flows)
- probably adding "history quest" where stored player's completed quests (which I'm unsure)
- and awaiting for someone to suggest more suggestion

Personally, I would prefer the quests tab over your system, but if one would agree to use your system, then, still, I think it is not nice having to access the system via "-quest".
I pretty much agree, so far keep typing "-quest" really pretty much annoying after a long time usage, hopefully could find a more convenient way for it though.

Maybe you could use some dummy hero button to access a dialog field or something.
That is a really nice suggestion, thanks for that, and could avoids annoying chat, will add it soon! :)

Also, some notice should flash up once a player has achieved some parts of a quest.
Hmm, I would need more feedback toward this, as i don't want to overwhelm player's screen with mass of texts.

Also it would be nice to be able to have sub-quests or more than one requirement per quest.
Afterall, this is just my opinion :)
Well, it already support more then one requirement per quest :) you can try to customize the quest requirements.

Are you planning on adding more quest types?
That is for sure, next step probably going for "Pick Up" quest type, which i will need some suggestion before starting it.

Maybe, add "go to some rect/region(s)" and "collect x of some unit or item"?
pretty specific requirement by player (which is unavoidable), but will plan to add it as additional requirement type.

Another question, why exactly dont you use the blizzard quest tab?
As quest tab is too small, limited description, and are usually reserved for game info, credits, and only able to categorized only 2 : Main / Optional
So created a custom text for more dynamic configuration option.
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.1.5 - Change Log :

System internal changes :
  • HERO will be changed to be manually defined by user at any function instead of limiting it in function Init()
    Reason : So that user can assign the loaded hero (if player loaded a hero) to the variable at anytime instead of only bounded to assign hero when map is in init
    --------------------------------------------------------------------
  • add dummy hero as replacement of "-quest" command
    Reason : A more convenient way to view quest details
Bug fixes :
  • Fixed a bug that causes "Talk" requirement malfunction
  • Fixed a bug that randomized quest failed to properly recognize the receive unit
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
I've thought of this myself at various points.
I never went all the way, but I did make some shortcuts in GUI which was way too limiting for a public release.
These days I would use the following approach:

Quest myQuest = Quest.allocate()
myQuest.name = "bla"
myQuest.desc = "do this"
//and so on
myQuest.onComplete = someFunctionToGiveRewards
myquest.cond = filtering function that returns a boolean (to check if quest is complete) this could probably be achieved with operators as well.

which would mean you only have to trigger the actual quest content while having a very clear API for the declaration and completion of a quest, which I think is quite fair.
 
Level 11
Joined
Dec 19, 2012
Messages
411
For the sample API you provided, it does works. Setting a quest toward 2 functions (rewardFunction and conditionFunction) gonna be a mess as repeating actions would occur for each function you defined (just little change toward the reward/condition). And also it won't be able to generate randomize quests (which some people would need it)

The things i want to achieve is, to make the quest system to be as dynamic as possible. It would certainly increases the difficulty to use the system, but you would end up with really nice effect that not only bounds to quest contents type like killing, picking and talking only.

Currently the system doesn't shine enough to attract more people as there is no unique features, that why it is in the lab so that i could have a more feedback/suggestion toward the future system development. :)

So far i'm gonna focus on basic features like killing, picking, talking. Condition like level condition, quest complete condition. Reward would be exp, gold and lumber.

Of course there would present some maps that are using custom resources (like custom resources integrated into multiboard) which gonna need a custom function to reward the player and other several features i am planning to add in (after completing the basic quest system).
 
Last edited:
Level 11
Joined
Dec 19, 2012
Messages
411
Chat system... a really nice idea to enhance the quest system. I've viewed the demo, demo is nice.

Even though, personally I'm not a fan of that chat system as :
  1. Completely changing the overall quest accepting mechanism
    Redoing the whole system gonna take huge time for me, which i'm not really going to do it​
  2. Requirements are too many
    I'm try to avoiding user to import too many libraries just to use a single system​

Anywhere, such a chat system can still be implemented without changing the overall mechanism, but requires a modification to suit into my system. I would consider your suggestion as future implementation :)
 
Level 11
Joined
Dec 19, 2012
Messages
411
The main thing that making this system complex is using the game text as display instead of the war3 quest dialog.

Or if you're saying toward the API, i can't really disagree with that, over long API can definitely scares people away.

I will plan to lower the amount of API using constant booleans, which user would need to define themselves whether to enable a specific feature or not.
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.2.0 - Change Log :

System internal changes :
  • Added PickUp content
    Reason : -
    --------------------------------------------------------------------
  • Modified that for each update of quest content will additionally display their corresponding quest name
    Reason : So that player would not confused if 2 quests have more then 2 same content get updated simultaneously.
    --------------------------------------------------------------------
  • Removed API : method operator difficulty= takes string s returns nothing
    Reason : Can easily be done though by adding the difficulty into the quest description
    --------------------------------------------------------------------
  • Removed API : method operator descriptionLines= takes integer lines returns nothing
    Reason : Going to integrating into the system itself and users would save 1 line
Bug Fixes :
  • Fixed a bug that causes Quest Info failed to create properly when 2 or more HERO init simultaneously
  • Fixed a bug that causes Quest Info's titles failed to display properly for Player 2 and above
  • Fixed a bug that causes Killing Quest failed to properly increasing kill count when 2 or more quests having the same unit type to kill




A side note : Sorry for the long delay update, I've reworked the PICK_UP content type 2 times, that the main reason for the long delay as the output of PICK_UP content doesn't statify my own requirement, hopefully you guys would like the output of it. :)
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.3.0 - Change Log :

General changes :
  • Hugely increases the system overall performance
System internal changes :
  • All API methods are changed from non-static to static
    Reason : Methods no longer requires struct reference since system mechanism changes hugely.
    --------------------------------------------------------------------
  • API of struct constructor changed from create to newwhich included QuestEvent, QuestCondition, QuestContent and QuestReward
    Reason : No longer requires return of the corresponding struct instance, replaced with a more appropriate name.
    --------------------------------------------------------------------
  • Added Kill & Obtain type content
    Reason : -
Constants Changes :
  • Added constant string QUEST_DEFAULT_COLOR
    Description : Default color to display quest title at dialog if quest title have no color code present

Bug fixes :
  • Fixed a bug that quest system failed to check the hero's level requirement
  • Fixed a bug that quest dialog failed to display the default quest color (white) properly starts from 2nd quest
  • Fixed a bug that talk effect display to all player instead of local player only



Details for code improvement :

Previously, the system is using full function calling for all data saving/loading:
JASS:
//register talk function
/**/
/**/       set table[temp][QUEST_CONTENT][this][subset].integer[0] = TALK
/**/       set table[temp][QUEST_CONTENT][this][subset].unit[1] = u
/**/       set table[temp][QUEST_CONTENT][this][subset].integer[2] = aId
/**/       set table[temp][QUEST_CONTENT][this][subset].string[3] = extraDescription
/**/       set table[temp][QUEST_CONTENT][this][subset].string[4] = extraDescription2
/**/       set table[temp][QUEST_CONTENT][this][subset].string[5] = effectPath
Now, the system is using saved table index and use it when needed :
JASS:
//register talk function
//tb is defined when calling QuestContent.create()
/**/
/**/        set tb.integer[0] = TALK
/**/        set tb.unit[1] = u
/**/        set tb.integer[2] = aId
/**/        set tb.string[3] = extraDescription
/**/        set tb.string[4] = extraDescription2
/**/        set tb.string[5] = effectPath
Such method could avoids recalling many unnecessary function calls, and thus improving the system overall performance.


By using JassPerfCounter() function (Sorry I forgot where did I got it) for rough testing with "Test" trigger (without pick&obtain quest type):
Previous performance time : ~3.8 - 4.5 milliseconds
Now performance time : ~2.0 - 2.5 milliseconds


Please be noted that API is changes that all methods are changed from "non-static" to "static"
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
I am not fun of your API at all, everything but one thing is static, so there is no point having structs at all, appart from one function(you could argue for the operators).

Also, what about having a quest "Kill 10 wolves" but my map has 3 different types of wolves that all qualify?(Lets say grey, black and dire wolf)
 
Level 11
Joined
Dec 19, 2012
Messages
411
I am not fun of your API at all, everything but one thing is static, so there is no point having structs at all, appart from one function(you could argue for the operators).
Struct is remains as :
  • Previously does requires struct reference, but since the internal changes of the system, that the reason changed from non-static to static.
  • The struct references is still using, struct references are stored in the system self and using it throughout all the static methods
  • Struct references for each Quest parts (QuestEvent, QuestCondition, QuestContent, QuestReward) are still used for generating a set of random quest event/condition/content/reward
  • Struct naming is pretty nice, like operators. Even without it, imagine like :

JASS:
//With struct
QuestContent.registerKill()

//Without struct
RegisterKill()

I still prefer first, as the second part RegisterKill() may be comes from other system which we probably don't know which component part it comes from.


Of course, that all of my opinion, unless there really present a better reason toward changing it, i will definitely do so.

Also, what about having a quest "Kill 10 wolves" but my map has 3 different types of wolves that all qualify?(Lets say grey, black and dire wolf)
Unfortunately that is currently not yet implemented, only specific unit type. I can implement it, if you need.
Misinterpreted your statement, that will have some difficulty, but sure, i will include it as to-do list.




Since the basic component of the quest is basically done, i can move toward to features improvement. Feel free to suggest stuff :)
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Also, what about having a quest "Kill 10 wolves" but my map has 3 different types of wolves that all qualify?(Lets say grey, black and dire wolf)

This is where Polymorphism comes in handy :D
Shame it is so poorly available in JASS.
But I wonder if it should be part of the system's core or if it should be given with the examples in the map.
I personally vote for the latter.
 
Level 11
Joined
Dec 19, 2012
Messages
411
But I wonder if it should be part of the system's core or if it should be given with the examples in the map.
I personally vote for the latter.
Since you voted for a map specific. If the map is going to need it, user will need to create their own, since the quest system is 100% incompatible with it. The user will definitely request the quest system creator to include the feature (if I'm the user will definitely do so :p). Perhaps i can make it as optional external library, but i will need to take care of the encapsulation, which gonna be a big deal.


Anywhere, it is a real hurry for it?
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.4.0 - Change Log :

General Changes :
  • Added "QuestExtension" library as features extension toward the original "QuestSystem" library (Completed)
    • Which included :
    1. QuestContent : GroupKill
System internal changes:
  • QuestContent : Added Goto type content
    Reason : -
  • QuestReward : Added Item type as reward
    Reason : -
Bug Fixes:
  • Fixed a bug that player could failed to return quest if both of the accepting unit and returning unit are not the same unit.



QuestExtension currently only included one "GroupKill" feature, it will have more features in future.
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.5.0 - Change Log :


General changes :
  • System now have built in debug checking, which optionally requires ErrorMessage
  • Improved the examples documentation
System internal changes (QuestSystem):
  • Moved few APIs from QuestSystem to QuestExtension which included :
    For struct Quest :
    • static method setEffectPathReceive takes string s, string attachPoint returns nothing
    • static method setEffectPathReturn takes string s, string attachPoint returns nothing
    • method operator questEventList= takes integer whichList returns nothing
    • method operator questConditionList= takes integer whichList returns nothing
    • method operator questContentList= takes integer whichList returns nothing
    • method operator questRewardList= takes integer whichList returns nothing

    For struct QuestEvent :
    • static method operator registerReceiveEvent= takes integer i returns nothing
    • static method operator registerReturnEvent= takes integer i returns nothing
    • static method operator receiveFrom= takes unit u returns nothing
    • static method operator returnTo= takes unit u returns nothing

    For struct QuestEvent, struct QuestCondition, struct QuestContent, struct QuestReward:
    • static method new takes Quest q returns nothing
    Reason : Addition features of QuestSystem should be moved to the QuestExtension library.
  • Added API : static method operator receiveReturnUnit= takes unit u returns nothing
    Reason : For replacement of missing basic API
System internal changes (QuestExtension):
  • Split all of the addition features each into a single trigger library thread
    Reason : Due to some unsolvable issues and information hiding problems, thus made such decision.
    --------------------------------------------------------------------
  • Quest : Added API :
    • method enable takes player whichPlayer returns nothing
    • method disable takes player whichPlayer returns nothing
    Reason : So that users could dynamically enable and disable quest at anytime depend on how their map event goes.
    --------------------------------------------------------------------
  • Quest : Added API :
    • method randomizeEvent takes nothing returns nothing
    • method randomizeCondition takes nothing returns nothing
    • method randomizeContent takes nothing returns nothing
    • method randomizeReward takes nothing returns nothing
    • method randomizeFull takes nothing returns nothing
    Reason : So that users could randomize quest more easily by just calling these methods.
    --------------------------------------------------------------------
  • Quest Content : Added "KillObtainItemUse" content type
    Reason : -



This update is mainly focus on system self safety check, reducing the number of API that user need to use for defining a basic quest and improves the quest examples documentation. I hope the amount of basic APIs is user-friendly enough. I would like some opinion from you guys though.


EDIT : Updated to v0.5.1 - Change Log :

Bug Fixes :
  • Fixed complied errors
  • Fixed some minor text errors
  • Fixed a bug where quest system failed to init quest effect when player hero is created while initialization
 
Last edited:
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to 0.6.0 - Change Log :


System internal changes (QuestSystem):
  • Optimized player's quest data storage
    Reasion :-
System internal changes (QuestExtension):
  • Quest Content : Added "VirtualContent" content type
    Reason : -
    --------------------------------------------------------------------
  • Quest Content : Removed "KillObtainItemUse" content type
    Reason : Function can be replaced by "VirtualContent"
    --------------------------------------------------------------------
  • Added struct QuestItem
    Description : "VirtualContent" may have "Quest Item" creation, this struct provides that the "Quest Item" could be clear properly after the completion of a quest
    --------------------------------------------------------------------
  • Added struct ReceiveEvent and struct ReturnEvent
    Description : A struct which enables user to perform custom actions if a quest is accepted or if a quest is returned back
API Changes :
  • Quest : Added method isReceived takes player p returns boolean
    Description : Enable user to check if a player received this Quest
Bug fixes:
  • Any quest contents that hold the parameter string description will now have color display properly
    Reason : Previously doing "text " + "|cffff0000color text|r" + " other text" will not have color display properly



Since no responses, i assume all working fine ._. Anywhere, started from 0.6.0 version, user is now enable to customize their own quest content using "VirtualContent", 2 examples which uses "VirtualContent" is included inside the map.

Quest Content is done, hopefully. From now on i will focus on the other parts :)
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.7.0 - Change Log :


System internal changes (QuestSystem):
  • Added "QUEST_EVENT_NEAR" event type
    Reason : -
System internal changes (QuestExtension):
  • Quest Reward : Added "CustomReward" reward type
    Reason : -
API Changes:
  • QuestContent - Added API : static method registerTalk2 takes unit u, string extraDescription, string extraDescription2, string effectPath
    Description : The difference between registerTalk is that this uses QUEST_EVENT_NEAR, while registerTalk uses QUEST_EVENT_SELECT


After a long time for waiting, an update for QuestSystem to v0.7.0 is here now! Originally I've planned to skip v0.7.0 and directly get to v0.8.0, but facing real hard problems while implementing "quest content precedence". I might decide not to implement it, probably depend on the requirement of you guys, or my own conscious.
 
Level 11
Joined
Dec 19, 2012
Messages
411
Updated to v0.8.0 - Change log :


System internal changes (Quest System) :
  • Quest : Added method method isCompleted takes player p returns boolean
    Description : Enable user to check if a quest is completed for player p
System internal changes (Quest Extension) :
  • Quest : Added "RepeatableQuest" to configure whether a quest is repeatable or not
    Reason : -
    --------------------------------------------------------------------
  • Quest : Added "QuestCategory" which allows user to define own quest category
    Reason : -
    --------------------------------------------------------------------
  • Quest Condition : Added "CustomCondition" as an addition condition type which enables user to define their custom condition type for certain quests.
    Reason : -
    --------------------------------------------------------------------
  • QE QuestItem : Added method static method removeItem takes item it returns nothing and static method removeUnit takes unit u returns nothing
    Description : Same function as the native function RemoveItem and RemoveUnit, but clear internal data stored inside the item/unit.
Constant changes :
  • Added constant string QUEST_LIST_FULL_MESSAGE
    Description : Message that will display if a player tries to receive more quest than he allowed to.


Slightly delayed the update time due to celebration of Chinese New Year. This update continue includes the missing basic features which I forgot to implement in earlier version. Next update will focus on addition features.
 
Level 6
Joined
Jul 30, 2013
Messages
282
library QuestExtension requires ....

it kind of irks me that this is a thing. I feel that the very existance of this lib is a code smell.

you have a lot of special cases in your code. eg:

JASS:
struct QuestCondition extends array
static method operator addLevel= takes integer levelRequired returns nothing
static method operator addQuest= takes Quest q returns nothing
...
endstruct

struct QuestContent extends array
 
static method registerKillUnit takes integer uId, integer noToKill returns nothing
static method registerKillObtain takes integer uId, string itemName, integer noToObtain, real chance returns nothing
....
endstruct

#1 i am puzzled as to how a function that would appear to modify the parameters of 1 particular quest could possibly be static and take no Quest parameter or the like.

#2 i feel that something of the like would serve better:

JASS:
/**
    got a chat command handling library that makes heavy use of this, it uses triggers underneath i think but it makes for very clean API-s if used well.
    ref: http://www.wc3c.net/vexorian/jasshelpermanual.html#funcinterf

    the QuestCondition interface allows you to easily handle different conditions.
    If you have any common conditions you can simply ship tghem with the system. and if somebody wants to add their own "custom" conditions then they can write them themself.
 
    It is important that a Quest is not too tightly coupled with Questconditions for that makes is very hard to extend by 3rd parties ( you would need to modify the code thus risk breaking it.. thus necessitating that hack of QE lib..)
*/
function interface QuestCondition takes Quest q, player p returns boolean

/**
also consider.. or the like, really the moment you begin to treat a function pointer like any other variable, your code will become so much simpler.
*/
function interface QuestReward takes Quest q, player p returns nothing

struct QuestConditions
private QuestCondition array[MAX_COMPLETE_CONDITIONS] // or use Table if you dont like fixed size..

private method add takes QuestCondition returns nothing

/**
*
*/
private method checkConditions takes nothing returns boolean

endstruct

struct Quest
private QuestConditions questCompletionConditions
private QuestConditions questBeginConditions

static if LIBRARY_QuestCustomData
/*optional include, provided by user of library, can be anything the user wants to store.*/
    private QuestCustomData questCustomData
endif
...
private static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.questCompletionConditions = QuestConditions.create()
set this.questBeginConditions= QuestConditions.create()
...
return this
endmethod
endstruct

usage like:
JASS:
function KillsSinceQuestBegunGTE30 takes Quest q returns boolean ....


set q = Quest.create()
...
call q.questCompletionConditions.add(QuestCondition.KillsSinceQuestBegunGTE30)

Vjass is more than just jass2 with structs, it is far more powerful and helps you write a lot better code.. if you just used it...

Same is with C and C++, C++ is not just C with classes even tho some people can never get past that particular stage..

Of course given that some things in jass2 are more expensive than other there might be reasons to create special cases for things, however these should NOT be visible to the user, ever. if you do shady shit then keep ur skeletons locked in your closet, and make sure the user will never be able to tell the difference... except maybe better perf.

JASS:
method operator questConditionList= takes integer whichList returns nothing
method operator questContentList= takes integer whichList returns nothing
method operator questRewardList= takes integer whichList returns nothing

// why not instead:
method operator questConditionList= takes QuestConditions questConditionList returns nothing
method operator questContentList= takes QuestContent questContent returns nothing
method operator questRewardList= takes QuestRewards questRewards returns nothing

// i object to taking a raw integer as a parameter when really its a pointer to a struct
 
Last edited:
Level 6
Joined
Jul 30, 2013
Messages
282
also interacting with the W3 built in quest display would be nice...

really this is not something hard(the easiest bit i think actually, just boilerplate.. why else you use libs if not to reduce boilerplate?!), at least there should be an optional include you can have that makes it work if wanted..
 
Level 11
Joined
Dec 19, 2012
Messages
411
I can hardly reply you within a short time, your sentences requires me to read over 20 times in order to almost understand them... And please refrain from double posting...

library QuestExtension requires ....

it kind of irks me that this is a thing. I feel that the very existance of this lib is a code smell.
Then you could completely throw it away and only uses the main library... It just extend the functionality of main library only

you have a lot of special cases in your code.
Examples and reasons?

#1 i am puzzled as to how a function that would appear to modify the parameters of 1 particular quest could possibly be static and take no Quest parameter or the like.
If you do read into how I deal with data storage, then you will know why all methods are static

#2 i feel that something of the like would serve better:
Function interface is the thing I haven't learn yet, because I couldn't understand it when trying to learn vjass. Now I do understand, but I hope I could know when this can help me code better and understandable code. According to your example, every quest condition user gonna need to create new function block then could add in?

Vjass is more than just jass2 with structs, it is far more powerful and helps you write a lot better code.. if you just used it...

Same is with C and C++, C++ is not just C with classes even tho some people can never get past that particular stage..
Unfortunately I have no programming background. vJASS is the only scripting language I deal with.

Of course given that some things in jass2 are more expensive than other there might be reasons to create special cases for things, however these should NOT be visible to the user, ever.
"these" are which?

also interacting with the W3 built in quest display would be nice...
Do you mean using the war3 built in quest dialog?

really this is not something hard(the easiest bit i think actually, just boilerplate.. why else you use libs if not to reduce boilerplate?!),
Trying to generalized a specific system will creates longer code, with the additional of several features.

at least there should be an optional include you can have that makes it work if wanted..
I'm sorry, what do you mean?


JASS:
method operator questConditionList= takes integer whichList returns nothing
method operator questContentList= takes integer whichList returns nothing
method operator questRewardList= takes integer whichList returns nothing

// why not instead:
method operator questConditionList= takes QuestConditions questConditionList returns nothing
method operator questContentList= takes QuestContent questContent returns nothing
method operator questRewardList= takes QuestRewards questRewards returns nothing

// i object to taking a raw integer as a parameter when really its a pointer to a struct
those lists are the things I forgot to change, thanks you.
 
Level 11
Joined
Dec 19, 2012
Messages
411
Its been a long time since the last time I've update this quest system, and now I've like to introduce version v1.0.0 of QuestSystem which I've working on several weeks, there is huge changes to API compare to last version (v0.8.1), as well as several new functions to accommodate custom inventory system.



General changes :
  • Restructuring of whole Quest System
    • Reconstruction for more intuitive API
    • Code readability and flexibility improvement
    • Code optimization
  • Changes to quest requirements.
  • Effects from now on will be applied for only one time for each units and rects.
    • Return effect will have priority over receive effect
    • For quest with dynamic effects, effects are stored in order and destroy in order
  • From now on supports load function (loaded unit which then added into the QuestSystem will be able to receive quest properly)
  • From now on able to support custom inventory system (using optional feature QE QuestItem)
  • Temporary removed debug operations, it will be added back later
System internal changes (QuestSystem):
  • From now on all methods will be non-static methods (excluded Quest.create() method)
    • Quest :
      • Added method createEvent takes nothing returns nothing
      • Added method createCondition takes nothing returns nothing
      • Added method createContent takes nothing returns nothing
      • Added method createReward takes nothing returns nothing
      • Renamed and modified method isReceived takes player p returns boolean to function IsQuestReceivedByPlayer takes player p, Quest qId returns boolean
      • Renamed and modified method isCompleted takes player p returns boolean to function IsQuestBeenCompleted takes player p, Quest qId returns boolean
    • QuestEvent :
      • Added method operator receiveUnit= takes unit receiveU returns nothing
      • Added method operator returnUnit= takes unit returnU returns nothing
      • Renamed static method operator questAbilityId= takes integer i returns nothing to method operator abilityId= takes integer aId returns nothing
    • QuestCondition :
      • Renamed static method operator addLevel= takes integer levelRequired returns nothing to method operator level= takes integer rqLevel returns nothing
    • QuestContent :
      • Renamed static method registerKillUnit takes integer unitType, integer noToKill returns nothing to method registerKill takes integer unitType, integer noToKill returns nothing
      • static method registerTalk takes unit u, integer aId, string extraDescription, string extraDescription2, string effectPath returns nothing changed to method registerTalk takes unit u, integer aId, string description, string exDescription returns nothing
      • registerTalk2 has similar changes as registerTalk
      • registerPickUp's parameter : extraDescription, has been changed to description, which will be displayed in Quest Info
      • registerGoto's parameter : effectPath, has been removed
      • ----------------------------------------------------------------------
      • description : Description will be displayed in Quest Info
      • exDescription : Description will be displayed once completed this content
    • QuestReward :
      • Renamed static method item takes integer itemId, integer amount returns nothing to method addItem takes integer itemId, integer amount returns nothing
  • Now user will no longer be self configure HERO, instead replaced by function QuestAddUnit takes unit u returns nothing
System Internal Changes (QuestExtension) :
  • Removed "QE DynamicUnitAssign"
    • QE CustomCondition :
      • Removed method assignReceiveEffectPath takes player p returns nothing and replace with function AssignReceiveEffect takes Quest qId, player p returns nothing
      • Removed method removeReceiveEffectPath takes player p returns nothing and replace with function RemoveReceiveEffect takes Quest qId, player p returns nothing
    • QE CustomReward :
      • method addCustomText takes string rewardText returns nothing now will be limited to be registered by once
    • QE DynamicEffectPath :
      • Renamed static method setEffectPathReceive takes string s, string attachPoint returns nothing to method operator effectPathReceive= takes string s returns nothing
      • Renamed static method setEffectPathReturn takes string s, string attachPoint returns nothing to method operator effectPathReturn= takes string s returns nothing
    • QE DynamicEnable :
      • Changes method enable takes player p returns nothing to method enable takes nothing returns nothing
      • Changes method enable takes player p returns nothing to method enable takes nothing returns nothing
      • Added method enableEx takes player user returns nothing
      • Added method disableEx takes player user reeturns nothing
    • QE DynamicEvent :
      • Renamed static method operator registerReceiveEvent= takes integer questEvent returns nothing to method operator receiveEvent= takes integer questEvent returns nothing
      • Renamed static method operator registerReturnEvent= takes integer questEvent returns nothing to method operator returnEvent= takes integer questEvent returns nothing
    • QE GroupKill :
      • UnitIdSet : Renamed method addUnitType takes integer uId returns nothing to method add takes integer unitTypeId returns nothing
    • QE QuestCategory :
      • Changed create method in QuestCategory struct from static method register takes integer categoryId, integer aId returns nothing to static method register takes integer categoryId, integer abilityId, integer max, string name returns nothing
      • In addition to the 2 parameters, (max) is the number of quests allowed to register into this category; (name) is the name of the category
      • Removed global constants MAX_QUEST_PER_CATEGORY
      • Category registration no longer be limited in QE QuestCategory
    • QE QuestItem :
      • Renamed static method createItem takes integer itemId, real x, real y, player p, QuestVirtual qv returns unit to static method create takes integer itemId, real x, real y, player p, CustomContent cc returns thistype
      • Renamed and modified from static method unitAddItem takes unit toAdd, unit it returns nothing to method addToUnit takes unit toAdd returns boolean
      • Renamed and modified from static method removeItem takes nothing returns nothing to method remove takes nothing returns nothing
      • Removed static method removeUnit takes unit u returns nothing
      • Added method destroy takes nothing returns nothing
      • Added static method destroyEx takes item toDestroy returns nothing
      • Added static method destroyRelated takes Quest qId returns nothing
      • Added static method createEmpty takes player p, CustomContent cc returns thistype
      • Added method setXY takes real x, real y returns nothing
      • Added static method selectRelated takes Quest qId returns Integer1D
      • Added method isOwned takes nothing returns boolean
      • Added method getOwner takes nothing returns player
      • Added method get takes nothing returns item
      • Now optionally requires QEQuestVirtual (or new name, QECustomContent)
    • QE QuestSound :
      • Added method enableSound takes nothing returns nothing
      • Added method setSoundEx takes integer soundType, Sound s returns nothing
    • QE ReceiveReturnEvent :
      • Renamed static method registerEx takes code c, Quest questId returns nothing to method registerOnReceive takes code c returns nothing and method registerOnReturn takes code c returns nothing for both struct ReceiveEvent and struct ReturnEvent
      • Removed both structs and add the API to struct Quest
    • QE RandomizeQuest :
      • Removed method randomizeEvent takes nothing returns nothing
      • Removed method randomizeCondition takes nothing returns nothing
      • Removed method randomizeContent takes nothing returns nothing
      • Removed method randomizeReward takes nothing returns nothing
      • Renamed method operator questEventList= takes integer whichList returns nothing to method operator eventInstance= takes integer instance returns nothing
      • Renamed method operator questConditionList= takes integer whichList returns nothing to method operator conditionInstance= takes integer instance returns nothing
      • Renamed method operator questContentList= takes integer whichList returns nothing to method operator contentInstance= takes integer instance returns nothing
      • Renamed method operator questRewardList= takes integer whichList returns nothing to method operator rewardInstance= takes integer instance returns nothing
      • Renamed method randomizeFull takes nothing returns nothing to method randomize takes nothing returns nothing
    • QE RepeatableQuest :
      • Modified and renamed static method setRepeatable takes code c returns nothing to method operator repeatable= takes boolean b returns nothing
    • QE VirtualContent :
      • Renamed to QE CustomContent
      • Renamed method registerVirtualContent takes string description, string updateDescription, integer updateTimes returns CustomContent to method registerCustomContent takes string description, string updateDescription, integer updateTimes returns CustomContent
      • Renamed struct QuestVirtual to struct CustomContent
      • Renamed method isComplete takes integer playerId returns boolean to method isCompleted takes integer playerId returns boolean
      • Remodified method update takes integer playerId, boolean displayUpdateDescription, boolean displayUpdateTimes returns nothing to method update takes integer playerId, integer updateNumber, boolean displayUpdateDescription, boolean displayUpdateTimes returns nothing
      • Removed method getQuestContentId takes nothing returns QuestContent
Globals Constants changes :
  • Removed constant MAX_CONTENT_PER_QUEST
  • Several constants has been renamed
Bug Fixes :
  • Fixed found bugs while restructuring Quest System
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Added method createEvent takes nothing returns nothin

and from documentation:

JASS:
            method createEvent takes nothing returns nothing
        //  -create new event
        //  -a quest can have multiple events
        //  -syntax for setting event is (assuming (q) is Quest instance) :
        //   q.event.(QuestEvent methods)

but then even if I register 100 events into a quest, I can only access one by the readonly variable event in the struct Quest, so there is no point registering more than one is there.

Maybe you could return the created event from the function, which would allow me to either write it down somewhere else and then modify it, or at least do in-place access to it.

Or maybe I misunderstood what is going on in this(I skimmed through the code, and it is fucking enormous to be honest :D)
 
Level 11
Joined
Dec 19, 2012
Messages
411
Thanks for your comment :)

but then even if I register 100 events into a quest, I can only access one by the readonly variable event in the struct Quest, so there is no point registering more than one is there.
It also pointless to do so as you (assuming you clearly know what you want) only need one creation if rest are left as unused.

The main reason it exists is to compatible with "QE RandomizeQuest" so that user doesn't need to change the registration method completely. Also mainly for randomizeQuest usage, where you could assign the current quest event/condition/content/reward instance from one of them.

Maybe you could return the created event from the function, which would allow me to either write it down somewhere else and then modify it, or at least do in-place access to it.
Talking about half game modifying the quest, I temporary left it aside as so far I don't think anyone will need it, but I can make it easily return any of the quest components struct index for mid game modification, yet currently I'm unsure if such modification will produce unwanted bug or not.
 
Status
Not open for further replies.
Top