System Quest Engines

Vestras

Retired
Reaction score
249
Quest Engines
For easy quest making

This is a package of two libraries; ItemQuestEngine and KillQuestEngine, both making it easier for the user to apply simple, boring-to-make quests. Everything is done with just a simple function call:

JASS:
call InitKillQuest(questunit,killunitrawcode,numberofkilledunits,rewardgold,rewardxp,questtitle,questicon,questdescription)
or
JASS:
call InitItemQuest(questunit,itemrawcode,numberofrecieveditems,rewardgold,rewardxp,questtitle,questicon,questdescription)


The two abilities needed uses 2 slightly edited models, each with 1 added attachment point, origin.

This makes attaching a neat ! for quests or ? for rewards above the units head much easier.

Now, I wanted this to be even more easy. So I decided to make those functions in the GUI too. If you want those two things to be available in GUI too, import the three .txt files I attached to this post into a folder named UI in the Warcraft III folder. If it's not there, create it. (This doesn't work if you have EGUI, WEU, UMSWE or any other GUI enhancing tool.)

Then copy the trigger category named Libraries from the attached map into the map you want to use the GUI functions in, and then you're done! Can't be much more easy, aye?

Now, I guess most of you would like to see the code right? Here it is;
JASS:
library KillQuestEngine

// configuration
globals
    private constant integer INTEGER_PLAYERS=8 // number of players in the map
    
    private constant integer RAWCODE_REWARD='A001' // raw code of the 'Reward' ability
    private constant integer RAWCODE_QUEST='A000' // raw code of the 'Quest' ability
    
    
    private constant string STRING_COLOR="|c00ff8000" // color of the messages
    
    
    private constant real REAL_RANGE=160.000 // trigger range
    private constant real REAL_DISPLAYTIME=6 // display time of the quest description
    
    
    public           boolean BOOL_REQ=false // quests is required? non-constant, you can always change
    private constant boolean BOOL_EYECANDY=true // level-up eyecandy showed?
endglobals

// kill quests
private struct killquest
    unit u
    integer id
    integer n
    integer gold
    integer xp
    integer count
    trigger t0
    trigger t1
    trigger t2
    boolean b
    // quest
    string content
    string name
    string icon
    quest q
    
        method onDestroy takes nothing returns nothing
            call DestroyTrigger(.t2)
        endmethod
endstruct

globals
    private killquest array KQ
    private integer N=0
endglobals

private function True takes nothing returns boolean
    return true
endfunction

private function onRange takes nothing returns boolean
    local killquest kq
    local integer j=N+1
    local integer i=-1
    local unit d=GetTriggerUnit()
    if IsUnitType(d,UNIT_TYPE_HERO)==true then
        loop
            set j=j-1
            exitwhen j<=0
                set kq=KQ[j]
                if kq.u==null then
                    return false
                endif
                //
                loop
                    set i=i+1
                    exitwhen i>INTEGER_PLAYERS
                        call DisplayTextToPlayer(Player(i),0,0,"Quest Completed: "+STRING_COLOR+kq.name+".|r")
                endloop
                call SetPlayerState(GetOwningPlayer(d),PLAYER_STATE_RESOURCE_GOLD,GetPlayerState(GetOwningPlayer(d),PLAYER_STATE_RESOURCE_GOLD)+kq.gold)
                call SetHeroXP(d,GetHeroXP(d)+kq.xp,BOOL_EYECANDY)
                call QuestSetCompleted(kq.q,true)
                call UnitRemoveAbility(kq.u,RAWCODE_REWARD)
                call kq.destroy()
                set N=N-1
        endloop
    endif
    set d=null
    return false
endfunction

private function onKill takes nothing returns boolean
    local killquest kq
    local integer j=N+1
    local integer i=-1
    local unit d=GetDyingUnit()
    loop
        set j=j-1
        exitwhen j<=0
            set kq=KQ[j]
            if GetUnitTypeId(d)==kq.id then
                set kq.count=kq.count+1
                loop
                    set i=i+1
                    exitwhen i>INTEGER_PLAYERS
                        call DisplayTextToPlayer(Player(i),0,0,STRING_COLOR+GetUnitName(d)+"s|r killed: "+I2S(kq.count)+"/"+I2S(kq.n)+".")
                endloop
                if kq.count>=kq.n and kq.b==false then
                    call DestroyTrigger(kq.t1)
                    call TriggerRegisterUnitInRange(kq.t2,kq.u,REAL_RANGE,null)
                    call TriggerAddCondition(kq.t2,Condition(function onRange))
                    set kq.b=true
                    call UnitRemoveAbility(kq.u,RAWCODE_QUEST)
                    call UnitAddAbility(kq.u,RAWCODE_REWARD)
                endif
            endif
    endloop
    set d=null
    return false
