• 🏆 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!

[General] Nulling a Variable - Integer

Status
Not open for further replies.
Level 16
Joined
Mar 27, 2011
Messages
1,349
Hi all, I want to do this:

JASS:
set ownedPokemon[index1][index2] = null

Though it receives a compile error "cannot convert null to integer". ownedPokemon is a 2D variable array of custom objects derived from my "Pokemon" class. So I take it custom objects are stored internally as integers?

I want to null them because they are initially null and I have other functions which check if I have assigned data to them.

JASS:
If ownedPokemon[index1][index2] == null
   // Found an unused element
   // Perform some actions
endif

Once I am finished with an element of ownedPokemon I want to null it so these other functions will recognise them as "available", so to speak. I'm aware of a workaround which involves me setting these variables to a set number like 0 and treat 0 as a "null" value of sorts, but I was wondering if there's a way to actually remove the value itself to make it truly null?

Edit: side question. The below will compile:

JASS:
set ownedPokemon[index1][index2] = 'null'

The apostrophe appears to internally convert text as a number, meaning it's not setting it to an null value but rather a number. Is this correct?
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
JASS does not support OOP. In vJASS it is emulated by giving each instance a unique integer that is used as array index for the struct member variables. Your variable is just another integer variable and they cannot be nulled.
From the jasshelper manual:
JassHelper is just a preprocessor not a hack so whatever adition to Jass we add is still limited by Jass' own limitations, in this case, structs use arrays which have a 8191 values limit, and we cannot use index 0 which is null for structs, so there is an 8190 instances limit. This limit is for instances of each type, so you may have 8190 objects of a pair struct type and still are able to have many other instances of other types.
Index 0 is used as null structs, so you can set it to 0, if you don't want the variable to refer to any struct instance.
 
Level 16
Joined
Mar 27, 2011
Messages
1,349
Ok thanks for the answer.

Index 0 is used as null structs, so you can set it to 0, if you don't want the variable to refer to any struct instance.

I don't think I can use 0 as a fake null value without modifying my systems because my system currently make use of the index 0. I suppose I can use a crazy high value like 250618 which I won't ever use. The new Blizzard patch increased the array size limit I hear so that should be ok.
 
For example 'hpea' is object id for peasant in 256 number base. Likewise, 'null' is also a number, so a normal integer, but also just in an other format. So it has nothing to do with null.

Would using a boolean flag as member help? boolean available. Nulling the object seems more like an attempt of freeing memory from it, so like the object itself isn't needed anymore.
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
I don't think I can use 0 as a fake null value without modifying my systems because my system currently make use of the index 0. I suppose I can use a crazy high value like 250618 which I won't ever use. The new Blizzard patch increased the array size limit I hear so that should be ok.
I don't think you understand. When you do (for example) local Pokemon MyInstance = Pokemon.create(), MyInstance is really just an integer. The value assigned to new struct (object) instances by the .create method starts at 1 and goes up every time a new one is allocated (and frees old instances when deallocated). Under no circumstances will MyInstance ever be set to 0 unless you try to refer to a struct that doesn't exist. There's no way you can 'use' index 0 because it will never give it to you; 0 is a protected value that specifically means 'no object' like null does. If you post your code I can make it more clear how this isn't a problem with specific examples.

If you insist, the best way to 'make your own null' is to do this:
JASS:
globals
  constant integer NO_POKEMON = -1
endglobals

//in your code
call ownedPokemon[index1][index2].destroy()
set ownedPokemon[index1][index2] = NO_POKEMON

//in your code
if ownedPokemon[index1][index2] == NO_POKEMON
  ...
endif
 
Last edited:
Level 16
Joined
Mar 27, 2011
Messages
1,349
I don't think you understand. When you do (for example) local Pokemon MyInstance = Pokemon.create(), MyInstance is really just an integer. The value assigned to new struct (object) instances by the .create method starts at 1 and goes up every time a new one is allocated (and frees old instances when deallocated). Under no circumstances will MyInstance ever be set to 0 unless you try to refer to a struct that doesn't exist. There's no way you can 'use' index 0 because it will never give it to you; 0 is a protected value that specifically means 'no object' like null does. If you post your code I can make it more clear how this isn't a problem with specific examples.

If you insist, the best way to 'make your own null' is to do this:
JASS:
globals
  constant integer NO_POKEMON = -1
endglobals

//in your code
call ownedPokemon[index1][index2].destroy()
set ownedPokemon[index1][index2] = NO_POKEMON

//in your code
if ownedPokemon[index1][index2] == NO_POKEMON
  ...
endif[/trigger]

Ahhh, when the other guy said index 0, I assumed he was referring to the index of the 2D array. So you're saying the value of the element is actually 0 when uninitialized. So I could do something like this:

JASS:
// Check Array If Pokemon Exists
If ownedPokemon[index1][index2] == null or ownedPokemon[index1][index2] == 0 then
   // Found an unused element
   // Perform some actions
endif

// Unregister Pokemon From Array
    set ownedPokemon[index1][index2] = 0

