JASS: An Introduction to Structs

Tutorial By DuckieKing

Structs
A basic tutorial by Duckieking

#T#
Table of Contents

#T# Table of Contents
#O1# Overview
#L1# Why use structs?
#L2# A glance at structs.
#O2# Syntax
#L3# Declaring structs.
#L4# Calling structs in functions.
#O3# Conclusion
#L5# Questions? Answers.
#L6# Links and attachments.
#C# Changelog

#O1#
Overview

#L1# Why use structs?
The purpose of structs and classes in real programming is encapsulation, the idea that tasks should be broken down into simpler tasks. The point of this is simpler, cleaner code that is easier to maintain.

The purpose of structs in WC3 is additional multi-instanceability. They are used for any real physics systems, and they aren't just good for that.

Structs are useful for creating MUI effects such as stacking abilities, stat systems, kill counters, timed abilities, item systems, and anything else you can think of. Normally, these things would confine you to one instance per player or else force you to use multiple arrays for each effect you wish to implement. Structs allow you to attach multiple variables to a unit or item, thus easing these things as much as cleaning leaks is easier in Jass.

#L2# A glance at structs.
Structs are not native to the World Editor. With his awesome programming skills, Vexorian has allowed us to create structs, or custom variable types. Structs can be declared to have member data (variables) and member functions (methods). Well, kind of...

Structs is a misnomer. To be entirely technical, structs can only have member data, but with Vexorian's World Editor hack, structs have methods.

A method is just a function able to be called from any function that has access to a struct. Member data are variables contained in a struct. What I mean by that is when a struct is created, memory is set aside for each of its variables, which in general are only set by the struct's member functions.

#O2#
Syntax

#L3# Declaring structs.
Structs are declared a lot like functions, but are really unique. I'll just show you:
struct unitData //"struct" plus the name of the struct to be declared; since this tutorial is meant to show MUI struct-making it is named unitData
  unit u //this will be the subject of the struct, just to cut down on call arguments; there\'s no need to type "local"
  real statCredits=3 //this variable holds the number of stats a hero has left to spend
  integer kills=0 //this variable holds how many kills the hero has made, so that kills can be rewarded with extra stats; normally this would limit you to one hero per player, but with structs you can do more

  method ModifyStatCredits takes real statBonus returns nothing //this is our first method; it is used to add or remove stat credits; this value can be negative but won\'t remove already-bought stats
    if this.statCredits <= 0 and this.statCredits+statBonus > 0 then //"this." is a pointer to the struct, this will be elaborated on in the next section, but for now accept that you reach a variable from within the same struct with "this."
      call SetPlayerAbilityAvailable(GetOwningPlayer(this.u), 'A000', true) //note that a function is called in a struct, this is legal; this call reactivates the spellbook if it wasn\'t active and if credits are now positive
    endif
    set this.statCredits=this.statCredits+statBonus //the real purpose of the method
  endmethod
    

  method BuyStat takes integer whichStat returns nothing //this method is called when a hero spends a stat credit; this will remove one stat credit and raise the stat that was bought
    set this.statCredits=this.statCredits-1 //minus one credit...
    if whichStat == 'A001' then //obviously the buy agility spell
      call this.RaiseAgi()
    endif
    if whichStat == 'A002' then //buy int spell
      call this.RaiseInt()
    endif
    if whichStat == 'A003' then //buy str spell
      call this.RaiseStr()
    endif
    if this.statCredits < 1 then
      call SetPlayerAbilityAvailable(GetOwningPlayer(this.u), 'A000', false) //here we disable the spellbook if there are no more credits to spend
    endif
  endmethod

  method RaiseAgi takes nothing returns nothing //encapsulation specifies that we let functions do as little as possible on their own
    call SetHeroAgi(this.u, GetHeroAgi(this.u, false)+1, true) //the "false" says do not evaluate bonus agi; the "true" says do make it permanent
  endmethod
  method RaiseInt takes nothing returns nothing //no need for another empty line, all three of these methods do the same thing
    call SetHeroInt(this.u, GetHeroInt(this.u, false)+1, true)
  endmethod
  method RaiseStr takes nothing returns nothing
    call SetHeroStr(this.u, GetHeroStr(this.u, false)+1, true)
  endmethod

  method AddKill takes nothing returns nothing
    set this.kills=this.kills+1
    call this.StatBonusForKills(3) //aha, bonus stats for kills...
  endmethod

  method StatBonusForKills takes integer statBonusModifier returns nothing
    if this.kills != 0 and ModuloInteger(this.kills, 10) == 0 then //we don\'t want any crashes due to dividing by zero, so we check if this.kills is 0 before checking its modulo
      call this.ModifyStatCredits(this.KillsStatBonusModifier()) //encapsulation\'s a cur sometimes
    endif
  endmethod

  method KillsStatBonusModifier takes nothing returns real
    return Pow(this.kills/20) //.25 stats at 10, 1 stat at 20, 2.25 stats at 30, 4 extra stats at 40 kills
  endmethod