endfunction

private function AddQuest takes nothing returns boolean
    local killquest kq
    local integer j=N+1
    local integer i=-1
    local integer index=0
    local boolexpr bxpr=Filter(function True)
    if IsUnitType(GetTriggerUnit(),UNIT_TYPE_HERO)==true then
        loop
            set j=j-1
            exitwhen j<=0
                set kq=KQ[j]
                loop
                    set i=i+1
                    exitwhen i>INTEGER_PLAYERS
                        call DisplayTimedTextToPlayer(Player(i),0,0,REAL_DISPLAYTIME,kq.content)
                endloop
                set kq.q=CreateQuest()
                call QuestSetCompleted(kq.q,false)
                call QuestSetIconPath(kq.q,kq.icon)
                call QuestSetDescription(kq.q,kq.content)
                call QuestSetRequired(kq.q,BOOL_REQ)
                call QuestSetTitle(kq.q,kq.name)
                call DestroyTrigger(kq.t0)
                loop
                    call TriggerRegisterPlayerUnitEvent(kq.t1,Player(index),EVENT_PLAYER_UNIT_DEATH,bxpr)

                    set index=index+1
                    exitwhen index==bj_MAX_PLAYER_SLOTS
                endloop
                call TriggerAddCondition(kq.t1,Condition(function onKill))
        endloop
    endif
    set bxpr=null
    return false
endfunction

function InitKillQuest takes unit u, integer id, integer n, integer gold, integer xp, string name, string icon, string content returns nothing
    local killquest kq=killquest.create()
    set kq.u=u
    set kq.id=id
    set kq.n=n
    set kq.gold=gold
    set kq.xp=xp
    set kq.count=0
    set kq.t0=CreateTrigger()
    set kq.t1=CreateTrigger()
    set kq.t2=CreateTrigger()
    set kq.b=false
    set kq.name=name
    set kq.icon=icon
    set kq.content=content
    call UnitAddAbility(kq.u,RAWCODE_QUEST)
    call TriggerRegisterUnitInRange(kq.t0,kq.u,REAL_RANGE,Filter(function AddQuest))
    set N=N+1
    set KQ[N]=kq
endfunction

endlibrary

JASS:
library ItemQuestEngine

globals
    private constant integer INTEGER_PLAYERS=8 // number of players in the map
    private constant integer INTEGER_MAXITEMS=100 // just the [] of the item array
    
    private constant integer RAWCODE_REWARD='A001' // raw code of the 'Reward' ability
    private constant integer RAWCODE_QUEST='A000' // raw code of the 'Quest' ability
    
    
    private constant string STRING_COLOR="|c00ff8000" // color of the messages
    
    
    private constant real REAL_RANGE=160.000 // trigger range
    private constant real REAL_DISPLAYTIME=6 // display time of the quest description
    
    
    public           boolean BOOL_REQ=false // quests is required? non-constant, you can always change
    private constant boolean BOOL_EYECANDY=true // level-up eyecandy showed?
endglobals

// item quests
private struct itemquest
    unit u
    integer id
    integer n
    integer gold
    integer xp
    integer count
    trigger t0
    trigger t1
    trigger t2
    boolean b
    boolean req
    item array ia[INTEGER_MAXITEMS]
    // quest
    string content
    string name
    string icon
    quest q
    
        method onDestroy takes nothing returns nothing
            call DestroyTrigger(.t2)
        endmethod
endstruct

globals
    private itemquest array KQ
    private integer N=0
endglobals

private function True takes nothing returns boolean
    return true
endfunction

