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

[JASS] Avoiding OP limit, please help to make this trigger work properly?

Status
Not open for further replies.
Level 14
Joined
Jul 1, 2008
Messages
1,314
Good Evening Everybody,

I am using the following functions in a huge map, to change terrain tiles according to season.

The Problem is, that a loop will hit the OP limit quite easily, as the map is that huge.

I thought of several ways to do this, and the current way to change a huge terrain into other tiles, is:

how it should work
1. Trigger: This trigger is called by the user. It loops up to 50 times and through all rects, that I want to change upon season.
2. Trigger: The called trigger from 1. itself calls another trigger once a second, that will try to change each rect one after each other until all rects are finished.
3. If the op limit is reached in the second trigger, it will be called again by the first one anyway. To achieve that, the second trigger always saves the last checked position and checks, if it already covered the whole rect. If yes, it should advance to the next rect.
Problem is: It seems, that sometimes the first trigger, that executes the actual changing attempts, crashes. I think, this happens most of the times on big rects.

Question: Can you guys maybe help me with suggestion on how to solve this? I am not very good with this whole OP limit thing, so I think, I do not understand this correctly ... Why does my main function in the first trigger crash sometimes? It should not reach the op limit, or does it?
I observe, that rect 0 and 1 are changed correctly but rect 2 is not even touched. Instead the first trigger stoppes somewhere at count 8 for example...

