How to create a WC3 Spell Book!

General talk about editing, cheating, and deprotecting maps.

Moderator: Cheaters

Black-Hole
Forum Fanatic
Posts: 315
Joined: October 16th, 2007, 7:32 pm

How to create a WC3 Spell Book!

Post by Black-Hole »

Copyed from another website!

The purpose of this tutorial is to introduce spellbooks and taech how to create and manipulate them.

REQUIREMENTS:
- An authentic copy of Warcraft 3: The Frozen Throne

TABLE OF CONTENTS
Chapter one – What's a spellbook&how to create one
Frequently Asked Questions
- How to add spells to a spellbook with triggers
- How to order a unit to open a spellbook
- How to cast spells inside a spellbook with triggers
- How to add passive spells to a unit without an icon showing up
- Why do the spells „jump around“ in my spellbook after casting/learning them
- Why do my spells disappear from the spellbook after transformation/metamorphism etc
- How can I detect if the spellbook is open or closed?

CHAPTER ONE – WHAT'S A SPELLBOOK&HOW TO CREATE ONE


A spellbook is quite simply what it states, a book containing magic spells, which allows a hero to bypass the limit of 5 hero spell.

There have been a lot of myths about creating a spellbook, for example, one has to know JASS in order to create a spellbook. That's not true.

Spellbook is actually an ability, accessible from Object Editor without the help of any triggering. Now, lets create our own spellbook.

Open Object Editor (F6) and go to the „Abilities“ tab. Next, search for the abilities folder „Special“ and open the „Items“ sub-folder. Scroll almost to the very bottom until you see the ability „Spell Book“. Select the ability and copy&paste it.



The first thing you want to do, is to make the spellbook a unit ability (or a hero ability). Change the field „Item Ability – true“ to false. This allows you do add tooltips for your spellbook.

Next, you have to change the „Data – Base Order ID“ field to something different than „spellbook“. By changing this option, you remove the possibility of colliding two or more spellbooks with the same ID (when they collide, things will get buggy and they will inherit each others spells). It's wise to choose those abilities from the list, which are rarely or not at all used in your map.

After that, you need to modify the „Data – Shared Spell Cooldown“ field and change it to false. This allows you to open the spellbook and cast other spells, when some spells are in their cooldown state.

Proceed by locating the „Data – Spell List“ field. This is here where the magic happens. Choose your spells and add them all there.
NOTE #1: You can add a maximum of 11 spells, which show up in the spellbook, nevertheless the UI has 12 slots. One slot is reserved by the game for the „Cancel“ ability which allows you to exit the spellbook.
Lastly, if you've added all the needed spells, count them and change the „Data – Maximum Spells“ field to that number. This ensures all the added spells actually show up when you open the spellbook. You can change the „Data – Minimum Spells“ to 1.
NOTE #2: The value entered to the minimum field cannot be greater than the one entered to the maximum field. The number of spells shown in the spellbook will be still equal to the value entered to the maximum field.
Now, you've set up your very own spellbook! You might want to do some finishing touches, like changing the icon, adding tooltips or even make the spellbook level-able.


FREQUENTLY ASKED QUESTIONS


HOW TO ADD SPELLS TO A SPELLBOOK WITH TRIGGERS
You need two spellbooks and the actual spell for that.

Firstly, create the spellbooks and name one of them „dummy“ and the other one the way you want it to display on the unit. Make sure the „Data – Base Order ID“ field is the same for both of them. Next, add the spell you want to give to the unit to the „dummy“ spellbook and add the real spellbook the unit.



After that, go the the Trigger Editor and make a trigger where you disable the „dummy“ spellbook.

Image

This ensures the „dummy“ spellbook icon will not show up on the hero when you add the spell.

Now, you've done the setup process and the only thing left to do is to learn how to add the spell to the right spellbook.

The trick lies in the bug I wrote about earlier – spellbooks with the same ID will collide and inherit spells. Meaning, if you add the disabled „dummy“ spellbook to the unit with the real spellbook, the real spellbook will inherit the spell(s) inside the „dummy“ book. The same goes for removing: if you remove the „dummy“ spellbook ability, the real spellbook will lose all the inherited abilities.
HOW TO ORDER A UNIT TO OPEN A SPELLBOOK
There is no way to do that.

HOW TO CAST SPELLS INSIDE A SPELLBOOK WITH TRIGGERS
There is no way to do that because there is no way to open the spellbook with triggers.