private function onRange takes nothing returns boolean
    local itemquest kq
    local integer j=N+1
    local integer i=0
    local unit d=GetTriggerUnit()
    if IsUnitType(d,UNIT_TYPE_HERO)==true then
        loop
            set j=j-1
            exitwhen j<=0
                set kq=KQ[j]
                if kq.u==null then
                    return false
                endif
                set KQ[j]=KQ[N]
                set N=N-1
                //
                loop
                    set i=i+1
                    exitwhen i>kq.n
                        call RemoveItem(kq.ia<i>)
                endloop
                set i=-1
                loop
                    set i=i+1
                    exitwhen i&gt;INTEGER_PLAYERS
                        call DisplayTextToPlayer(Player(i),0,0,&quot;Quest Completed: &quot;+STRING_COLOR+kq.name+&quot;.|r&quot;)
                endloop
                call SetPlayerState(GetOwningPlayer(d),PLAYER_STATE_RESOURCE_GOLD,GetPlayerState(GetOwningPlayer(d),PLAYER_STATE_RESOURCE_GOLD)+kq.gold)
                call SetHeroXP(d,GetHeroXP(d)+kq.xp,BOOL_EYECANDY)
                call QuestSetCompleted(kq.q,true)
                
                call UnitRemoveAbility(kq.u,RAWCODE_REWARD)
                call kq.destroy()
        endloop
    endif
    set d=null
    return false
endfunction

private function onGetItem takes nothing returns boolean
    local itemquest kq
    local integer j=N+1
    local integer i=-1
    local item d=GetManipulatedItem()
    loop
        set j=j-1
        exitwhen j&lt;=0
            set kq=KQ[j]
            if GetItemTypeId(d)==kq.id then
                set kq.count=kq.count+1
                set kq.ia[kq.count]=d
                loop
                    set i=i+1
                    exitwhen i&gt;INTEGER_PLAYERS
                        call DisplayTextToPlayer(Player(i),0,0,STRING_COLOR+GetItemName(d)+&quot;s|r recieved: &quot;+I2S(kq.count)+&quot;/&quot;+I2S(kq.n)+&quot;.&quot;)
                endloop
                if kq.count&gt;=kq.n and kq.b==false then
                    call DestroyTrigger(kq.t1)
                    call TriggerRegisterUnitInRange(kq.t2,kq.u,REAL_RANGE,null)
                    call TriggerAddCondition(kq.t2,Condition(function onRange))
                    set kq.b=true
                    call UnitRemoveAbility(kq.u,RAWCODE_QUEST)
                    call UnitAddAbility(kq.u,RAWCODE_REWARD)
                endif
            endif
    endloop
    set d=null
    return false
endfunction

private function AddQuest takes nothing returns boolean
    local itemquest kq
    local integer j=N+1
    local integer i=-1
    local integer index=0
    local boolexpr bxpr=Filter(function True)
    if IsUnitType(GetTriggerUnit(),UNIT_TYPE_HERO)==true then
        loop
            set j=j-1
            exitwhen j&lt;=0
                set kq=KQ[j]
                loop
                    set i=i+1
                    exitwhen i&gt;INTEGER_PLAYERS
                        call DisplayTimedTextToPlayer(Player(i),0,0,REAL_DISPLAYTIME,kq.content)
                endloop
                set kq.q=CreateQuest()
                call QuestSetCompleted(kq.q,false)
                call QuestSetIconPath(kq.q,kq.icon)
                call QuestSetDescription(kq.q,kq.content)
                call QuestSetRequired(kq.q,BOOL_REQ)
                call QuestSetTitle(kq.q,kq.name)
                call DestroyTrigger(kq.t0)
                loop
                    call TriggerRegisterPlayerUnitEvent(kq.t1,Player(index),EVENT_PLAYER_UNIT_PICKUP_ITEM,bxpr)

                    set index=index+1
                    exitwhen index==bj_MAX_PLAYER_SLOTS
                endloop
                call TriggerAddCondition(kq.t1,Condition(function onGetItem))
        endloop
    endif
    set bxpr=null
    return false
endfunction

