• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Search a grid

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
Farm Land System

This is a Farm Land System. Basicially you can decide which terrain should allow the spawning of farmland and how much return (0-100%) that pieace of land should give. You can use it to change income or food supply given by a unit or what ever you want. I'd like to make this into a system and not only for my personal use so any feedback on that would be appreciated.

If you just want to copy and paste the code to test it you need to use ashenvale tilsets, a worker to build farms and a unit indexing system.

JASS:
library FarmLand initializer init

globals
    private real array x
    private real array y
    private integer max
    private hashtable hash = InitHashtable()
    real array farmReturn
endglobals

private function IsUnitFarm takes unit u returns boolean
    if (GetUnitTypeId(u) == 'hhou') then
        return true
    endif
    return false
endfunction

private function GrowFarm takes unit u returns nothing
    local integer uID = GetUnitUserData(u)
    local real x0 = GetUnitX(u)
    local real y0 = GetUnitY(u)
    local real x1
    local real y1    
    local texttag tt
    local integer i = 0
    local real c = 0
    local real d = 0
    local real r
    local string s
    loop
        exitwhen i > max
        set x1 = x0 + x[i]
        set y1 = y0 + y[i]
        set r = LoadReal(hash, 0, GetTerrainType(x1,y1))
        if (r > 0) then
            call SaveInteger(hash, R2I(x1), R2I(y1), GetTerrainType(x1,y1))
            call SaveUnitHandle(hash, R2I(x1), R2I(y1), u)
            call SetTerrainType(x1,y1,'Vcrp',-1,1,1)
            set c = c + r
            set d = d + 1
        endif
        set i = i + 1
    endloop
    set farmReturn[uID] = (farmReturn[uID]*(max+1-d) + c)/(max+1)
    if (GetLocalPlayer() == GetOwningPlayer(u)) then // this wont crash?
        set tt = CreateTextTag()
        call SetTextTagPermanent(tt,false)
        call SetTextTagColor(tt, 255, 255, 255, 255)
        call SetTextTagPosUnit(tt,u,50)
        call SetTextTagVelocityBJ(tt,30,90)
        //
        //call SetTextTagVisibility(tt,true) // This isnt needed?
        //
        call SetTextTagFadepoint(tt,3.5)
        call SetTextTagLifespan(tt,4)
        if (farmReturn[uID] < 0.2) then
            set s = "|c00ff0000"
        elseif (farmReturn[uID] >= 0.2 and farmReturn[uID] < 0.4) then
            set s = "|c00bf3f00"
        elseif (farmReturn[uID] >= 0.4 and farmReturn[uID] < 0.6) then
            set s =  "|c007f7f00"
        elseif (farmReturn[uID] >= 0.6 and farmReturn[uID] < 0.8) then
            set s = "|c003fbf00"
        elseif (farmReturn[uID] >= 0.8) then 
            set s = "|c0000ff00"
        endif
        call SetTextTagText(tt,s + I2S(R2I(farmReturn[uID]*100)) + "%|r", 12*0.023/10)
        //
        set tt = null // is this even needed?
        // 
    endif
endfunction


private function Update takes nothing returns nothing
    local unit u = GetEnumUnit()
    if (IsUnitFarm(u) == true and GetUnitState(u, UNIT_STATE_LIFE) >= 1) then
        call GrowFarm(u)
    endif
    set u = null
endfunction

private function RemoveFarm takes unit u returns nothing
    local real x0 = GetUnitX(u)
    local real y0 = GetUnitY(u)
    local real x1
    local real y1
    local integer i = 0
    loop
        exitwhen i > max
        set x1 = x0+x[i]
        set y1 = y0+y[i]
        if (GetTerrainType(x1,y1) == 'Vcrp' and u == LoadUnitHandle(hash, R2I(x1), R2I(y1))) then
            call SetTerrainType(x1,y1,LoadInteger(hash, R2I(x1),R2I(y1)),-1,1,1)
            call RemoveSavedInteger(hash, R2I(x1),R2I(y1))
        endif
        set i = i + 1
    endloop
    call RemoveSavedHandle(hash, R2I(x1), R2I(y1))
    set farmReturn[GetUnitUserData(u)] = 0 
endfunction