HOW TO ADD PASSIVE SPELLS TO A UNIT WITHOUT AN ICON SHOWING UP
The progress is simple:

Create a spellbook, add all the needed passive abilities to the spellbook and disable the book to remove the icon.

You can add/remove the spellbook as you like to add/remove the effects gained by the spells inside the book.

WHY DO THE SPELLS „JUMP AROUND“ IN MY SPELLBOOK AFTER CASTING/LEARNING THEM
The spells ignore tooltip position inside a spellbook.

The only way to fix this issue is to use add spells with triggers into a spellbook in an ordered fashion.
WHY DO MY SPELLS DISAPPEAR FROM THE SPELLBOOK AFTER TRANSFORMATION/METAMORPHISM ETC.
This is because the abilities inside the spellbook are not permanent; they are gained via a spellbook.

To fix this issue, you must make the ability permanent. The only way is to use a piece of custom script because the action is not accessible via GUI:

„call UnitMakeAbilityPermanent(whichUnit, true, abilCode)“

Replace whichUnit with the unit who has the ability and abilCode with the raw code of the ability.

HOW CAN I DETECT IF THE SPELLBOOK IS OPEN OR CLOSED?
There is no way to detect that.


This concludes this little tutorial about spellbooks.

Hope you liked and learned something out of it.

As always, feedback is appreciated!

Happy mapping,

Your favourite Pandaren!

Andrewgosu
Black-Hole
Forum Fanatic
Posts: 315
Joined: October 16th, 2007, 7:32 pm

Re: How to create a WC3 Spell Book!

Post by Black-Hole »

Advanced Skill Learning System


Have you ever whined about heroes not having enough learnable hero abilities? I mean, the heroes can have a maximum of five levelable abilities and that sucks. Plus the fact if they all are learned, they take so much space and you don't have enough room on the hero to display other important abilities. Well...not anymore!

Advanced Skill System allows you to increase the limit from 5 to 11! That is more than double the amount! How does the system do that? It transfers the learned spells to a Spell Book! Yes, that's right.

This system is quite complex to use in the beginning, so lets learn how to set up the system.


Step 1 – Creating the custom Spell Book and hero abilities menu

To begin, copy the ability “Spell Book” and paste it twice. Name one of them “Hero Abilities”.

Image

These two spell books need to have different base order ids, so change them. In my case, I changed them to “acidbomb” and “absorb”, for “Spell Book” and “Hero Abilities” respectively.

Image

Lastly, change the to unit abilities by setting the “Stats – Item ability” to false to make the ability a unit ability.

Image

Step 2 – Creating the custom ability for the hero to learn

The system requires 6 custom abilities to learn a single spell, so bear with me, as the process to make them takes some time. I've divided this step into 3 smaller steps.

- Creating the actual spell and its “container” spellbook.

Firstly, choose a spell you want the system to use. I've chosen “Breath of Fire”. Start by making the ability a unit ability (follow step 1 if you don't know how). Next, copy-paste the spell book with the “acidbomb” base order id. Change its name to “Breath of Fire (container)” and modify the spell list to contain “Breath of Fire”. Do not change this spell book's order id.

Image

- Creating the ability needed to learn the spell and its “container” spellbook.

To learn the spell “Breath of Fire”, you need a dummy ability with three levels. I've based mine off “Channel” and renamed it to “Breath of Fire (Learn)”. Also, I've changed its base order id to remove the possiblity of clashing with other spells. As this skill has to display information about the spell “Breath of Fire” you have to change the tooltips to reflect the “Breath of Fire” tooltips.

Image

Next, copy-paste the spell book with the "absorb" base order id. Change its name to "Breath of Fire (learn)(container)". Modify the spell list to contain "Breath of Fire (learn)". Again, do not change the order id.


- Creating the "requirement" ability and its container

To clarify, the "requirement" ability is the ability you cannot click on and displays the requirements needed to learn the next level of the spell. To create one, you need a non-aura, but passive ability, which does nothing. I've based mine of "Barrage", changed the fields to do nothing and made it a three level spell. Also, I've renamed it to "Breath of Fire (req)" and changed the tooltips.

Image

To finish with the abilities, you need to create one last "container". Copy-paste the spell book with the "absorb" base order id, rename it to "Breath of Fire (req)(container)" and modify its spell list to contain "Breath of Fire (req)".


Step 3 – Adding the ability to the hero and modifying the system options