function InitItemQuest takes unit u, integer id, integer n, integer gold, integer xp, string name, string icon, string content returns nothing
    local itemquest kq=itemquest.create()
    set kq.u=u
    set kq.id=id
    set kq.n=n
    set kq.gold=gold
    set kq.xp=xp
    set kq.count=0
    set kq.t0=CreateTrigger()
    set kq.t1=CreateTrigger()
    set kq.t2=CreateTrigger()
    set kq.b=false
    set kq.name=name
    set kq.icon=icon
    set kq.content=content
    call UnitAddAbility(kq.u,RAWCODE_QUEST)
    call TriggerRegisterUnitInRange(kq.t0,kq.u,REAL_RANGE,Filter(function AddQuest))
    set N=N+1
    set KQ[N]=kq
endfunction

endlibrary</i>


I tried to do as good as I could, and this was the outcome. (I looked it over many times, so I hope that there are no errors in my coding. :)) It requires no other libraries, nothing. Just import into your map and edit the rawcodes to your needs.

Give credit to me when using.
 

Attachments

  • QuestEngines.w3x
    26.7 KB · Views: 196

Flare

Stops copies me!
Reaction score
662
JASS:
//
    trigger t0
    trigger t1
    trigger t2

        method onDestroy takes nothing returns nothing
            call DestroyTrigger(.t2)
        endmethod

3 triggers created, only one gets destroyed - are #0 and #1 being destroyed elsewhere, or did you just forget to remove them?

JASS:
private constant integer INTEGER_MAXITEMS=8190

End-user might not realise the implications of that value, so reduce it please - one item quest instance sucks :D
 

Vestras

Retired
Reaction score
249
They're being destroyed elsewhere. :)
And I'll reduce it to ~1000 then? Oh, and it's not the struct array, it's the item array inside the struct.
 

Flare

Stops copies me!
Reaction score
662
And I'll reduce it to ~1000 then?
100 is probably better - I've never seen any maps with quests that require 100 kills/items to be collected. 50 would probably be the most, but 100 will give you a bit of room to breath, and it's not likely that you will have 80 quests at once :p

Oh, and it's not the struct array, it's the item array inside the struct.
I know that... the size of a struct member array reduces the instanceability of the struct itself. From what I recall, it is
Code:
(8190/Largest array size) + 1
instances, even though if the division is perfect (e.g. with 4095, 8190), you would only have 1 (or 2, in the case of 4095) instances
 

AceHart

Your Friendly Neighborhood Admin
Reaction score
1,495
The bare minimum when submitting random crap from your maps would be a note about what it is doing.
All I got from the intro was "it's boring"...


And why does it matter how many players my map has?
What if only half are playing?
And, can't you figure that out yourself?


Or this:
> raw code of the 'Quest' ability

Some comment is better than no comment.
Though, then again, what this would be doing is anyone's guess it seems.



Dynamic triggers...
8K in-struct array...

This so says Graveyard all over....
 

Vestras

Retired
Reaction score
249
This is what annoys me so when submitting something... I try to do something good for this site, or someone who uses it, and yet I get no thanks, only negative stuff.

I agree, maybe I didn't explain what this did properly, but then you can just say it. This isn't random crap from my maps. I made it for my map, not from my map.

> raw code of the 'Quest' ability

If you had taken a look at the demo map you would have understood, but you obviously haven't, so I'll have to explain it, again. In order to have easy special effect attaching and removing, I use abilities. Doesn't increase handle count with lame effect variables. I said so in the first post.

What is wrong with using dynamic triggers when I use triggerconditions?
And the reason I have the player count in a variable, is so I can loop through them and display messages.
 

Flare

