User Tools

Site Tools


cw4:4rpl_tools

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
cw4:4rpl_tools [2024/09/10 21:41] – Updated manipulateUnitPosition and added SetTerrainHigherThan20 Vertucw4:4rpl_tools [2025/06/30 20:12] (current) – [Pseudo Random Number Generator, based on sinus] kalli
Line 3544: Line 3544:
  endonce  endonce
 endif endif
 +</code>
 +
 +</hidden>
 +
 +----
 +
 +
 +===== Request Viable Build- or Move-Cell =====
 +
 +<hidden click here for source code>
 +
 +This script allows for other scripts to SEND A MESSAGE TO REQUEST A VIABLE BUILD- OR MOVE-CELL. 
 +
 +The script is a compromise between high performance and high fidelity. The reason that it's setup to run as a separate script and not as a function, is so that it can take move/build requests from multiple sources into account: by default it won't recheck cells that were recently rejected + new searches will continue where recent previous queries stopped after finding a solution. A very high performance version exists for 3x3 units in my SQUADS map.
 +
 +
 +How to use in a cpack:
 +Run the script as a global script in Pre. Do not run while paused.
 +
 +
 +Use example for building miners:
 +
 +
 +<code 4rpl example.4rpl>500 0 do
 + "requestWxWviableCell" 0 5 3 128 128 v2 256 0 0 0 0 1 0 1 1 0 14 listN sendmsg # Length > width = x direction.
 + <-*WxWviableCellFound if 
 + "miner" <-*WxWviableCell ev2 0 createunitonterrain dup 1 SetUnitOccupiesLand 999 constructunit 
 + # Unlike existing units that move, newly build units do not instantly occupy the cells that they were created on, so we force a refresh of that with SetUnitOccupiesLand.
 + endif
 +loop
 +
 +500 0 do
 + "requestWxWviableCell" 0 3 5 128 128 v2 256 0 0 0 0 1 0 1 1 0 14 listN sendmsg # Length > width = z direction. With Z-direction we also need to change the orientation below.
 + <-*WxWviableCellFound if 
 + "miner" <-*WxWviableCell ev2 0 createunitonterrain dup dup 1 setunitorientation 1 SetUnitOccupiesLand 999 constructunit
 + endif
 +loop</code>
 +
 +INPUT variables explained:
 +
 +<-moveUID: can be left 0, but it's needed if you want to allow a unit to check if it's current location is viable.
 +
 +<-unitLength: footprint length in X direction.
 +
 +<-unitWidth: footprint length in Z direction.
 +
 +<-cellV2: V2 vector. The cell where your requesting script wants to build/move the unit.
 +
 +<-searchRange: the cell distance around the requested cell location, where the function will look for a viable build/move cell.
 +
 +<-maxCreeperDepth: this will check the average creeper depth in a circle with range 4.
 +
 +<-noMesh: set to 1 to not allow solutions with ACTIVE mesh.
 +
 +<-likePlatform: the unit can be placed in the void.
 +
 +<-likeBeacon: the unit can be placed in the void and on uneven terrain.
 +
 +<-likeMiner: the unit can only be placed on resource special terrain that covers atleast 75% of the unit footprint.
 +
 +<-likeNullifier: the unit has to be placed within nullification range of an enemy creeper. The creeper enemey has to be known to a list inside the function script.
 +
 +<-ignoreCooldown: set to 1 to ignore time based computation optimizations. Best left at 0 when the function is called by an AI script (more optimized) and set to 1 when it's initiated by the player (feels more responsive).
 +
 +<-noSkip: set to 1 for max placement coverage, but slower computations. If left at 0, then the script will not check cells that are likely to not have a proper solution, but it will also mistakenly not check some cells that do have a proper solution. For spamming 3x3 units, "1" tends to be 40% slower, while only placing about 10% more units.
 +
 +<-resetSearch: if set to 1, then all previous search history will be forgotten. 
 +
 +
 +
 +<code 4rpl LxW-FindViableCell-NRP-Pre.4rpl>
 +# --LxW-FindViableCell-NRP-Pre--
 +
 +# INPUTS: "requestWxWviableCell" <-moveUID <-unitLength <-unitWidth <-cellV2 <-searchRange <-maxCreeperDepth <-noMesh <-likePlatform <-likeBeacon <-likeMiner <-likeNullifier <-ignoreCooldown <-noSkip <-resetSearch 14 listN 
 +# OUTPUT: global varialbles named <-*WxWviableCellFound <-*WxWviableCell
 +
 +# Contains snapping tool movecell requests, so that multiple units can request a viable cell from a single source, without double checking cells.
 +# BASED ON SNAPPING TOOL CODE, but without the Indicator unit or the Mouse commands, and only for SQUARE units.
 +
 +$radiusLandingSpot:4 # INTEGER. The creeper threshhold will be checked as an average over more than the unit landing location.
 +$cellsWithinLandingRange:69 # INTEGER, see previous radius. The creeper threshhold will be checked as an average over more than the unit landing location.
 +
 +$$defaultSearchRange:20 # INTEGER. The floodfill distance. If no searchRange is send in the message, this is the range that will be used.
 +
 +$groupDist:7 # Integer or float distance. Requests that are closer together than this distance, will be grouped together, so that later searches can continue on from previous searches.
 +$blockCellTimer:90 # INTEGER. Amount of frames under which queries will be grouped together & searches around cells that were found to be blocked, will not be done again.
 +$clearCellTimer:270 # INTEGER. Amount of frames after which the blocked cells will be forgotten. In between these 2 values, searches will only be done again if there are less units around than when the cell was last found to be blocked.
 +
 +$$contSlot:8 # VALUE EDITOR SETTING. Units cannot be placed on this terrain slot. 8 is the default contaminant slot.
 +$$resoSlot:1 # VALUE EDITOR SETTING. Miners can only be placed on resource terrain. 1 is the default resource slot.
 +$$meshSlot:6 # VALUE EDITOR SETTING. Units that can be placed in the void, cannot be placed on mesh inside the void. 6 is the default mesh slot.
 +
 +$$nullifyUnits:" emitter; airsaccauldron; blobnest; darktower; skimmerfactory; sporelauncher" # Enemy units that would be suppressed by building a nullifier near them.
 +
 +:requestWxWviableCellFunction
 + <-_DATA[0] 0 setunitoccupiesland # With this line, an existing unit's current cell will be a possible result from the search for a viable cell.
 + <-_DATA ListToStack @searchWxWviableCell ->*WxWviableCellFound ->*WxWviableCell
 + <-_DATA[0] 1 setunitoccupiesland
 +
 +:once
 +# listening channels:
 + "requestWxWviableCell" "requestWxWviableCellFunction" registerformsg
 + 1 ->resetSearch
 + 1 ->ignoreCooldown
 +
 + # When the loop was ran through fully without finding a resolution, remember that cell for 90 frames before trying the same cell again.
 + list ->blockedCellV2List 
 + list ->blockedCellTimeList
 + list ->nearbyUnitsCountList
 +
 + <-nullifyUnits removewhitespace ";" split ->suppressList
 +
 +:searchWxWviableCell # Output: viableCell as v2 + true/false
 +# See vanilla snapping tool cpack for full commments.
 +->resetSearch
 +->noSkip
 +->ignoreCooldown
 +->checkN
 +->checkR
 +->placeA
 +->placeV
 +->noMesh
 +->creDepth
 +->searchRange
 +->cell
 +->width
 +->length
 +pop # The UID has no further use.
 +
 + # The following switch checks if the search should start from scratch (=1), or if it should continue (=0) on from a previous search.
 + switch
 + <-resetSearch case 1 endcase
 + <-length <-prevLength neq case 1 endcase
 + <-width <-prevWidth neq case 1 endcase
 + <-checkN <-prevCheckN neq case 1 endcase
 + <-checkR <-prevCheckR neq case 1 endcase
 + <-placeA <-prevPlaceA neq case 1 endcase
 + <-placeV <-prevPlaceV neq case 1 endcase
 + <-cell ev2 <-prevSearchCell ev2 distancecell <-groupDist gt if 1 else 
 + <-prevSearchCell ->cell # Group the search query together with previous closeby queries.
 + <-ignoreCooldown if getgametickcount else getgameupdatecount endif dup <-prevSearchUpdateCount gt if
 + ->prevSearchUpdateCount
 + 1
 + else
 + pop 
 + 0
 + endif
 + endif
 + endswitch if # If the search starts from scratch, then the new settings have to be remembered for the next function call.
 + # Adjust the footprint array depending on the requested width in the function call.
 + <-width dup <-prevWidth neq if
 + dup ->prevWidth
 + 1 add 2.0 div asint dup ->rHz 1 sub ->rLz # For the footprint loop.
 + else pop endif
 + <-length dup <-prevLength neq if
 + dup ->prevLength
 + 1 add 2.0 div asint dup ->rHx 1 sub ->rLx # For the footprint loop.
 + else pop endif
 +
 + <-checkN ->prevCheckN
 + <-checkR ->prevCheckR
 + <-placeA ->prevPlaceA
 + <-placeV ->prevPlaceV
 + <-cell ->prevSearchCell
 +
 + # Recreate the area to search:
 + <-searchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange
 + <-cell ev2 0 21 <-floodRange floodfillterrain dup getlistcount ->potentialCellCount ->potentialCells
 + 0 ->startLoop
 + 0 ->skip
 +
 + # If the search is being reset, then clear the blocked lists.
 + <-resetSearch If
 + 1 ->ignoreCooldown
 + <-blockedCellV2List clearlist
 + <-blockedCellTimeList clearlist
 + <-nearbyUnitsCountList clearlist
 + endif
 + else
 + # If the search continues on from before, but the search range was changed, then the potential cells will need to be adjusted, without adjusting the start of the loop.
 + <-searchRange dup <-prevSearchRange neq if 
 + dup ->prevSearchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange
 + <-cell ev2 0 21 <-floodRange floodfillterrain dup getlistcount ->potentialCellCount ->potentialCells
 + else pop endif
 + endif
 +
 + # If the new searchRange is larger than the old one, then do not check if the cell was recently found to be blocked.
 + <-searchRange <-prevSearchRange gt <-ignoreCooldown OR not if
 + # Check if the cell is not on the list of blocked cells.
 + <-blockedCellV2List <-cell getlistindex dup -1 eq if pop else
 + ->index
 +
 + getgameupdatecount ->updateCount
 + # Try to purge a few old blocked cells from the lists, otherwise the list bloats.
 + -1 <-index 1 sub do
 + <-blockedCellTimeList[i] <-clearCellTimer add <-updateCount lt if
 + <-blockedCellV2List i removelistelement
 + <-blockedCellTimeList i removelistelement
 + <-nearbyUnitsCountList <-index removelistelement
 + <-index 1 sub ->index
 + endif
 + loop
 +
 + switch
 + # The last check on this spot was less than 3 seconds ago, so we're not checking it again now.
 + <-blockedCellTimeList[<-index] <-blockCellTimer add <-updateCount gt case
 + -1 -1 v2 false return
 + endcase
 + # The last check on this spot was longer than 9 seconds ago, so we can check it again now.
 + <-blockedCellTimeList[<-index] <-clearCellTimer add <-updateCount lt case
 + <-blockedCellV2List <-index removelistelement
 + <-blockedCellTimeList <-index removelistelement
 + <-nearbyUnitsCountList <-index removelistelement
 + endcase
 + # Check the cell again if there are now less units nearby than when it was blocked.
 + <-cell ev2 fromcell 13 0 0 0 0 0 0 getunitsinrange getlistcount <-nearbyUnitsCountList[<-index] lt case 
 + <-blockedCellV2List <-index removelistelement
 + <-blockedCellTimeList <-index removelistelement
 + <-nearbyUnitsCountList <-index removelistelement
 + endcase
 + # Too recent and still the same amount of units nearby, so don't check again:
 + -1 -1 v2 false return
 + endswitch
 + endif
 + endif
 +
 + <-potentialCellCount <-startLoop do
 + <-skip if
 + <-skip 1 sub ->skip
 + else
 + <-potentialCells[i] ev2 @checkCenterCell if
 + @checkFootPrint if
 + i <-noSkip if 1 else <-width endif add ->startLoop # Optimization for mass placement. When placing the next unit, skip the next few potential cells.
 + <-potentialCells[i] true return # LEFT ON STACK.
 + endif
 + else
 + <-noSkip if 0 else <-width 3 div asint 1 add endif ->skip # If the center cell was blocked, then also skip the next X potential cells.
 + endif
 + endif
 + loop
 + <-potentialCellCount ->startLoop
 + # If the loop was not interrupted by return, then all cells were blocked in some way.
 + <-blockedCellV2List <-cell appendtolist
 + <-blockedCellTimeList getgameupdatecount appendtolist
 + <-nearbyUnitsCountList <-cell ev2 fromcell 13 0 0 0 0 0 0 getunitsinrange getlistcount appendtolist
 + -1 -1 v2 false # LEFT ON STACK.
 +
 +:checkCenterCell # Inputs: x and z of cell.
 + dup2 <-radiusLandingSpot 0 1 0 getcreeperinrange <-cellsWithinLandingRange div <-creDepth gt if pop pop false return endif
 + dup2 getterrain dup ->height if
 + <-placeV if pop pop false return endif
 + 0 ->checkO
 + else 
 + <-placeV <-placeA OR if 0 else 1 endif ->checkO
 + endif
 + <-checkR if 0 ->minePot endif
 + # dup2 is still on the stack.
 + switch
 + dup2 getcelloccupiedcount <-checkO neq case pop pop false return endcase
 + # dup2 getterrain <-height neq case pop pop false return endcase # Useless on center cell.
 + # dup2 getcreeper <-creDepth gt case pop pop false return endcase
 + dup2 getmeshhealth gt0 <-noMesh AND case pop pop false return endcase
 + dup2 getterraformmarker gt0 case pop pop false return endcase
 + dup2 getterrainspecial dup ->special <-contSlot eq case pop pop false return endcase
 + <-special <-meshSlot eq <-height 0 eq AND case pop pop false return endcase # Units cannot be placed on mesh in the void.
 + ->cZ ->cX # Used in checkFootPrint.
 + <-checkR if <-special <-resoSlot eq if 
 + <-minePot 1 add ->minePot 
 + else
 + pop pop false return
 + endif endif
 + <-checkO if 
 + "platform" <-cX 0 <-cZ v3 2.9 0 0 0 2 1 0 getunits 0 getlistelement 
 + getunitcell <-cX <-cZ distancecell 2.828427 approximately not if pop pop false return endif
 + endif
 + <-checkN if 
 + <-suppressList <-cX 0 <-cZ v3 9 0 0 0 1 0 1 getunitsinrange 0 getlistelement getunittype listcontains not if pop pop false return endif 
 + endif
 + endswitch
 + true
 +
 +:checkFootPrint
 +# See vanilla snapping tool cpack for full commments.
 +
 + <-cZ <-rHz add <-cZ <-rLz sub do
 + <-cX <-rHx add <-cX <-rLx sub do
 + switch
 + i j
 + # dup2 <-cZ eq swap <-cX eq AND case pop pop endcase # The center cell was already checked and OK.
 + dup2 getcelloccupiedcount <-checkO neq case pop pop false return endcase
 + dup2 getterrain <-height neq case pop pop false return endcase
 + # dup2 getcreeper <-creDepth gt case pop pop false return endcase
 + dup2 getmeshhealth gt0 <-noMesh AND case pop pop false return endcase
 + dup2 getterraformmarker gt0 case pop pop false return endcase
 + dup2 getterrainspecial dup ->special <-contSlot eq case pop pop false return endcase
 + # dup2 getterrainspecial <-meshSlot eq <-height 0 eq AND case pop pop false return endcase # Prevents platforms from being placed on mesh in the void. Not relevant for this script.
 + <-special <-meshSlot eq <-height 0 eq AND case pop pop false return endcase # Units cannot be placed on mesh in the void.
 + <-placeA case pop pop endcase
 + pop pop
 + <-checkR if <-special <-resoSlot eq if <-minePot 1 add ->minePot endif endif
 + endswitch
 + loop
 + loop
 + <-checkR if <-minePot <-length <-width mul 0.75 mul lt if false return endif endif
 + true
 +
 +</code>
 +
 +</hidden>
 +
 +----
 +
 +
 +===== Pseudo Random Number Generator, based on sinus =====
 +
 +<hidden click here for source code>
 +
 +Several basic functions based on Grabz' rng lite function of "<-seed sinus 10000 mul dup floor sub".
 +
 +
 +Copy the functions directly into your script or run the below script in your cpack and have other scripts send messages to request a randfloat or randint.
 +
 +
 +Difference between the functions:
 +  * "seeded": provide a seed to the generator. The generated number will always be the same for the same seed.
 +  * "index": these seeds are sequenced. In the same map for the same index, the generated number 1,2,3,... will be the same after map restart.
 +  * "spiked": the generator is seeded with the elapsedtime of the gaming session, making the outcome uncontrollable.
 +  * No prefix: these are sequenced seeds and they use index 0. 
 +
 +Example code with messages:
 +<code example.4rpl>
 +12345 ->int
 +1 ->index
 +0 ->first
 +100 ->last # The last integer itself will be excluded.
 +"sinRandInt" <-first <-last 2 listN sendmsg
 +<-*sinRandInt trace
 +"sinRand01" 0 sendmsg
 +<-*sinRand01 trace
 +"indexSinRandInt" <-index <-first <-last 3 listN sendmsg
 +<-*sinRandInt trace
 +"indexSinRand01" <-index sendmsg
 +<-*sinRand01 trace
 +"spikedSinRandInt" <-first <-last 2 listN sendmsg
 +<-*sinRandInt trace
 +"spikedSinRand01" 0 sendmsg
 +<-*sinRand01 trace
 +"seededSinRandInt" <-seed <-first <-last 3 listN sendmsg
 +<-*sinRandInt trace
 +"seededSinRand01" <-seed sendmsg
 +<-*sinRand01 trace
 +</code>
 +
 +----
 +
 +<code SinusRNG.4rpl>
 +:once
 + # Creating lists and a starting mapconstant for the seed sequences.
 + createlist ->prevSeedList
 + createlist ->seedCountList
 + getmapsize 2 div swap 2 div swap dup2 getterrain 1 add dup 99999 floodfillterrain getlistcount ->mapConstant
 +
 + # Setting up the messages so that other scripts can request a random seed.
 + "sinRandInt" "sinRandInt_CALL" registerformsg
 + "sinRand01" "sinRand01_CALL" registerformsg
 + "indexSinRandInt" "indexSinRandInt_CALL" registerformsg
 + "indexSinRand01" "indexSinRand01_CALL" registerformsg
 + "spikedSinRandInt" "spikedSinRandInt_CALL" registerformsg
 + "spikedSinRand01" "spikedSinRand01_CALL" registerformsg
 + "seededSinRandInt" "seededSinRandInt_CALL" registerformsg
 + "seededSinRand01" "seededSinRand01_CALL" registerformsg
 +
 +:sinRandInt_CALL
 + <-_DATA listtostack @sinRandInt ->*sinRandInt
 +:sinRand01_CALL
 + @sinRand01 ->*sinRand01
 +:indexSinRandInt_CALL
 + <-_DATA listtostack @indexSinRandInt ->*sinRandInt
 +:indexSinRand01_CALL
 + <-_DATA @indexSinRand01 ->*sinRand01
 +:spikedSinRandInt_CALL
 + <-_DATA listtostack @spikedSinRandInt ->*sinRandInt
 +:spikedSinRand01_CALL
 + @spikedSinRand01 ->*sinRand01
 +:seededSinRandInt_CALL
 + <-_DATA listtostack @seededSinRandInt ->*sinRandInt
 +:seededSinRand01_CALL
 + <-_DATA @seededSinRand01 ->*sinRand01
 +
 +:sinRandInt # INPUT: integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt.
 +->last
 +->first
 + 0 @indexSinRand01 <-last <-first sub mul <-first add asint
 +
 +:sinRand01 # INPUT: none.
 + 0 @indexSinRand01
 +
 +:indexSinRandInt # INPUT: the index of the seed sequence + integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt.
 +->last
 +->first
 + @indexSinRand01 <-last <-first sub mul <-first add asint
 +
 +:indexSinRand01 # INPUT: the "index" of the seed sequence. OUTPUT: the next seed from that sequence.
 +# The random numbers will be generated with the previous seed that is stored under that index.
 +->i
 + <-prevSeedList[<-i] eq0 if
 + <-i <-mapConstant add 2357 mul @seededSinRand01 dup 10000000 mul 1 add asint ->prevSeedList[<-i]
 + 1 ->seedCountList[<-i]
 + else
 + <-prevSeedList[<-i] @seededSinRand01 dup 10000000 mul <-seedCountList[<-i] 1 add dup ->seedCountList[<-i] add asint ->prevSeedList[<-i]
 + endif
 +
 +:spikedSinRandInt # INPUT: integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt.
 +->last
 +->first
 + elapsedtime asint @seededSinRand01 <-last <-first sub mul <-first add asint
 +
 +:spikedSinRand01
 + elapsedtime asint @seededSinRand01
 +
 +:seededSinRandInt # INPUT: integer seed + integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt.
 +->last
 +->first
 + @seededSinRand01 <-last <-first sub mul <-first add asint
 +
 +:seededSinRand01
 + # abs <-power pow <-add add sin <-sinMul mul dup floor sub
 + 1.05 pow 99419 sub sin 619 mul dup floor sub
 </code> </code>
  
cw4/4rpl_tools.1726004486.txt.gz · Last modified: 2025/02/14 14:56 (external edit)