Before you can add the “Breath of Fire” ability to the hero, you need to change a few system options (found in the configuration globals block):

- SPELL_BOOK – change this to the raw code of the “Spell Book” ability
- HERO_ABILITIES – change this to the raw code of the “Hero abilities” ability.

Now, if you've changed the system options, you need to add the “Spell Book” and custom hero abilities menu to the unit you want, by using the “UnitAddAdvancedSpellbook” function.

Here's an example how to do that using GUI:
- Set tmpunit = Pandaren Brewmaster 0004 <gen>
- Custom script: call UnitAddAdvancedSpellbook(udg_tmpunit)
After you've done that, you need to add all of the previously made abilities to the unit using the "UnitAddAbilityAdvanced" function:

- the function parameters are:
1) the unit you want to have the ability

2) the raw code of the ability
3) the raw code of the ability's container

4) the raw code of the ability needed to learn to "right" ability
5) the raw code of the "learn" ability's container

6) the raw code of the "requirement" ability
7) the raw code of the "requirement" ability's container
And here's an example how to call the function using GUI:

NB! Make sure you call the function after you've added the custom spell book and hero abilities menu!
- Set tmpunit = Pandaren Brewmaster 0004 <gen>
- Custom script: call UnitAddAdvancedSpellbook(udg_tmpunit)
- Custom script: call UnitAddAbilityAdvanced(udg_tmpunit, 'A002', 'A004', 'A00A', 'A001', 'A003', 'A000')
If you have followed these steps correctly, you should have a working custom ability, which will be transferred to the spellbook when learnt!