JASS:
library SEASON

  /* use:
set SEASON_REQUEST_SEASON_TYPE = "winter"
  call TriggerExecute( SEASON_SEASONAL_CHANGE_TRIGGER)
*/

  globals
     private  rect array  SeasonalRects
     private  integer  currentRect  = 0
     private constant integer  maxRects  = 2
     private constant integer  OpLimitMax  = 50
     private  boolean array currentRectCompleted
     private  real array  currentPosX
     private  real array  currentPosY
     private  real array  rectMinX
     private  real array  rectMaxX
     private  real array  rectMinY
     private  real array  rectMaxY
     public  constant trigger  SEASONAL_CHANGE_TRIGGER  = CreateTrigger()
     private constant trigger  SEASONAL_CHANGE_TRIGGER_EX  = CreateTrigger()
     public  string  REQUEST_SEASON_TYPE  = "winter"  // this will be changed outside upon seasonal request
     private constant integer  TYPE_WINTER_SNOW  = 'cNc1'  // Terrain tile type
     private constant integer  TYPE_SUMMER_THICK_GRASS  = 'cAc1'  // Terrain tile type
     private constant integer  TYPE_WINTER_ICE  = 'Nice'  // Terrain tile type
     private constant integer  TYPE_SUMMER_DARK_GRASS  = 'Vgrt'  // Terrain tile type
  endglobals

  private function ConvertTerrainTypeSeasonal takes real x, real y, string season returns integer
     // list all terrain types that are allowed to change seasonally. Link terrain types here
     if season == "winter" then
        // change all linked types to their spring form
        if (GetTerrainType( x, y) == TYPE_SUMMER_THICK_GRASS) then
           return TYPE_WINTER_SNOW
        elseif (GetTerrainType( x, y) == TYPE_SUMMER_DARK_GRASS) then
           return TYPE_WINTER_ICE
        endif
     elseif season == "spring" then
        // change all linked types to their spring form
        if (GetTerrainType( x, y) == TYPE_WINTER_SNOW) then
           return TYPE_SUMMER_THICK_GRASS
        elseif (GetTerrainType( x, y) == TYPE_WINTER_ICE) then
           return TYPE_SUMMER_DARK_GRASS
        endif
      endif
      return 0 // return 0 if tile is not of any type to be changed
  endfunction

  private function SeasonChangeTerrainEx takes nothing returns nothing
     // this function executes seasonal terrain changes for actual "current" rects, until they are completed
     local boolean exit = FALSE
     local integer REQUESTED_TYLE = 0

     loop
        if ((currentPosX[currentRect] >= rectMaxX[currentRect]) and (currentPosY[currentRect] >= rectMaxY[currentRect])) then
           call BJDebugMsg("SEASON right top corner reached of current rect index " + I2S(currentRect))
           set exit = TRUE // exit this rect
           set currentRectCompleted[currentRect] = TRUE // mark rect as completed
       endif
       exitwhen exit

       // go through current rect from bottom left to top right and apply changes
       // loop through every x loc at current y
      set currentPosX[currentRect] = rectMinX[currentRect] // reset x position start
      loop
         exitwhen currentPosX[currentRect] > rectMaxX[currentRect]
         call BJDebugMsg("SEASON currentX " + R2S(currentPosX[currentRect]))
         // --------------------------
         // apply channges
         // request terrain type and change it if requested. Do not allow unregistered terrain types (which would be ==0)
         set REQUESTED_TYLE = ConvertTerrainTypeSeasonal( currentPosX[currentRect], currentPosY[currentRect], REQUEST_SEASON_TYPE)
         if (REQUESTED_TYLE != 0) then
            // Terrain type of tile is requested to be changed
            call SetTerrainType( currentPosX[currentRect], currentPosY[currentRect], REQUESTED_TYLE, -1, 1, 0)
          endif
         // --------------------------
        // move along the x axis until rect end is reached
        set currentPosX[currentRect] = currentPosX[currentRect] + 128.
    endloop

          // move to next y loc
          set currentPosY[currentRect] = currentPosY[currentRect] + 128.
         call BJDebugMsg("SEASON currentY " + R2S(currentPosY[currentRect]))
      endloop
  endfunction

  private function SeasonChangeTerrain takes nothing returns nothing
      // loop through all rects, until every rect has been completed in seasonal change
       // use global variables to keep track of locations, if the op limit of actions was reached in between
       local integer opLimit = 0
       loop
          if currentRect > maxRects then
            call BJDebugMsg("SEASON all rects completed")
          else
             call BJDebugMsg("SEASON not completed yet")
         endif
         exitwhen (currentRect >= maxRects or opLimit > OpLimitMax)
         set opLimit = opLimit + 1
           call BJDebugMsg("SEASON index " + I2S(opLimit))
        if currentRectCompleted[currentRect] then
             // if rect was completed, advance to next rect
             set currentRectCompleted[currentRect] = FALSE
             set currentPosX[currentRect] = rectMinX[currentRect]
             set currentPosY[currentRect] = rectMinY[currentRect]
               set currentRect = currentRect + 1
             call BJDebugMsg("SEASON next rect")
           else
             // if rect not completed, excute terrain change for this rect
             call TriggerSleepAction( 1.)
              call TriggerExecute( SEASONAL_CHANGE_TRIGGER_EX)
              call BJDebugMsg("SEASON executing change")
             endif
       endloop
      // if loop has ended, double check, if rects were completed succesfully
       if currentRect < maxRects then
            call TriggerExecute( SEASONAL_CHANGE_TRIGGER)
       endif

      // reset seasonal rect count for nex execution
       set currentRect = 0
  endfunction

  // initializer function (called in Game_Initialization)
  public function REGISTER_RECTS takes nothing returns nothing
  local integer i = 0
  // register all snowy rects
  set SeasonalRects[0]  = gg_rct_LAND_1
  set SeasonalRects[1]  = gg_rct_LAND_2
  set SeasonalRects[2]  = gg_rct_LAND_3
  loop
  exitwhen i > 2
  set rectMinX[i]  = GetRectMinX( SeasonalRects[i])
  set rectMaxX[i]  = GetRectMaxX( SeasonalRects[i])
  set rectMinY[i]  = GetRectMinY( SeasonalRects[i])
  set rectMaxY[i]  = GetRectMaxY( SeasonalRects[i])
  set currentPosX[i]  = rectMinX[i]
  set currentPosY[i]  = rectMinY[i]
  set currentRectCompleted[i] = FALSE
  set i = i + 1
  endloop
  // register change triggers
  call DisableTrigger( SEASONAL_CHANGE_TRIGGER)
  call TriggerAddAction( SEASONAL_CHANGE_TRIGGER, function SeasonChangeTerrain)
  call DisableTrigger( SEASONAL_CHANGE_TRIGGER_EX)
  call TriggerAddAction( SEASONAL_CHANGE_TRIGGER_EX, function SeasonChangeTerrainEx)

  endfunction

endlibrary

ps: sry for the messy code, but the new forum messes up all code tags, I post. I did not figure out yet, how to post properly ...

pps: thank you for any help :)
 
Last edited:
Level 37
Joined
Mar 6, 2006
Messages
9,240
One thing I noticed is that you should use "true" and "false". Not "TRUE" and "FALSE".