I still check for a null value because an element of ownedPokemon will always be null until I set a pokemon or a 0 to it (because I haven't initialized the array with 0's upon creation)?
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
An integer can never be compared to null; the first bit of your code will always throw a compile error. Null is only for objects that extends handle like unit, destructable, player, etc.
because I haven't initialized the array with 0's upon creation
If you try to load an array index that has never been assigned a value, it will return the default for that object type. 0 for ints, 0.00 for reals, "" for strings, null for handle-based objects, and 0 for all custom vJASS objects (since they're internally just integers too).

When you, for example, remove a unit from the game any unit variables will still point to the same place in memory which is now occupied by "null" since the unit is gone. That means the following code will properly display the text "unit gone":
JASS:
globals
  unit U
endglobals

set U = CreateUnit(...)
call RemoveUnit(U)
//later
if U == null then //U's value got updated 'automatically' because it points to the location in memory where the unit used to exist but is now occupied by the value "null"
  call BJDebugMsg("unit gone")
endif
If you try to do the same thing with a custom type (struct), it will not work properly. Integers don't point to a place in memory, they are assigned an actual value that doesn't update until you set it again later. JASSHelper does not add code that will retroactively remove all references to a struct once you call .destroy() or .deallocate() on it, you must do that yourself. The following code will not display the "object gone" text:
JASS:
globals
  SomeObject B
endglobals

set B = SomeObject.create(...) //internally maybe B = 13
call B.destroy()
//later
if B == 0 then //at this point B is still = 13, even though the 13th instance of the SomeObject that B pointed to was destroyed; B's value doesn't get updated automatically
  call BJDebugMsg("object gone")
endif
Instead you must do:
JASS:
globals
  SomeObject B
endglobals

set B = SomeObject.create(...)
call B.destroy()
set B = 0 //have to do this manually
//later
if B == 0 then
  call BJDebugMsg("object gone")
endif

In your case I think you might benefit from storing a Linked List of pokemon for each player rather than trying to slot them into an array, but that's just me. I really depends on how your code is set up internally.
 
Level 16
Joined
Mar 27, 2011
Messages
1,349
In your case I think you might benefit from storing a Linked List of pokemon for each player rather than trying to slot them into an array, but that's just me. I really depends on how your code is set up internally.

I haven't used or considered using lists before but now I'm seeing a good reason to. I've been using arrays up until now, though changing means I'd need to redesign my systems :/

An integer can never be compared to
null
; the first bit of your code will always throw a compile error. Null is only for objects that
extends handle
like unit, destructable, player, etc.

But I have been doing this and it seems to work. When I get home from work, I'll run some tests to confirm this.

JASS:
globals
  SomeObject B
endglobals

set B = SomeObject.create(...)
call B.destroy()
set B = 0 //have to do this manually
//later
if B == 0 then
  call BJDebugMsg("object gone")
endif

I haven't got around to calling the deconstructor when a Pokemon dies yet though I have setup my "transfer" function when a Pokemon is transferred from one player to another. This involves finding a null or 0 value for a players respective array (which is an unused array slot) then assigning the object to that player. The old player's slot is set to 0. When I get home, I'll share my code but it is something like this off memory:

JASS:
// DRAFT INCOMPLETE deomonstration only
// This is part of the code that transfers a pokemon from one player to another.

globals
    readonly static Pokemon ownedPokemon[NUMPLAYERS][MAXNUMPOKEMON]
endglobals

local unit pokemon = GetTriggerUnit()
local integer order = GetPokemonIndex(pokemon) // Checks what index the pokemon in question belongs to.

// Find a free element to place the new Pokemon in
loop
    exitwhen ownedPokemon[catchingPlayer][emptyIndex] == null or ownedPokemon[catchingPlayer][emptyIndex] == 0
    set emptyIndex = emptyIndex + 1
endloop

    // emptyIndex now contains a free element

    set ownedPokemon[catchingPlayer][emptyIndex] = ownedPokemon[wildPlayer][order]
    set ownedPokemon[wildPlayer][order] = 0


When I get home from work, I'll take another look at things. I'll share the code which is working in game. I'll also consider changing to lists if it's not too much work. I'll have to toss up if it's worth it or not now that the system is far into development.
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
Typing for fun, I would structure things like this:
JASS:
globals
  constant integer NUM_TYPES = 19 //includes TYPE_NONE
  constant integer NUM_PLAYERS = 24
  constant integer BELT_SIZE = 6

  Belt array PlayerBelts

  Type TYPE_NORMAL //Assign these on map init
  Type TYPE_FIGHTING
  Type TYPE_FLYING
  //etc.
  Type TYPE_NONE
endglobals

struct Type
  private integer array dmgFactor[NUM_TYPES]

  method damageTo takes Type which returns real
      return dmgFactor[which] //this would get inlined anyway so it's not an extra function call
  endmethod

  method damageFrom takes Type which returns real
      return which.damageTo(this)
  endmethod

  method create takes string dmgString returns thistype
      //parse dmgString for the commas that delimit the damage a type does to another type
      //then use that data to fill dmgFactor such that dmgFactor[2] is the damage from this type to type 2 (in this example, Fighting)
      //also dmgFactor[NUM_TYPES] = dmgFactor[TYPE_NONE] = 1.00 for all types
  endmethod

  private static method onInit takes nothing returns nothing
      //I used this chart as reference, but it doesn't matter what you use as long as the order you create them is the order they're displayed on the reference chart you use
      // https://rankedboost.com/pokemon-sun-moon/type-chart/

      set TYPE_NORMAL   = Type.create("1,1,1,1,1,0.5,1,0,0.5,1,1,1,1,1,1,1,1,1")
      set TYPE_FIGHTING = Type.create("2,1,0.5,0.5,1,2,5,0,2,1,1,1,1,0.5,2,1,2,0.5")
      set TYPE_FLYING   = Type.create("1,2,1,1,1,0.5,2,1,0.5,1,1,2,0.5,1,1,1,1,1")
      //etc. for all types
      set TYPE_NONE    = type.create("1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1")
  endmethod
endstruct


struct Pokemon
  unit mon = null
  player owner = null
  integer id = 0 //rawcode of the unit, probably
  integer level = 0
  Type type1 = TYPE_NONE
  Type type2 = TYPE_NONE
 
  real statHP = 0.00 //or whatever other stat system you use
  real statMP = 0.00
  real statSTR = 0.00
  real statAGI = 0.00
  real statINT = 0.00

  method create takes real x, real y, integer id, integer level, Type type1, Type type2, <other arguments> returns thistype
      local thistype this = thistype.allocate()

      set this.mon = CreateUnit(...)
      set this.owner = owner
      set this.id = id
      set this.type1 = type1
      set this.type2 = type2

      set this.<stats> = <stat> //etc.

      return this
  endmethod

  //other pokemon-specific methods would go here
endstruct

struct Belt
  Pokemon array mons[BELT_SIZE]
  player owner = null

  method catch takes Pokemon pm returns boolean
      //search through mons[], if an open slot is found then put it in that slot and return ture
      //if no slot found, display error message and return false
  endmethod

  method release takes Pokemon pm returns boolean
      //search this Belt for pm; if a match is found, release it and return true
      //otherwise display an error message return false
  endmethod

  method releaseBySlot takes integer slot returns nothing
       //release the slot'th pokemon from this Belt
  endmethod

  static method fromPlayer takes player which returns Belt
      return PlayerBelts[GetPlayerId(which)]
  endmethod

  private static method onInit takes nothing returns nothing
      local integer i = 0
 
      loop
          exitwhen i >= NUM_PLAYERS
          set PlayerBelts[i] = thistype.create()
          set PlayerBelts[i].owner = Player(i)
          set i = i+1
      endloop
  endmethod

  //other belt-specific methods would go here
endstruct
 
Level 16
Joined
Mar 27, 2011
Messages
1,349
Ahh it's nice to see how other people do things. Also interesting to see that we've done some things the same. I'll show you my code when I get home :)

Pretty excited to get this game finished to be honest :D

Edit: I can see I could have done some things better too :/
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
When you, for example, remove a unit from the game any unit variables will still point to the same place in memory which is now occupied by "null" since the unit is gone. That means the following code will properly display the text "unit gone":
I am not too sure about that... As far as I am aware null is a hard coded handle value primitive and as such has a value. Hence why it is valid to assign a handle variable to null. Unless you explicitly set U to null after removing the unit then that test might not work as it still retains its old handle value even if it functions effectively as null if passed to various natives.

Even if that does work, I still strongly recommend nulling such variables. Not only will it allow the handle index to be recycled, but also removes a potential bug source in the form of dangling no longer used handle values.
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
Unless you explicitly set U to null after removing the unit then that test might not work as it still retains its old handle value even if it functions effectively as null if passed to various natives.
You are indeed correct and I was wrong. I used this test:
JASS:
library test initializer init
  globals
    unit U
  endglobals
 
  private function foo takes nothing returns nothing
    call RemoveUnit(U)
    call BJDebugMsg(I2S(GetHandleId(U)))
    call BJDebugMsg(I2S(GetUnitTypeId(U)))
    if U == null then
        call BJDebugMsg("null")
    else
        call BJDebugMsg("not null")
    endif
  
    call TriggerSleepAction(5.00)
  
    call BJDebugMsg(I2S(GetHandleId(U)))
    call BJDebugMsg(I2S(GetUnitTypeId(U)))
    if U == null then
        call BJDebugMsg("null")
    else
        call BJDebugMsg("not null")
    endif
  endfunction

  private function init takes nothing returns nothing
    set U = CreateUnit(Player(0), 'hfoo', 0.00, 0.00, 0.00)
    call TriggerSleepAction(2.00)
    call foo()
  endfunction
endlibrary
"not null" appears both times, and it retains its handle id after the 5 second TSA. GetUnitTypeId() actually returns 'hfoo' the first time but returns 0 after the TSA.
 
Status
Not open for further replies.
Top