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
Next revisionBoth sides next revision
cw4:4rpl_tools [2023/07/01 11:01] – [VSCode] Update link Up-Levelcw4:4rpl_tools [2024/03/16 19:53] – [Automated Tower Grid Building] Added a statement for clarity of how to use it. LiteralNoob
Line 10: Line 10:
 //Note: to get a default editor associated with the .4rpl filetype, please read about [[cw4:tutorials:file association]].// //Note: to get a default editor associated with the .4rpl filetype, please read about [[cw4:tutorials:file association]].//
  
-===== NotePad++ =====+===== Notepad++ =====  
 +<note>Make sure you download only from the official Notepad++ website to avoid malware. https://notepad-plus-plus.org/</note>
  
 ==== Syntax Highlighting ==== ==== Syntax Highlighting ====
Line 581: Line 582:
  
 $rmb:1 # right click button $rmb:1 # right click button
-$index:0 
  
-Once 
- @MakeSoundList 
-endOnce 
  
-if (GetMappedKeyDown("Custom1" false)) +if(<-inputDelay gt0) <-inputDelay 1 - ->inputDelay endif 
- Mod(<-Index 1 +, <-soundCount) ->index+ 
 +if (GetMappedKey("Custom1" false) <-inputDelay eq0 &&) 
 + 2 ->inputDelay 
 + Mod(<-index 1 +, <-soundCount) ->index
  <-soundList[<-index] ->soundName  <-soundList[<-index] ->soundName
  TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ)  TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ)
 endIf endIf
  
-if (GetMappedKeyDown("Custom2" false)) +if(GetMappedKey("Custom2" false) <-inputDelay eq0 &&) 
- Mod2(<-Index 1 -, <-soundCount) ->index+ 2 ->inputDelay 
 + Mod2(<-index 1 -, <-soundCount) ->index
  <-soundList[<-index] ->soundName  <-soundList[<-index] ->soundName
  TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ)  TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ)
Line 600: Line 601:
  
 if (GetMouseButtonDown(<-rmb true )) if (GetMouseButtonDown(<-rmb true ))
- PlaySound(<-soundName 8) + PlaySoundAtPosition(<-soundName 8 GetCameraPosition V3(0 0.1 0) +)
 endif endif
- + 
 +:Once 
 + -1 ->index #So we start at 0, not 1. 
 + @MakeSoundList 
 :MakeSoundList :MakeSoundList
- +
  "ADAMessage"    "ADAMessage"  
  "ADAMessagesClose"    "ADAMessagesClose"  
Line 632: Line 637:
  "Explosion"    "Explosion"  
  "Explosion_1"    "Explosion_1"  
- "Explosion_10"   
- "Explosion_11"   
- "Explosion_12"   
  "Explosion_2"    "Explosion_2"  
  "Explosion_3"    "Explosion_3"  
Line 642: Line 644:
  "Explosion_8"    "Explosion_8"  
  "Explosion_9"    "Explosion_9"  
 + "Explosion_10"  
 + "Explosion_11"  
 + "Explosion_12"  
  "HoverOpen"    "HoverOpen"  
  "InfoCacheCollected"    "InfoCacheCollected"  
Line 714: Line 719:
  "Warning2"    "Warning2"  
  List ->soundList  List ->soundList
- +
  "AlarmClock"   "AlarmClock" 
  "GreenarRefinery"    "GreenarRefinery"  
Line 723: Line 728:
  "SoundLoop_alarm0"    "SoundLoop_alarm0"  
  "SoundLoop_alarm1"    "SoundLoop_alarm1"  
- "SoundLoop_alarm10"   
- "SoundLoop_alarm11"   
- "SoundLoop_alarm12"   
- "SoundLoop_alarm13"   
- "SoundLoop_alarm14"   
- "SoundLoop_alarm15"   
  "SoundLoop_alarm2"    "SoundLoop_alarm2"  
  "SoundLoop_alarm3"    "SoundLoop_alarm3"  
Line 737: Line 736:
  "SoundLoop_alarm8"    "SoundLoop_alarm8"  
  "SoundLoop_alarm9"    "SoundLoop_alarm9"  
 + "SoundLoop_alarm10"  
 + "SoundLoop_alarm11"  
 + "SoundLoop_alarm12"  
 + "SoundLoop_alarm13"  
 + "SoundLoop_alarm14"  
 + "SoundLoop_alarm15"  
  "SoundLoop_ambience0"    "SoundLoop_ambience0"  
- "SoundLoop_ambience1"   + "SoundLoop_ambience1"   
- "SoundLoop_ambience10"  +
  "SoundLoop_ambience2"    "SoundLoop_ambience2"  
  "SoundLoop_ambience3"    "SoundLoop_ambience3"  
Line 748: Line 752:
  "SoundLoop_ambience8"    "SoundLoop_ambience8"  
  "SoundLoop_ambience9"    "SoundLoop_ambience9"  
 + "SoundLoop_ambience10"  
  "SoundLoop_gun0"    "SoundLoop_gun0"  
  "SoundLoop_gun1"    "SoundLoop_gun1"  
Line 753: Line 758:
  "SoundLoop_gun3"    "SoundLoop_gun3"  
  "SoundLoop_hum0"    "SoundLoop_hum0"  
- "SoundLoop_hum1"  + "SoundLoop_hum1"  
 + "SoundLoop_hum2"   
 + "SoundLoop_hum3"   
 + "SoundLoop_hum4"   
 + "SoundLoop_hum5"   
 + "SoundLoop_hum6"   
 + "SoundLoop_hum7"   
 + "SoundLoop_hum8"   
 + "SoundLoop_hum9"   
  "SoundLoop_hum10"    "SoundLoop_hum10"  
  "SoundLoop_hum11"    "SoundLoop_hum11"  
Line 763: Line 776:
  "SoundLoop_hum17"   "SoundLoop_hum17" 
  "SoundLoop_hum18"    "SoundLoop_hum18"  
- "SoundLoop_hum2"   
- "SoundLoop_hum3"   
- "SoundLoop_hum4"   
- "SoundLoop_hum5"   
- "SoundLoop_hum6"   
- "SoundLoop_hum7"   
- "SoundLoop_hum8"   
- "SoundLoop_hum9"   
  "SoundLoop_machine0"    "SoundLoop_machine0"  
  "SoundLoop_machine1"    "SoundLoop_machine1"  
