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

[JASS] Structs

Status
Not open for further replies.
Level 19
Joined
Oct 12, 2007
Messages
1,821
I'm working for something in my map, and I should really be using structs now because I have a lot of data that has to be saved at the start of an action and I was told using structs is the good way to do this.

However, I'm trying to learn things about structs but can't find any basic tutorial explaining to me what exactly IS a struct. Could someone try to explain this to me in common human (non-coding) language so I can start from the very beginning by getting to understand it?
Or does anyone know of a good tutorial I could check out?
 
Hmm, well.. a struct is really just a way to bundle up data. For instance, in your case you want to make a spell which uses a number of different variables. One way to solve this is to make an array for each variable, and then some way keep track of a unique ID for each spell instance which you can use as index. Let's say we have an effect dummy named "u", which has an id named "id", we can do it like this with pure jass:

set udg_effectX[id] = GetUnitX(u)
set udg_effectY[id] = GetUnitY(u)
set udg_timeLeft[id] = 10

....etc. Now, if we want to pass data from one function to another, say using a timer, we only need to pass the ID of the effect to access all the data. But this solution is a bit ugly - we need to write out the index for every single variable even though we only want to work on one specific instance. Structs work on this exact same principle, but they work to "hide" this ugliness from you. This is how a basic struct can look:

JASS:
struct MySpell

	real effectX
	real effectY
	real timeLeft
	unit effectDummy
	effect effectArt

	static method create takes real x, real y, real time, integer dummyid, string effectpath returns MySpell
		local MySpell this = MySpell.allocate() //The "allocate" function gives our struct a unique index. "this" marks the current instance.

		set .effectX = x 	//Once we have defined "this", we can access it's member variables using for example "this.effectX".
		set .effectY = y 	//However, we don't actually need to write "this" for the compiler to understand. I usually place a singe dot in front of the variable to
		set .timeLeft = time 	//Signify that it is a member of the struct and not some local variable

		//....and the same for all variables we want to initialize

		
		return this //Every "create" method must return an instance of the struct!

	endmethod
endstruct

Now, we can just do this:

JASS:
local MySpell somename = MySpell.create(0, 0, 10, 'dumm', "effect.mdl")

set somename.timeLeft = 4 //We can access members like this, using a dot between the instance name and the variable name.

Now you might be wondering, what is a method? And what does "static" mean?

A method is just a function which is associated to your struct. Usually it is a function which somehow manipulates the variables you have defined. Let's say you have a struct named "missile", and you want to add some kind of functionality to it. One thing you might want is a method called "explode", in which you code whatever will happen when it explodes. Another might be "bounce", in which the missile bounces on the terrain.

"static" means that the method works like a normal function, which is not associated to any specific instance. This means that the "this" variable is not automatically set. However, you can create a local variable named "this" inside the method and set it to something, and then you can acces the member variables in the same way as you are used to. You need to use static methods if you want to use them as a trigger action, or as a function that runs upon timer expiration (as theese need to take nothing and return nothing whereas regular methods take the struct index, even though you cannot see it).

On important thing to remember is to destroy the struct once you are done with it. This you do using "instancename.destroy()". If you don't destroy your struct instances, the index will not be recycled, and you will eventually run out of indexes. This will prevent new structs from being created.

A destroy method is automatically created for each struct, but you can link a kind of trigger action to the struct destroy event by defining a method named "onDestroy". This is useful if you want some stuff to be cleaned up as the struct is destroyed. Be mindful though that it uses some stuff which might reduce performance ever so slightly, so it is not recommended for heavier stuff such as projectile system. I recommend reading the jasshelper manual if you want to know more about this.

Ugh, i am really tired and might have confused more than i infored. Hope it helped somewhat though, otherwise just read the toturials which IcemanBo linked.