The code behind the system (will be optimised, but does it's job):
Spoiler:

Code: Select all

library SPELLBOOK initializer InitializeSystem
    // ver. 1.0
    // SYSTEM CONFIGURATION GLOBALS
    globals
        // determines whether the system is preloaded or not.
        // when loaded, increases the loading time but removes first-use lag.
        private constant boolean PRELOAD              = true
        // the number of skillpoints the hero starts with at level 1.
        private constant integer DEFAULT_SKILLPOINTS  = 1
        // the maximum level of the modified 'spellbook' abilities.
        private constant integer SPELL_MAX_LEVEL      = 3
    endglobals
    
    // the level requirement "skip" for the abilities.
    // e.g to learn the second level of the spell, the hero itself must be level 3 etc.
    constant function GetLevelLearnRequirement takes integer level returns integer
        return level * 2 + 1
    endfunction    

    // RAWCODE CONFIGURATION GLOBALS
    globals
        // the raw code of the modified spellbook, where the learned abilities will be transferred.
        private constant integer SPELL_BOOK     = 'A008'
        // the raw code of the modified spellbook, which acts as the place from where to learn the spells.
        private constant integer HERO_ABILITIES = 'A009'
    endglobals

    // SYSTEM BUFFER GLOBALS
    // do not modify!
    globals     
        private constant integer SPELL_AMOUNT = 12 //amount of max spells possible in spellbook + 1.
    endglobals

    private struct herodata
        integer freePoints   = DEFAULT_SKILLPOINTS
        integer spellCount   = 0
        integer learntSpells = 0
       
        integer array skill[SPELL_AMOUNT]
        integer array skillSet[SPELL_AMOUNT]
        integer array learn[SPELL_AMOUNT]
        integer array learnSet[SPELL_AMOUNT]
        integer array reqSet[SPELL_AMOUNT]
        integer array req[SPELL_AMOUNT]
    endstruct
    
    // this function must be ran before trying to add "advanced" abilities for a unit.
    function UnitAddAdvancedSpellbook takes unit whichUnit returns nothing
        call UnitAddAbility(whichUnit, SPELL_BOOK)
        call UnitAddAbility(whichUnit, HERO_ABILITIES) 
        call SetUnitUserData(whichUnit, herodata.create())
    endfunction
    
    function UnitAddAbilityAdvanced takes unit whichUnit, integer skillId, integer skillContainerId, integer learnSkillId, integer learnSkillContainerId, integer reqSkillId, integer reqSkillContainerId returns nothing
        local herodata data = GetUnitUserData(whichUnit)
        local integer  n    = 0
        local unit     dum
        
        set data.spellCount = data.spellCount + 1
        // store the raw codes of the abilities, because we need them later.
        set data.skill[data.spellCount] = skillId
        set data.skillSet[data.spellCount] = skillContainerId
        set data.learn[data.spellCount] = learnSkillId
        set data.learnSet[data.spellCount] = learnSkillContainerId       
        set data.req[data.spellCount] =  reqSkillId
        set data.reqSet[data.spellCount] = reqSkillContainerId  
        // disable the "container" spellbooks as we don't want them to appear on the unit
        loop
            exitwhen (n > bj_MAX_PLAYERS)
            call SetPlayerAbilityAvailable(Player(n), skillContainerId, false)
            call SetPlayerAbilityAvailable(Player(n), learnSkillContainerId, false) 
            call SetPlayerAbilityAvailable(Player(n), reqSkillContainerId, false) 
            set n = n + 1
        endloop
        // preload the abilities, if wanted.
        if (PRELOAD) then
            set dum = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
            call UnitAddAbility(dum, skillContainerId) 
            call UnitAddAbility(dum, learnSkillContainerId)
            call UnitAddAbility(dum, reqSkillContainerId)
            call RemoveUnit(dum)
            set dum = null
        endif 
        // add the "learn" ability to the unit.
        call UnitAddAbility(whichUnit, learnSkillContainerId)
    endfunction
    
    private function GetLearnSkillIndex takes unit whichUnit, integer abilCode returns integer
        local herodata data  = GetUnitUserData(whichUnit)
        local integer  index = 0
        
        loop
            exitwhen (abilCode == data.learn[index])
            if (index > data.spellCount) then
                return -1 // the unit does not have the specified "learn" ability
            endif
            set index = index + 1
        endloop
        return index
    endfunction

    private function UnitDisableAllLearnAbilities takes unit whichUnit returns nothing
        local herodata data  = GetUnitUserData(whichUnit)
        local integer  index = 0
        local integer  level
        
        loop
            exitwhen (index > data.spellCount)
            // remember the level of the "learn" ability
            set level = GetUnitAbilityLevel(whichUnit, data.skill[index])
            if (level < SPELL_MAX_LEVEL) then
                // remove the container with the "learn" ability
                call UnitRemoveAbility(whichUnit, data.learnSet[index])
                // add the the container with the "requirement" ability
                call UnitAddAbility(whichUnit, data.reqSet[index])
                // set the "requirement" ability to the right level
                call SetUnitAbilityLevel(whichUnit, data.req[index], level + 1)
            endif
            set index = index + 1
        endloop
    endfunction
    
    private function UnitDisableLearnAbility takes unit whichUnit, integer abilcode returns nothing
        local integer  index = GetLearnSkillIndex(whichUnit, abilcode)
        local integer  level
        local herodata data
 
        if (index > -1) then
            set data = GetUnitUserData(whichUnit)
            set level = GetUnitAbilityLevel(whichUnit, data.learn[index])
            call UnitRemoveAbility(whichUnit, data.learnSet[index])
            call UnitAddAbility(whichUnit, data.reqSet[index])
            call SetUnitAbilityLevel(whichUnit, data.req[index], level)
        endif
    endfunction    
    
    private function UnitEnableLearnAbility takes unit whichUnit, integer abilcode returns nothing
        local integer  index = GetLearnSkillIndex(whichUnit, abilcode)
        local integer  level
        local herodata data
 
        if (index > -1) then
            set data = GetUnitUserData(whichUnit)
            set level = GetUnitAbilityLevel(whichUnit, data.skill[index])
            call UnitRemoveAbility(whichUnit, data.reqSet[index])
            call UnitAddAbility(whichUnit, data.learnSet[index])
            call SetUnitAbilityLevel(whichUnit, data.learn[index], level + 1)
        endif     
    endfunction
    
    private function HeroLearnsSkill_eventresponse takes nothing returns nothing
        local integer  id     = GetSpellAbilityId()
        local unit     caster = GetTriggerUnit()
        local integer  index  = GetLearnSkillIndex(caster, id)
        local herodata data
        local integer  level
        
        if (index > -1) then
            set data = GetUnitUserData(caster)
            // deduct one skillpoint.
            set data.freePoints = data.freePoints - 1             
            set level = GetUnitAbilityLevel(caster, data.skill[index])
            
            if (level == 0) then
                // the hero learns the spell for the first time. add it.
                call UnitAddAbility(caster, data.skillSet[index])
                //call SetUnitAbilityLevel(caster, data.learn[index], level + 1)
            else
                // otherwise, increase the level of the spell.
                call SetUnitAbilityLevel(caster, data.skill[index], level + 1)
            endif
            set id = GetUnitAbilityLevel(caster, data.learn[index])
            call SetUnitAbilityLevel(caster, data.learn[index], id + 1)
            
            // the hero has maxed out the spell, remove the "learn" ability.
            if (id == SPELL_MAX_LEVEL) then
                call UnitRemoveAbility(caster, data.learnSet[index])
                set data.learntSpells = data.learntSpells + 1
                if (data.learntSpells == data.spellCount) then
                    call UnitRemoveAbility(caster, HERO_ABILITIES)
                else
                    if (data.freePoints == 0) then
                        call UnitDisableAllLearnAbilities(caster)
                    endif
                endif
            else
                // do we need to disable learning of the abilities because there are not enough skill points.
                if (data.freePoints == 0) then
                    call UnitDisableAllLearnAbilities(caster)
                    // do we need to disable only learning of the just-learnt ability because the hero does
                    // not have level high enough to learn the next level of the spell. 
                elseif (GetHeroLevel(caster) < GetLevelLearnRequirement(level + 1)) then
                    call UnitDisableLearnAbility(caster, data.learn[index])
                endif
            endif
        endif
        
        set caster = null
    endfunction
    
    private function HeroGainsLevel_eventresponse takes nothing returns nothing
        local unit     gainer = GetTriggerUnit()
        local herodata data   = GetUnitUserData(gainer)
        local integer  level  = GetHeroLevel(gainer)
        local integer  index  = 0
        local integer  spellL
        
        set data.freePoints = data.freePoints + 1        
        loop
            exitwhen (index > data.spellCount)
            set spellL = GetUnitAbilityLevel(gainer, data.skill[index])  
            // we have to enable all these abilities, which are not maxed out and are low enough for the hero to learn.
            if (level >= GetLevelLearnRequirement(spellL)) and (spellL < SPELL_MAX_LEVEL) then
                call UnitEnableLearnAbility(gainer, data.learn[index])
            endif
            set index = index + 1
        endloop
        
        set gainer = null
    endfunction
    
    //===========================================================================
    private function InitializeSystem takes nothing returns nothing
        local trigger trig      = CreateTrigger() 
        local trigger levelGain = CreateTrigger()

        local integer index = 0
        local player  play
        local unit    dum
        
        if (PRELOAD) then
            set dum = CreateUnit(Player(0), 'hfoo', 0, 0, 0)
            call UnitAddAbility(dum, SPELL_BOOK)
            call UnitAddAbility(dum, HERO_ABILITIES)         
            call RemoveUnit(dum)
            set dum = null
        endif
        loop
            exitwhen (index == bj_MAX_PLAYER_SLOTS)
            set play = Player(index)
            call TriggerRegisterPlayerUnitEvent(trig, play, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            call TriggerRegisterPlayerUnitEvent(levelGain, play, EVENT_PLAYER_HERO_LEVEL, null)
            set index = index + 1            
        endloop        
        call TriggerAddAction(levelGain, function HeroGainsLevel_eventresponse)
        call TriggerAddAction(trig, function HeroLearnsSkill_eventresponse)
        
        set trig      = null
        set levelGain = null
    endfunction
endlibrary
A pros/cons list:

legend
- "+" means that issue will be fixed in future versions.

cons
- it is complex to set up the skills and the process takes some time.
- every skill you want to add requires creating 6 custom abilities.
- does not support 1-level ultimates skills. (+)
- does not support different level "gap" requirements for separate spells (e.g you have to be level 6 to learn spell x, but level 9 to learn y). (+)
- does not support setting the "advanced" skills for unit type, you have to add them to every unit you want to have the spellbook, manually (+)
- does not support more than one "advanced" spellbook on a unit.
- the system does not display the current amount of free skill points. (+)
- while all of the spells are not learnt, their icons jump around because the spellbook ignores tooltip button positions(+)
- does not support the 'Tome of Retraining' (+)
- without custom icons, the DISBTN versions of the "requirement" abilities show green.

pros
- multi unit instanceable.
- when preloaded, lag free.
- allows learnt skills to be transferred into a spellbook (up to 11 skills, as this is as much as the spellbook itself can hold).
- allows you to save space for other abilities you do not want appear in a spell book.

sidenotes
- does not use any attaching system but unit custom value (will be changed)
- requires newgen package



Download and the test the demo map with 4 abilities!

Happy mapping!
You do not have the required permissions to view the files attached to this post.