Name | Type | is_array | initial_value |
Quest | integer | No | |
unit | unit | No |
//TESH.scrollpos=63
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
function interface CodeHandler takes nothing returns nothing
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
private function triggerTimeout takes nothing returns nothing
local timer t = GetExpiredTimer()
local CodeHandler c = GetTimerData(t)
call ReleaseTimer(t)
call c.evaluate()
endfunction
private function triggerInterval takes nothing returns nothing
local timer t = GetExpiredTimer()
local CodeHandler c = GetTimerData(t)
call c.evaluate()
endfunction
function timeout takes real time, CodeHandler callback returns timer
local timer t = NewTimer()
call SetTimerData(t, callback)
call TimerStart(t, time, false, function triggerTimeout)
return t
endfunction
function resetTimeout takes timer t returns nothing
call PauseTimer(t)
call ReleaseTimer(t)
endfunction
function interval takes real time, CodeHandler callback returns timer
local timer t = NewTimer()
call SetTimerData(t, callback)
call TimerStart(t, time, true, function triggerInterval)
return t
endfunction
function resetInterval takes timer t returns nothing
call PauseTimer(t)
call ReleaseTimer(t)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Array requires Table
struct Array
private Table data
readonly integer size
public static method create takes nothing returns Array
local Array this = Array.allocate()
set this.size = 0
set this.data = Table.create()
return this
endmethod
public method push takes integer i returns nothing
set this.data[this.size] = i
set this.size = this.size + 1
endmethod
public method operator [] takes integer i returns integer
return this.data[i]
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library StringReplace
// attempts to find any and all instances of 'toreplace' in 's' and replaces with 'replacer'
function StringReplaceAll takes string s, string toreplace, string replacer returns string
local integer i = StringLength(s)
local integer j = StringLength(toreplace)
local integer n = 0
if (toreplace==replacer) then
return s
endif
loop
exitwhen (n>i-j)
if (SubString(s, n, n+j)==toreplace) then
set s = SubString(s, 0, n)+replacer+SubString(s, n+j, i)
set i = StringLength(s)
else
set n = n+1
endif
endloop
return s
endfunction
// same as above, but will only replace the first instance
function StringReplace takes string s, string toreplace, string replacer returns string
local integer i = StringLength(s)
local integer j = StringLength(toreplace)
local integer n = 0
if (toreplace==replacer) then
return s
endif
loop
exitwhen (n>i-j)
if (SubString(s, n, n+j)==toreplace) then
set s = SubString(s, 0, n)+replacer+SubString(s, n+j, i)
exitwhen (true)
else
set n = n+1
endif
endloop
return s
endfunction
endlibrary
//TESH.scrollpos=436
//TESH.alwaysfold=0
library QuestSystem requires Table, TimerUtils, GroupUtils, Array, StringReplace
// ***********************************************
// *
// * Configuration Options
// *
// ***********************************************
globals
constant integer QUEST_DUMMY_ID = 'qdum'
constant boolean QUEST_CREATE_UI = true
constant boolean QUEST_TEXT_NOTIFICATION = true
constant boolean QUEST_PING_NOTIFICATION = true
constant boolean QUEST_EFFECT_NOTIFICATION = true
constant boolean QUEST_TEXT_DESCRIPTION = true // if true, title & description are displayed on screen at the appropriate time
constant string QUEST_TEXT_ACCEPTED = "Quest accepted: %s" // %s will be replaced by quest title
constant string QUEST_TEXT_COMPLETED = "Quest completed: %s. Return to receive rewards." // %s = title
constant string QUEST_TEXT_REWARDED = "Quest completed: %s."
constant string QUEST_TEXT_FAILED = "Quest failed: %s" // %s = title
constant string QUEST_TEXT_UPDATED = "%s"
constant string QUEST_TEXT_ITEM_UPDATED = "%s updated: %s" // first %s = quest title, second %s = quest item description. Set to "" if you don't want item updates to be displayed on screen
constant string QUEST_TEXT_ITEM_COMPLETED = "%s completed: %s"
constant boolean QUEST_PING_EFFECTS = true
constant real QUEST_PING_DURATION = 10.00
constant string QUEST_EFFECT_AVAILABLE = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl" // "Objects\\InventoryItems\\Rune\\Rune.mdl"
constant string QUEST_EFFECT_COMPLETED = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"
constant string QUEST_EFFECT_ACCEPTED = "Objects\\RandomObject\\RandomObject.mdl"
// priority of effects: AVAILABLE > COMPLETED > ACCEPTED
// i.e. if a quest is available, show AVAILABLE, else if completed, show COMPLETED, else if accepted, show ACCEPTED, finally show nothing
endglobals
function interface QuestEventCallback takes QuestInstance instance returns nothing
function interface QuestItemEventCallback takes QuestItemInstance instance returns nothing
function interface QuestHeroEventCallback takes player p, unit old, unit new returns nothing
// ***********************************************
// *
// * Quest Condition
// * requirement for accepting a quest
// *
// ***********************************************
struct QuestCondition
readonly Quest quest
// Bind QuestCondition to Quest
public method bind takes Quest qt returns nothing
set this.quest = qt
endmethod
// check condition for player
public stub method check takes player p returns boolean
call BJDebugMsg("QuestCondition::check - unimplemented")
return false
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg("QuestCondition::onDestroy - should not be called!")
endmethod
endstruct
// ***********************************************
// *
// * Quest Reward
// * rewards to receive when finishing a quest
// *
// ***********************************************
struct QuestReward
// give reward to player
public stub method give takes player p returns nothing
call BJDebugMsg("QuestReward::give - unimplemented")
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg("QuestReward::ondestroy - should not be called!")
endmethod
endstruct
// ***********************************************
// *
// * Quest Item
// * requirement for finishing the quest
// *
// ***********************************************
struct QuestItem
readonly Quest quest
readonly integer requiredStatus
readonly string description
// Create a new QuestItem
public static method create takes integer requiredStatus, string description returns QuestItem
local QuestItem this = QuestItem.allocate()
set this.requiredStatus = requiredStatus
set this.description = description
return this
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg("QuestItem::onDestroy - should not be called!")
endmethod
// bind QuestItem to Quest
public method bind takes Quest qt returns nothing
set this.quest = qt
endmethod
// get updated description (based on QuestItemInstance
public stub method getDescription takes QuestItemInstance instance returns string
return this.description
endmethod
// Retrieve QuestItemInstance for player p
public method instance takes player p returns QuestItemInstance
local QuestItemInstance it = LoadInteger(QuestItemInstance.database, this, GetPlayerId(p))
if (it == 0 and this.quest.instance(p) > 0) then
set it = QuestItemInstance.create(this, p)
endif
return it
endmethod
endstruct
// ***********************************************
// *
// * Quest Item Instance
// * 1 instance per player, per quest item
// *
// ***********************************************
struct QuestItemInstance
readonly static hashtable database
private static Array onUpdateCallbacks
private static Array onInstanceCallbacks
readonly QuestItem type
readonly player owner
readonly questitem ui
readonly integer status
private static method onInit takes nothing returns nothing
set database = InitHashtable()
set onUpdateCallbacks = Array.create()
set onInstanceCallbacks = Array.create()
endmethod
public static method create takes QuestItem qi, player p returns QuestItemInstance
local QuestItemInstance this = QuestItemInstance.allocate()
set this.type = qi
set this.owner = p
set this.status = 0
static if (QUEST_CREATE_UI) then
set this.ui = QuestCreateItem(this.type.quest.instance(p).ui)
call QuestItemSetDescription(this.ui, this.type.getDescription(this))
call QuestItemSetCompleted(this.ui, this.finished)
endif
call SaveInteger(database, this.type, GetPlayerId(p), this)
call this.triggerEvent(onInstanceCallbacks)
return this
endmethod
public static method onUpdate takes QuestItemEventCallback callback returns nothing
call onUpdateCallbacks.push(callback)
endmethod
public static method onInstance takes QuestItemEventCallback callback returns nothing
call onInstanceCallbacks.push(callback)
endmethod
public method onDestroy takes nothing returns nothing
call RemoveSavedInteger(database, this.type, GetPlayerId(this.owner))
endmethod
public method operator finished takes nothing returns boolean
return this.status >= this.type.requiredStatus
endmethod
public method operator failed takes nothing returns boolean
return this.status < 0
endmethod
private method triggerEvent takes Array callbacks returns nothing
local integer i = 0
loop
exitwhen i >= callbacks.size
call QuestItemEventCallback(callbacks[i]).evaluate(this)
set i = i + 1
endloop
endmethod
public method update takes boolean plus returns nothing
// Update quest item status
if (plus) then
set this.status = IMinBJ(this.status + 1, this.type.requiredStatus)
else
set this.status = this.status - 1
endif
static if (QUEST_CREATE_UI) then
call QuestItemSetDescription(this.ui, this.type.getDescription(this))
endif
// check if this item is completed
if (this.status == this.type.requiredStatus) then
static if (QUEST_CREATE_UI) then
call QuestItemSetCompleted(this.ui, true)
if (GetLocalPlayer() == this.owner) then
call FlashQuestDialogButton()
endif
endif
static if (QUEST_TEXT_NOTIFICATION) then
if (QUEST_TEXT_ITEM_COMPLETED != "") then
call DisplayTextToPlayer(this.owner, 0, 0, StringReplace(StringReplace(QUEST_TEXT_ITEM_COMPLETED, "%s", this.type.quest.title), "%s", this.type.getDescription(this)))
endif
endif
// update quest status
call this.type.quest.instance(this.owner).update()
elseif (this.status < 0) then // this item failed
call this.fail()
else // this item isn't completed yet
static if (QUEST_TEXT_NOTIFICATION) then
if (QUEST_TEXT_ITEM_UPDATED != "") then
call DisplayTextToPlayer(this.owner, 0, 0, StringReplace(StringReplace(QUEST_TEXT_ITEM_UPDATED, "%s", this.type.quest.title), "%s", this.type.getDescription(this)))
endif
endif
endif
call this.triggerEvent(onUpdateCallbacks)
endmethod
public method fail takes nothing returns nothing
static if (QUEST_CREATE_UI) then
call QuestItemSetCompleted(this.ui, false)
if (GetLocalPlayer() == this.owner) then
call FlashQuestDialogButton()
endif
endif
call this.type.quest.instance(this.owner).update()
endmethod
endstruct
// ***********************************************
// *
// * Quest Position
// * position to ping minimap
// *
// ***********************************************
private struct QuestPosition
private real x
private real y
public static method create takes real x, real y returns QuestPosition
local QuestPosition this = QuestPosition.allocate()
set this.x = x
set this.y = y
return this
endmethod
public method pingMinimap takes integer red, integer green, integer blue returns nothing
call PingMinimapEx(this.x, this.y, QUEST_PING_DURATION, red, green, blue, QUEST_PING_EFFECTS)
endmethod
endstruct
// ***********************************************
// *
// * Quest Hero
// *
// ***********************************************
struct QuestHero extends array
private static unit array heroes
private static Array onChangeCallbacks
private static method onInit takes nothing returns nothing
set onChangeCallbacks = Array.create()
endmethod
public static method operator [] takes player p returns unit
return heroes[GetPlayerId(p)]
endmethod
public static method operator []= takes player p, unit u returns nothing
local unit old = heroes[GetPlayerId(p)]
local integer i = 0
set heroes[GetPlayerId(p)] = u
loop
exitwhen i >= onChangeCallbacks.size
call QuestHeroEventCallback(onChangeCallbacks[i]).evaluate(p, old, u)
set i = i + 1
endloop
endmethod
public static method onChange takes QuestHeroEventCallback callback returns nothing
call onChangeCallbacks.push(callback)
endmethod
endstruct
// ***********************************************
// *
// * Quest
// *
// ***********************************************
struct Quest
// database is used for:
// mapping (owner, player ID) --> dummy
// mapping (owner, player) --> effect
private static hashtable database
private static Table id2quest
private static HandleTable owner2quests
private static integer lastQuest = 0
private static trigger onSelectOwner = CreateTrigger()
private static trigger onSellItem = CreateTrigger()
readonly unit owner // the unit that gives you the quest & rewards
private integer itemId // RAW ID of item button, as well as ID of QuestType
public string title
public string description
public string iconPath
public boolean required
public boolean repeatable
private Array conditions
private Array items
private Array rewards
private Array positions
private static method getDummy takes unit u, player p returns unit
return LoadUnitHandle(database, GetHandleId(u), GetPlayerId(p)) // use GetPlayerId to avoid collision with effect
endmethod
private static method setDummy takes unit u, player p, unit dummy returns nothing
call SaveUnitHandle(database, GetHandleId(u), GetPlayerId(p), dummy)
endmethod
private static method getEffect takes unit u, player p returns effect
return LoadEffectHandle(database, GetHandleId(u), GetHandleId(p)) // use GetHandleId to avoid collision with dummy
endmethod
private static method setEffect takes unit u, player p, effect e returns nothing
call SaveEffectHandle(database, GetHandleId(u), GetHandleId(p), e)
endmethod
private static method getInstance takes Quest q, player p returns QuestInstance
return LoadInteger(database, q, GetPlayerId(p))
endmethod
public static method setInstance takes Quest q, player p, QuestInstance instance returns nothing
if (instance == 0) then
call RemoveSavedInteger(database, q, GetPlayerId(p))
else
call SaveInteger(database, q, GetPlayerId(p), instance)
endif
endmethod
// This function creates an Array of QuestItemInstance, instances for this quest's items
public method createQuestItemInstances takes player p returns Array
local Array result = 0
local integer i = 0
if (this.items != 0) then
set result = Array.create()
loop
exitwhen i >= this.items.size
call result.push(QuestItem(this.items[i]).instance(p))
set i = i + 1
endloop
endif
return result
endmethod
// This function selects a dummy unit when a quest owner is selected
private static method selectDummyAction takes nothing returns nothing
local unit u = null
if (GetLocalPlayer() == GetTriggerPlayer()) then
set u = Quest.getDummy(GetTriggerUnit(), GetTriggerPlayer())
if (u != null and GetOwningPlayer(u) == GetTriggerPlayer()) then
call ClearSelection()
call SelectUnit(u, true)
set u = null
endif
endif
endmethod
// This function responds to the quest button click (on a dummy)
private static method sellItemAction takes nothing returns nothing
local unit u = GetSellingUnit()
local player p = GetOwningPlayer(u)
local Quest this = Quest[GetItemTypeId(GetSoldItem())]
local QuestInstance instance = this.instance(p)
if (this != 0) then
if (instance == -1) then // unique quest was already finished
call RemoveItemFromStock(u, this.itemId)
call BJDebugMsg("Quest::sellItemAction - instance = -1; shouldn't happen!")
elseif (instance == 0) then // quest not taken yet
set instance = QuestInstance.create(this, p)
call Quest.setInstance(this, p, instance)
call instance.update()
// ping minimap & display text
static if (QUEST_PING_NOTIFICATION) then
if (GetLocalPlayer() == p) then
call this.pingMinimap(p)
endif
endif
static if (QUEST_TEXT_NOTIFICATION and QUEST_TEXT_DESCRIPTION) then
call DisplayTextToPlayer(p, 0, 0, StringReplace(QUEST_TEXT_ACCEPTED, "%s", this.title) + "\n" + this.description)
endif
elseif (instance.finished) then // quest is finished
call instance.reward()
else
// ping minimap & display text if applicable
static if (QUEST_PING_NOTIFICATION) then
if (GetLocalPlayer() == p) then
call this.pingMinimap(p)
endif
endif
static if (QUEST_TEXT_NOTIFICATION and QUEST_TEXT_DESCRIPTION) then
call DisplayTextToPlayer(p, 0, 0, StringReplace(QUEST_TEXT_UPDATED, "%s", this.title) + "\n" + this.description)
endif
endif
//call Quest.updateEffect(this.owner, p)
call this.updateConditions(p)
endif
set u = null
endmethod
private static method onHeroChange takes player owner, unit old, unit new returns nothing
call Quest.updateAllEffects(owner)
endmethod
private static method onInit takes nothing returns nothing
set database = InitHashtable()
set id2quest = Table.create()
set owner2quests = HandleTable.create()
call TriggerAddAction(onSelectOwner, function Quest.selectDummyAction)
call TriggerAddAction(onSellItem, function Quest.sellItemAction)
call TriggerRegisterAnyUnitEventBJ(onSelectOwner, EVENT_PLAYER_UNIT_SELECTED)
call QuestHero.onChange(Quest.onHeroChange)
endmethod
public static method operator [] takes integer itemid returns Quest
return id2quest[itemid]
endmethod
private method initDummy takes nothing returns nothing
local integer i = 0
local player p = Player(0)
local unit u = Quest.getDummy(this.owner, p)
// If the quest owner already has dummies, just return
if (u != null) then
return
endif
// If the quest owner has no dummies yet, create dummies, and register owner2quests
loop
exitwhen i >= 12
set p = Player(i)
set u = CreateUnit(p, QUEST_DUMMY_ID, GetUnitX(this.owner), GetUnitY(this.owner), 0)
call Quest.setDummy(this.owner, p, u) // map owner/player to dummy
call TriggerRegisterUnitEvent(onSellItem, u, EVENT_UNIT_SELL_ITEM)
set i = i + 1
endloop
set owner2quests[this.owner] = Array.create()
set u = null
endmethod
public static method create takes integer id, unit owner returns Quest
local Quest this = Quest[id]
if (this != 0) then
call BJDebugMsg("Quest::create - error: ID " + I2S(id) + " is already taken")
return 0
endif
set this = Quest.allocate()
set this.owner = owner
set this.itemId = id
set id2quest[id] = this
set this.title = "Generic Quest Title"
set this.description = "Generic Quest Description"
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNAmbush.blp"
set this.required = false
set this.repeatable = false
set this.conditions = 0
set this.items = 0
set this.rewards = 0
set this.positions = 0
call this.initDummy()
call Array(owner2quests[this.owner]).push(this)
set lastQuest = this
return this
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg("Error: you're not supposed to destroy() a Quest")
endmethod
public method instance takes player p returns QuestInstance
return Quest.getInstance(this, p)
endmethod
public method operator id takes nothing returns integer
return this.itemId
endmethod
public method addCondition takes QuestCondition c returns nothing
if (this.conditions == 0) then
set this.conditions = Array.create()
endif
call this.conditions.push(c)
call c.bind(this)
endmethod
public method addItem takes QuestItem i returns nothing
if (this.items == 0) then
set this.items = Array.create()
endif
call this.items.push(i)
call i.bind(this)
endmethod
public method addReward takes QuestReward r returns nothing
if (this.rewards == 0) then
set this.rewards = Array.create()
endif
call this.rewards.push(r)
endmethod
public method addPosition takes real x, real y returns nothing
if (this.positions == 0) then
set this.positions = Array.create()
endif
call this.positions.push(QuestPosition.create(x, y))
endmethod
public method checkConditions takes player p returns boolean
local integer i = 0
if (this.instance(p) < 0) then
return false
endif
if (this.conditions == 0) then
return true
endif
loop
exitwhen i >= this.conditions.size
if (not QuestCondition(this.conditions[i]).check(p)) then
return false
endif
set i = i + 1
endloop
return true
endmethod
public method giveRewards takes player p returns nothing
local integer i = 0
if (this.rewards != 0) then
loop
exitwhen i >= this.rewards.size
call QuestReward(this.rewards[i]).give(p)
set i = i + 1
endloop
endif
static if (QUEST_CREATE_UI) then
if (GetLocalPlayer() == p) then
call FlashQuestDialogButton()
endif
endif
static if (QUEST_TEXT_NOTIFICATION) then
call DisplayTextToPlayer(p, 0, 0, StringReplace(QUEST_TEXT_REWARDED, "%s", this.title))
endif
if (not this.repeatable) then
call RemoveItemFromStock(Quest.getDummy(this.owner, p), this.itemId)
endif
call Quest.updateEffect(this.owner, p)
endmethod
public method pingMinimap takes player p returns nothing
local integer i = 0
static if (QUEST_PING_NOTIFICATION) then
if (GetLocalPlayer() == p and this.positions != 0) then
loop
exitwhen i >= this.positions.size
call QuestPosition(this.positions[i]).pingMinimap(255, 0, 0)
set i = i + 1
endloop
endif
endif
endmethod
// Updates "overhead" effects on a unit for player p
// i.e. checks if any quests are available, then creates "Exclamation Mark!" above that unit
public static method updateEffect takes unit owner, player p returns nothing
local Array quests = owner2quests[owner]
local QuestInstance instance = 0
local string effectPath = ""
local effect e = Quest.getEffect(owner, p)
local integer availableQuests = 0
local integer acceptedQuests = 0
local integer completedQuests = 0
local integer i = 0
// Check if the owner has any quests at all
if (quests == 0) then
return
endif
// Destroy previous effect
if (e != null) then
call DestroyEffect(e)
endif
if (GetLocalPlayer() == p) then // to test: desyncs? don't think it'll happen, else move it to "change effect path"
// Count quest statuses
loop
exitwhen i >= quests.size
set instance = Quest(quests[i]).instance(p)
if (instance == 0) then // no quest instance yet
if (Quest(quests[i]).checkConditions(p)) then // quest is available
set availableQuests = availableQuests + 1
endif
elseif (instance > 0) then // quest instance found
if (instance.finished) then
set completedQuests = completedQuests + 1
else
set acceptedQuests = acceptedQuests + 1
endif
endif
set i = i + 1
endloop
// change effect path
if (availableQuests > 0) then
set effectPath = QUEST_EFFECT_AVAILABLE
elseif (completedQuests > 0) then
set effectPath = QUEST_EFFECT_COMPLETED
elseif (acceptedQuests > 0) then
set effectPath = QUEST_EFFECT_ACCEPTED
endif
endif
// Change the effect
set e = AddSpecialEffectTarget(effectPath, owner, "overhead")
call Quest.setEffect(owner, p, e)
endmethod
public static method updateAllEffects takes player p returns nothing
local integer this = 1
local group g = NewGroup()
loop
exitwhen this > lastQuest
if (not IsUnitInGroup(Quest(this).owner, g)) then
call GroupAddUnit(g, Quest(this).owner)
call Quest.updateEffect(Quest(this).owner, p)
endif
set this = this + 1
endloop
endmethod
public method updateConditions takes player p returns nothing
if (this.checkConditions(p)) then
call AddItemToStock(Quest.getDummy(this.owner, p), this.itemId, 1, 1)
elseif (Quest.getInstance(this, p) <= 0) then
call RemoveItemFromStock(Quest.getDummy(this.owner, p), this.itemId)
endif
call Quest.updateEffect(this.owner, p)
endmethod
public static method updateAllConditions takes player p returns nothing
local integer this = 1
loop
exitwhen this > lastQuest
call Quest(this).updateConditions(p)
set this = this + 1
endloop
endmethod
public method finished takes player p returns boolean
local QuestInstance instance = this.instance(p)
return (instance < 0) or (instance > 0 and instance.finished)
endmethod
endstruct
// ***********************************************
// *
// * Quest Instance
// * 1 instance per player, per quest
// *
// ***********************************************
struct QuestInstance
private static Array onCreateCallbacks
private static Array onUpdateCallbacks
private static Array onFinishCallbacks
private Array items
readonly player owner
readonly quest ui
readonly Quest type
private static method onInit takes nothing returns nothing
set onCreateCallbacks = Array.create()
set onUpdateCallbacks = Array.create()
set onFinishCallbacks = Array.create()
endmethod
public static method onCreate takes QuestEventCallback callback returns nothing
call onCreateCallbacks.push(callback)
endmethod
public static method onUpdate takes QuestEventCallback callback returns nothing
call onUpdateCallbacks.push(callback)
endmethod
public static method onFinish takes QuestEventCallback callback returns nothing
call onFinishCallbacks.push(callback)
endmethod
// QuestInstance triggers an event. The event is defined by the callbacks parameter (e.g. onCreateCallbacks)
private method triggerEvent takes Array callbacks returns nothing
local integer i = 0
loop
exitwhen i >= callbacks.size
call QuestEventCallback(callbacks[i]).evaluate(this)
set i = i + 1
endloop
endmethod
public static method create takes Quest qt, player p returns QuestInstance
local QuestInstance this = QuestInstance.allocate()
local integer i = 0
set this.owner = p
set this.type = qt
static if (QUEST_CREATE_UI) then
set this.ui = CreateQuest()
call QuestSetTitle(this.ui, this.type.title)
call QuestSetDescription(this.ui, this.type.description)
call QuestSetIconPath(this.ui, this.type.iconPath)
call QuestSetRequired(this.ui, this.type.required)
call QuestSetEnabled(this.ui, GetLocalPlayer() == this.owner)
if (GetLocalPlayer() == this.owner) then
call FlashQuestDialogButton()
endif
endif
call Quest.setInstance(this.type, p, this)
set this.items = this.type.createQuestItemInstances(p)
call this.triggerEvent(onCreateCallbacks)
return this
endmethod
private method onDestroy takes nothing returns nothing
local integer i = 0
// if unique quest, make sure we remember the quest was already taken
if (this.type.repeatable) then
call Quest.setInstance(this.type, this.owner, 0)
else
call Quest.setInstance(this.type, this.owner, -1)
endif
// fail quest if not completed
static if (QUEST_CREATE_UI) then
if (not IsQuestCompleted(this.ui)) then
call QuestSetFailed(this.ui, true)
endif
endif
// destroy all quest items
if (this.items != 0) then
loop
exitwhen i >= this.items.size
call QuestItemInstance(this.items[i]).destroy()
set i = i + 1
endloop
call this.items.destroy()
endif
call this.type.updateConditions(this.owner)
endmethod
public method operator finished takes nothing returns boolean
local integer i = 0
if (this.items == 0) then
return true
endif
loop
exitwhen i >= this.items.size
if (not QuestItemInstance(this.items[i]).finished) then
return false
endif
set i = i + 1
endloop
return true
endmethod
public method operator failed takes nothing returns boolean
local integer i = 0
if (this.items == 0) then
return false
endif
loop
exitwhen i >= this.items.size
if (QuestItemInstance(this.items[i]).failed) then
return true
endif
set i = i + 1
endloop
return false
endmethod
public method update takes nothing returns nothing
local boolean finished = true
local boolean failed = false
local integer i = 0
// calculate if quest is finished or failed (or still in progress)
if (this.items != 0) then
loop
exitwhen i >= this.items.size
if (not QuestItemInstance(this.items[i]).finished) then
set finished = false
endif
if (QuestItemInstance(this.items[i]).failed) then
set failed = true
endif
set i = i + 1
endloop
endif
call this.triggerEvent(onUpdateCallbacks)
if (failed) then
static if (QUEST_CREATE_UI) then
call QuestSetFailed(this.ui, true)
if (GetLocalPlayer() == this.owner) then
call FlashQuestDialogButton()
endif
endif
static if (QUEST_TEXT_NOTIFICATION) then
call DisplayTextToPlayer(this.owner, 0, 0, StringReplace(QUEST_TEXT_FAILED, "%s", this.type.title))
endif
//call this.type.updateConditions(this.owner)
call this.triggerEvent(onFinishCallbacks)
call this.destroy()
elseif (finished) then
static if (QUEST_CREATE_UI) then
call QuestSetCompleted(this.ui, true)
if (GetLocalPlayer() == this.owner) then
call FlashQuestDialogButton()
endif
endif
static if (QUEST_TEXT_NOTIFICATION) then
call DisplayTextToPlayer(this.owner, 0, 0, StringReplace(QUEST_TEXT_COMPLETED, "%s", this.type.title))
endif
static if (QUEST_PING_NOTIFICATION) then
if (GetLocalPlayer() == this.owner) then
call PingMinimapEx(GetUnitX(this.type.owner), GetUnitY(this.type.owner), QUEST_PING_DURATION, 255, 255, 0, QUEST_PING_EFFECTS)
endif
endif
call Quest.updateEffect(this.type.owner, this.owner)
call this.triggerEvent(onFinishCallbacks)
endif
endmethod
public method reward takes nothing returns nothing
if (this.finished) then
call this.type.giveRewards(this.owner)
call this.destroy()
endif
endmethod
endstruct
endlibrary
//TESH.scrollpos=128
//TESH.alwaysfold=0
================
= Quest System =
================
by Sadalbari
'Introduction'
'Installation'
1. Copying the required scripts
Open the trigger editor
All scripts in the "Libraries" and "Quest System" folders are required.
Copy both folders into your own map.
2. Copying the required objects
Open the object editor
Copy the "Quests" unit (the only custom unit in this map) to your own map
3. Enter the "QuestSystem" trigger in your map
At the top of the trigger you find a section labeled:
globals
constant integer ...
...
endglobals
Change the values after the "=" mark into the correct values.
Description of each constant follows below
a. constant integer QUEST_DUMMY_ID = 'qdum'
This is the RAW DATA ID of the "Quest" unit you have copied in step 2.
To find the RAW DATA ID of an object, go to the object editor, press ctrl+D and note the id in front of the unit name
b. constant boolean QUEST_CREATE_UI = true
Values: true/false
If this value is true, the quest window will be used to display quests (F9 in-game)
c. constant boolean QUEST_TEXT_NOTIFICATION = true
Values: true/false
If this value is true, text notifications will be displayed on screen whenever a quest is updated
You must also modify:
constant string QUEST_TEXT_<something>
into the applicable values
d. constant boolean QUEST_PING_NOTIFICATION = true
Values: true/false
If this value is true, minimap PINGS will be visible when applicable
e. constant boolean QUEST_EFFECT_NOTIFICATION = false
Values: true/false
If this value is true, an effect will be created to show that a unit has quests available
'Quest Creation'
See 'API Reference' for a detailed reference
'API Reference'
1. QuestType API
----------------
QuestType.create takes unit questOwner, integer itemId returns QuestType
unit questOwner the unit that gives you the quest
integer itemId RAW ID of the item (to get the quest). Also serves as <unique> QuestType ID
operator QuestType[] takes integer itemId returns QuestType
integer itemId RAW ID of the QuestType item. Will return the related QuestType
QuestType.checkConditionsAll takes player p returns nothing
Executes questtype.checkConditions on all questtypes in the game
questtype.title
string title: Get / Set the title of the quest
questtype.description
string description: Get / Set the description of the quest
questtype.iconPath
string iconPath: Get / Set the iconPath of the quest interface (QUEST_CREATE_UI must be true)
questtype.required
boolean required: Get / Set if the quest is required or optional
questtype.unique
boolean unique: Get / Set if the quest can be repeated or not
questtype.locationX (not 0)
real locationX: Get / Set the X coordinate of the quest objective
questtype.locationY (not 0)
real locationY : Get / Set the Y coordinate of the quest objective
questtype.id
integer id: Get the ID of the QuestType
questtype.addCondition takes QuestCondition c returns nothing
Adds a condition to the quest. Conditions must be met before the quest can be taken
questtype.addItem takes QuestItemType it returns nothing
Adds an itemtype to the quest. Itemtypes must be completed before the quest can be completed
questtype.addReward takes QuestReward r returns nothing
Adds a reward to the quest. Rewards are given when the quest is completed.
questtype.finished takes player p returns boolean
Returns true if the player has finished the questtype
questtype.checkConditions takes player p returns boolean
Returns true if the player has met all conditions to receive the quest
questtype.updateConditions takes player p returns nothing
Checks if the player has met all conditions. If a change is detected
(either the player no longer meets the conditions, or now meets the conditions),
the quest is made available/unavailable for the player
questtype.instance takes player p returns nothing
Retrieves a Quest instance for player p
Checks if conditions are met by player p
Checks if player p has already taken the quest
2. QuestCondition API
---------------------
QuestConditionLevel.create takes integer minLevel, integer maxLevel returns QuestConditionLevel
integer level: The hero must be between minLevel and maxLevel
QuestConditionType.create takes integer unittypeid returns QuestConditionType
integer typeid: The hero must be of unittype unittypeid (e.g. 'Hpal' for Paladin)
QuestConditionQuest.create takes integer questTypeId returns QuestConditionQuest
integer questTypeId:The QuestType with the relevant ID must be finished
3. QuestReward API
------------------
QuestRewardResource.create takes integer gold, integer lumber returns QuestRewardResource
integer gold: The rewarded gold
integer lumber: The rewarded lumber
QuestRewardExperience.create takes integer xp returns QuestRewardExperience
integer xp: The rewarded experience
QuestRewardItem.create takes integer itemId, integer charges returns QuestRewardItem
integer itemId: The RAW ID of the rewarded item
integer charges: The number of charges on the item (use 0 if not applicable)
4. QuestItemType API
--------------------
QuestItemKillUnit.create takes integer unitTypeId, integer num, string description returns QuestItem
integer unitTypeId: The RAW ID of the unittype that must be killed
integer num: The number of units that must be killed
string description: The description of the questitem. Use "%s" to refer to the number of killed units
QuestItemKillUnit.allowedPlayers
force: Units owned by players in this force can be killed for this questitem
Default player in force: Neutral hostile
QuestItemGatherItem.create takes integer itemId, integer num, string description returns QuestItem
integer itemId: The RAW ID of the itemtype that must be gathered. Make sure the item is consumed when acquired (like tome of strength)
integer num: The number of items that must be gathered
string description: The description of the questitem. Use "%s" to refer to the number of gathered items
'Plugin Creation'
Creating your own quest plugins
1. Conditions
a. Your struct must extend the struct "QuestCondition"
struct MyQuestCondition extends QuestCondition
b. Your struct must implement the "check" method
public method check takes player p returns boolean
Returns true if the condition is met by player p
c. Your struct must call
this.questType.updateConditions(player)
Whenever the condition has changed for the player.
For example, QuestConditionLevel detects whenever a hero gains a level, and
calls this.questType.updateConditions(p) automatically
2. Rewards
a. Your struct must extend the struct "QuestReward"
struct MyQuestReward extends QuestReward
b. Your struct must implement the "give" method
public method give takes player p returns nothing
Gives the reward to the player.
3. QuestItems
a. Your struct must extend the struct "QuestItemType"
struct MyQuestItemType extends QuestItemType
b. Your struct constructor must take a "string description" argument
struct MyQuestItemType this = QuestItemType.allocate(integer requiredStatus, string description)
integer requiredStatus: The required status for the questitem to be completed.
A requiredStatus must be greater than 0
string description: The questitem description
c. Your struct must implement the "getDescription" method
public stub method getDescription takes QuestItem it returns string
Returns an updated description string (for example, by replacing "%s" by the number of killed units
d. Your struct must implement functionality for updating the status of the questitem.
See the implemented QuestItems for examples.
You can use the ItemType API as described below:
ItemType it = some_itemtype
it.status: Retrieves the status of the QuestItem
-1 means: questitem failed
equal to requiredStatus: questitem completed
inbetween: not yet completed
it.finished: True if the questitem is completed
it.failed: True if the questitem is failed
it.update(boolean)
Updates the QuestItem status:
If true: +1
If false: set status to -1 (item failed)
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestConditionLevel requires QuestSystem, Array
struct QuestConditionLevel extends QuestCondition
private static trigger onGainLevel
private static Array conditions
private integer minLevel
private integer maxLevel
private static method onGainLevelAction takes nothing returns nothing
local integer i = 0
local player p = GetTriggerPlayer()
loop
exitwhen i >= conditions.size
call QuestConditionLevel(conditions[i]).quest.updateConditions(p)
set i = i + 1
endloop
endmethod
private static method onInit takes nothing returns nothing
set conditions = Array.create()
set onGainLevel = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(onGainLevel, EVENT_PLAYER_HERO_LEVEL)
call TriggerAddAction(onGainLevel, function QuestConditionLevel.onGainLevelAction)
endmethod
public static method create takes integer minLevel, integer maxLevel returns QuestConditionLevel
local QuestConditionLevel this = QuestConditionLevel.allocate()
set this.minLevel = minLevel
set this.maxLevel = maxLevel
call conditions.push(this)
return this
endmethod
public method check takes player p returns boolean
return GetUnitLevel(QuestHero[p]) >= this.minLevel and GetUnitLevel(QuestHero[p]) <= this.maxLevel
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestConditionType requires QuestSystem, Array
struct QuestConditionType extends QuestCondition
private static Array conditions
private integer type
private static method onHeroChange takes player p, unit old, unit new returns nothing
local integer i = 0
if (GetUnitTypeId(old) != GetUnitTypeId(new)) then
loop
exitwhen i >= conditions.size
if (QuestConditionType(conditions[i]).type == GetUnitTypeId(old) or QuestConditionType(conditions[i]).type == GetUnitTypeId(new)) then
call QuestConditionType(conditions[i]).quest.updateConditions(p)
endif
set i = i + 1
endloop
endif
endmethod
private static method onInit takes nothing returns nothing
set conditions = Array.create()
call QuestHero.onChange(QuestConditionType.onHeroChange)
endmethod
public static method create takes integer typeid returns QuestConditionType
local QuestConditionType this = QuestConditionType.allocate()
set this.type = typeid
call conditions.push(this)
return this
endmethod
public method check takes player p returns boolean
return GetUnitTypeId(QuestHero[p]) == this.type
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestConditionQuest requires QuestSystem, Array
struct QuestConditionQuest extends QuestCondition
private static Array conditions
private integer requirementId // store ID instead of QuestType because a QuestType may add this condition for another QuestType that isn't yet created
private static method onFinishCallback takes QuestInstance q returns nothing
local integer i = 0
loop
exitwhen i >= conditions.size
if (QuestConditionQuest(conditions[i]).requirementId == q.type.id) then
call QuestConditionQuest(conditions[i]).quest.updateConditions(q.owner)
endif
set i = i + 1
endloop
endmethod
private static method onInit takes nothing returns nothing
set conditions = Array.create()
call QuestInstance.onFinish(QuestConditionQuest.onFinishCallback)
endmethod
public static method create takes integer requirementId returns QuestConditionQuest
local QuestConditionQuest this = QuestConditionQuest.allocate()
set this.requirementId = requirementId
call conditions.push(this)
return this
endmethod
public method check takes player p returns boolean
return Quest[this.requirementId].finished(p)
endmethod
endstruct
endlibrary
//TESH.scrollpos=2
//TESH.alwaysfold=0
library QuestRewardResource requires QuestSystem
struct QuestRewardResource extends QuestReward
private integer gold
private integer lumber
public static method create takes integer gold, integer lumber returns QuestRewardResource
local QuestRewardResource this = QuestRewardResource.allocate()
set this.gold = gold
set this.lumber = lumber
return this
endmethod
public stub method give takes player p returns nothing
call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) + this.gold)
call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) + this.lumber)
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestRewardExperience requires QuestSystem
struct QuestRewardExperience extends QuestReward
private integer xp
public static method create takes integer xp returns QuestRewardExperience
local QuestRewardExperience this = QuestRewardExperience.allocate()
set this.xp = xp
return this
endmethod
public method give takes player p returns nothing
local unit u = QuestHero[p]
if (u != null) then
call AddHeroXP(u, this.xp, true)
set u = null
endif
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestRewardItem requires QuestSystem
struct QuestRewardItem extends QuestReward
private integer itemId
private integer itemCharges
public static method create takes integer itemId, integer charges returns QuestRewardItem
local QuestRewardItem this = QuestRewardItem.allocate()
set this.itemId = itemId
set this.itemCharges = itemCharges
return this
endmethod
public stub method give takes player p returns nothing
local unit u = QuestHero[p]
local item it
if (u != null) then
set it = CreateItem(this.itemId, GetUnitX(u), GetUnitY(u))
if (this.itemCharges != 0) then
call SetItemCharges(it, this.itemCharges)
endif
call UnitAddItem(u, it)
set u = null
set it = null
else
debug call BJDebugMsg("QuestRewardItem::give - Error: player does not have a QuestHero")
endif
endmethod
endstruct
endlibrary
//TESH.scrollpos=2
//TESH.alwaysfold=0
library QuestRewardTrigger requires QuestSystem
struct QuestRewardTrigger extends QuestReward
public static player player
private trigger t
public static method create takes trigger t returns QuestRewardTrigger
local QuestRewardTrigger this = QuestRewardTrigger.allocate()
set this.t = t
return this
endmethod
public method give takes player p returns nothing
set QuestRewardTrigger.player = p
call TriggerEvaluate(t)
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestItemKillUnit requires QuestSystem, Table
struct QuestItemKillUnit extends QuestItem
public static constant force allowedPlayers = CreateForce()
private static Table unitid2questitems
private static trigger onDeath = CreateTrigger()
private static method onDeathAction takes nothing returns nothing
local player killer = GetOwningPlayer(GetKillingUnit())
local Array questitems = unitid2questitems[GetUnitTypeId(GetTriggerUnit())]
local QuestItemInstance instance = 0
local integer i = 0
if (questitems == 0 or not IsPlayerInForce(GetOwningPlayer(GetTriggerUnit()), allowedPlayers)) then
return
endif
loop
exitwhen i >= questitems.size
set instance = QuestItem(questitems[i]).instance(killer)
if (instance != 0 and not QuestItem(questitems[i]).quest.finished(killer)) then
call instance.update(true)
endif
set i = i + 1
endloop
endmethod
public stub method getDescription takes QuestItemInstance it returns string
return StringReplaceAll(this.description, "%s", I2S(it.status))
endmethod
private static method onInit takes nothing returns nothing
set unitid2questitems = Table.create()
call ForceAddPlayer(allowedPlayers, Player(PLAYER_NEUTRAL_AGGRESSIVE))
call TriggerRegisterAnyUnitEventBJ(onDeath, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(onDeath, function QuestItemKillUnit.onDeathAction)
endmethod
public method addUnitType takes integer unitid returns QuestItemKillUnit
local Array questitems = unitid2questitems[unitid]
if (questitems == 0) then
set questitems = Array.create()
set unitid2questitems[unitid] = questitems
endif
call questitems.push(this)
return this
endmethod
public static method create takes integer unitid, integer num, string description returns QuestItemKillUnit
local QuestItemKillUnit this = QuestItemKillUnit.allocate(num, description)
call this.addUnitType(unitid)
return this
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestItemGatherItem requires QuestSystem, Table
struct QuestItemGatherItem extends QuestItem
private static Table itemid2questitems // maps itemid --> Array of QuestItems
private static trigger onPickup = CreateTrigger()
private static method onPickupAction takes nothing returns nothing
local player p = GetOwningPlayer(GetTriggerUnit())
local Array questitems = itemid2questitems[GetItemTypeId(GetManipulatedItem())]
local QuestItemInstance instance = 0
local integer i = 0
if (questitems == 0) then
return
endif
loop
exitwhen i >= questitems.size
set instance = QuestItem(questitems[i]).instance(p)
if (instance != 0 and not QuestItem(questitems[i]).quest.finished(p)) then
call instance.update(true)
endif
set i = i + 1
endloop
endmethod
public stub method getDescription takes QuestItemInstance it returns string
return StringReplaceAll(this.description, "%s", I2S(it.status))
endmethod
private static method onInit takes nothing returns nothing
set itemid2questitems = Table.create()
call TriggerRegisterAnyUnitEventBJ(onPickup, EVENT_PLAYER_UNIT_PICKUP_ITEM)
call TriggerAddAction(onPickup, function QuestItemGatherItem.onPickupAction)
endmethod
private method addItemType takes integer itemid returns nothing
local Array questitems = itemid2questitems[itemid]
if (questitems == 0) then
set questitems = Array.create()
set itemid2questitems[itemid] = questitems
endif
call questitems.push(this)
endmethod
public static method create takes integer itemid, integer num, string description returns QuestItemGatherItem
local QuestItemGatherItem this = QuestItemGatherItem.allocate(num, description)
call this.addItemType(itemid)
return this
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestItemVisitRegion requires QuestSystem, Table
struct QuestItemVisitRegion extends QuestItem
private static HandleTable region2questitem
private static trigger enterRegion
private region area
private static method onEnterRegion takes nothing returns nothing
local player p = GetOwningPlayer(GetEnteringUnit())
local QuestItemVisitRegion this = region2questitem[GetTriggeringRegion()]
local QuestItemInstance it = this.instance(p)
if (GetEnteringUnit() == QuestHero[p] and it != 0 and not it.type.quest.finished(p)) then
call it.update(true)
endif
endmethod
private static method onInit takes nothing returns nothing
set region2questitem = HandleTable.create()
set enterRegion = CreateTrigger()
call TriggerAddAction(enterRegion, function QuestItemVisitRegion.onEnterRegion)
endmethod
public method addRegion takes rect area returns nothing
call RegionAddRect(this.area, area)
endmethod
public static method create takes rect area, string description returns QuestItemVisitRegion
local QuestItemVisitRegion this = QuestItemVisitRegion.allocate(1, description)
set this.area = CreateRegion()
set region2questitem[this.area] = this
call this.addRegion(area)
call TriggerRegisterEnterRegion(enterRegion, this.area, null)
return this
endmethod
endstruct
endlibrary
//TESH.scrollpos=19
//TESH.alwaysfold=0
library QuestItemTimer requires QuestSystem, TimerUtils
private struct TimerHelper
public timer t
public timerdialog ui
endstruct
struct QuestItemTimer extends QuestItem
private static Array deadlines
private static Table instance2helper
private real duration
private boolean deadline
private boolean doDialog
public static method createDeadline takes real duration, boolean doDialog, string description returns QuestItemTimer
local QuestItemTimer this = QuestItemTimer.allocate(0, description)
set this.duration = duration
set this.deadline = true
set this.doDialog = doDialog
call deadlines.push(this)
return this
endmethod
public static method createCountdown takes real duration, boolean doDialog, string description returns QuestItemTimer
local QuestItemTimer this = QuestItemTimer.allocate(1, description)
set this.duration = duration
set this.deadline = false
set this.doDialog = doDialog
call deadlines.push(this)
return this
endmethod
private static method create takes nothing returns QuestItemTimer
return 0
endmethod
private static method cleanupGarbage takes timer t returns nothing
local QuestItemInstance instance = GetTimerData(t)
local QuestItemTimer this = instance.type
local TimerHelper helper = instance2helper[instance]
call ReleaseTimer(t)
if (this.doDialog) then
call DestroyTimerDialog(helper.ui)
endif
call helper.destroy()
call instance2helper.flush(instance)
endmethod
private static method onDeadlineExpires takes nothing returns nothing
local timer t = GetExpiredTimer()
local QuestItemInstance instance = GetTimerData(t)
call QuestItemTimer.cleanupGarbage(t)
call instance.update(not QuestItemTimer(instance.type).deadline)
endmethod
private static method onDeadlineInstance takes QuestItemInstance instance returns nothing
local timer t
local TimerHelper helper
local QuestItemTimer this = 0
if (instance.type.getType() == QuestItemTimer.typeid) then
set this = instance.type
set t = NewTimer()
call SetTimerData(t, instance)
set helper = TimerHelper.create()
set helper.t = t
if (this.doDialog) then
// TO TEST: eh, TimerDialog *does* update as timer updates, right?
// AND: it doesn't update timer itself, does it?
set helper.ui = CreateTimerDialog(t)
call TimerDialogDisplay(helper.ui, GetLocalPlayer() == instance.owner)
call TimerDialogSetTitle(helper.ui, this.getDescription(instance))
endif
set instance2helper[instance] = helper
call TimerStart(t, this.duration, false, function QuestItemTimer.onDeadlineExpires)
endif
endmethod
private static method onQuestFinish takes QuestInstance instance returns nothing
local integer i = 0
local QuestItemTimer deadline = 0
local QuestItemInstance itemInstance = 0
local TimerHelper helper = 0
loop
exitwhen i >= deadlines.size
set deadline = QuestItemTimer(deadlines[i])
if (deadline.quest == instance.type) then // this is a deadline of the instance
set itemInstance = deadline.instance(instance.owner)
if (itemInstance != 0) then
set helper = TimerHelper(instance2helper[itemInstance])
if (helper.t != null) then
call QuestItemTimer.cleanupGarbage(helper.t)
endif
endif
endif
set i = i + 1
endloop
endmethod
private static method onInit takes nothing returns nothing
set deadlines = Array.create()
set instance2helper = Table.create()
call QuestItemInstance.onInstance(QuestItemTimer.onDeadlineInstance)
call QuestInstance.onFinish(QuestItemTimer.onQuestFinish)
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library QuestItemSurvive requires QuestSystem
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
function createCorpse takes integer unitId, real x, real y, real angle returns nothing
local unit u = CreateCorpse(Player(1), unitId, x, y, angle)
call SetUnitBlendTime(u, 0)
call SetUnitAnimation(u, "decay flesh")
call GroupAddUnit(bj_suspendDecayFleshGroup, u)
endfunction
function Trig_Caravan_Corpses_Actions takes nothing returns nothing
call createCorpse('hrdh', -1654, 383, 63)
call createCorpse('hrdh', -2000, 666, 190)
call createCorpse('hrdl', -1940, 260, 350)
call createCorpse('hbew', -1800, 400, 70)
call createCorpse('hbew', -1860, 70, 80)
call createCorpse('hcth', -2060, 650, 161)
call createCorpse('hcth', -2240, 470, 161)
call createCorpse('nhea', -1700, 250, 300)
call createCorpse('nbel', -1950, 250, 300)
call TimerStart(bj_delayedSuspendDecayTimer, 0.05, false, null)
endfunction
//===========================================================================
function InitTrig_Caravan_Corpses takes nothing returns nothing
set gg_trg_Caravan_Corpses = CreateTrigger( )
call TriggerAddAction( gg_trg_Caravan_Corpses, function Trig_Caravan_Corpses_Actions )
endfunction
//TESH.scrollpos=23
//TESH.alwaysfold=0
library Respawn requires Table, TimerUtils
private struct UnitData
private static HandleTable unit2data
readonly real x
readonly real y
readonly real angle
readonly integer type
public unit u
private static method onRespawnFinish takes nothing returns nothing
local timer t = GetExpiredTimer()
local UnitData data = GetTimerData(t)
call SetUnitInvulnerable(data.u, false)
call PauseUnit(data.u, false)
call ReleaseTimer(t)
endmethod
private static method onRespawn takes nothing returns nothing
local timer t = GetExpiredTimer()
local UnitData data = GetTimerData(t)
call unit2data.flush(data.u)
set data.u = CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), data.type, data.x, data.y, data.angle)
call SetUnitInvulnerable(data.u, true)
call PauseUnit(data.u, true)
set unit2data[data.u] = data
call TimerStart(t, 5, false, function UnitData.onRespawnFinish)
endmethod
private static method onDeath takes nothing returns nothing
local timer t = NewTimer()
local UnitData data = unit2data[GetTriggerUnit()]
call SetTimerData(t, data)
call TimerStart(t, 150, false, function UnitData.onRespawn)
endmethod
private static method onInitUnit takes nothing returns nothing
if (GetOwningPlayer(GetEnumUnit()) == Player(PLAYER_NEUTRAL_AGGRESSIVE)) then
call UnitData.create(GetEnumUnit())
endif
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local group g = NewGroup()
set unit2data = HandleTable.create()
call TriggerRegisterPlayerUnitEvent(t, Player(PLAYER_NEUTRAL_AGGRESSIVE), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddAction(t, function UnitData.onDeath)
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
call ForGroup(g, function UnitData.onInitUnit)
endmethod
public static method create takes unit u returns UnitData
local UnitData this = UnitData.allocate()
set this.x = GetUnitX(u)
set this.y = GetUnitY(u)
set this.angle = GetUnitFacing(u)
set this.type = GetUnitTypeId(u)
set unit2data[u] = this
return this
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestKillGnollScout initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qgnl', gg_unit_Hpb1_0001)
set this.title = "Gnoll Scouts"
set this.description = "The major has spotted gnoll scouts right outside the village. This doesn't promise much good. Kill them!"
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNGnoll.blp"
call this.addItem(QuestItemKillUnit.create('ngno', 4, "%s/4 Gnolls killed"))
call this.addReward(QuestRewardResource.create(50, 0))
call this.addReward(QuestRewardItem.create('pclr', 0))
call this.addReward(QuestRewardExperience.create(200))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestKillGnollInvasion initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qgno', gg_unit_Hpb1_0001)
set this.title = "Gnoll invasion"
set this.description = "The farmlands have been plagued with a gnoll invasion. The major has asked you to kill as many gnolls as possible!\n\nRepeatable."
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNGnollArcher.blp"
set this.repeatable = true
call this.addPosition(1380, -190)
call this.addCondition(QuestConditionLevel.create(2, 5))
call this.addCondition(QuestConditionQuest.create('qgnl'))
call this.addItem(QuestItemKillUnit.create('ngnb', 3, "%s/3 Gnoll Brutes killed."))
call this.addItem(QuestItemKillUnit.create('ngns', 3, "%s/3 Gnoll Assassins killed."))
call this.addItem(QuestItemKillUnit.create('ngnw', 3, "%s/3 Gnoll Wardens killed."))
call this.addReward(QuestRewardResource.create(150, 0))
call this.addReward(QuestRewardItem.create('phea', 0))
endfunction
endscope
//TESH.scrollpos=1
//TESH.alwaysfold=0
scope QuestKillGnollOverseer initializer onInit
private function overseerHeadRespawn takes nothing returns nothing
if (GetUnitTypeId(GetDyingUnit()) == 'ngnv') then
call CreateItem('Ignv', GetUnitX(GetDyingUnit()), GetUnitY(GetDyingUnit()))
endif
endfunction
private function initOverseerHeadRespawn takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(t, function overseerHeadRespawn)
endfunction
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qgnk', gg_unit_Hpb1_0001)
set this.title = "Gnoll Overseer"
set this.description = "Our scouts have discovered a hideout of the Gnoll Overseer. The beast must be killed to avoid further spreading of the gnolls."
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNGnollKing.blp"
set this.required = true
call this.addPosition(1300, 2670)
call this.addCondition(QuestConditionLevel.create(2, 6))
call this.addCondition(QuestConditionQuest.create('qgnl'))
call this.addItem(QuestItemKillUnit.create('ngnv', 1, "Kill the Gnoll Overseer"))
call this.addItem(QuestItemGatherItem.create('Ignv', 1, "Return the Overseer Head to the major"))
call this.addReward(QuestRewardExperience.create(100))
call this.addReward(QuestRewardItem.create('hlst', 0))
call initOverseerHeadRespawn()
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestKillBandits initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qban', gg_unit_Hpb1_0001)
set this.title = "Bandit ambushs"
set this.description = "Recently, we have received reports of trade caravans being ambushed by bandits near the mercenary camp at the south road. Kill them all."
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNBanditSpearThrower.blp"
set this.repeatable = true
call this.addPosition(230, -2500)
call this.addCondition(QuestConditionLevel.create(5, 8))
call this.addItem(QuestItemKillUnit.create('nban', 15, "%s/15 bandits killed").addUnitType('nbrg').addUnitType('nrog').addUnitType('nass').addUnitType('nenf'))
call this.addReward(QuestRewardResource.create(250, 0))
call this.addReward(QuestRewardExperience.create(100))
call this.addReward(QuestRewardItem.create('hslv', 5))
call this.addReward(QuestRewardItem.create('pclr', 2))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestMissingRangers initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qran', gg_unit_Hvwd_0123)
set this.title = "The missing rangers"
set this.description = "Rumors have been spreading that gnolls are invading our lands, so the major has requested a scouting party. I've sent out some of my finest rangers to scout the forests to the north, but haven't heard back of them. Please find them, they must be nearby."
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNHighElvenArcher.blp"
call this.addItem(QuestItemVisitRegion.create(gg_rct_Cage_Area, "Find the missing rangers"))
call this.addReward(QuestRewardItem.create('rag1', 1))
call this.addReward(QuestRewardExperience.create(75))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestCaravan initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qca1', gg_unit_Hvwd_0123)
set this.title = "The Caravan"
set this.description = "We've been expecting our trade caravan to return to our town yesterday. We haven't heard of them yet. We want you to go to the bridge and tell us if everything's ok."
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNBloodElfSupplyWagon.blp"
set this.required = true
call this.addPosition(-850, 1500)
call this.addItem(QuestItemVisitRegion.create(gg_rct_Bridge_Area, "Visit the Bridge Area"))
call this.addReward(QuestRewardItem.create('pman', 1))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestCaravan2 initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qca2', gg_unit_Hvwd_0123)
set this.title = "The Caravan (part 2)"
set this.description = "All right, we'll have to find another route across the river. We believe there may be wadeable areas at the river. Try and reach the other side of the bridge."
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNBloodElfSupplyWagon.blp"
set this.required = true
call this.addPosition(-1900, 100)
call this.addCondition(QuestConditionLevel.create(4, 10))
call this.addItem(QuestItemVisitRegion.create(gg_rct_Wadeable_River_Area, "Find a wadeable river area"))
call this.addItem(QuestItemVisitRegion.create(gg_rct_Caravan_Area, "Reach the other side of the bridge"))
call this.addReward(QuestRewardItem.create('belv', 1))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestCaravan3 initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qca3', gg_unit_Hvwd_0123)
set this.title = "The Caravan (part 3)"
set this.description = "We don't know who attacked the caravan! No matter, it could only have been those damned bandits. Or maybe trolls? I'm not sure... And I don't care! Kill all of them, and let our dead be avenged!"
set this.iconPath = "ReplaceableTextures\\CommandButtons\\BTNBloodElfSupplyWagon.blp"
set this.required = true
call this.addPosition(230, -2500)
call this.addPosition(-2850, -160)
call this.addCondition(QuestConditionLevel.create(6, 10))
call this.addItem(QuestItemKillUnit.create('nban', 5, "%s/5 bandits killed").addUnitType('nbrg').addUnitType('nrog').addUnitType('nass').addUnitType('nenf'))
call this.addItem(QuestItemKillUnit.create('nftb', 5, "%s/5 trolls killed").addUnitType('nfsh').addUnitType('nftk'))
call this.addReward(QuestRewardItem.create('hval', 1))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope QuestShrine initializer onInit
private function onInit takes nothing returns nothing
local Quest this = Quest.create('qshr', gg_unit_Hkal_0124)
set this.title = "Shrine of the Highborn"
set this.description = "As you may or may not know, every highborn elf must make an offering before the moon turns full. It's nearly full moon and I'm too busy taking care of the wounded rangers. Would you please visit the shrine for me before full moon?"
set this.iconPath = "ReplaceableTextures\\PassiveButtons\\PASBTNElunesBlessing.blp"
set this.required = true
call this.addPosition(-850, -2000)
call this.addCondition(QuestConditionQuest.create('qran'))
call this.addCondition(QuestConditionLevel.create(5, 10))
call this.addItem(QuestItemVisitRegion.create(gg_rct_Shrine_Area, "Visit the shrine"))
call this.addItem(QuestItemTimer.createDeadline(240, true, "Visit the shrine within 3 minutes"))
call this.addReward(QuestRewardItem.create('penr', 0))
endfunction
endscope
function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
call KillUnit( gg_unit_Hvwd_0123 )
call KillUnit( gg_unit_Hkal_0124 )
call MoveRectToLoc( gg_rct_Bridge_Area, GetRectCenter(GetPlayableMapRect()) )
call MoveRectToLoc( gg_rct_Caravan_Area, GetRectCenter(GetPlayableMapRect()) )
call MoveRectToLoc( gg_rct_Wadeable_River_Area, GetRectCenter(GetPlayableMapRect()) )
call MoveRectToLoc( gg_rct_Cage_Area, GetRectCenter(GetPlayableMapRect()) )
endfunction
//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
set gg_trg_Untitled_Trigger_001 = CreateTrigger( )
call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
Visit shrine for sick mage > mage
Gather components for gateway > mage
Protect Jaina and go to the teleport area (north-west) > mage
Investigate blighted forests > mage