EDIT: as a side note, items and units in warcraft can be seen as structs. They have some variables inside them, such as life, mana, fly height etc, and there are functions you can use to manipulate them (even though you don't use the dot operator to do so). For instance you can use use GetUnitX() to get the X coordinate. As a struct, units would look kinda like this:

JASS:
struct unit //Don't try to use "unit" as a struct name, i am just doing it to demonstrate. 

	real x
	real y
	real health
	real mana
	real flyheight

	method GetUnitFlyHeight takes nothing returns real //Same with these function names. 
		return .flyheight
	endmethod

	method GetUnitX takes nothing returns real
		return .x
	endmethod

	method GetUnitY takes nothing returns real
		return .y
	endmerthod

	method SetUnitFlyHeight takes real height returns nothing
		set .flyheight = height
	endmethod


endstruct


function TestFunction takes nothing returns nothing
	local unit u = unit.create() //We create an instance. Note that it is not legal to call a struct "unit", i just do it to demonstrate the similarities between structs and built-in classes.

	local real x = u.GetUnitX()
	local real y = u.GetUnitY()
	
	call u.SetUnitFlyHeight(100)

	call u.destroy() //Clean up. This is kinda like removing leaks.
endfunction
 
Level 19
Joined
Oct 12, 2007
Messages
1,821
Ah, both very helpful!
Especially the part where you created a struct for a 'unit' in the end Fingolfin.
I think I get it now. But I'm still a bit stuck with the code I'll have to be using my struct in.
I'm gonna try to approach it from the beginning again and write down all the steps I have to go through. Maybe I'll figure it out.
 
I think i thought of a better way of explaining structs which might be helpful. Let's observe some structs and how they will roughly be compiled into regular jass.

JASS:
struct TEST
	real x
	real y
	unit u
endstruct

Will turn into this:

JASS:
real TEST_x[8192] //They turn into arrays!!
real TEST_y[8192]
unit TEST_u[8192]

Some other things are also created, such as an allocation and deallocation function. What these do is to retrieve or recycle a unique index for each instance. This sounds more complicated than it is, but let's take a look at an example:

JASS:
static method someFunction takes nothing returns nothing
	//The create method exists wether you define it or not, but by default it only creates a unique index and nothing else.
	local TEST a = TEST.create() //'a' is now an integer with the value 1. //Starts at 1 because 0 counts as null for structs.
	local TEST b = TEST.create() //'b' is now an integer with the value 2.
	local test c = TEST.create() // 'c' is now an integer with the value 3.
	local test d

	call b.destroy()

	set d = TEST.create() //'d' now has the value 2, which is the old value of 'b' which has now been recycled, since we destroyed it. 
	

	call BJDebugMsg(I2S(c)) //Will return 3. 
endmethod

We can also observe the difference between static and non-static method by checking out how they are compiled.

JASS:
method move takes velX, velY returns nothing
	set .x = .x+velX
	set .y = .y+velY
	call SetUnitPosition(.u, .x, .y)
endmethod

This will compile into something roughly like this:

JASS:
function TEST_move takes integer this, real velx, real vely returns nothing
	set TEST_x[this] = TEST_x[this]+velx
	set TEST_y[this] = TEST_y[this]+vely
	call SetUnitPosition(TEST_u[this], TEST_x[this], TEST_y[this])
endfunction

You will notice that the compiled function has an extra argument named "this". It represents the index of the struct instance. So when you do this:

JASS:
local TEST a = TEST.create()
call a.move(5,2)

It turns into this:

JASS:
local integer a = TEST_create()
call TEST_move(a, 5, 2) //Notice how we are passing the index!

There you go! Not that hard. You have to admit though that the structs look much more neat than using the arrays. Well, what about static methods then? Let's have a look:

JASS:
static method onTimerExpires takes nothing returns nothing
	local TEST t = GetTimerData(GetExpiredTimer()) //GetTimerData is a TimerUtils function which uses hashtables

	call t.move(2,6)
endmethod

Turns into:

JASS:
function TEST_onTImerExpires takes nothing returns nothing
	local integer t = GetTimerData(GetExpiredTimer())

	call TEST_move(t,2,6)
endfunction

Wait, what?? It turns into a normal function? ..Well, that is the idea. Static methods are just like regular functions, except you can use the dot syntax to access them, which is really just an aesthetical thing.

So, how can we use this for something practical? Well, let's say we want to attach some data to every NPC unit in the map. The code could look roughly like this:

JASS:
struct NPC

	unit u
	boolean alerted = false //Perhaps we want to add some stealth element. Notice we can have a default value!
	itempool drop //Itempoosl are a cool way of rolling a random item from a preset pool. A must have for drops.

	real spawn_x //For the respawn point.
	real spawn_y

	timer respawn

	static method create takes unit u returns NPC
		local NPC this = NPC.create() //Get a free index

		set .u = u
		set .drop = CreateItemPool()
		set .respawn = NewTimerEx(this) //TimerUtils function which attaches this index to the timer.

		set .spawn_x = GetUnitX(u)
		set .spawn_y = GetUnitY(u)

		call ItemPoolAddItemType(.drop, 'itm1', 0.3) //30% chance of dropping item 1
		call ItemPoolAddItemType(.drop, 'itm2', 0.7) //70% chance of dropping item 2

		call SetUnitUserData(.u, this) //We attach the struct to the custom value of the unit.

		return this
	endmethod

	static method get takes unit u returns NPC
		return GetUnitUserData(u) //Get the index
	endmethod

	static method onRespawn takes nothing returns nothing
		local NPC this = GetTimerData(GetExpiredTimer())

		set .u = CreateUnit(....) //blah blah, create a unit of the same type  at respawn coordinates. You might want to save the unit id inside the struct for this.
	endmethod

	static method onDeath takes nothing returns nothing //You can use this as an action for a "unit dies" event
		local NPC this = NPC.get(GetTriggerUnit())

		if this == 0 then
			return //We don't want to run the trigger if there is no NPC struct attached to the unit!
		endif
		call PlaceRandomItem(.drop, GetUnitX(.u), GetUnitY(.u)) //Picks a random item from the pool and places it
		call TimerStart(.respawn, 60, false, function NPC.onRespawn)

	endmethod

endstruct


Notice though that this will collide with other systems using the custom data. Instead you should either use hashtables, or learn how to use a unit indexer. With a unit indexer, you can store the structs in an array and use the custom data as index! What a unit indexer does is to give each unit an index between 0 and 8192 (max array size) and save it in the custom data. To apply the system, all you need to do is loop through all units in the map owned by the npc player, and call "NPC.create(GetEnumUnit())" on them.
 
Level 19
Joined
Oct 12, 2007
Messages
1,821
I'm currently already using a unit indexer, and I'm using a lot of array variables to store information specific to all units in the map. It's just that there's too much going on in the system I'm trying to create and I'm losing track of it.
People told me to use hashtables or structs, but I have no clue how to use them.
I'm starting to understand what structs are a bit, but still I don't see why they are any better than using variables.
 
Status
Not open for further replies.
Top