private function main takes nothing returns boolean
    local unit u = GetTriggerUnit()
    local location l
    if (IsUnitFarm(u) == true) then
        if (GetTriggerEventId() == EVENT_PLAYER_UNIT_CONSTRUCT_FINISH) then
            call GrowFarm(u) 
        else
            set l = GetUnitLoc(u)
            call RemoveFarm(u)
            set bj_wantDestroyGroup = TRUE
            call ForGroup(GetUnitsInRangeOfLocMatching(700,l,null), function Update) 
            // the 700 number should be replaced with something flexible based on the radian of the grid. (Don't know how!)
            call RemoveLocation(l)
        endif
    endif
    set u = null
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    local player p
    loop
        exitwhen i > 11
        set p = Player(i)
        call TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null)
        call TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_DEATH, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Condition(function main))
    set t = null
    // Change this!
    call SaveReal(hash, 0, 'Agrs', 1.00)
    call SaveReal(hash, 0, 'Adrg', 0.75)
    call SaveReal(hash, 0, 'Adrd', 0.40)
    // Change this!
    set x[0] = 0
    set y[0] = 0
    set x[1] = 128
    set y[1] = 0
    set x[2] = -128
    set y[2] = 0
    set x[3] = 0
    set y[3] = 128
    set x[4] = 0
    set y[4] = -128
    set x[5] = 128
    set y[5] = 128
    set x[6] = -128
    set y[6] = 128
    set x[7] = 128
    set y[7] = -128
    set x[8] = -128
    set y[8] = -128
    set x[9] = 0
    set y[9] = 256
    set x[10] = 0
    set y[10] = -256
    set x[11] = 256
    set y[11] = 0
    set x[12] = -256
    set y[12] = 0
    set max = 12
endfunction
endlibrary
 

Attachments

  • farmland system 1.0.w3x
    165.3 KB · Views: 48
Last edited:
He might be referring to searching for a patch of Terrain in an area around the building. A region array is useful in this case, just add them to an array.

Srch_Region[1] = Region 001
Srch_Region[2] = Region 002
Srch_Region[3] = Region 003

And so on.

Loop through each region and check the terrain type. Each region must have around 128 size in terms of coordinates.
 
Level 25
Joined
May 11, 2007
Messages
4,650
Something like this:
12.PNG


Going to have to make it check in sideways too, but you get the idea.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
Do note that terrain is defined from terrain nodes and not the surface made up between terrain nodes. This is crucial when dealing near the edge of the map however it should be avoided in any case due to how unstable WC3 is with edge of map stuff.

In any case the idea is you need to sample each node and test the terrain type. The fastest way to do this is to determine a grid aligned point and then apply very X and Y offsets to it from a list. This has been suggested already. The list can be any sort of order.

When using GUI where you cannot really use X/Y cords directly you will need to be careful not to leak any locations. Location leaks can quickly degrade map performance.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
When using GUI where you cannot really use X/Y cords directly you will need to be careful not to leak any locations. Location leaks can quickly degrade map performance.

This and the overhead from using location are why they are so discouraged? About how many locations until a noticeable degradation?
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
The point about locations is that in many cases you simply dont need them. Sure, some natives expect locations as arguments (however in one of the latest patches we got some natives which take X,Y instead), but if you just want to save a point its better to use x and y coordinates (2 real values).

Background: Locations are a reference data type, which mean they have to be destroyed manually and therefore can leak. Reals are non-reference data types, so they dont leak and dont have to be destroyed.
Other reference data types like effects, groups, etc. have the same problem, but in contrast to locations you cant just replace them so easily. But that doesnt mean locations are incredibly slow (i can hardly imagine that as few as 100 locations cause lag...).
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
Several hundreds. The degradation is rather unnoticeable, but FPS becomes unstable when the screen hits units.
That sounds more like effect leaks than location leaks.

Also maps with leaks tend to cause players to disconnect a lot easier in multiplayer.
Should make no difference when players disconnect. Deterministic is deterministic and leaking a million deterministic locations should not be any less deterministic than leaking none.