Stops copies me!
Reaction score
662
What is wrong with using dynamic triggers when I use triggerconditions?
Dynamic triggers can cause bugs - AFAIK, it can cause a situation where the next created handles are allocated the same handle index (not really sure of the general implications of that, but it doesn't sound too good). Read this for a bit more info (WC3C is down at the moment :-()
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
Dynamic triggers can cause bugs - AFAIK, it can cause a situation where the next created handles are allocated the same handle index (not really sure of the general implications of that, but it doesn't sound too good). Read this for a bit more info (WC3C is down at the moment :-()

The bug is when you starts to add actions and then uses "DestroyTrigger()". If you only work with conditions then "DestroyTrigger()" works properly. Not sure about events.. gotta check if it says anything about that on WC3C when it gets up.
 

Darius34

New Member
Reaction score
30
DestroyTrigger() causes problems when a trigger is destroyed without having finished execution. It bugs triggers with waits, for that reason. From what I've read, if you really have to use dynamic triggers, destroy them only when you're absolutely sure they've finished execution (avoid using waits with them and destroying them from "outside", for example), or after a long time (2 minutes?) to be on the safe side.

(I can't remember the details about actions and conditions. I do remember conditions being always recommended though.)

Hmm... What would I use then? I've always used triggerconditions...
I've also read that everything that can be done with dynamic triggers can be done with static triggers. Work around the problems with attachment, unit indexing, etc.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
A fairly easy work-around is a global trigger and a global group of some kind (add and remove units to the group in order to see who's to be affected in the trigger).
 

Leazy

You can change this now in User CP.
Reaction score
50
Great system, I couldn't even imagine this. I don't know if you got this already, but do so that when you talk to a unit that gives a quest, the '!' is removed & replaced with a silver '?' (I think you got this already). Then remove the ! only for the player taking the quest and show a '?' only for the player taking the quest. Also add the quest only for the player taking the quest. If you add this, I would be tempted to start a RPG without a doubt :)
 

Nerfpl

New Member
Reaction score
53
so can i simply put it in for exemple in map init in GUI
Code:
call InitKillQuest(questunit,killunitrawcode,numberofkilledunits,rewardgold,rewardxp,questtitle,questicon,questdescription)
??

btw is there way to create the quest with some own event? and skip part with quest giver?
 

Vestras

Retired
Reaction score
249
> Leazy

I don't think I'm gonna do that, becauses of showing and hiding effects for local players desync (I think) and because I like how it is currently.

> Nerfpl

If you do as told in the first post, and import the GUI functions, you can do it even easier, in GUI. Yes, everything that uses this should be done at map init.
 

Nerfpl

New Member
Reaction score
53
> Nerfpl

If you do as told in the first post, and import the GUI functions, you can do it even easier, in GUI. Yes, everything that uses this should be done at map init.

but i have UMSWE always on :D without it WE is poor for me ;p (0 jass knowlage)
 

Nerfpl

New Member
Reaction score
53
>,< thats what i mean by aking about using script in GUI.

As future release (if you do) id would be cool if you would make:
-item/skill/lumber/etc rewards
-chain linking quests (posibly run typed trigger on rewarding)
-creating quest without questgiver (so you could make event in GUI, and then just call... and quest pops up)
 

Flare

Stops copies me!
Reaction score
662
showing and hiding effects for local players desync (I think)
Creating the effect for the local player desyncs, but modifying the modelfile to be used doesn't

JASS:
local string s = &quot;...&quot; //modelfile
if GetLocalPlayer () != TargetPlayer then
  set s = &quot;&quot;
endif
call AddSpecialEffectTarget (...) //I forget order of arguments <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite8" alt=":D" title="Big Grin    :D" loading="lazy" data-shortname=":D" />

Same effect is generated for all players, but everyone but the target player gets a blank modelfile, so they see nothing
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • WildTurkey WildTurkey:
    is there a stephen green in the house?
    +1
  • The Helper The Helper:
    What is up WildTurkey?
  • The Helper The Helper:
    Looks like Google fixed whatever mistake that made the recipes on the site go crazy and we are no longer trending towards a recipe site lol - I don't care though because it motivated me to spend alot of time on the site improving it and at least now the content people are looking at is not stupid and embarrassing like it was when I first got back into this like 5 years ago.
  • The Helper The Helper:
    Plus - I have a pretty bad ass recipe collection now! That section of the site is 10 thousand times better than it was before
  • The Helper The Helper:
    We now have a web designer at my job. A legit talented professional! I am going to get him to redesign the site theme. It is time.
  • Varine Varine:
    I got one more day of community service and then I'm free from this nonsense! I polished a cop car today for a funeral or something I guess
  • Varine Varine:
    They also were digging threw old shit at the sheriff's office and I tried to get them to give me the old electronic stuff, but they said no. They can't give it to people because they might use it to impersonate a cop or break into their network or some shit? idk but it was a shame to see them take a whole bunch of radios and shit to get shredded and landfilled
  • The Helper The Helper:
    whatever at least you are free
  • Monovertex Monovertex:
    How are you all? :D
    +1
  • Ghan Ghan:
    Howdy
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top