- "SoundLoop_machine10"   
- "SoundLoop_machine11"   
- "SoundLoop_machine12"   
  "SoundLoop_machine2"    "SoundLoop_machine2"  
  "SoundLoop_machine3"    "SoundLoop_machine3"  
Line 784: Line 786:
  "SoundLoop_machine8"   "SoundLoop_machine8" 
  "SoundLoop_machine9"    "SoundLoop_machine9"  
 + "SoundLoop_machine10"  
 + "SoundLoop_machine11"  
 + "SoundLoop_machine12"  
  "SoundLoop_misc0"    "SoundLoop_misc0"  
  "SoundLoop_misc1"    "SoundLoop_misc1"  
Line 796: Line 801:
  "ambient"   "ambient" 
  List ->loopSoundList  List ->loopSoundList
- +
  GetListCount(<-soundList) ->soundCount  GetListCount(<-soundList) ->soundCount
- TraceAllSp ("List of " <-soundCount "sounds available"+ TraceAllSp("List of " <-soundCount "sounds available"
- TraceAllSp ("There are also " GetListCOunt(<-loopSoundList) "looping sounds that cannot be played in this module"+ TraceAllSp("There are also " GetListCOunt(<-loopSoundList) "looping sounds that cannot be played in this module"
- TraceAllSP ("Press and hold user key 1/2 to scroll through the sound list." ) + TraceAllSP("Press and hold KeyPad 1 / 2 to scroll through the sound list."
- TraceAllSp ("Left-click mouse to listen to it.")+ TraceAllSp("Left-click mouse to listen to it.")
 </code> </code>
  
Line 811: Line 816:
  
 A few notes. A few notes.
- +  * The script will attempt to unpause the game, since looping sounds only play when not paused due to being attached to a unit.
-  * The Greenar Refinery sound is spatial and you have to be in freeview and right upon the unit in location (1,1) to hear it. +
-  * The script will attempt to unpause the game, since looping sounds only play when not paused.+
   * Remember to unmute game music.   * Remember to unmute game music.
  
Line 823: Line 826:
  
 $rmb:1 # right click button $rmb:1 # right click button
-$index:0 
  
-Once +if(<-inputDelay gt0<-inputDelay 1 - ->inputDelay endif 
- @MakeSoundList + 
- CreateUnitOnTerrain("infocache" 1 1 1) ->unitUID +SetUnitPosition(<-unitUID GetCameraPosition V3(0 1.5 0) +)
- false ->soundOn +
- Getpause ->pause +
-endOnce+
  
-if (GetMappedKeyDown("Custom1" false)) +if(GetMappedKey("Custom1" false) <-inputDelay eq0 &&) 
- Mod(<-Index 1 +, <-soundCount) ->index+ 2 ->inputDelay 
 + Mod(<-index 1 +, <-soundCount) ->index
  <-loopSoundList[<-index] ->soundName  <-loopSoundList[<-index] ->soundName
- TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ)+ TraceAll("This is sound #" <-index ", " DQ <-soundName DQ)
 endIf endIf
  
-if (GetMappedKeyDown("Custom2" false)) +if(GetMappedKey("Custom2" false) <-inputDelay eq0 &&) 
- Mod2(<-Index 1 -, <-soundCount) ->index+ 2 ->inputDelay 
 + Mod2(<-index 1 -, <-soundCount) ->index
  <-loopSoundList[<-index] ->soundName  <-loopSoundList[<-index] ->soundName
- TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ)+ TraceAll("This is sound #" <-index ", " DQ <-soundName DQ)
 endIf endIf
  
-if (GetMouseButtonDown(<-rmb true )) +if(GetMouseButtonDown(<-rmb true)) 
- If (<-soundOn)+ If(<-soundOn)
  StopSoundLoop(<-unitUID)  StopSoundLoop(<-unitUID)
  Not(<-soundOn) ->soundOn  Not(<-soundOn) ->soundOn
  SetPause(<-pause)  SetPause(<-pause)
- TraceAllSp ("Stopping " <-soundName " from playing")+ TraceAllSp("Stopping " <-soundName " from playing")
  else  else
- TraceAllSp ("Can you hear " <-soundName " playing?")+ TraceAllSp("Can you hear " <-soundName " playing?")
  SetPause (false)  SetPause (false)
- PlaySoundLoop(<-soundName <-unitUID) + PlaySoundLoop(<-soundName <-unitUID)
  Not(<-soundOn) ->soundOn  Not(<-soundOn) ->soundOn
  endif  endif
-  
 endif endif
  
Line 862: Line 862:
  TraceAllSp("Goodbye!")  TraceAllSp("Goodbye!")
  DestroyUnit(<-unitUID true true true)  DestroyUnit(<-unitUID true true true)
 + if(<-soundOn) SetPause(true) endif
 +
 +:Once
 + -1 ->index #So we start at 0, not 1.
 + @MakeSoundList
 + CreateUnitOnTerrain("infocache" 1 1 1) ->unitUID
 + false ->soundOn
 + Getpause ->pause
  
-  
 :MakeSoundList :MakeSoundList
- +
  "ADAMessage"    "ADAMessage"  
  "ADAMessagesClose"    "ADAMessagesClose"  
Line 893: Line 900:
  "Explosion"    "Explosion"  
  "Explosion_1"    "Explosion_1"  
- "Explosion_10"   
- "Explosion_11"   
- "Explosion_12"   
  "Explosion_2"    "Explosion_2"  
  "Explosion_3"    "Explosion_3"  
Line 903: Line 907:
  "Explosion_8"    "Explosion_8"  
  "Explosion_9"    "Explosion_9"  
 + "Explosion_10"  
 + "Explosion_11"  
 + "Explosion_12"  
  "HoverOpen"    "HoverOpen"  
  "InfoCacheCollected"    "InfoCacheCollected"  
Line 913: Line 920:
  "MissionObjectiveFail"    "MissionObjectiveFail"  
  "MissionObjectiveRequiredComplete"    "MissionObjectiveRequiredComplete"  
- "MissionScan"     + "MissionScan"    "InhibitorFiring"   
- "InhibitorFiring"   +
  "MissionSpaceInitiateJump"    "MissionSpaceInitiateJump"  
  "MissionSpacePanelClose"    "MissionSpacePanelClose"  
Line 962: Line 968:
  "SporePrelaunch"    "SporePrelaunch"  
  "SprayerFire"    "SprayerFire"  
- "Stun"     + "Stun"    "CannonFire"  
- "CannonFire"  +
  "SurviveBaseOffline"    "SurviveBaseOffline"  
  "SurviveBaseWarn"    "SurviveBaseWarn"  
  "ThorGun"    "ThorGun"  
- "TotemExplosion"    + "TotemExplosion"   "UnitHoverLand"  
- "UnitHoverLand"  +
  "UnitBuild"    "UnitBuild"  
  "UnitExplosion"    "UnitExplosion"  
Line 987: Line 991:
  "SoundLoop_alarm0"    "SoundLoop_alarm0"  
  "SoundLoop_alarm1"    "SoundLoop_alarm1"  
- "SoundLoop_alarm10"   
- "SoundLoop_alarm11"   
- "SoundLoop_alarm12"   
- "SoundLoop_alarm13"   
- "SoundLoop_alarm14"   
- "SoundLoop_alarm15"   
  "SoundLoop_alarm2"    "SoundLoop_alarm2"  
  "SoundLoop_alarm3"    "SoundLoop_alarm3"  
Line 1001: Line 999:
  "SoundLoop_alarm8"    "SoundLoop_alarm8"  
  "SoundLoop_alarm9"    "SoundLoop_alarm9"  
 + "SoundLoop_alarm10"  
 + "SoundLoop_alarm11"  
 + "SoundLoop_alarm12"  
 + "SoundLoop_alarm13"  
 + "SoundLoop_alarm14"  
 + "SoundLoop_alarm15"  
  "SoundLoop_ambience0"    "SoundLoop_ambience0"  
- "SoundLoop_ambience1"   + "SoundLoop_ambience1"   
- "SoundLoop_ambience10"  +
  "SoundLoop_ambience2"    "SoundLoop_ambience2"  
  "SoundLoop_ambience3"    "SoundLoop_ambience3"  
Line 1012: Line 1015:
  "SoundLoop_ambience8"    "SoundLoop_ambience8"  
  "SoundLoop_ambience9"    "SoundLoop_ambience9"  
 + "SoundLoop_ambience10"  
  "SoundLoop_gun0"    "SoundLoop_gun0"  
  "SoundLoop_gun1"    "SoundLoop_gun1"  
Line 1017: Line 1021:
  "SoundLoop_gun3"    "SoundLoop_gun3"  
  "SoundLoop_hum0"    "SoundLoop_hum0"  
- "SoundLoop_hum1"  + "SoundLoop_hum1"  
 + "SoundLoop_hum2"   
 + "SoundLoop_hum3"   
 + "SoundLoop_hum4"   
 + "SoundLoop_hum5"   
 + "SoundLoop_hum6"   
 + "SoundLoop_hum7"   
 + "SoundLoop_hum8"   
 + "SoundLoop_hum9"   
  "SoundLoop_hum10"    "SoundLoop_hum10"  
  "SoundLoop_hum11"    "SoundLoop_hum11"  
Line 1027: Line 1039:
  "SoundLoop_hum17"   "SoundLoop_hum17" 
  "SoundLoop_hum18"    "SoundLoop_hum18"  
- "SoundLoop_hum2"   
- "SoundLoop_hum3"   
- "SoundLoop_hum4"   
- "SoundLoop_hum5"   
- "SoundLoop_hum6"   
- "SoundLoop_hum7"   
- "SoundLoop_hum8"   
- "SoundLoop_hum9"   
  "SoundLoop_machine0"    "SoundLoop_machine0"  
  "SoundLoop_machine1"    "SoundLoop_machine1"  
- "SoundLoop_machine10"   
- "SoundLoop_machine11"   
- "SoundLoop_machine12"   
  "SoundLoop_machine2"    "SoundLoop_machine2"  
  "SoundLoop_machine3"    "SoundLoop_machine3"  
Line 1048: Line 1049:
  "SoundLoop_machine8"   "SoundLoop_machine8" 
  "SoundLoop_machine9"    "SoundLoop_machine9"  
 + "SoundLoop_machine10"  
 + "SoundLoop_machine11"  
 + "SoundLoop_machine12"  
  "SoundLoop_misc0"    "SoundLoop_misc0"  
  "SoundLoop_misc1"    "SoundLoop_misc1"  
Line 1060: Line 1064:
  "ambient"   "ambient" 
  List ->loopSoundList  List ->loopSoundList
- +
  GetListCount(<-loopSoundList) ->soundCount  GetListCount(<-loopSoundList) ->soundCount
- TraceAllSp ("List of " <-soundCount "looping sounds available"+ TraceAllSp("List of " <-soundCount "looping sounds available"
- TraceAllSP ("Press and hold user key 1/2 to scroll through the sound list." ) + TraceAllSP("Press and hold KeyPad 1 / 2 to scroll through the sound list."
- TraceAllSp ("Move mouse out of edit box. Left-click mouse to listen to a sound. " ) + TraceAllSp("Move mouse out of edit box. Left-click mouse to listen to a sound. ") 
- TraceAllSP ("Left-click to turn off again."+ TraceAllSP("Left-click to turn off again."
- TraceAllSp ("Remember to mute music!")+ TraceAllSp("Remember to mute music!")
 </code> </code>
  
Line 2328: Line 2332:
  Trace5("'/' = disperse selected with random height from " <-RAND_MIN ":" <-RAND_MAX " with RandFloat too.")  Trace5("'/' = disperse selected with random height from " <-RAND_MIN ":" <-RAND_MAX " with RandFloat too.")
 </code></hidden> </code></hidden>
 +
 +----
 +
 +===== Automated Tower Grid Building =====
 +
 +The below script originally existed as a map script that builds out the tower grid for/alongside the player. I've adapted it for console usage in the editor, by blocking out specicific code and instantly constructing all towers that it places.
 +
 +If you have a large map with varied terrain and you want to want to wire it up fast, then this script is worth a try.
 +
 +New towers will be constructed if they can connect to: existing riftlab, m-rift, pylons, energy pods and towers.
 +
 +To run the console script, simply let it run continuously.
 +
 +<hidden click here for source code>
 +
 +<code 4rpl file name.4rpl>
 +# AutoTowerGrid
 +# by Kalli
 +# Script is to run continuously in the console!
 +
 +$pacMode:0 # Makes all units built by the pilot unselectable and undeletable by the player
 +
 +# Quick guide to the variables: 
 +# 1) largeSkip makes the script less accurate, but it saves on computation time.
 +# 2) optimalCellSelection will keep looking for the cell with the most connections. Or until optimalTowerTarget is met.
 +# 3) Multiply optimalCellSelection with largeSkip to get an idea of what cellrange will be checked. Higher = more triangles even when square + more towers close to minimum distance.
 +# 4) Good settings for a well connected tower grid with low average computation times: mintowerdistance=0; gosquare=1; optimalselection=3; largeSkip=8.
 +
 +$maxTowerDistance:12 # LT to exclude 12. Mostly max is 12, but sometimes 2 towers at an exact distance of 12 will not connect. And other units with a different connecting height, might not connect at all.
 +$minTowerDistance:0 # Set to 0 to let the script calc the min distance as a function of the max distance.
 +$disLOS:1 # Check LOS when checking for towers within the minimum distance. It makes it easier to climb walls or cliffs, but it becomes messier.
 +
 +$contaminentSlot:8 # Towers shouldn't build on contaminated terrain, but the mapmaker could move the slots around in the editor.
 +$creeperRange:3 # Towers will be build up to this distance from creep. Closer creep will make it a "blocked" tower.
 +
 +$goSQUARE:1 # The script will either work angular or perpendicular.
 +$optimalCellSelection:3 # after finding a suitable cell, the script will loop through x more cells to maybe find a better connected one.
 +$largeSkip:8 # When a computation heavy check fails, skip a few cells. Works well with lowering optimalCellSelection.
 +$smallSkip:1 # Only used for avoiding void and map limits atm. 
 +$optimalTowerTarget:2 # The loop will stop if the cell has this many towers at a good distance. Minimum 2.
 +
 +$$minTowerCellDistance:1 # Could lower execution time, but will make it harder to scale some cliffs. If this is increased, decrease the $optimalCellSelection variable. Test per map.
 +$$maxTowerCellDistance:12 # LTE. something to play with. Cells further away than the maxTowerDistance are also automatically removed.
 +$$connectionHeight:2.8 # 2.9 is very rarely too high for the LOS check along slope edges.
 +$debugPrint:0
 +
 +"Everything" @startChrono
 +<-debugPrint if elapsedtime ->starttime endif
 +# GetEnergyStore ->energyStored
 +# GetEnergyUse GetEnergyGeneration 1 max div ->energyUsage
 +GetTimer3 ->timer3
 +GetTimer0 ->timer0
 +switch
 + # "Nodes" @startChrono
 + case(<-timer0 eq0)
 + 151 Settimer0
 + @findOtherNodes
 + endcase
 + # "Towers" @startChrono
 + case(<-timer0 100 eq)
 + @findOtherTowers
 + endcase
 + # "Cleaning" @startChrono
 + case(<-timer0 50 eq)
 + @cleanOldNodeList
 + endcase
 + # "Planned" @startChrono
 + case(<-timer3 0 eq)
 + 11 Settimer3
 + @buildPlannedTower
 + @monitorTowers
 + endcase
 + # "Blocked" @startChrono
 + case(<-timer3 10 eq)
 + @buildBlockedTower
 + @monitorTowers
 + endcase
 + # # "Supply" @startChrono
 + # case(<-energystored eq0)
 + # # The cases above don't need much energy, the cases below do. Only start large scale expansion when there is energy stored.
 + # 0 @supplyNonEssentialInfrastructure
 + # endcase
 + # "Planned" @startChrono
 + case(<-timer3 9 eq)
 + @buildPlannedTower
 + @monitorTowers
 + endcase
 + # "Blocked" @startChrono
 + case(<-timer3 8 eq)
 + @buildBlockedTower
 + @monitorTowers
 + endcase
 + # case(<-energyUsage 1.0 gt <-energyStored 20 lt and)
 + # # Try to prevent overbuilding again.
 + # endcase
 + # "Planned" @startChrono
 + case(<-timer3 7 eq)
 + @buildPlannedTower
 + @monitorTowers
 + endcase
 + # "Blocked" @startChrono
 + case(<-timer3 6 eq)
 + @buildBlockedTower
 + @monitorTowers
 + endcase
 + # "Planned" @startChrono
 + case(<-timer3 5 eq)
 + @buildPlannedTower
 + @monitorTowers
 + endcase
 + # "Blocked" @startChrono
 + case(<-timer3 4 eq)
 + @buildBlockedTower
 + @monitorTowers
 + endcase
 + # case(<-energyUsage 1.0 gt <-energyStored 40 lt or)
 + # # Try to prevent overbuilding.
 + # endcase
 + # "PlannedS" @startChrono
 + case(<-timer3 3 eq)
 + @buildPlannedTower
 + # 1 @supplyNonEssentialInfrastructure #turn on supply again for non essentials if there are enough supplies
 + endcase
 + # "Blocked" @startChrono
 + case(<-timer3 2 eq)
 + @buildBlockedTower
 + @monitorTowers
 + endcase
 + # "Planned" @startChrono
 + case(<-timer3 1 eq)
 + @buildPlannedTower
 + @monitorTowers
 + endcase
 +endswitch
 +@stopChrono
 +
 +:startChrono
 + # the ID is left on the stack, swap to send it.
 + # <-debugPrint if "StartElapsedMSG" swap sendmsg endif
 + "StartElapsedMSG" swap sendmsg
 +
 +:stopChrono
 + # <-debugPrint if "EndElapsedMSG" "" sendmsg endif
 + "EndElapsedMSG" "" sendmsg
 +
 +:Once
 + createlist ->uid_BlockedTower # use indexBl to cycle through these between frames
 + createlist ->dir_BlockedTower # not the coordinates, but the direction
 +
 + createlist ->uid_PlannedTower # use indexPl to cycle through these between frames
 + createlist ->dir_PlannedTower # not the coordinates, but the direction
 +
 + createlist ->uid_MonitorTower # use indexMT to cycle through these between frames
 + createlist ->dir_MonitorTower # not the coordinates, but the direction
 + createlist ->uid_MonitoredTower # The actually build towers, whose eventual destruction has to be monitored.
 +
 + # Use existing or playerbuild nodes (microrifts, pylons, pods and towers) to plan towers around
 + createlist ->oldNodeList 
 + createlist ->oldNodeList # riftlab, pylons, pods and microrifts. Gets purged overtime.
 +
 + # If PAC mode is enabled, make friendly pre existing units unselectable or vice versa
 + do(9999 0)
 + if(GetUnitCreeperDamages(I))
 + I @SetSelectable
 + endif
 + loop
 +
 + # The minimum distance is to block placing a tower within a perfect square of other towers. The absolute minimum ratio is roughly 7.25 / 12.
 + if(<-minTowerDistance eq0)
 + <-maxTowerDistance 7.25 mul 12 div ->minTowerDistance
 + endif
 + # The following 2 variables are used in finding other towers at an "optimal" range
 + <-maxTowerDistance 1.0 mul ->maxOptimalRange
 + <-maxTowerDistance 0.85 mul <-minTowerDistance max ->minOptimalRange
 +
 + # 4 DIRECTIONS
 + createlist ->relativePositionList1
 + createlist ->relativePositionList2
 + createlist ->relativePositionList3
 + createlist ->relativePositionList4
 +
 + # The below cell lists + list sorting were created in excel.
 + <-goSQUARE if
 + list( v2(12 0) v2(11 0) v2(11 1) v2(11 -1) v2(11 2) v2(11 -2) v2(11 3) v2(11 -3) v2(11 4) v2(11 -4) v2(10 0) v2(10 1) v2(10 -1) v2(10 2) v2(10 -2) v2(10 3) v2(10 -3) v2(10 4) v2(10 -4) v2(10 5) v2(10 -5) v2(10 6) v2(9 0) v2(9 1) v2(9 -1) v2(10 -6) v2(9 2) v2(9 -2) v2(9 3) v2(9 -3) v2(9 4) v2(9 -4) v2(9 5) v2(9 -5) v2(9 6) v2(9 -6) v2(9 7) v2(9 -7) v2(8 0) v2(8 1) v2(8 -1) v2(8 2) v2(8 -2) v2(8 3) v2(8 4) v2(8 -3) v2(8 5) v2(8 -4) v2(8 6) v2(8 -5) v2(8 7) v2(8 -6) v2(8 8) v2(8 -7) v2(7 7) v2(7 6) v2(7 5) v2(7 -6) v2(7 -5) v2(7 4) v2(7 -4) v2(7 3) v2(7 -3) v2(7 2) v2(7 -2) v2(7 1) v2(7 -1) v2(7 0) v2(6 6) v2(6 5) v2(6 -5) v2(6 4) v2(6 -4) v2(6 3) v2(6 -3) v2(6 2) v2(6 -2) v2(6 1) v2(6 -1) v2(6 0) v2(5 5) v2(5 4) v2(5 -4) v2(5 3) v2(5 -3) v2(5 2) v2(5 -2) v2(5 1) v2(5 -1) v2(5 0) v2(4 4) v2(4 3) v2(4 -3) v2(4 2) v2(4 -2) v2(4 1) v2(4 -1) v2(4 0) v2(3 3) v2(3 2) v2(3 -2) v2(3 1) v2(3 -1) v2(3 0) v2(2 2) v2(2 1) v2(2 -1) v2(2 0) v2(1 1) v2(1 0) ) ->relativePositionList1 # loop through this reversed list in normal order
 + else
 + list( v2(10 6) v2(6 10) v2(9 7) v2(7 9) v2(8 8) v2(11 4) v2(4 11) v2(10 5) v2(5 10) v2(9 6) v2(6 9) v2(8 7) v2(7 8) v2(11 3) v2(3 11) v2(10 4) v2(4 10) v2(12 0) v2(11 2) v2(9 5) v2(2 11) v2(5 9) v2(8 6) v2(6 8) v2(7 7) v2(10 3) v2(11 1) v2(3 10) v2(1 11) v2(9 4) v2(11 0) v2(4 9) v2(10 2) v2(2 10) v2(8 5) v2(5 8) v2(7 6) v2(6 7) v2(10 1) v2(9 3) v2(3 9) v2(1 10) v2(10 0) v2(8 4) v2(4 8) v2(9 2) v2(2 9) v2(7 5) v2(5 7) v2(6 6) v2(9 1) v2(8 3) v2(1 9) v2(3 8) v2(9 0) v2(7 4) v2(4 7) v2(8 2) v2(6 5) v2(2 8) v2(5 6) v2(8 1) v2(1 8) v2(7 3) v2(3 7) v2(8 0) v2(6 4) v2(4 6) v2(5 5) v2(7 2) v2(2 7) v2(7 1) v2(1 7) v2(6 3) v2(3 6) v2(7 0) v2(5 4) v2(4 5) v2(6 2) v2(2 6) v2(6 1) v2(5 3) v2(1 6) v2(3 5) v2(4 4) v2(6 0) v2(5 2) v2(2 5) v2(4 3) v2(3 4) v2(5 1) v2(1 5) v2(5 0) v2(4 2) v2(2 4) v2(3 3) v2(4 1) v2(1 4) v2(4 0) v2(3 2) v2(2 3) v2(3 1) v2(1 3) v2(3 0) v2(2 2) v2(2 1) v2(1 2) v2(2 0) v2(1 1) v2(1 0) ) ->relativePositionList1 # loop through this reversed list in normal order
 + endif
 +
 + # throw out the tower positions that are too close or too far.
 + <-maxTowerDistance <-maxTowerCellDistance lt if <-maxTowerDistance ->maxTowerCellDistance endif
 + do(-1 <-relativePositionList1 getlistcount 1 sub)
 + if (distancecell(0 0 <-relativePositionList1[i] ev2) dup <-minTowerCellDistance lt swap <-maxTowerCellDistance gte or)
 + <-relativePositionList1 i removelistelement
 + endif
 + loop
 + <-relativePositionList1 getlistcount ->relativePositionsCount
 +
 + # Find back roughly the center cell of the position list and add it to index [0], so that a pre-emptive tower check can be ran against it.
 + <-goSQUARE if
 + do(<-relativePositionsCount 0)
 + <-relativePositionList1[i] ev2 eq0 if # ->x
 + <-maxX max ->maxX
 + else pop
 + endif
 + loop
 + <-maxX 2 div 1 add floor ->centralX
 + <-relativePositionList1 <-centralX 0 v2 prependtolist
 + <-maxX 2 div 1 add ceil <-minTowerDistance max ->quickRangeCheck # the range of the pre-emptive tower check
 + else
 + do(<-relativePositionsCount 0)
 + <-relativePositionList1[i] ev2 dup2 eq if # ->z ->x
 + pop <-maxX max ->maxX
 + else pop pop
 + endif
 + loop
 + <-maxX 2 div 0.7 add floor dup ->centralX ->centralZ
 + <-relativePositionList1 <-centralX <-centralZ v2 prependtolist
 + <-maxX 2 div 0.7 add 2 sqrt mul ceil <-minTowerDistance max ->quickRangeCheck # the range of the pre-emptive tower check
 + endif
 + <-relativePositionsCount 1 add ->relativePositionsCount
 +
 + # Generate the 3 other relative position lists by rotating the 1 1 list.
 + <-relativePositionsCount 0 do
 + <-relativePositionList1[i] dup dup ev2 -1 mul swap v2 ->relativePositionList2[i]
 + ev2 -1 mul swap -1 mul swap v2 ->relativePositionList3[i]
 + ev2 swap -1 mul v2 ->relativePositionList4[i]
 + loop
 + # <-debugPrint if <-relativePositionList1 <-relativePositionList2 <-relativePositionList3 <-relativePositionList4 printallsp endif
 +
 +:SetSelectable
 + <-pacMode ! SetUnitSelectable
 +
 +:supplyNonEssentialInfrastructure
 +->supplyON
 + "porter" <-supplyON @supplyUnitType
 + "runway" <-supplyON @supplyUnitType
 + "greenarrefinery" <-supplyON @supplyUnitType
 +
 + "rocketpad" <-supplyON @constructUnitType
 +
 +:supplyUnitType
 +->supplyON ->unitType
 +
 + <-unitType 0 GetUnitsByType dup ->list getlistcount ->listCount
 +
 + <-supplyON if
 + <-listCount 0 do 
 + <-list[i] dup getunitenabled not if 
 + 1 setunitenabled 
 + return 
 + endif
 + loop
 + else
 + -1 <-listCount 1 sub do
 + <-list[i] dup getunitenabled if 
 + 0 setunitenabled 
 + return 
 + endif
 + loop
 + endif
 +
 +:constructUnitType
 +# the same functiion as supplyunittype, only aimed at infrastructure which does not consume energy once build.
 +->supplyON ->unitType
 + <-supplyON if
 + # supply should be turned on for all units of this type, since construction could have finished with the packets that were already underway.
 + <-unitType 0 GetUnitsByType dup ->list getlistcount ->listCount
 + else
 + <-unitType 2 GetUnitsByType dup ->list getlistcount ->listCount
 + endif
 +
 + <-supplyON if
 + <-listCount 0 do 
 + <-list[i] dup getunitenabled not if 
 + 1 setunitenabled 
 + return 
 + endif
 + loop
 + else
 + -1 <-listCount 1 sub do
 + <-list[i] dup getunitenabled if 
 + 0 setunitenabled 
 + return 
 + endif
 + loop
 + endif
 +
 +:findOtherNodes
 + "pylon" 1 getunitsbytype ->pylonList
 + "microrift" 1 getunitsbytype ->microriftList
 + "pod" 1 getunitsbytype ->podList
 + do(<-podList 0)
 + <-podList[i] ->UID
 + # only add pods that are on the ground + containing energy
 + <-UID GetUnitOccupiesLand if
 + <-UID getunitsettings "Resource" gettableelement eq0 if
 + <-oldNodeList <-UID listcontains not if
 + <-UID @plan4Towers
 + endif
 + endif
 + endif
 + loop
 + do(<-microriftList 0)
 + <-microriftList[i] ->UID
 + <-oldNodeList <-UID listcontains not if
 + <-UID @plan4Towers
 + endif
 + loop
 + do(<-pylonList 0)
 + <-pylonList[i] ->UID
 + <-oldNodeList <-UID listcontains not if
 + <-UID @plan4Towers
 + endif
 + loop
 + getriftlab ->UID @plan4Towers
 + <-oldNodeList <-UID listcontains not if
 + <-UID @plan4Towers
 + endif
 +
 +:findOtherTowers # A part of the findOtherNodes script, but separated to run during a different frame
 + "tower" 1 getunitsbytype ->*towerList
 + do(<-*towerList 0)
 + <-*towerList[i] ->UID
 + <-oldNodeList <-UID listcontains not if
 + <-UID @plan4Towers
 + endif
 + loop
 +
 +:findOtherTowers2 # Less effective, but better computation wise. Spam this when out of planned towers.
 + # The below code requires less compute time, but is somehow less effective than the code above. How is getunitsbytype ordered?
 + "tower" 1 getunitsbytype dup ->*towerList getlistcount ->listCount
 + <-*towerList <-lastTower getlistindex 0 max ->listStart
 + do(<-listCount <-listStart)
 + <-*towerList[i] ->UID
 + <-oldNodeList <-UID listcontains not if
 + <-UID @plan4Towers
 + endif
 + loop
 + <-*towerList[<-listCount 1 sub] ->lastTower
 +
 +:plan4Towers
 +->UID
 + <-UID getunitcell pop eq0 if return endif
 + clearstack <-UID dup dup2 <-uid_PlannedTower PrependStackToList
 + 1 2 3 4 <-dir_PlannedTower PrependStackToList
 + <-oldNodeList <-UID appendtolist
 +
 +:cleanOldNodeList
 + # remove destroyed nodes from the list
 + do(-1 <-oldNodeList 1 sub)
 + <-oldNodeList[i] ->UID
 + <-UID getunitcell pop eq0 if
 + <-oldNodeList i removelistelement
 + endif
 + loop
 + # surviving old nodes need to be removed from the old node list periodically, because if they are unknown in the monitor, then no towers will be rebuild around them. So a constant slow purge is needed. Only remove 1 per 3 cycles.
 + if(<-cycleCounter eq0)
 + 3 ->cycleCounter
 + do(<-oldNodeList 0)
 + <-uid_MonitorTower <-oldNodeList[i] listcontains not if
 + <-oldNodeList i removelistelement
 + return
 + endif
 + loop
 + else
 + <-cycleCounter 1 sub ->cycleCounter
 + endif
 +
 +:buildPlannedTower
 + # randint(0 GetListCount(<-uid_PlannedTower)) ->indexPl # Builds faster in multiple directions.
 + <-uid_PlannedTower getlistcount 1 sub ->indexPl # Builds more systematic, but can leave a front waiting if it's expanding on multiple sides.
 + <-indexPl -1 eq if
 + @findOtherTowers2
 + else
 + <-uid_PlannedTower[<-indexPl] ->UID <-dir_PlannedTower[<-indexPl] ->direction
 + <-uid_PlannedTower <-indexPl RemoveListElement
 + <-dir_PlannedTower <-indexPl RemoveListElement
 + <-UID <-direction @buildOneTower
 + endif
 +
 +:buildBlockedTower
 + # randint(0 GetListCount(<-uid_BlockedTower)) ->indexBl
 + <-uid_BlockedTower getlistcount 1 sub ->indexBl
 + <-indexBl -1 eq if
 + @buildPlannedTower
 + else
 + <-uid_BlockedTower[<-indexBl] ->UID <-dir_BlockedTower[<-indexBl] ->direction
 + <-uid_BlockedTower <-indexBl RemoveListElement
 + <-dir_BlockedTower <-indexBl RemoveListElement
 + <-UID <-direction @buildOneTower
 + endif
 +
 +:monitorTowers
 + if(<-indexMT 0 lte)
 + GetListCount(<-uid_MonitoredTower) 1 sub ->indexMT
 + else
 + <-indexMT 1 sub ->indexMT
 + endif
 + if(GetUnitCell(<-uid_MonitoredTower[<-indexMT]) pop eq0)
 + if(GetUnitCell(<-uid_MonitorTower[<-indexMT]) pop neq0)
 + <-uid_BlockedTower <-uid_MonitorTower[<-indexMT] PrependToList
 + <-dir_BlockedTower <-dir_MonitorTower[<-indexMT] PrependToList
 + endif
 + <-uid_MonitoredTower <-indexMT RemoveListElement
 + <-uid_MonitorTower <-indexMT RemoveListElement
 + <-dir_MonitorTower <-indexMT RemoveListElement
 + # else
 + # # Some randomized tower building, which might unstuck tower building
 + # <-uid_MonitorTower[<-indexMT] @buildOneRandomTower
 + endif
 +
 +:buildOneRandomTower
 +->UID
 + randint(1 5) ->rand1_4
 + <-UID <-rand1_4 @buildOneTower
 +
 +:buildOneTower
 +->direction ->UID
 + if(GetUnitCell(<-UID) pop eq0)
 + return
 + endif
 +
 + GetUnitPosition(<-UID) ->UIDPos
 + <-UIDPos.y <-connectionHeight add ->UIDPos.y
 +
 + GetUnitType(<-UID) ->unitType
 +
 + <-debugPrint if 
 + elapsedtime ->startTime
 + <-UID " " <-direction concat3 " " <-UIDPos concat3 ->debugString
 + endif
 +
 + switch
 + case(<-direction 1 eq)
 + <-relativePositionList1 ->relativeList
 + endcase
 + case(<-direction 2 eq)
 + <-relativePositionList2 ->relativeList
 + endcase
 + case(<-direction 3 eq)
 + <-relativePositionList3 ->relativeList
 + endcase
 + case(<-direction 4 eq)
 + <-relativePositionList4 ->relativeList
 + endcase
 + endswitch
 +
 + # Do a pre-emptive tower check and abort if another tower is found in the middle of the positions list
 + <-relativeList[0] ev2 <-UIDPos.z add ->testPos.z <-UIDPos.x add ->testPos.x
 + GetTerrain(<-testPos.x <-testPos.z) ->testPos.y
 + <-unitType "tower" eq if 1 ->check else 0 ->check endif
 + GetUnits("tower" <-testPos <-quickRangeCheck 0 1 0 0 0 0) getlistcount <-check gt if 
 + return 
 + # <-debugPrint if <-debugString " " "QuickRangeCheck" concat3 print endif
 + endif
 +
 + # Do a pre-emptive creeper check and classify it as blocked + abort if there is creeper in the middle of the positions list
 + <-quickRangeCheck <-creeperRange sub <-creeperRange max ->checkRange
 + GetCreeperInRange(<-testPos.x <-testPos.z <-checkRange 0 1 0) neq0 if
 + <-uid_BlockedTower <-UID PrependToList
 + <-dir_BlockedTower <-direction PrependToList
 + return
 + # <-debugPrint if <-debugString " " "CreeperRangeCheck" concat3 print endif
 + endif
 +
 + # Check if the chosen direction is (partly) outside the map
 + GetTerrain(<-testPos.x <-testPos.z) lt0 if return endif
 +
 + <-optimalCellSelection ->cellsToCheck
 + -1 ->towersInRange # Even if no optimal towers are found nearby, construction still has to go ahead.
 + 0 ->skipI
 + do(<-relativePositionsCount 1) # Index 0 is used for pre-emptive checks.
 + switch
 + case(<-cellsToCheck lte0)
 + <-debugPrint if <-debugString " " 1 concat3 ->debugString endif
 + break
 + endcase
 + case(<-skipI) # The find units in range are the most taxing. If one of those conditions is true, skip the next few i indices.
 + <-skipI 1 sub ->skipI
 + <-debugPrint if <-debugString " " 2 concat3 ->debugString endif
 + endcase
 + if(<-towersInRange -1 gt)
 + <-cellsToCheck 1 sub ->cellsToCheck
 + endif
 + <-relativeList[i] ev2 <-UIDPos.z add ->testPos.z <-UIDPos.x add ->testPos.x
 + GetTerrain(<-testPos.x <-testPos.z) ->testPos.y
 + case(<-testPos.y 0 lte)
 + # cells outside the map will have a terrain height of -1
 + <-debugPrint if <-debugString " " 3 concat3 ->debugString endif
 + # <-smallSkip ->skipI # Avoid using this, it degrades the grid.
 + endcase
 + case(GetCellOccupiedCount(<-testPos.x <-testPos.z))
 + <-debugPrint if <-debugString " " 4 concat3 ->debugString endif
 + endcase
 + # case(IsV3InMap(<-testPos) not)
 + # <-debugPrint if <-debugString " " 5 concat3 ->debugString endif
 + # endcase
 + case(GetTerrainSpecial(<-testPos.x <-testPos.z) <-contaminentSlot eq)
 + <-debugPrint if <-debugString " " 6 concat3 ->debugString endif
 + endcase
 + <-testPos.y <-connectionHeight add ->testPos.y # the next 3 checks are done slightly below the height of the connections. Slightly below because it was sometimes not getting terrainLOS right.
 + distance(<-testPos <-UIDPos) ->testDist
 + case(<-testDist <-maxTowerDistance gte)
 + <-debugPrint if <-debugString " " 7 concat3 ->debugString endif
 + # Do NOT use skipI here.
 + # Sometimes 2 towers at an exact distance of 12 will not connect, so gte and not gt? This was a problem in version 1, also in 2?
 + endcase
 + case(<-testDist <-minTowerDistance lt <-disLOS not and)
 + <-debugPrint if <-debugString " " 8 concat3 ->debugString endif
 + <-largeSkip ->skipI
 + <-cellsToCheck 1 sub ->cellsToCheck # be less picky
 + if(<-towersInRange -1 gt) break endif
 + endcase
 + GetTerrainLOS(<-testPos <-UIDPos 0) ->losPos
 + <-testPos.y <-UIDPos.y min ->connPosMinY
 + <-testPos.y <-UIDPos.y max ->connPosMaxY
 + case(<-losPos.y <-connPosMinY gte <-losPos.y <-connPosMaxY lte and)
 + # <-cellsToCheck 1 sub ->cellsToCheck # be less picky
 + if(<-towersInRange -1 gt) break endif
 + <-debugPrint if <-debugString " " 9 concat3 ->debugString endif
 + endcase
 + <-disLOS if 
 + GetUnits("tower" <-testPos <-minTowerDistance 0 1 1 0 0 0) getlistcount ->amtCloseTowers 
 + <-testPos.y <-connectionHeight sub ->testPos.y # With LOS, check from an elevated position to get better viezing angles.
 + else 
 + <-testPos.y <-connectionHeight sub ->testPos.y
 + GetUnits("tower" <-testPos <-minTowerDistance 0 1 0 0 0 0) getlistcount ->amtCloseTowers
 + endif
 + case(<-amtCloseTowers) # are there other towers too close by.
 + <-debugPrint if <-debugString " " 10 concat3 ->debugString endif
 + <-largeSkip ->skipI
 + <-cellsToCheck 1 sub ->cellsToCheck # be less picky
 + if(<-towersInRange -1 gt) break endif
 + endcase
 + # check the next few cells to look for more towers at a near optimal distance, to form extra connections.
 + case(<-cellsToCheck)
 + <-debugPrint if <-debugString " " 11 concat3 ->debugString endif
 + 0 ->goodTowersNearCell # gt(-1) atleast one suitable cell has been found, so the coordinates should be set.
 + GetUnits("tower" <-testPos <-maxTowerDistance 0 1 0 0 0 0) getlistcount ->goodTowersNearCell
 + if(<-goodTowersNearCell <-towersInRange lt) break endif
 + if(<-goodTowersNearCell <-towersInRange gt)
 + <-goodTowersNearCell ->towersInRange
 + <-testPos.x ->optimalX
 + <-testPos.z ->optimalZ
 + endif
 + if(<-goodTowersNearCell <-optimalTowerTarget gte) break endif
 + <-largeSkip ->skipI
 + endcase
 + <-debugPrint if <-debugString " " 12 concat3 ->debugString endif
 + break # break the loop and build (or plan) the optimal tower outside of the loop
 + endswitch
 + loop
 +
 + if(<-towersInRange -1 gt)
 + switch
 + case(GetMeshHealth(<-optimalX <-optimalZ) 0 gt)
 + <-uid_BlockedTower <-UID PrependToList
 + <-dir_BlockedTower <-direction PrependToList
 + endcase
 + case(GetCreeperInRange(<-optimalX <-optimalZ <-creeperRange 0 1 0) neq0)
 + <-uid_BlockedTower <-UID PrependToList
 + <-dir_BlockedTower <-direction PrependToList
 + endcase
 + CreateUnitOnTerrain("tower" <-optimalX <-optimalZ 0) ->newTower
 + <-debugPrint if <-newTower <-UID "-" <-direction concat3 setunitdebugtext endif
 + # Start monitoring of that one tower
 + <-uid_MonitoredTower <-newTower PrependToList
 + <-uid_MonitorTower <-UID PrependToList
 + <-dir_MonitorTower <-direction PrependToList
 + <-indexMT 1 add ->indexMT
 +
 + <-newTower 999 constructunit
 + endswitch
 + endif
 +
 + <-debugPrint if 
 + elapsedtime ->endTime 
 + <-endTime <-startTime sub ->spentTime
 + if(<-spentTime 10 gt)
 + getgameupdatecount " " <-spentTime concat3 " " <-debugString concat3 printallsp
 + endif
 + endif
 +</code>
 +
 +</hidden>
 +
 +----
 +
 +===== Faster vanilla unit placement : Snapping Tool =====
 +
 +The snapping tool makes use of a unit for highlighting the nearest buildable cell, so it's a cpack instead of a script : [[https://knucklecracker.com/wiki/doku.php?id=cw4:cpack:docs:e58ef3f2-1468-42fa-ac84-c3561ddd9452|Kalli - SnappingTool]]
  
 ---- ----
Line 2685: Line 3280:
  
 ---- ----
-===== Random X-Z location on radius =====+===== Random X-Z position within the area of circle =====
  
 <hidden click here for source code> <hidden click here for source code>
-This is a simple piece of trigonometry that is able to pick a random position within a radius around a center on the X-Z plain with a definable radius. This is useful in the event you wish to introduce circles into your 4RPL code's behavior and examples of its use will be provided.\\ +This is a simple piece of trigonometry that is able to pick a random position within the area of circle on the X-Z plain with a definable radius. This is useful in the event you wish to introduce circles into your 4RPL code's behavior.\\ 
-Uses [[4rpl:commands:randint|RandInt]] and [[4rpl:commands:randfloat|RandFloat]] to choose a random location along the circumference of a circle with a definable radius.\\+Uses [[4rpl:commands:randint|RandInt]] and [[4rpl:commands:randfloat|RandFloat]] to choose a random location within the area of a circle with a definable radius.\\
 Note: Floats will work for the radius.\\ Note: Floats will work for the radius.\\
 \\ \\
cw4/4rpl_tools.txt · Last modified: 2024/05/13 11:42 by Kalli