Location leaks have 3 main negative side effects.
1. Increased memory usage. This is trivial on modern systems with several gigabytes of memory but on older systems that do not it can cause page faults to start to be generated in which case performance shoots through the floor. Players may or may not disconnect due to appearing unresponsive from the extremely poor performance.
2. Some kind of procedural hazard is left by location leaks. The result is that operations that generate locations or manipulate locations (not sure which) perform worse. This means that the same trigger operations will require more time to execute. Potentially the game can come to a crawl on old systems which can then start to be registered as unresponsive and eventually dropped. Modern systems will just suffer extremely poor performance the likes of which are worse than moving multiple thousand units at once in SC2.
3. Finite structure leaks as a result of the remaining locations from the leaks may eventually cause the game to crash when it runs out of a finite structure. If the crash is not synchronous this would point towards some kind of memory access defect. In the case of maps with slow leaks that run over several hours this can be a major cause of crashes.

Usually you will see location leaks in two ways.
1. Extremely bad game performance no mater what is being viewed. It can eventually reach the stage that the game is unresponsive for considerable length at a time ("slide show" frame rate people often refer). At this point players usually leave as progression is as good as impossible.
2. Crashes in games with excessively long run time. Some hint of poor performance before can lead you to believe it is due to leaks however usually the crash happens with out warning in a map that will not crash over short periods of time.

99% of cases will manifest as 1. The game will reach unplayable frame rate usually long before any crash occurs. Crashes are usually the result of more silly things such as incorrectly designed damage detection systems or multiple damage detection systems as both those require huge numbers of events to operate and do not generate poor performance.

I was wondering how to setup a loop that searches an area as seen in the screenshot with the red region as the center?
As mentioned earlier, create a list of X/Y offsets from the centre to all the points. This would be 2 real arrays. You get a sample point from the central location simply by adding the X and Y at a specific index to it. Generating the list can be done from images such as what you have above since you know that terrain nodes in WC3 are spaces 128 units apart (I think).

How would one define the other regions without actually pre placing them?
What do regions have to do anything with this problem? Firstly you show rects in your image (blame GUI for confusing you) and these have absolutely no meaning what so ever when it comes to sampling terrain type at a location.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
I want to know:
1) how to save location(x1,y1) into a hashtable attached to a unit.
2) How to update the farm area around when a farm dies. Basicially farm can overlap, so I need to make a update function that growns over the removed farmland. I was thinking to first remove the farm land stored in the hashtable and then picking every unit of type farm and adding farmland in cleared land.

Will this work:
JASS:
call SaveInteger(hash, R2I(x1), R2I(y1), GetTerrainType(x1,y1))
call SaveUnitHandle(hash, R2I(x1), R2I(y1), u)

and this:
JASS:
                set x1 = x0+x[i]
                set y1 = y0+y[i]
                set u = LoadUnitHandle(hash, R2I(x1), R2I(y1))
                if (GetTerrainType(x1,y1) == 'Vcrp' and u != null) then
                    call SetTerrainType(x1,y1,LoadInteger(hash, R2I(x1),R2I(y1)),-1,1,1)
                    // How to flush parent and child?
                endif

*Edit: it works! now for some reason it doesn't limit the tile creationg to only two types... Could someone explain it to me?

JASS:
library FarmLand initializer init

globals
    private real array x
    private real array y
    private integer max
    private hashtable hash = InitHashtable()
    private integer array ter
    private real array terrain
    private real array yield
    real array farmReturn
endglobals

private function GrowFarm takes unit u returns nothing
    local real x0 = GetUnitX(u)
    local real y0 = GetUnitY(u)
    local real x1
    local real y1
    local integer i = 0
    local integer j
    loop
        exitwhen i > max
        set x1 = x0 + x[i]
        set y1 = y0 + y[i]
        set j = 0
        loop
            exitwhen terrain[j] == null
            if (GetTerrainType(x1,y1) == terrain[j]) then
                call SaveInteger(hash, R2I(x1), R2I(y1), GetTerrainType(x1,y1))
                call SaveUnitHandle(hash, R2I(x1), R2I(y1), u)
                call SetTerrainType(x1,y1,'Vcrp',-1,1,1)
            endif
            set j = j + 1
        endloop
        set i = i + 1
    endloop
endfunction

private function update takes nothing returns nothing
    local unit u = GetEnumUnit()
    if (GetUnitTypeId(u) == 'hhou' and GetUnitState(u, UNIT_STATE_LIFE) > 0) then
        call GrowFarm(u)
    endif
    set u = null
endfunction