endstruct

Note that by itself, this code does nothing. It just tells the game engine how much memory a "unitData" needs, and supplies methods to affect that memory. You need a function to create a unitData and call the methods or it's just a wasted 2 kb in your map file.

#L4# Calling structs in functions.
Now we get to the real stuff.

To attach a struct to a unit and be able to use it in other functions, all you do is assign the struct to the unit's custom value, but first you have to know how to create the struct. The following function will create a new hero with the Id given and attach a struct to it.
(I know, it leaks a unit variable, but it's a miniscule leak and unavoidable for this demonstration. :))
function CreateHero takes integer playerId, integer heroTypeId, real x, real y returns unit
  local unit u=CreateUnit(playerId, heroTypeId, x, y, 270) //creates the hero and puts it in the variable to be returned
  local unitData uD=unitData.create() //the only time you will put the full struct name on either side of the period; "create" is a standard constructor and you don\'t have to write this method
  call SetUnitUserData(u, uD) //that\'s it; you just set the unit\'s custom value to the unitData you just created
endfunction
And thus a new unit is created, with its own custom data. Now you need to know how to use and change those data.

Remember how you used "this." to call methods and change data? In a function, you would replace "this" with the name you gave the struct when you created or copied it. The following function would be the action of a trigger fired when any hero on the map levels.
function HeroLevelsUp takes nothing returns nothing
  local unitData uD=GetUnitUserData(GetTriggerUnit()) //it\'s assumed every hero in the map has this struct attached
  call uD.ModifyStatCredits(3) //all methods except "create" are called by the variable name, not the variable type, and if you wish to set member data in a function it is true for them, too, but this is bad practice, it\'s convention to use accessor functions to modify member data
endfunction

You now have everything you need to know to make a basic struct.

#O3#
Conclusion

#L5# Questions? Answers.
Ninja_sheep;515275 wrote:

Whats better at structs then at local vars?
For one, the syntax for using gamecaches can be bulky and confusing. They're also considered to use very slow functions. Like gamecaches, structs allow you to continuously redeclare variables of the same name like locals but still access these variables from any function you need to.

#L6# Links and attachments.
Jass NewGen Pack v3d -- is necessary to use structs. It also includes bunches of other nifty features like a Jass parser that doesn't crash.
TESH for NewGen -- is a syntax highlighter for the above World Editor hack. It's almost as pretty and easy-to-use as JassCraft with the added bonus of writing directly into WE.
Wc3mapoptimizer 4.3 -- can reduce your map's size by a huge amount. A bit out of place in this tutorial but an amazingly useful tool nonetheless. :)

I've lost my CD-key for RoC. I can't log onto b.net or even open the world editor. If you have something to add to the tutorial, PM me and I'll read it as soon as I can.

#C#
Changelog

v1.01 Fixed some minor syntax errors in the struct declaration.

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.