You could use an expiring timer with 0.00 second period and a global variable. The global keeps track how much looping you have covered. You execute a different area of the map during each expiration of the timer.

This could be shorter with a hashtable
JASS:
private function ConvertTerrainTypeSeasonal takes real x, real y, string season returns integer
     // list all terrain types that are allowed to change seasonally. Link terrain types here
     if season == "winter" then
       // change all linked types to their spring form
       if (GetTerrainType( x, y) == TYPE_SUMMER_THICK_GRASS) then
           return TYPE_WINTER_SNOW
       elseif (GetTerrainType( x, y) == TYPE_SUMMER_DARK_GRASS) then
           return TYPE_WINTER_ICE
       endif
     elseif season == "spring" then
       // change all linked types to their spring form
       if (GetTerrainType( x, y) == TYPE_WINTER_SNOW) then
           return TYPE_SUMMER_THICK_GRASS
       elseif (GetTerrainType( x, y) == TYPE_WINTER_ICE) then
           return TYPE_SUMMER_DARK_GRASS
       endif
     endif
     return 0 // return 0 if tile is not of any type to be changed
endfunction

JASS:
private function ConvertTerrainTypeSeasonal takes real x, real y, integer season returns integer
     // list all terrain types that are allowed to change seasonally. Link terrain types here
       return LoadInteger(hashtable, season, GetHandleId(GetTerrainType(x, y)))
endfunction

You'd just need to initialize the hashtable values.
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
Hi Maker, thank you for your answer.:thumbs_up:

One thing I noticed is that you should use "true" and "false". Not "TRUE" and "FALSE".
What is wrong with the capital ones?

You could use an expiring timer with 0.00 second period and a global variable. The global keeps track how much looping you have covered. You execute a different area of the map during each expiration of the timer.
how is that different from what I am doing now? I actually had used a timer before I tried triggers, maybe I did something wrong in another way but it stopped executing big rects as well ... I am going to try it again. Maybe I should just use smaller rects?...

This could be shorter with a hashtable
JASS:
private function ConvertTerrainTypeSeasonal takes real x, real y, string season returns integer
     // list all terrain types that are allowed to change seasonally. Link terrain types here
     if season == "winter" then
       // change all linked types to their spring form
       if (GetTerrainType( x, y) == TYPE_SUMMER_THICK_GRASS) then
           return TYPE_WINTER_SNOW
       elseif (GetTerrainType( x, y) == TYPE_SUMMER_DARK_GRASS) then
           return TYPE_WINTER_ICE
       endif
     elseif season == "spring" then
       // change all linked types to their spring form
       if (GetTerrainType( x, y) == TYPE_WINTER_SNOW) then
           return TYPE_SUMMER_THICK_GRASS
       elseif (GetTerrainType( x, y) == TYPE_WINTER_ICE) then
           return TYPE_SUMMER_DARK_GRASS
       endif
     endif
     return 0 // return 0 if tile is not of any type to be changed
endfunction

JASS:
private function ConvertTerrainTypeSeasonal takes real x, real y, integer season returns integer
     // list all terrain types that are allowed to change seasonally. Link terrain types here
       return LoadInteger(hashtable, season, GetHandleId(GetTerrainType(x, y)))
endfunction

You'd just need to initialize the hashtable values.
wow, thanks a lot, what a neat optimization! :grin: The lack of inlined GetTerrainType(x,y) was not nice anyway.
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
Hey Everybody, thanks a lot for all the suggestions, they all helped me to solve my problem. The Op limit is now stably avoided, and everything works fine.

So basicly, from what I see, my attempt was kind of correct, but I should have used a repeating trigger or timer, and not a loop inside the first trigger instead. I did not know that using a loop like this may be unstable, but this was the cause of the problem in the end. If someone searches for this, just use a repetitive timer or trigger or this dummy force thing by Troll Brain..

@Troll-Brain: Pretty cool thing, I would never have tried to use a dummy force :)

@ZerGreenOne: thanks for clarifying.

This thread is solved.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Using a timer(0) will delay the execution of the code, since a Timer(0) is not instant, it could prevent a lag because of massive "new threads opened" and/or heavy code inside them.
But sure you have to consider that the end of the loop will be delayed.
 
Status
Not open for further replies.
Top