private function RemoveFarm takes unit u returns nothing
    local real x0 = GetUnitX(u)
    local real y0 = GetUnitY(u)
    local real x1
    local real y1
    local unit uu 
    local integer i = 0
    loop
        exitwhen i > max
        set x1 = x0+x[i]
        set y1 = y0+y[i]
        set uu = LoadUnitHandle(hash, R2I(x1), R2I(y1))
        if (GetTerrainType(x1,y1) == 'Vcrp' and u == uu) then
            call SetTerrainType(x1,y1,LoadInteger(hash, R2I(x1),R2I(y1)),-1,1,1)
        endif
        set i = i + 1
    endloop
    set uu = null
endfunction

private function main takes nothing returns boolean
    local unit u = GetTriggerUnit()
    local location l
    
    if (GetUnitTypeId(u) == 'hhou') then
        if (GetTriggerEventId() == EVENT_PLAYER_UNIT_CONSTRUCT_FINISH) then
            call GrowFarm(u) 
        else
            set l = GetUnitLoc(u)
            call RemoveFarm(u)
            set bj_wantDestroyGroup = TRUE
            call ForGroup(GetUnitsInRangeOfLocMatching(1000,l,null), function update) 
            call RemoveLocation(l)
        endif
    endif
    set u = null
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    local player p
    loop
        exitwhen i > 11
        set p = Player(i)
        call TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null)
        call TriggerRegisterPlayerUnitEvent(t,p,EVENT_PLAYER_UNIT_DEATH, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Condition(function main))
    set t = null
    
    set terrain[0] = 'Agrs'
    set yield[0] = 1.
    set terrain[1] = 'Adrg'
    set yield[1] = .75
    
    set x[0] = 0
    set y[0] = 0
    set x[1] = 128
    set y[1] = 0
    set x[2] = -128
    set y[2] = 0
    set x[3] = 0
    set y[3] = 128
    set x[4] = 0
    set y[4] = -128
    set x[5] = 128
    set y[5] = 128
    set x[6] = -128
    set y[6] = 128
    set x[7] = 128
    set y[7] = -128
    set x[8] = -128
    set y[8] = -128
    set x[9] = 0
    set y[9] = 256
    set x[10] = 0
    set y[10] = -256
    set x[11] = 256
    set y[11] = 0
    set x[12] = -256
    set y[12] = 0
    set max = 12
endfunction
endlibrary

I completed it, only problem remaining is the bug that it creates farm land over dirt and other terrain aswell when it should only produce farm on terrian[0] and terrain[1].

If someone could check out the code for leaks and help me clean up the hash it would be great! ;)

If you want to test the trigger, implement it by building a farm with a worker on ashenvale tilsets.
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
1. You don't save the location. You have a list of coordinates, and get locations from them when needed, by using point with offset (location of farm +X, +Y)
Depends what exactly you want to do.

2. Change terrain of everything nearby, and then "fix" the terrain for nearby farms.

I updated the code posted. Only 1 issue remaining, is some tilesets passes through the filter and I dont quite understand why.

After that is done im gonna try implementing a color gradiant displaying the farm yield when ever the function GrowFarm is called. Would like it to be red-orange-yellow-green 0-100%.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
Your script appears to leak hashtable entries? You need to remove them when they are no longer required otherwise they will sit in the hashtable forever degrading its manipulation performance. Sure if a farm is ever rebuilt in the same location the data will be used again so it is not really a big leak, however it still will keep data longer than the life cycle of the object that the data is for (why I class it as a leak).

Each tile type of each tyle set is a unique ID. This means dirt from one tile set is different from dirt in another tile set.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Your script appears to leak hashtable entries? You need to remove them when they are no longer required otherwise they will sit in the hashtable forever degrading its manipulation performance. Sure if a farm is ever rebuilt in the same location the data will be used again so it is not really a big leak, however it still will keep data longer than the life cycle of the object that the data is for (why I class it as a leak).

Could you show me how its done?

Each tile type of each tyle set is a unique ID. This means dirt from one tile set is different from dirt in another tile set.

No the issue is they dont apear to be. Because I'm only searching For Ashenvale Grass and Ashenvale Grassy Dirt ye it get built on alot of other ashenvale tilsets, except for; Rock, vines and leaves. Hashtable solves! Why you think it was malfunctioning tho, loop in a loop?
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
Could you show me how its done?
JASS:
// Use this to flush an entire hashtable in one call. Not entirely sure what you would use it for but it should be by far the most efficient way to flush all entries in a hashtable.
native FlushParentHashtable takes hashtable table returns nothing
// Use this to flush entire parents at once. All children indices of the parent will be flushed (cleaned) with a single call. Use when possible as it should be faster than flushing many individual children.
native FlushChildHashtable takes hashtable table,integer parentKey returns nothing
// Otherwise use these to remove specific types.
native RemoveSavedBoolean takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedHandle takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedInteger takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedReal takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedString takes hashtable table,integer parentKey,integer childKey returns nothing
It should be noted that removing the correct type with the specific remove natives is recommended. At one stage type mismatches would cause a game crash and are still known to be unstable.

Remove any hashtable entry you do not need. This is usually the case with object specific children at the end of object life. Example a farm dies so all mappings related to that farm are no longer required.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
JASS:
// Use this to flush an entire hashtable in one call. Not entirely sure what you would use it for but it should be by far the most efficient way to flush all entries in a hashtable.
native FlushParentHashtable takes hashtable table returns nothing
// Use this to flush entire parents at once. All children indices of the parent will be flushed (cleaned) with a single call. Use when possible as it should be faster than flushing many individual children.
native FlushChildHashtable takes hashtable table,integer parentKey returns nothing
// Otherwise use these to remove specific types.
native RemoveSavedBoolean takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedHandle takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedInteger takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedReal takes hashtable table,integer parentKey,integer childKey returns nothing
native RemoveSavedString takes hashtable table,integer parentKey,integer childKey returns nothing
It should be noted that removing the correct type with the specific remove natives is recommended. At one stage type mismatches would cause a game crash and are still known to be unstable.

Remove any hashtable entry you do not need. This is usually the case with object specific children at the end of object life. Example a farm dies so all mappings related to that farm are no longer required.

Thanks yet again, ever so useful. I implemented the flushes you ask for and pray nothing got messed up (atleast it didn't seem to be).

Also created a local floating text, if you could check out if its done ok. If so this is basicially finished.

I revised the entire starting post. Into a new "request" so please read that if you have time. (The new code is posted there aswell.)
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
Last question, how to detect if a grid is pathable (doodads etc.) and how to find the the pathing block size of a building placed on the grid.
No direct way. You can only detect the pathin as defined by the w3e file (doodads + terrain) and not any pathing modifiers (not all widgets, basically items, destructibles and units cannot be directly detected).

You will need to use some kind of widget and measure how it gets displaced to detect pathability at a point. For your purposes I am guessing this will be reasonably resource intensive. It would be more efficient to map a JASS inputted interpretation of the pathing to the building type and then factor that in during procedures.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
No direct way. You can only detect the pathin as defined by the w3e file (doodads + terrain) and not any pathing modifiers (not all widgets, basically items, destructibles and units cannot be directly detected).

You will need to use some kind of widget and measure how it gets displaced to detect pathability at a point. For your purposes I am guessing this will be reasonably resource intensive. It would be more efficient to map a JASS inputted interpretation of the pathing to the building type and then factor that in during procedures.

Whats happening is that the tileset is being changed to farmland. If a tree or anything else is there I dont wan't the tilesets to be changed. What if I make a dummy unit try to construct something there or something? If construction starts, the tilesets change - if not nothing happens. Would in this case be 12 units spawned and ordered.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
It is rumoured that units do leak to some extent (not all memory they use gets cleared on death). Some people like myself have evidence to prove this while others have evidence to disprove it. In any case that sounds too resource intensive.

The usual approach is to move a hidden item to the point and check if it is physically nearby where you specified after the move. Only 1 such item needs to exist the entire game as you can move it as much as you like and instantly measure (as displacement occurs as part of move call).

You could also do a local area search for destructibles like trees and if any are found not change the tile.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
The usual approach is to move a hidden item to the point and check if it is physically nearby where you specified after the move. Only 1 such item needs to exist the entire game as you can move it as much as you like and instantly measure (as displacement occurs as part of move call).

Thats a nice way of doing it. Even Removes the need of storing every buildings grid size for clearing land.
 
Status
Not open for further replies.
Top