Contributors to this page is encouraged to attribute their contributions((Invitation issued on November 11th, 2022)). {{page>cw4:header&nofooter}} {{page>4rpl:header&nofooter}} ====== Editors ====== //Note: to get a default editor associated with the .4rpl filetype, please read about [[cw4:tutorials:file association]].// ===== Notepad++ ===== Make sure you download only from the official Notepad++ website to avoid malware. https://notepad-plus-plus.org/ ==== Syntax Highlighting ==== Some community members created syntax highlighters for Notepad++. More detailed descriptions can be found on the respective pages for each highlighter. Please check out the [[cw4:nppguide|Notepad++ Guide]] on how to install highlighters if you don't know how. * [[4rpl:syntax_highlighter|Highlighter by Sanian.]] Comes with basic spellcheck by underlining offending text in red. A Standard and Dark version are available. * [[4rpl:syntax_highlighter2|Highlighter by Alophox.]] Does not include spellcheck. This highlighter can be used with multiple themes. ===== VSCode ===== [[https://marketplace.visualstudio.com/items?itemName=Up-Level.4rpl-lang|Language Extension by Up-Level.]] Has syntax highlighting, hover information from the wiki, autocomplete and basic error detection. ====== Console Scripts ====== These scripts can be run from the [[cw4:4rpl_console|Console]] in the [[Mods]] section of the [[cw4:custom_maps#Mission Editor]] ---- ===== Copy/Paste Utility ===== This script can be used to copy and paste terrain in a map. New: now can copy arbitrary rectangle, use terrain overlay to show marked and copied areas. Also copy special terrain (breeder, mesh, etc.) # Copy/Paste 2021-02-20 # --------------------- # With grateful thanks to knucracker for his patient assistance # --------------------- # Also copies special terrain $lmb:0 # left click button $rmb:1 # right click button $themeSlot:1 # theme overlay slot to use $copy:1 # true for copy $markActive:0 # false until area selection start GetPointerTerrainCoords ->mouseZ ->mouseX @BlitImage if (GetMouseButtonDown(<-lmb false)) # start copy area selection true ->markActive --area # delete copy buffer if (@IsMouseValid) <-mouseZ ->areaStartZ <-mouseX ->areaStartX endIf endIf if (NOT(<-markActive) AND (NOT(-?area))) Return endIf # don't run rest if not selecting terrain if (GetMouseButton(<-lmb false)) # track copy area size amd mark terrain if (@IsMouseValid) <-mouseZ ->areaEndZ <-mouseX ->areaEndX MIN(<-areaStartZ <-areaEndZ) ->minZ MAX(<-areaStartZ <-areaEndZ) ->maxZ MIN(<-areaStartX <-areaEndX) ->minX MAX(<-areaStartX <-areaEndX) ->maxX FromCell(<-minX <-minZ) ->minV FroMCell(<-maxX <-MaxZ) ->maxV @MarkArea (V4(0 0 0 0) <-minVOld <-maxVOld) # Clear marker before painting new marker @MarkArea (V4(.4 .4 .4 .4) <-minV <-maxV) # mark terrain <-minV ->minVOld <-maxV ->maxVOld endIf endif if (GetMouseButtonUp(<-lmb false)) # copy terrain, create blit image @MakeCopy endIf if (GetMouseButtonDown(<-rmb true)) # let's paste If (-?area) @Paste else TraceAllSp ("Copy buffer is empty!") endIf endIf ## mainline end - functiosn/routines follow :MakeCopy CreateList ->area CreateList ->pasteImage toCell(<-maxV) ->_maxCellZ ->_maxCellX toCell(<-minV) ->_minCellZ ->_minCellX <-_maxCellX <-_minCellX - ->blitWidth <-_maxCellZ <-_minCellZ - ->blitHeight # Iterate over marked area, copying both cell height and setting texture overlay Do (<-_maxCellZ <-_minCellZ) Do (<-_maxCellX <-_minCellX) GetTerrain (I J ) ->_ter GetTerrainSpecial(I J) ->_terS V2(<-_ter <-_terS) ->_terData <-_ter 20.0 / ->c v4(<-c <-c <-c .6) ->_color AppendToList(<-area <-_terData) AppendToList(<-pasteImage <-_color) loop Loop TraceAllSp ("Copied " GetListCount(<-area) "cells. From min/max" <-minV <-maxV) If (GetListCount(<-area) eq0) --area endif # can't paste 0 zells :BlitImage If(-?PasteImage) ClearThemeOverlay(<-themeSlot Vector0) # remove old mark SetThemeOverlayPixels(<-themeSlot, <-mouseX, <-mouseZ, # set paste overlay <-blitWidth <-blitHeight <-pasteImage) EndIf :MarkArea toCell ->_maxCellZ ->_maxCellX toCell ->_minCellZ ->_minCellX ->_tex <-_maxCellX <-_minCellX - ->_width <-_maxCellZ <-_minCellZ - ->_height SetThemeOverlayRectPixels(<-themeSlot <-_minCellX <-_minCellZ <-_width <-_height <-_tex) :Paste if (@isMouseValid) <-mouseZ ->areaStartZ <-mouseX ->areaStartX else PlaySoundAtPosition("ADAMessage" 8 FromCell(<-mouseX <-mouseZ)) Return endIf if (not(-?area)) Trace ("Copy buffer empty!") PlaySoundAtPosition("ADAMessage" 8 FromCell(<-mouseX <-mouseZ)) Return endif EditAddUndo(0) ->listItem (0) toCell(<-maxV) ->_maxCellZ ->_maxCellX toCell(<-minV) ->_minCellZ ->_minCellX <-_maxCellZ <-_minCellZ - ->sizeZ <-_maxCellX <-_minCellX - ->sizeX Do (<-mouseZ <-sizeZ +, <-mouseZ) Do (<-mouseX <-sizeX +, <-mouseX) <-area[<-listItem] ->_terData EV2(<-_terdata) ->_terS ->_ter SetTerrain(I J <-_ter) SetTerrainSpecial(I J <-_terS) <-listItem 1 add ->listItem loop loop Trace3 ("pasted " <-listitem "cells.") :IsMouseValid if (<-mouseX gte0 and(<-mouseZ gte0) and(<-mouseX lt(<-mapSizeX)) and(<-mouseZ lt(<-mapSizeZ))) true else false endIf :once @Instructions # null mouse positions -1 ->mouseOldZ -1 ->mouseOldX GetMapSize ->mapSizeZ ->mapSizeX CreateThemeOverlay(<-themeSlot <-mapSizeX <-mapSizeZ Vector0) SetThemeOverlayEnabled(<-themeSlot true) # SetThemeOverlayPointFilter(<-themeSlot true) GetCameraTopDown ->initCamera # remember camera position SetCameraTopDown(true) # end Once :Instructions TraceAllSp ("Left Mouse to mark terrain,") TraceAllSp ("right mouse to paste") TraceAllSp ("Drag mouse to select area") :Destroyed SetCameraTopDown(<-initCamera) SetThemeOverlayEnabled(<-themeSlot false) # delete DestroyThemeOverlay(<-themeSlot) # stomp on it \\ ---- ===== Terrain Export/Map Copy ===== Sometimes one might want to copy a terrain feature from one map to another. That's not simple. This code will endeavor to assist, but it is a multi-step process. * Download the ''**Map Export.4RPL***'' file and run it in the Console of the map that has the desired terrain feature. Note that every time you click the right mouse button, an export operation will be performed. * The Export operation creates code in the file %HOMEPATH%\Documents\My Games\creeperworld4\rpl.txt If you close and open a new map in the game, the file will be erased, so make sure to save the contents before opening the next map. * There will be one or more (depending on how many exports you created) 4RPL programs in the file. All lines in the file will start with ''CONSOLE: '' that you need to remove in a text editor. A global change should suffice. * Import these scripts into the Console and run them in the map where you desire to create the exported terrain feature. # BigExport 2121-05-17 # exports a set of 4RPL instructions that can be used to replicate # part of a map in another map. # Exports Terrain and special terrain settings. # # A 4RPL script will be created in the rpl.txt file in the Creeper World 4 folder # every time you copy terrai, another script will be appended to the file (in the same map editing session). # You should save these scripts because next mission you open will overwrite the file. # Edit the output file to remove the "Console:" prefix to every line. # run the clean 4RPL script in the map where you want the terrain to be imported. # # With grateful thanks to knucracker who helped resolve my dumb blunders # and provided the necessary APIs $exports:0 # increment counter every export operation. $lmb:0 # left click button $rmb:1 # right click button $themeSlot:1 # theme overlay slot to use $markActive:0 # false until area selection start GetPointerTerrainCoords ->mouseZ ->mouseX if (GetKeyDown("A" false)) Trace("A Pressed") GetMapSize ->maxCellZ ->maxCellX 0 ->minCellX 0 ->minCellZ @export endif if (GetMouseButtonDown(<-lmb false)) # start copy area selection if (@IsMouseValid) <-mouseZ ->areaStartZ <-mouseX ->areaStartX true ->markActive --area # delete old copy buffer endIf endIf if (NOT(<-markActive)) # don't run rest if not selecting terrain Return endIf if (GetMouseButton(<-lmb false)) # track copy area size and mark terrain if (@IsMouseValid) <-mouseZ ->areaEndZ <-mouseX ->areaEndX MIN(<-areaStartZ <-areaEndZ) ->minZ MAX(<-areaStartZ <-areaEndZ) ->maxZ MIN(<-areaStartX <-areaEndX) ->minX MAX(<-areaStartX <-areaEndX) ->maxX FromCell(<-minX <-minZ) ->minV FroMCell(<-maxX <-MaxZ) ->maxV @MarkArea (V4(0 0 0 0) <-minVOld <-maxVOld) # Clear marker before painting new marker @MarkArea (V4(.4 .4 .4 .4) <-minV <-maxV) # mark terrain <-minV ->minVOld <-maxV ->maxVOld endIf endif if (GetMouseButtonUp(<-lmb false)) # copy terrain, create blit image @MarkArea (V4(0 0 0 0) <-minVOld <-maxVOld) # Clear marker toCell(<-maxV) ->maxCellZ ->maxCellX toCell(<-minV) ->minCellZ ->minCellX <-maxCellX <-minCellX - ->blitWidth <-maxCellZ <-minCellZ - ->blitHeight @Export endIf # --- end mainline --- :Export <-exports 1 + ->exports @PrintPreamble @CopyTerrain @PrintPostamble TraceAll ("Exported " <-cells " terrain cells in " <-rows " rows and " <-columns " columns.") :CopyTerrain 0 ->cells 0 ->rows PrintAll (":BuildListTerrain ") # Iterate over marked area, copying cell height # Copy Terrain "" ->terData Do (<-maxCellZ <-minCellZ) <-rows 1 + ->rows 0 ->columns Do (<-maxCellX <-minCellX) <-columns 1 + ->columns <-cells 1 + ->cells GetTerrain (I J ) ->ter if (<-ter 10 <) <-terData " " concat ->tmp endif <-terData <-ter concat ->terData <-terData " " concat ->terData loop PrintAll (<-terData) "" ->terData loop PrintAll (" List ->pasteTerrain ") #copy special terrain PrintAll (":BuildListSpecial ") "" ->terSData Do (<-maxCellZ <-minCellZ) Do (<-maxCellX <-minCellX) GetTerrainSpecial(I J) ->terS if (<-terS 10 <) <-terSData " " concat ->tmp endif <-terSData <-terS concat ->terSData <-terSData " " concat ->terSData loop PrintAll (<-terSData) "" ->terSData loop PrintAll (" List ->pasteSpecial ") TraceAllSp ("Copied " <-cells "cells. From min/max" <-minV <-maxV) If (GetListCount(<-area) eq0) --area endif # can't paste 0 zells :MarkArea toCell ->maxCellZ ->maxCellX toCell ->minCellZ ->minCellX ->_tex <-maxCellX <-minCellX - ->_width <-maxCellZ <-minCellZ - ->_height SetThemeOverlayRectPixels(<-themeSlot <-minCellX <-minCellZ <-_width <-_height <-_tex) :IsMouseValid if (<-mouseX gte0 and(<-mouseZ gte0) and(<-mouseX lt(<-mapSizeX)) and(<-mouseZ lt(<-mapSizeZ))) true else TraceAllSp ("Mouse not over terrain.") PlaySoundAtPosition("ADAMessage" 8 FromCell(<-mouseX <-mouseZ)) false endIf :once @Instructions # null mouse positions -1 ->mouseOldZ -1 ->mouseOldX GetMapSize ->mapSizeZ ->mapSizeX CreateThemeOverlay(<-themeSlot <-mapSizeX <-mapSizeZ Vector0) SetThemeOverlayEnabled(<-themeSlot true) # SetThemeOverlayPointFilter(<-themeSlot true) GetCameraTopDown ->initCamera # remember camera position SetCameraTopDown(true) GetPrintPrefixEnabled ->enabled SetPrintPrefixEnabled(false) # end Once :Instructions TraceAll ("Press " DQ "A" DQ " to export the entire map.") TraceAllSp (" OR ") TraceAllSp ("Left Mouse to mark terrain,") TraceAllSp ("Drag mouse to select area") TraceAllSp ("Mouse up to export selected area") TraceAllSp TraceAllSP ("Beeping sound means mouse is not over valid area.") :Destroyed TraceAllSP ("") If(<-exports eq0) TraceAllSp ("No map copy operations were performed.") else TraceAllSP ("Remember to save rpl.txt now!") If (<-exports eq (1)) TraceAllSp ("One map export operation performed.") else TraceAllSp (<-exports "Map copy operations performed.") endIf endIf TraceAllSP ("") SetCameraTopDown(<-initCamera) SetPrintPrefixEnabled(<-enabled) SetThemeOverlayEnabled(<-themeSlot false) # delete DestroyThemeOverlay(<-themeSlot) # stomp on it :PrintPreamble PrintAll ("# Terrain Import V 2.0. Copy Tick Timer: " GetGameTickCount ) PrintAll (" ") PrintAll ("$lmb:0 # left click button ") PrintAll ("$rmb:1 # right click button ") PrintAll ("$themeSlot:1 # theme overlay slot to use ") Printall (" ") PrintAll ("Once ") PrintAll ("TraceAllSp ( " DQ "Use right mouse button to paste." DQ ")" ) PrintAll (<-blitWidth " ->blitWidth ") PrintAll (<-blitHeight " ->blitHeight ") PrintAll (" GetMapSize ->mapSizeZ ->mapSizeX ") PrintAll (" GetCameraTopDown ->initCamera # remember camera position ") PrintAll (" SetCameraTopDown(true) ") PrintAll (" @BuildListTerrain ") PrintAll (" @BuildListSpecial ") PrintAll (" @MakeBlitImage ") PrintAll ("endOnce ") PrintAll (" ") PrintAll ("GetPointerTerrainCoords ->mouseZ ->mouseX ") PrintAll ("@BlitImage ") PrintAll (" ") PrintAll ("if (GetMouseButtonDown(<-rmb true )) ") PrintAll (" if (@isMouseValid) ") printall (" @Import ") PrintAll (" endIf ") printall ("endif ") PrintAll (" ") PrintAll (":Import ") PrintAll (" EditAddUndo (0) ") PrintAll (" 0 ->rows ") Printall (" 0 ->cell ") Printall (" Do (<-mouseZ <-blitHeight + , <-mouseZ ) ") Printall (" <-rows 1 + ->rows ") PrintAll (" 0 ->columns ") Printall (" Do (<-mouseX <-blitWidth +, <-mouseX ) ") Printall (" SetTerrain (I J <-pasteTerrain[<-cell]) ") Printall (" SetTerrainSpecial (I J <-pasteSpecial[<-cell]) ") Printall (" <-cell 1 + ->cell ") Printall (" <-columns 1 + ->columns ") Printall (" loop ") Printall (" loop ") PrintAll (" TraceAll (" DQ "Imported " DQ " <-cell " DQ " terrain cells in " DQ " <-rows " DQ " rows and " DQ " <-columns " DQ " columns." DQ ") ") PrintAll (" ") PrintAll (":BlitImage ") PrintAll (" ClearThemeOverlay(<-themeSlot Vector0) ") # remove old mark ") PrintAll (" SetThemeOverlayPixels(<-themeSlot, <-mouseX, <-mouseZ, # set paste overlay ") PrintAll (" <-blitWidth <-blitHeight <-blitMap) ") Printall (" ") PrintAll (":MakeBlitImage ") pRINTaLL (" CreateList ->blitMap ") pRINTaLL (" 0 ->i ") pRINTaLL (" Do (<-blitHeight 0) ") pRINTaLL (" Do (<-blitWidth 0 ) ") pRINTaLL (" <-pasteTerrain[<-i] ->c ") pRINTaLL (" <-c 20.0 / ->c ") pRINTaLL (" v4(<-c <-c <-c .6) ->color ") pRINTaLL (" AppendToList(<-blitMap <-color) ") pRINTaLL (" <-i 1 + ->i ") pRINTaLL (" loop ") pRINTaLL (" Loop ") pRINTaLL (" GetMapSize ->mapSizeZ ->mapSizeX ") pRINTaLL (" CreateThemeOverlay(<-themeSlot <-mapSizeX <-mapSizeZ Vector0) ") pRINTaLL (" SetThemeOverlayEnabled(<-themeSlot true) ") pRINTaLL (" TraceAllSp (<-i " DQ " cells in blit map " DQ " ) ") Printall (" ") PrintAll (" ") PrintAll (":IsMouseValid ") PrintAll (" if (<-mouseX gte0 ") PrintAll (" and(<-mouseZ gte0) ") PrintAll (" and(<-mouseX lt(<-mapSizeX)) ") PrintAll (" and(<-mouseZ lt(<-mapSizeZ))) ") PrintAll (" true ") PrintAll (" else ") PrintAll (" false ") PrintAll (" endIf ") PrintAll (" ") PrintAll (":Destroyed") PrintAll (" SetCameraTopDown(<-initCamera) ") PrintAll (" SetThemeOverlayEnabled(<-themeSlot false) # delete ") PrintAll (" DestroyThemeOverlay(<-themeSlot) # stomp on it ") PrintAll (" ") PrintAll (" ") :PrintPostamble PrintAll ("### End ### ") \\ \\ \\ \\ Change notes: * V3 * now can export whole map with one key. Press "a" * message indicating how many operations were performed. For each uperation, there will be one program in RPL.txt. * V2 Change notes: * New version. Uses Terrain texture to show image * V2.1 Change notes * The` CONSOLE:` prefix is now suppressed, making it a bit easier to run. ----===== Sound Player ===== Looking for just that right sound for your next unit? Here is a player that lets you evaluate all the available in-game sounds. You know the drill. Download, and run from the Console. # Sounds $rmb:1 # right click button if(<-inputDelay gt0) <-inputDelay 1 - ->inputDelay endif if (GetMappedKey("Custom1" false) <-inputDelay eq0 &&) 2 ->inputDelay Mod(<-index 1 +, <-soundCount) ->index <-soundList[<-index] ->soundName TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ) endIf if(GetMappedKey("Custom2" false) <-inputDelay eq0 &&) 2 ->inputDelay Mod2(<-index 1 -, <-soundCount) ->index <-soundList[<-index] ->soundName TraceAll ("This is sound #" <-index ", " DQ <-soundName DQ) endIf if (GetMouseButtonDown(<-rmb true )) PlaySoundAtPosition(<-soundName 8 GetCameraPosition V3(0 0.1 0) +) endif :Once -1 ->index #So we start at 0, not 1. @MakeSoundList :MakeSoundList "ADAMessage" "ADAMessagesClose" "ADAMessagesOpen" "Achievement" "Aircraft" "Aircraft2" "BaseLand" "BerthaExplosion" "BerthaFire" "BombDrop" "BombDropSlide" "BombExplosion" "BraveWarning" "ButtonClick" "Click 1" "Click" "Click2" "CloseClick" "Damper" "DamperDone" "Dematerialize" "EggBrave" "EggBurst" "EggReleased" "ErrorBuild" "Explosion" "Explosion_1" "Explosion_2" "Explosion_3" "Explosion_4" "Explosion_5" "Explosion_7" "Explosion_8" "Explosion_9" "Explosion_10" "Explosion_11" "Explosion_12" "HoverOpen" "InfoCacheCollected" "Materialize" "MinimapAlert" "MissileExplosion" "MissileLaunch" "MissionFail" "MissionObjective" "MissionObjectiveFail" "MissionObjectiveRequiredComplete" "MissionScan" "InhibitorFiring" "MissionSpaceInitiateJump" "MissionSpacePanelClose" "MissionSpacePanelOpen" "MissionSpaceRegionNavSelect" "MissionWin" "MortarFire" "MortarShellExplosion" "NeutronBurst" "Notify" "PlanetView" "PlanetView2" "RainDrop" "Rematerialize" "RiftLabDepart" "Sci Fi Explosion 01" "Sci Fi Explosion 02" "Sci Fi Explosion 03" "Sci Fi Explosion 04" "Sci Fi Explosion 05" "Sci Fi Explosion 06" "Sci Fi Explosion 07" "Sci Fi Explosion 08" "Sci Fi Explosion 09" "Sci Fi Explosion 10" "Sci Fi Explosion 11" "Sci Fi Explosion 12" "Sci Fi Explosion 13" "Sci Fi Explosion 14" "Sci Fi Explosion 15" "Sci Fi Explosion 16" "Sci Fi Explosion 17" "Sci Fi Explosion 18" "Sci Fi Explosion 19" "Sci Fi Explosion 20" "Sci Fi Explosion 21" "Sci Fi Explosion 22" "Sci Fi Explosion 23" "Sci Fi Explosion 24" "Shutdown" "Singularity" "SingularityDone" "SingularityLaunch" "SniperFire" "SporeHit" "SporeLaunch" "SporePrelaunch" "SprayerFire" "Stun" "CannonFire" "SurviveBaseOffline" "SurviveBaseWarn" "ThorGun" "TotemExplosion" "UnitHoverLand" "UnitBuild" "UnitExplosion" "UnitHoverTakeoff" "UnitLand" "UnitPlace" "UnitTakeoff" "Warning" "Warning2" List ->soundList "AlarmClock" "GreenarRefinery" "InhibitorArming" "Overloading" "Pulsing Ambience" "Rocket" "SoundLoop_alarm0" "SoundLoop_alarm1" "SoundLoop_alarm2" "SoundLoop_alarm3" "SoundLoop_alarm4" "SoundLoop_alarm5" "SoundLoop_alarm6" "SoundLoop_alarm7" "SoundLoop_alarm8" "SoundLoop_alarm9" "SoundLoop_alarm10" "SoundLoop_alarm11" "SoundLoop_alarm12" "SoundLoop_alarm13" "SoundLoop_alarm14" "SoundLoop_alarm15" "SoundLoop_ambience0" "SoundLoop_ambience1" "SoundLoop_ambience2" "SoundLoop_ambience3" "SoundLoop_ambience4" "SoundLoop_ambience5" "SoundLoop_ambience6" "SoundLoop_ambience7" "SoundLoop_ambience8" "SoundLoop_ambience9" "SoundLoop_ambience10" "SoundLoop_gun0" "SoundLoop_gun1" "SoundLoop_gun2" "SoundLoop_gun3" "SoundLoop_hum0" "SoundLoop_hum1" "SoundLoop_hum2" "SoundLoop_hum3" "SoundLoop_hum4" "SoundLoop_hum5" "SoundLoop_hum6" "SoundLoop_hum7" "SoundLoop_hum8" "SoundLoop_hum9" "SoundLoop_hum10" "SoundLoop_hum11" "SoundLoop_hum12" "SoundLoop_hum13" "SoundLoop_hum14" "SoundLoop_hum15" "SoundLoop_hum16" "SoundLoop_hum17" "SoundLoop_hum18" "SoundLoop_machine0" "SoundLoop_machine1" "SoundLoop_machine2" "SoundLoop_machine3" "SoundLoop_machine4" "SoundLoop_machine5" "SoundLoop_machine6" "SoundLoop_machine7" "SoundLoop_machine8" "SoundLoop_machine9" "SoundLoop_machine10" "SoundLoop_machine11" "SoundLoop_machine12" "SoundLoop_misc0" "SoundLoop_misc1" "SoundLoop_misc2" "SoundLoop_misc3" "SoundLoop_misc4" "SoundLoop_misc5" "SoundLoop_misc6" "SoundLoop_misc7" "SoundLoop_misc8" "TotemFire" "ambient" List ->loopSoundList GetListCount(<-soundList) ->soundCount TraceAllSp("List of " <-soundCount "sounds available") TraceAllSp("There are also " GetListCOunt(<-loopSoundList) "looping sounds that cannot be played in this module") TraceAllSP("Press and hold KeyPad 1 / 2 to scroll through the sound list.") TraceAllSp("Left-click mouse to listen to it.") ===== Looping Sound Player ===== Looking for just that right **looping** sound for your next unit? 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. * Remember to unmute game music. # SoundLoops $rmb:1 # right click button if(<-inputDelay gt0) <-inputDelay 1 - ->inputDelay endif SetUnitPosition(<-unitUID GetCameraPosition V3(0 1.5 0) +) if(GetMappedKey("Custom1" false) <-inputDelay eq0 &&) 2 ->inputDelay Mod(<-index 1 +, <-soundCount) ->index <-loopSoundList[<-index] ->soundName TraceAll("This is sound #" <-index ", " DQ <-soundName DQ) endIf if(GetMappedKey("Custom2" false) <-inputDelay eq0 &&) 2 ->inputDelay Mod2(<-index 1 -, <-soundCount) ->index <-loopSoundList[<-index] ->soundName TraceAll("This is sound #" <-index ", " DQ <-soundName DQ) endIf if(GetMouseButtonDown(<-rmb true)) If(<-soundOn) StopSoundLoop(<-unitUID) Not(<-soundOn) ->soundOn SetPause(<-pause) TraceAllSp("Stopping " <-soundName " from playing") else TraceAllSp("Can you hear " <-soundName " playing?") SetPause (false) PlaySoundLoop(<-soundName 5 <-unitUID) Not(<-soundOn) ->soundOn endif endif :Destroyed TraceAllSp("Goodbye!") 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 "ADAMessage" "ADAMessagesClose" "ADAMessagesOpen" "Achievement" "Aircraft" "Aircraft2" "BaseLand" "BerthaExplosion" "BerthaFire" "BombDrop" "BombDropSlide" "BombExplosion" "BraveWarning" "ButtonClick" "Click 1" "Click" "Click2" "CloseClick" "Damper" "DamperDone" "Dematerialize" "EggBrave" "EggBurst" "EggReleased" "ErrorBuild" "Explosion" "Explosion_1" "Explosion_2" "Explosion_3" "Explosion_4" "Explosion_5" "Explosion_7" "Explosion_8" "Explosion_9" "Explosion_10" "Explosion_11" "Explosion_12" "HoverOpen" "InfoCacheCollected" "Materialize" "MinimapAlert" "MissileExplosion" "MissileLaunch" "MissionFail" "MissionObjective" "MissionObjectiveFail" "MissionObjectiveRequiredComplete" "MissionScan" "InhibitorFiring" "MissionSpaceInitiateJump" "MissionSpacePanelClose" "MissionSpacePanelOpen" "MissionSpaceRegionNavSelect" "MissionWin" "MortarFire" "MortarShellExplosion" "NeutronBurst" "Notify" "PlanetView" "PlanetView2" "RainDrop" "Rematerialize" "RiftLabDepart" "Sci Fi Explosion 01" "Sci Fi Explosion 02" "Sci Fi Explosion 03" "Sci Fi Explosion 04" "Sci Fi Explosion 05" "Sci Fi Explosion 06" "Sci Fi Explosion 07" "Sci Fi Explosion 08" "Sci Fi Explosion 09" "Sci Fi Explosion 10" "Sci Fi Explosion 11" "Sci Fi Explosion 12" "Sci Fi Explosion 13" "Sci Fi Explosion 14" "Sci Fi Explosion 15" "Sci Fi Explosion 16" "Sci Fi Explosion 17" "Sci Fi Explosion 18" "Sci Fi Explosion 19" "Sci Fi Explosion 20" "Sci Fi Explosion 21" "Sci Fi Explosion 22" "Sci Fi Explosion 23" "Sci Fi Explosion 24" "Shutdown" "Singularity" "SingularityDone" "SingularityLaunch" "SniperFire" "SporeHit" "SporeLaunch" "SporePrelaunch" "SprayerFire" "Stun" "CannonFire" "SurviveBaseOffline" "SurviveBaseWarn" "ThorGun" "TotemExplosion" "UnitHoverLand" "UnitBuild" "UnitExplosion" "UnitHoverTakeoff" "UnitLand" "UnitPlace" "UnitTakeoff" "Warning" "Warning2" List ->soundList "AlarmClock" "GreenarRefinery" "InhibitorArming" "Overloading" "Pulsing Ambience" "Rocket" "SoundLoop_alarm0" "SoundLoop_alarm1" "SoundLoop_alarm2" "SoundLoop_alarm3" "SoundLoop_alarm4" "SoundLoop_alarm5" "SoundLoop_alarm6" "SoundLoop_alarm7" "SoundLoop_alarm8" "SoundLoop_alarm9" "SoundLoop_alarm10" "SoundLoop_alarm11" "SoundLoop_alarm12" "SoundLoop_alarm13" "SoundLoop_alarm14" "SoundLoop_alarm15" "SoundLoop_ambience0" "SoundLoop_ambience1" "SoundLoop_ambience2" "SoundLoop_ambience3" "SoundLoop_ambience4" "SoundLoop_ambience5" "SoundLoop_ambience6" "SoundLoop_ambience7" "SoundLoop_ambience8" "SoundLoop_ambience9" "SoundLoop_ambience10" "SoundLoop_gun0" "SoundLoop_gun1" "SoundLoop_gun2" "SoundLoop_gun3" "SoundLoop_hum0" "SoundLoop_hum1" "SoundLoop_hum2" "SoundLoop_hum3" "SoundLoop_hum4" "SoundLoop_hum5" "SoundLoop_hum6" "SoundLoop_hum7" "SoundLoop_hum8" "SoundLoop_hum9" "SoundLoop_hum10" "SoundLoop_hum11" "SoundLoop_hum12" "SoundLoop_hum13" "SoundLoop_hum14" "SoundLoop_hum15" "SoundLoop_hum16" "SoundLoop_hum17" "SoundLoop_hum18" "SoundLoop_machine0" "SoundLoop_machine1" "SoundLoop_machine2" "SoundLoop_machine3" "SoundLoop_machine4" "SoundLoop_machine5" "SoundLoop_machine6" "SoundLoop_machine7" "SoundLoop_machine8" "SoundLoop_machine9" "SoundLoop_machine10" "SoundLoop_machine11" "SoundLoop_machine12" "SoundLoop_misc0" "SoundLoop_misc1" "SoundLoop_misc2" "SoundLoop_misc3" "SoundLoop_misc4" "SoundLoop_misc5" "SoundLoop_misc6" "SoundLoop_misc7" "SoundLoop_misc8" "TotemFire" "ambient" List ->loopSoundList GetListCount(<-loopSoundList) ->soundCount TraceAllSp("List of " <-soundCount "looping sounds available") 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("Left-click to turn off again.") TraceAllSp("Remember to mute music!") ---- ===== Map Information ===== Looking for some general map information on a specific map, including how much of various resources can be produced? This script is primarily income related information, but there's a little bit of general information in there, too. All values should be correct, but errors are possible. Below is a sample output of a chronum map, and the source itself that you can put in as a console script. There's some notes about the output in the top of the source code. Ground Tiles: 12340 Resource Tiles: 620 Void Tiles: 904 Eco: 347 / 347 Potential Soylent Energy: 34.06 Eco Energy Value: 3.83 Resource Terrain could be Energy Gen: 41 or Bluite: 516.67 /min Map resources per minute Bluite: 0 Redon: 90 Greenar Crystals: 9.68 Liftic Produced: 116.2 #"Notes: You have to advance a frame for eco counts to be updated." #"Values assume no ERNs" #"Soylent value assumes flat terrain, and 100% efficiency." GetMapSize ->sizeZ ->sizeX 0 ->tiles 0 ->void 0 ->resource 0 ->crystalsMin 0 ->blueMin 0 ->redMin Createtable ->table 0 ->table{1} <-sizeZ 0 do <-sizeX 0 do GetTerrain(i j) ->terrainHeight if (<-terrainHeight gt0) <-tiles 1 add ->tiles GetTerrainSpecial(i j) ->specialTerrainSlot <-table{<-specialTerrainSlot} 1 add ->table{<-specialTerrainSlot} else <-void 1 add ->void endif loop loop GetUnitsByType("resourcered" 1) ->units <-units GetListCount 0 do GetUnitSettings(<-units[i]) ->unitSettings <-redMin 1800 <-unitSettings{"Interval"} div add ->redMin loop GetUnitsByType("resourceblue" 1) ->units <-units GetListCount 0 do GetUnitSettings(<-units[i]) ->unitSettings <-blueMin 1800 <-unitSettings{"Interval"} div add ->blueMin loop GetUnitsByType("greenarmother" 1) ->units <-units GetListCount 0 do 0 ->validTiles GetUnitCell(<-units[i]) ->posZ ->posX <-posZ 11 add <-posZ 10 sub do <-posX 11 add <-posX 10 sub do DistanceCell((<-posX) (<-posZ) i j) ->dist if (<-dist 10.5 lte) i ->x j ->z @TileOkay if (<-okay gt0) <-validTiles 1 add ->validTiles endif endif loop loop <-validTiles 339.0 div ->pct GetUnitSettings(<-units[i]) ->unitSettings <-crystalsMin 1800 <-unitSettings{"Interval"} div <-pct mul add ->crystalsMin loop GetEcoCounts ->liveCount ->stumpCount <-stumpCount <-liveCount add ->totalEco " Ground Tiles:" <-tiles LF "Resource Tiles:" <-table{1} LF "Void Tiles:" <-void LF "Eco:" <-liveCount "/" <-totalEco LF LF "Potential Soylent Energy:" <-tiles 0.00276 mul 2 round lf "Eco Energy Value:" <-totalEco 0.01104 mul 2 round lf lf "Resource Terrain could be" lf "Energy Gen:" <-table{1} 15 div "or" lf "Bluite:" <-table{1} 1.2 div 2 round "/min" lf LF "Map resources per minute" LF "Bluite:" <-blueMin 2 round lf "Redon:" <-redMin 2 round lf "Greenar Crystals:" <-crystalsMin 2 round lf "Liftic Produced: " <-crystalsMin 12 mul 1 round lf traceallsp :TileOkay 1 ->okay GetTerrain(<-x <-z) ->terrainHeight if (<-terrainHeight lte0 GetCellOccupiedCount(<-x <-z) gt0 or) 0 ->okay return endif <-z 2 add <-z 1 sub do <-x 2 add <-x 1 sub do if (GetTerrain(i j) <-terrainHeight neq ) 0 ->okay break endif loop loop ---- ===== Measure Execution Time ===== Optimizing code is important. This helps you quickly test how well different options run and choose the best one. Also can check for loose items on the stack if you want. $avgNum:10 #The average, min, and max will be over this many invocations. $showMin:1 $showAvg:1 $showMax:1 $checkStack:1 $iterations:1000 #How many times to execute the code every invocation. ElapsedTime ->start <-iterations 0 do #CODE TO MEASURE GOES HERE loop @EndMeasure #=============== :Once <-avgNum CreateListStartingSize ->timeList :EndMeasure ElapsedTime <-start sub ->timeTaken if(StackSize <-checkStack &&) Trace(StackSize <-iterations div " items left on the stack!" concat) endif ClearStack <-timeTaken ->timeList[InvocationCount <-avgNum mod] 9999999 ->min 0 ->max 0 InvocationCount 1 add <-avgNum min 0 do #Find the average <-timeList[i] dup <-min min ->min dup <-max max ->max add loop <-avgNum InvocationCount min div ->avg <-timeTaken @Pad if(<-showMin) "min: " <-min @Pad endif if(<-showAvg) "avg: " <-avg 1 round @Pad endif if(<-showMax) "max: " <-max @Pad endif TraceAll #Gets close, but non-monospace fonts (like the tracelog) aren't exact. #Doesn't use a tab because sometimes that makes the issue worse. :Pad dup StringLength 8 swap sub 0 do " " loop ---- ===== Map Symmetry/Transform Tool ===== Run this script in console to provide several functions that can copy half/quarter of the map and reflect/rotate and paste it (special terrain types are also copied, but units and creeper are not). Any operation may be undone with Ctrl-Z or redone with Ctrl-Y. Key binds are shown at the top of the script, and are also shown/described here for ease of use. Be aware that some functions will not execute on non-square maps since symmetry cannot be maintained. # Symmetrifier # How to use: Paste script into console script file, run (not just once), then press one of the keys shown in the list below to perform the the described symmetry/map transform operation # NOTE: Units are not affected by any of the below functions # NOTE: On maps with odd-numbered side lengths, a 1-wide strip of terrain in the middle is unaffected # NOTE: Script uses above-keyboard number keys and left-hand modifier keys (left alt, left shift and left control) # ================================= # |1| = Left to Right Reflectional # |2| = Right to Left Reflectional # |3| = Bottom to Top Reflectional # |4| = Top to Bottom Reflectional # |5| = Bottom Left to Top Right Diagonal Reflectional (Requires Square Map) # |6| = Top Right to Bottom Left Diagonal Reflectional (Requires Square Map) # |7| = Bottom Right to Top Left Diagonal Reflectional (Requires Square Map) # |8| = Top left to Bottom Right Diagonal Reflectional (Requires Square Map) # |9| = Bottom Left Quadrilateral Reflectional # |0| = Bottom Right Quadrilateral Reflectional # |Shift+1| = Top Left Quadrilateral Reflectional # |Shift+2| = Top Right Quadrilateral Reflectional # ============================================= # |Shift+3| = Left Half Rotational # |Shift+4| = Right Half Rotational # |Shift+5| = Top Half Rotational # |Shift+6| = Bottom Half Rotational # |Shift+7| = Bottom Left Half Diagonal Rotational # |Shift+8| = Bottom Right Half Diagonal Rotational # |Shift+9| = Top Left Half Diagonal Rotational # |Shift+0| = Top Right Half Diagonal Rotational # |Alt+1| = Bottom Left Quarter Rotational (Requires Square Map) # |Alt+2| = Bottom Right Quarter Rotational (Requires Square Map) # |Alt+3| = Top Left Quarter Rotational (Requires Square Map) # |Alt+4| = Top Right Quarter Rotational (Requires Square Map) # |Alt+5| = Left Quarter Rotational (Requires Square Map) # |Alt+6| = Right Quarter Rotational (Requires Square Map) # |Alt+7| = Top Quarter Rotational (Requires Square Map) # |Alt+8| = Bottom Quarter Rotational (Requires Square Map) # ==================================== # |Ctrl+1| = Rotate Map 90 Clockwise (Requires Square Map) # |Ctrl+2| = Rotate Map 90 Counterclockwise (Requires Square Map) # |Ctrl+3| = Rotate Map 180 # |Ctrl+4| = Flip Map Left to Right # |Ctrl+5| = Flip Map Top to Bottom # |Ctrl+6| = Flip Map Bottom Left to Top Right Diagonal (Requires Square Map) # |Ctrl+7| = Flip Map Bottom Right to Top Left Diagonal (Requires Square Map) # ============================================= # |Ctrl+Arrow Key| = Scroll Map 1 tile in direction # |Shift+Arrow Key| = Scroll Map 10 tiles in direction once GetMapSize ->mapHeight ->mapWidth if(<-mapWidth 2 mod neq0) <-mapWidth 1 sub 2 div ->halfWidth else <-mapWidth 2 div ->halfWidth endif if(<-mapHeight 2 mod neq0) <-mapHeight 1 sub 2 div ->halfHeight else <-mapHeight 2 div ->halfHeight endif if(<-mapHeight <-mapWidth eq) true ->squareMap else false ->squareMap endif CreateListStartingSize(<-mapHeight <-mapWidth mul) ->terrainBuffer #Only used in flipping/rotating/scrolling map CreateListStartingSize(<-mapHeight <-mapWidth mul) ->specialBuffer #Ditto endonce if(GetKey("LeftShift" true)) if(GetKeyDown("Alpha1" true)) EditAddUndo(0) @TopLeftQuadrilateral endif if(GetKeyDown("Alpha2" true)) EditAddUndo(0) @TopRightQuadrilateral endif if(GetKeyDown("Alpha3" true)) EditAddUndo(0) @LeftHalfRotational endif if(GetKeyDown("Alpha4" true)) EditAddUndo(0) @RightHalfRotational endif if(GetKeyDown("Alpha5" true)) EditAddUndo(0) @TopHalfRotational endif if(GetKeyDown("Alpha6" true)) EditAddUndo(0) @BottomHalfRotational endif if(GetKeyDown("Alpha7" true)) EditAddUndo(0) @BottomLeftHalfRotational endif if(GetKeyDown("Alpha8" true)) EditAddUndo(0) @BottomRightHalfRotational endif if(GetKeyDown("Alpha9" true)) EditAddUndo(0) @TopLeftHalfRotational endif if(GetKeyDown("Alpha0" true)) EditAddUndo(0) @TopRightHalfRotational endif if(GetKeyDown("LeftArrow" true)) EditAddUndo(0) 10 ->scr @ScrollMapLeft endif if(GetKeyDown("RightArrow" true)) EditAddUndo(0) 10 ->scr @ScrollMapRight endif if(GetKeyDown("UpArrow" true)) EditAddUndo(0) 10 ->scr @ScrollMapUp endif if(GetKeyDown("DownArrow" true)) EditAddUndo(0) 10 ->scr @ScrollMapDown endif else if(GetKey("LeftAlt" true)) if(GetKeyDown("Alpha1" true) true eq <-squareMap true eq and) EditAddUndo(0) @BottomLeftQuarterRotational endif if(GetKeyDown("Alpha2" true) true eq <-squareMap true eq and) EditAddUndo(0) @BottomRightQuarterRotational endif if(GetKeyDown("Alpha3" true) true eq <-squareMap true eq and) EditAddUndo(0) @TopLeftQuarterRotational endif if(GetKeyDown("Alpha4" true) true eq <-squareMap true eq and) EditAddUndo(0) @TopRightQuarterRotational endif if(GetKeyDown("Alpha5" true) true eq <-squareMap true eq and) EditAddUndo(0) @LeftQuarterRotational endif if(GetKeyDown("Alpha6" true) true eq <-squareMap true eq and) EditAddUndo(0) @RightQuarterRotational endif if(GetKeyDown("Alpha7" true) true eq <-squareMap true eq and) EditAddUndo(0) @TopQuarterRotational endif if(GetKeyDown("Alpha8" true) true eq <-squareMap true eq and) EditAddUndo(0) @BottomQuarterRotational endif else if(GetKey("LeftControl" true)) if(GetKeyDown("Alpha1" true) true eq <-squareMap true eq and) EditAddUndo(0) @Rotate90Clockwise endif if(GetKeyDown("Alpha2" true) true eq <-squareMap true eq and) EditAddUndo(0) @Rotate90Counterclockwise endif if(GetKeyDown("Alpha3" true)) EditAddUndo(0) @Rotate180 endif if(GetKeyDown("Alpha4" true)) EditAddUndo(0) @FlipLeftToRight endif if(GetKeyDown("Alpha5" true)) EditAddUndo(0) @FlipTopToBottom endif if(GetKeyDown("Alpha6" true) true eq <-squareMap true eq and) EditAddUndo(0) @FlipBottomLeftToTopRight endif if(GetKeyDown("Alpha7" true) true eq <-squareMap true eq and) EditAddUndo(0) @FlipBottomRightToTopLeft endif if(GetKeyDown("LeftArrow" true)) EditAddUndo(0) 1 ->scr @ScrollMapLeft endif if(GetKeyDown("RightArrow" true)) EditAddUndo(0) 1 ->scr @ScrollMapRight endif if(GetKeyDown("UpArrow" true)) EditAddUndo(0) 1 ->scr @ScrollMapUp endif if(GetKeyDown("DownArrow" true)) EditAddUndo(0) 1 ->scr @ScrollMapDown endif else if(GetKeyDown("Alpha1" true)) EditAddUndo(0) @LeftToRight endif if(GetKeyDown("Alpha2" true)) EditAddUndo(0) @RightToLeft endif if(GetKeyDown("Alpha3" true)) EditAddUndo(0) @BottomToTop endif if(GetKeyDown("Alpha4" true)) EditAddUndo(0) @TopToBottom endif if(GetKeyDown("Alpha5" true) true eq <-squareMap true eq and) EditAddUndo(0) @BottomLeftToTopRight endif if(GetKeyDown("Alpha6" true) true eq <-squareMap true eq and) EditAddUndo(0) @TopRightToBottomLeft endif if(GetKeyDown("Alpha7" true) true eq <-squareMap true eq and) EditAddUndo(0) @BottomRightToTopLeft endif if(GetKeyDown("Alpha8" true) true eq <-squareMap true eq and) EditAddUndo(0) @TopLeftToBottomRight endif if(GetKeyDown("Alpha9" true)) EditAddUndo(0) @BottomLeftQuadrilateral endif if(GetKeyDown("Alpha0" true)) EditAddUndo(0) @BottomRightQuadrilateral endif endif endif endif :LeftToRight ######################################################################################### do(<-mapHeight 0) do(<-halfWidth 0) SetTerrain(<-mapWidth 1 sub I sub J GetTerrain(I J)) SetTerrainSpecial(<-mapWidth 1 sub I sub J GetTerrainSpecial(I J)) loop loop :RightToLeft ######################################################################################### do(<-mapHeight 0) do(<-halfWidth 0) SetTerrain(I J GetTerrain(<-mapWidth 1 sub I sub J)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub I sub J)) loop loop :BottomToTop ######################################################################################### do(<-halfHeight 0) do(<-mapWidth 0) SetTerrain(I <-mapHeight 1 sub J sub GetTerrain(I J)) SetTerrainSpecial(I <-mapheight 1 sub J sub GetTerrainSpecial(I J)) loop loop :TopToBottom ######################################################################################### do(<-halfHeight 0) do(<-mapWidth 0) SetTerrain(I J GetTerrain(I <-mapHeight 1 sub J sub)) SetTerrainSpecial(I J GetTerrainSpecial(I <-mapheight 1 sub J sub)) loop loop :BottomLeftToTopRight ################################################################################ do(<-mapHeight 0) do(<-mapWidth 0) if(I J add <-mapWidth lte) SetTerrain(<-mapWidth 1 sub J sub <-mapHeight 1 sub I sub GetTerrain(I J)) SetTerrainSpecial(<-mapWidth 1 sub J sub <-mapHeight 1 sub I sub GetTerrainSpecial(I J)) endif loop loop :TopRightToBottomLeft ################################################################################ do(<-mapHeight 0) do(<-mapWidth 0) if(I J add <-mapWidth lte) SetTerrain(I J GetTerrain(<-mapWidth 1 sub J sub <-mapHeight 1 sub I sub)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub J sub <-mapHeight 1 sub I sub)) endif loop loop :BottomRightToTopLeft ################################################################################ do(<-mapHeight 0) do(<-mapWidth 0) if(<-mapWidth 1 sub I sub J add <-mapWidth lte) SetTerrain(J I GetTerrain(I J)) SetTerrainSpecial(J I GetTerrainSpecial(I J)) endif loop loop :TopLeftToBottomRight ################################################################################ do(<-mapHeight 0) do(<-mapWidth 0) if(<-mapWidth 1 sub I sub J add <-mapWidth lte) SetTerrain(I J GetTerrain(J I)) SetTerrainSpecial(I J GetTerrainSpecial(J I)) endif loop loop :BottomLeftQuadrilateral ############################################################################# @LeftToRight @BottomToTop :BottomRightQuadrilateral ############################################################################ @RightToLeft @BottomToTop :TopLeftQuadrilateral ################################################################################ @LeftToRight @TopToBottom :TopRightQuadrilateral ############################################################################### @RightToLeft @TopToBottom :LeftHalfRotational ################################################################################## do(<-mapHeight 0) do(<-halfWidth 0) SetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrain(I J)) SetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrainSpecial(I J)) loop loop :RightHalfRotational ################################################################################# do(<-mapHeight 0) do(<-halfWidth 0) SetTerrain(I J GetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) loop loop :TopHalfRotational ################################################################################### do(<-halfHeight 0) do(<-mapWidth 0) SetTerrain(I J GetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) loop loop :BottomHalfRotational ################################################################################ do(<-halfHeight 0) do(<-mapWidth 0) SetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrain(I J)) SetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrainSpecial(I J)) loop loop :BottomLeftHalfRotational ############################################################################ do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(I) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt) SetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrain(I J)) SetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrainSpecial(I J)) endif loop loop :TopRightHalfRotational ############################################################################## do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(I) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt) SetTerrain(I J GetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) endif loop loop :BottomRightHalfRotational ########################################################################### do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(<-mapWidth 1 sub I sub) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt) SetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrain(I J)) SetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrainSpecial(I J)) endif loop loop :TopLeftHalfRotational ############################################################################### do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(<-mapWidth 1 sub I sub) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt) SetTerrain(I J GetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub )) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub )) endif loop loop :BottomLeftQuarterRotational ######################################################################### do(<-halfHeight 0) do(<-halfWidth 0) SetTerrain(J <-mapHeight 1 sub I sub GetTerrain(I J)) SetTerrainSpecial(J <-mapHeight 1 sub I sub GetTerrainSpecial(I J)) loop loop @LeftHalfRotational :TopLeftQuarterRotational ############################################################################ do(<-halfHeight 0) do(<-halfWidth 0) SetTerrain(I J GetTerrain(J <-mapHeight 1 sub I sub)) SetTerrainSpecial(I J GetTerrainSpecial(J <-mapHeight 1 sub I sub)) loop loop @LeftHalfRotational :BottomRightQuarterRotational ######################################################################## do(<-halfHeight 0) do(<-halfWidth 0) SetTerrain(I J GetTerrain(<-mapWidth 1 sub J sub I)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub J sub I)) loop loop @BottomHalfRotational :TopRightQuarterRotational ########################################################################### do(<-halfHeight 0) do(<-halfWidth 0) SetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrain(<-mapWidth 1 sub J sub I)) SetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub GetTerrainSpecial(<-mapWidth 1 sub J sub I)) loop loop @RightHalfRotational :LeftQuarterRotational ############################################################################### do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(I) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt asfloat(I) asfloat(<-mapWidth) div asfloat(<-mapHeight 1 sub J sub) asfloat(<-mapHeight) div add 1 lt and) SetTerrain(J <-mapHeight 1 sub I sub GetTerrain(I J)) SetTerrainSpecial(J <-mapHeight 1 sub I sub GetTerrainSpecial(I J)) endif loop loop @TopLeftHalfRotational :RightQuarterRotational ############################################################################## do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(I) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt asfloat(I) asfloat(<-mapWidth) div asfloat(<-mapHeight 1 sub J sub) asfloat(<-mapHeight) div add 1 lt and) SetTerrain(J <-mapHeight 1 sub I sub GetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) SetTerrainSpecial(J <-mapHeight 1 sub I sub GetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub)) endif loop loop @TopRightHalfRotational :TopQuarterRotational ################################################################################ do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(I) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt asfloat(I) asfloat(<-mapWidth) div asfloat(<-mapHeight 1 sub J sub) asfloat(<-mapHeight) div add 1 lt and) SetTerrain(I J GetTerrain(J <-mapHeight 1 sub I sub)) SetTerrainSpecial(I J GetTerrainSpecial(J <-mapHeight 1 sub I sub)) endif loop loop @TopLeftHalfRotational :BottomQuarterRotational ############################################################################# do(<-mapHeight 0) do(<-mapWidth 0) if(asfloat(I) asfloat(<-mapWidth) div asfloat(J) asfloat(<-mapHeight) div add 1 lt asfloat(I) asfloat(<-mapWidth) div asfloat(<-mapHeight 1 sub J sub) asfloat(<-mapHeight) div add 1 lt and) SetTerrain(I J GetTerrain(<-mapWidth 1 sub J sub I)) SetTerrainSpecial(I J GetTerrainSpecial(<-mapWidth 1 sub J sub I)) endif loop loop @BottomLeftHalfRotational :Rotate90Clockwise ################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(<-mapWidth 1 sub J sub I) ->h GetTerrainSpecial(<-mapWidth 1 sub J sub I) ->sp @SetBuffersAt loop loop @PrintBuffers :Rotate90Counterclockwise ############################################################################ do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(J <-mapHeight 1 sub I sub) ->h GetTerrainSpecial(J <-mapheight 1 sub I sub) ->sp @SetBuffersAt loop loop @PrintBuffers :Rotate180 ########################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub) ->h GetTerrainSpecial(<-mapWidth 1 sub I sub <-mapHeight 1 sub J sub) ->sp @SetBuffersAt loop loop @PrintBuffers :FlipLeftToRight ##################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(<-mapWidth 1 sub I sub J) ->h GetTerrainSpecial(<-mapWidth 1 sub I sub J) ->sp @SetBuffersAt loop loop @PrintBuffers :FlipTopToBottom ##################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(I <-mapHeight 1 sub J sub) ->h GetTerrainSpecial(I <-mapHeight 1 sub J sub) ->sp @SetBuffersAt loop loop @PrintBuffers :FlipBottomLeftToTopRight ############################################################################ do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(<-mapHeight 1 sub J sub <-mapWidth 1 sub I sub) ->h GetTerrainSpecial(<-mapHeight 1 sub J sub <-mapWidth 1 sub I sub) ->sp @SetBuffersAt loop loop @PrintBuffers :FlipBottomRightToTopLeft ############################################################################ do(<-mapHeight 0) do(<-mapWidth 0) I ->x J ->z GetTerrain(J I) ->h GetTerrainSpecial(J I) ->sp @SetBuffersAt loop loop @PrintBuffers :ScrollMapLeft ####################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) I <-scr add ->x2 if(<-x2 <-mapWidth gte) <-x2 <-mapWidth sub ->x2 endif I ->x J ->z GetTerrain(<-x2 J) ->h GetTerrainSpecial(<-x2 J) ->sp @SetBuffersAt loop loop @PrintBuffers :ScrollMapRight ###################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) I <-scr sub ->x2 if(<-x2 lt0) <-x2 <-mapWidth add ->x2 endif I ->x J ->z GetTerrain(<-x2 J) ->h GetTerrainSpecial(<-x2 J) ->sp @SetBuffersAt loop loop @PrintBuffers :ScrollMapUp ######################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) J <-scr sub ->z2 if(<-z2 lt0) <-z2 <-mapHeight add ->z2 endif I ->x J ->z GetTerrain(I <-z2) ->h GetTerrainSpecial(I <-z2) ->sp @SetBuffersAt loop loop @PrintBuffers :ScrollMapDown ####################################################################################### do(<-mapHeight 0) do(<-mapWidth 0) J <-scr add ->z2 if(<-z2 <-mapHeight gte) <-z2 <-mapHeight sub ->z2 endif I ->x J ->z GetTerrain(I <-z2) ->h GetTerrainSpecial(I <-z2) ->sp @SetBuffersAt loop loop @PrintBuffers :SetBuffersAt ######################################################################################## SetListElement(<-terrainBuffer <-z <-mapWidth mul <-x add <-h) SetListElement(<-specialBuffer <-z <-mapWidth mul <-x add <-sp) :PrintBuffers ######################################################################################## do(GetListCount(<-terrainBuffer) 0) SetTerrain(I asint(<-mapWidth) mod I asint(<-mapWidth) div GetListElement(<-terrainBuffer I)) SetTerrainSpecial(I asint(<-mapWidth) mod I asint(<-mapWidth) div GetListElement(<-specialBuffer I)) loop # Made with luv by Argonwolf Note: All reflections/rotations leave the original half/quarter intact when pasting a copy 1 - Reflect left half of map to right 2 - Reflect right half of map to left 3 - Reflect bottom half of map to top 4 - Reflect top half of map to bottom 5 - Reflect bottom-left half of map to top-right diagonally (Requires square map) 6 - Reflect top-right half of map to bottom-left diagonally (Requires square map) 7 - Reflect bottom-right half of map to top-left diagonally (Requires square map) 8 - Reflect top-left half of map to bottom-right diagonally (Requires square map) 9 - Reflect bottom-left quarter of map to all four corners 0 - Reflect bottom-right quarter of map to all four corners Shift+1 - Reflect top-left quarter of map to all four corners Shift+2 - Reflect top-right quarter of map to all four corners Shift+3 - Rotate left half of map to right Shift+4 - Rotate right half of map to left Shift+5 - Rotate top half of map to bottom Shift+6 - Rotate bottom half of map to top Shift+7 - Rotate bottom-left half of map to top-right diagonally Shift+8 - Rotate bottom-right half of map to top-left diagonally Shift+9 - Rotate top-left half of map to bottom-right diagonally Shift+0 - Rotate top-right half of map to bottom-left diagonally Alt+1 - Rotate bottom-left quarter of map to all four corners (Requires square map) Alt+2 - Rotate bottom-right quarter of map to all four corners (Requires square map) Alt+3 - Rotate top-left quarter of map to all four corners (Requires square map) Alt+4 - Rotate top-right quarter of map to all four corners (Requires square map) Alt+5 - Rotate left triangular quarter of map to all four sides (Requires square map) Alt+6 - Rotate right triangular quarter of map to all four sides (Requires square map) Alt+7 - Rotate top triangular quarter of map to all four sides (Requires square map) Alt+8 - Rotate bottom triangular quarter of map to all four sides (Requires square map) Ctrl+1 - Rotate map 90 degrees clockwise (Requires square map) Ctrl+2 - Rotate map 90 degrees counterclockwise (Requires square map) Ctrl+3 - Rotate map 180 degrees Ctrl+4 - Flip map left to right Ctrl+5 - Flip map top to bottom Ctrl+6 - Flip map bottom-left to top-right (Requires square map) Ctrl+7 - Flip map bottom-right to top-left (Requires square map) Ctrl+Arrow - Scroll map 1 tile in direction of arrow (off-map tiles wrap to other side) Shift+Arrow - Scroll map 10 tiles in direction of arrow (off-map tiles wrap to other side) ---- ===== Reset Mesh ===== # ResetMesh # For mesh values, see: https://knucklecracker.com/wiki/doku.php?id=4rpl:commands:getmeshhealth GetMapSize ->sizeZ ->sizeX do(<-sizeZ 0) do(<-sizeX 0) if(GetMeshHealth(I J) neq0) SetMeshHealth(I J -1000000) endif loop loop ---- ===== Locate all cells in a range ===== 4RPL code that will locate cells in range of a midpoint and within a certain radius. # CellinRange 2021/06/26 # Return a list of cells given a midpoint and a radius. 115 ->x 54 ->z 20 ->radius CreateList ->cells # will contain list of cells in range of call Do (<-x <-radius 1 + +, <-x <-radius -) #x #J Do (<-z <-radius 1 + +, <-z <-radius -) #z #I If (DistanceCell(<-x <-z J I) LTE (<-radius 0.66 +)) If (IsV2InMap (V2(J I))) FC(J I) #push to stack EndIf EndIf Loop Loop AppendStackToList (<-cells) # populate list @CellOut #replace this with a routine to do what you desire. #--------- :CellOut # set up theme overlay V4(1 1 0 1) List ->yellow V4(0 1 0 1) list ->green CreateThemeOverlay(0 GetMapSize Vector0) SetThemeOverlayEnabled(0 true) SetThemeOverlayPointFilter(0 true) # do the real work Do (GetListCount(<-cells) 0) SetThemeOverlayPixels(0, UFC(<-cells[i]),, 1, 1, <-green) Loop ---- ===== Reset Special Terrain (Decayable) ===== In all versions up to and including V2.1.4, there is a bug in the editor that affects the Undo function for special terrain of type decayable (Shattered Terrain). If you place shattered terrain and then undo, the bug leaves terrain in a state where it still thinks it has the shattered terrain special though it looks like normal terrain. It will drop 1 elevation level when it first makes contact with creeper, but then doesn't change again after that happens. The easiest fix is to floodfill special terrain to clear the hidden shattered land property, but if some cases that may not be appropriate, eg. when there are many and varied types of special terrain on a map. In that case this utility, written by Discord User Auri (GitHub @Durikkan) can be used to reset the terrain left in the wrong state. GetMapSize ->sizeZ ->sizeX <-sizeZ 0 do <-sizeX 0 do if (GetTerrainSpecial(i j) eq0) SetTerrainSpecial(i j 0) endif loop loop ---- ===== Fully Build Selected Units ===== A simple piece of code that will build all selected units and give them full ammo.\\ This is very useful when making PAC(( Play As Creeper )) maps so you don't need to have the map run to let the units get their ammo if that is of any concern, even as little as for sake of story or emergence. However units like the Missile Launcher which have a visual change from having ammo will still need a single frame to complete before they can apply this visual change in reaction to their new ammo amount.\\ The code is designed to work both when running once and when running constantly. When running constantly it will Build and Fully Stock every unit you select. # Build selected units #By Vertu. GetSelectedUnits ->units if(GetListCount(<-units) gt0) do(GetListCount(<-units) 0) ConstructUnit(<-units[I] 10000) SetUnitAmmo(<-units[I] GetUnitMaxAmmo(<-units[I])) SetUnitHealth(<-units[I] GetUnitMaxHealth(<-units[I])) loop ClearTraceLog Trace3("Fully built " GetListCount(<-units) " selected units.") else if(GetUnitUpdateCount lt(2)) Trace("No units where selected for building.") else if(GetUnitUpdateCount 10 % eq0) ClearTraceLog if(<-count eq0) Trace("Operating. |") 1 ->count else if(<-count eq(1)) Trace("Operating. /") 2 ->count else if(<-count eq(2)) Trace("Operating. -") 3 ->count else Trace("Operating. \") 0 ->count endif endif endif endif endif endif :Destroyed if(GetUnitUpdateCount gt(5)) ClearTraceLog endif ---- ===== Mission Control Stats (mcs.dat) manipulation ===== There are two 4RPL commands added in Beta 2.3.4 that allows inspecting and altering the mcs.dat file. Primarily, this allows for deletion/reset of the status of maps in Colonies. It is possible to detect and delete either completed or incomplete missions. It is highly advised that a backup of mcs.dat is taken before this code is executed. The new version of this code has a GUI based on CW4 4RPL buttons that allows for either selective deletion of entries or mass-deletion of entries. # MCSButton 2022/08/31 If (<-deleteSet GT0) TraceAllSp ("DeleteSet is active") <-deleteSet 1 - ->deleteSet If(<-deleteSet eq0) @DeleteList endIf endIf :Awake RegisterForMSG("ClickExit" "ButtonExit") RegisterForMSG("ClickBackup" "ButtonBackup") RegisterForMSG("ClickContinue" "ButtonContinue") RegisterForMSG("ClickList" "ButtonList") RegisterForMSG("ClickNext" "ButtonNext") RegisterForMSG("ClickPrev" "ButtonPrev") RegisterForMSG("ClickDelete" "ButtonDelete") RegisterForMSG("ClickMode" "ButtonMode") V4(0 1 0 1) ->green V4(1 0 0 1) ->red V4(1 1 0 1) ->yellow V4(0 1 1 1) ->blue V4(0 0 0 1) ->black V4(0 0 0 0) ->invisible V4(1 1 1 1) ->white SetMSGButton(0 true LF " Exit" LF " " concat concat concat <-red "ClickExit" "X") SetMSGButton(1 true LF " mcs.dat is not backed up" LF " " concat concat concat <-yellow "ClickBackup" "N") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 False " " " " " " " ") SetMSGButton(4 true LF " mcs.dat is backed up" LF " " concat concat concat <-green "ClickContinue" "Y") :ButtonBackup SetMSGButton(0 true LF " Exit" LF " " concat concat concat <-red "ClickExit" "X") SetMSGButton(1 TRUE "Only use when you have backed up mcs.dat " <-white " " " ") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 FALSE " " " " " " " ") SetMSGButton(4 FALSE " " " " " " " ") :ButtonExit SetMSGButton(0 FALSE " " " " " " " ") SetMSGButton(1 FALSE " " " " " " " ") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 FALSE " " " " " " " ") SetMSGButton(4 FALSE " " " " " " " ") StopConsole :ButtonContinue @ParseMCS SetMSGButton(0 true LF " Exit" LF " " concat concat concat <-red "ClickExit" "X") SetMSGButton(1 FALSE " " " " " " " ") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 true LF " List " <-completedCount " Completed maps" LF " " concat concat concat concat concat <-green "ClickList" "C") SetMSGButton(4 true LF " List " <-incompleteCount " Incomplete maps" LF " " concat concat concat concat concat <-yellow "ClickList" "I") :ButtonList # CreateList ->processList If (<-_DATA "C" EQ) # Do (3 0) testing code # AppendToList ( <-processList <-coloniesComplete[I]) # Loop <-coloniesComplete ->processList <-completedCount ->listCount else # Do (3 0) testing code # AppendToList ( <-processList <-coloniesIncomplete[I]) # Loop <-ColoniesIncomplete ->processList <-incompleteCount ->listCount endIf 0 ->listPos SetMSGButton(0 true LF " Exit" LF " " concat concat concat <-red "ClickExit" "X") SetMSGButton(1 FALSE " " " " " " " ") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 true LF " Select maps to remove " LF " " concat concat concat <-green "ClickMode" "S") SetMSGButton(4 true LF " Remove all maps " LF " " concat concat concat <-yellow "ClickMode" "A") :ButtonMode If (<-_DATA "S" EQ) @ProcessButtons else @ScheduleDelete endIf :ButtonNext If (<-listPos GetListCount(<-processList) 1 - LT) <-listPos 1 + ->listPos endIf @ProcessButtons :ButtonPrev If (<-listPos GTE0) <-listPos 1 - ->listPos endIf @ProcessButtons :ButtonDelete DeleteMCSEntry(<-Processlist[<-listPos][0]) RemoveListElement(<-Processlist <-listPos ) if (<-listPos gt0, <-listPos GetListCount(<-processList) gte, &&) <-listPos 1 - ->listPos endif @ProcessButtons :ScheduleDelete SetMSGButton(0 FALSE " " " " " " " ") SetMSGButton(1 TRUE " This may take some time. Please wait." <-white " " " ") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 FALSE " " " " " " " ") SetMSGButton(4 FALSE " " " " " " " ") 2 ->deleteSet :DeleteList 0 ->deleteCount do( getListCount(<-ProcessList) 0) DeleteMCSEntry(<-ProcessList[I][0]) <-deleteCount 1 + ->deleteCount Loop SetMSGButton(0 true LF " Exit" LF " " concat concat concat <-red "ClickExit" "X") SetMSGButton(1 TRUE <-deleteCount " Map entries deleted." Concat <-white " " " ") SetMSGButton(2 FALSE " " " " " " " ") SetMSGButton(3 FALSE " " " " " " " ") SetMSGButton(4 FALSE " " " " " " " ") TraceAllSp(<-deleteCount "Map entries deleted.") :ProcessButtons TraceAllSp ("List position:" <-listPos) SetMSGButton(0 true LF " Exit" LF " " Concat Concat Concat <-red "ClickExit" "X") SetMSGButton(4 TRUE "#" <-Processlist[<-listPos][0] ": " <-Processlist[<-listPos][1] Concat Concat Concat " " " " " ") If (<-listPos LTE0) setMSGButton(1 TRUE LF "Prev map" LF " " Concat Concat Concat <-black "ClickPrev" -1) else SetMSGButton(1 TRUE LF "Prev map" LF " " Concat Concat Concat <-blue "ClickPrev" -1) endIf If (<-listPos GetListCount(<-processList) 1 - GTE) setMSGButton(2 TRUE LF "Next map" LF " " Concat Concat Concat <-black "ClickNext" -1) else SetMSGButton(2 TRUE LF "Next map" LF " " Concat Concat Concat <-blue "ClickNext" 1) endIf SetMSGButton(3 TRUE LF "Delete Map #" <-Processlist[<-listPos][0] LF " " Concat Concat Concat Concat <-red "ClickDelete" <-listPos) TraceAllSP ("I have " GetListCount (<-ProcessList) "entries to process") :ParseMCS GetPrintPrefixEnabled ->enabled SetPrintPrefixEnabled(FALSE) ClearPrintLog GetMCSEntries ->MCSList TraceAllSp ("MCS contains:" GetListCount(<-MCSList) "entries") CreateList ->coloniesComplete CreateList ->ColoniesIncomplete 0 ->completedCount 0 ->incompleteCount # Split completed Colonies maps from incomplete do (GetListCount(<-MCSList) 0) If (<-MCSList[I][0] NEQ (-1)) If (<-MCSList[I][2]) AppendToList( <-coloniesComplete <-MCSList[I]) <-completedCount 1 + ->completedCount Else AppendToList( <-coloniesIncomplete <-MCSList[I]) <-incompleteCount 1 + ->incompleteCount endIf endIf Loop TraceAllSp ("Colonies completed: " GetListCount(<-coloniesComplete) ) TraceAllSp ("Colonies incompleted:" GetListCount(<-ColoniesIncomplete) ) # Output all lists: PrintAllSp ("Colonies Incomplete") do( getListCount(<-coloniesIncomplete) 0) PrintAllSP(<-coloniesIncomplete[I]) Loop PrintAllSP(" ") PrintAllSp ("Colonies Completed") do( getListCount(<-coloniesComplete) 0) PrintAllSP(<-coloniesComplete[I]) Loop # @DeleteIncomplete ### USE AT OWN RISK! Make a Copy of MCS.Dat First! ### SetPrintPrefixEnabled(<-enabled) ---- ===== Manipulate Selected Unit Positions ===== This console script is designed to run constantly and manipulate the position of the selected units in 3 dimensions. Also it operates for as long as a key is pressed down. This can make precision somewhat annoying but far from impossible.\\ Key bindings: * All arrow keys. (X and Z axis) * Page Up & Page down (Y axis) * Backspace (set Y axis position defined within the script, must edit the script to change this). * / disperse the Y axis position of all selected units between two integers which include floats. The lower and upper bounds must be edited within the script. #Maniuplate Selected Unit Positions #By Vertu $HEIGHT:100.0 #Float or Integer $RAND_MAX:8 #Integer $RAND_MIN:-8 #Integer if(GetKey("RightArrow" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x 1 + <-unitPosPre.y <-unitPosPre.z)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("LeftArrow" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x 1 - <-unitPosPre.y <-unitPosPre.z)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("UpArrow" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x <-unitPosPre.y <-unitPosPre.z 1 +)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("DownArrow" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x <-unitPosPre.y <-unitPosPre.z 1 -)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("PageUp" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x <-unitPosPre.y 1 + <-unitPosPre.z)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("PageDown" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x <-unitPosPre.y 1 - <-unitPosPre.z)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("Backspace" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x <-HEIGHT <-unitPosPre.z)) GetTerrain(GetUnitCell(<-units[I])) ->terrainH GetUnitPosition(<-units[I]) ->unitPos if(approximately(<-unitPos.y <-terrainH)) SetUnitOccupiesLand(<-units[I] true) else if(<-unitPos.y lt(<-terrainH)) SetUnitOccupiesLand(<-units[I] false) endif endif loop endif if(GetKey("Slash" false)) GetSelectedUnits ->units do(GetListCount(<-units) 0) RandInt(<-RAND_MIN <-RAND_MAX 1 +) RandFloat + ->randHeight GetUnitPosition(<-units[I]) ->unitPosPre SetUnitPosition(<-units[I] V3(<-unitPosPre.x <-randHeight <-unitPosPre.z)) loop endif :Once Trace("Left/Right Arrow = Left/Right by 1 cell.") Trace("Up/Down Arrow = North/South by 1 cell.") Trace("Page Up = Up by 1 terrain unit.") Trace("Page Down = Down by 1 terrain unit.") Trace2("Backspace = Set to height defined within this console script which is" <-HEIGHT) Trace5("'/' = disperse selected with random height from " <-RAND_MIN ":" <-RAND_MAX " with RandFloat too.") ---- ===== 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. # 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 ---- ===== 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]] ---- ===== Next Utility goes here ===== ---- ====== Unit Scripts ====== ===== Custom CellOccupiedCount shape ===== Here's a copy-paste ready script for setting unit occupied cells in shapes other than the default box. This will allow you to create a unit that occupies a non-standard amount of cells, letting you have both filled areas and hollow areas that other units could occupy. It should cover all of the obvious cases such as creating the unit, destroying it, moving it in editor, lifting off and landing (for a movable unit). Make sure the unit is set to not occupy land in unit settings. Recompiling scripts will cause leftovers but they are eliminated when the map is loaded, so fixing that is not worth the additional complexity. To set up a custom shape, you only have to alter the SetCustomOccupiedLand function. The default example is set up for a 9x9 unit that only wants the outer edge of the unit to occupy space, with a hollow middle. You can change the algorithm to set/unset whichever cells you want around the unit's center cell, as long as you make sure to add 1 to each when SOL_bSet is TRUE, and subtract 1 from each when SOL_bSet is FALSE. If you need help with this script or you want to report a bug please message me at Grabz#4707 on Discord, you can find me through the Knuckle Cracker discord server. :awake once RegisterForMSG("MSG_PostUpdate" "PostUpdate") endonce :once @awake :destroyed <-G_iCurrentlyOccupiedX <-G_iCurrentlyOccupiedZ FALSE @SetCustomOccupiedLand :gameloaded -?G_iCurrentlyOccupiedX if <-G_iCurrentlyOccupiedX <-G_iCurrentlyOccupiedZ TRUE @SetCustomOccupiedLand endif :PostUpdate Self GetUnitMoveCell ->POU_iMoveZ ->POU_iMoveX Self GetUnitCell ->POU_iZ ->POU_iX #Set the initial configuration of our unit once <-POU_iX <-POU_iZ TRUE @SetCustomOccupiedLand <-POU_iX <-POU_iZ ->G_iCurrentlyOccupiedZ ->G_iCurrentlyOccupiedX endonce #If the unit has been ordered to move <-POU_iMoveX <-POU_iLastMoveX neq <-POU_iMoveZ <-POU_iLastMoveZ neq or <-POU_iMoveX -1 neq and if #We clear the last area the unit was set to occupy, and #we set the occupied land at the new target location our unit is meant to land in <-G_iCurrentlyOccupiedX <-G_iCurrentlyOccupiedZ FALSE @SetCustomOccupiedLand <-POU_iMoveX <-POU_iMoveZ TRUE @SetCustomOccupiedLand <-POU_iMoveX <-POU_iMoveZ ->G_iCurrentlyOccupiedZ ->G_iCurrentlyOccupiedX #If the unit is stationary else <-POU_iMoveX <-POU_iLastMoveX eq <-POU_iMoveZ <-POU_iLastMoveZ eq and <-POU_iMoveX -1 eq and if #If the unit was moved in the editor, clear last position and set new position <-POU_iLastX <-POU_iX neq <-POU_iLastZ <-POU_iZ neq or if <-G_iCurrentlyOccupiedX <-G_iCurrentlyOccupiedZ FALSE @SetCustomOccupiedLand <-POU_iX <-POU_iZ TRUE @SetCustomOccupiedLand <-POU_iX <-POU_iZ ->G_iCurrentlyOccupiedZ ->G_iCurrentlyOccupiedX endif endif endif <-POU_iMoveZ <-POU_iMoveX ->POU_iLastMoveX ->POU_iLastMoveZ <-POU_iZ <-POU_iX ->POU_iLastX ->POU_iLastZ #i1 i2 b3 - #Uniformly set or unset a chunk of occupied land #Arguments: CellX and CellY of the last position (if unsetting) or current position (if setting), # boolean that determines if to set or unset #Result: None :SetCustomOccupiedLand ->SOL_bSet ->SOL_iCellZ ->SOL_iCellX 4 ->SOL_iRadius <-SOL_iCellZ <-SOL_iRadius add 1 add <-SOL_iCellZ <-SOL_iRadius sub do <-SOL_iCellX <-SOL_iRadius add 1 add <-SOL_iCellX <-SOL_iRadius sub do <-SOL_iCellX <-SOL_iRadius sub I eq <-SOL_iCellX <-SOL_iRadius add I eq or <-SOL_iCellZ <-SOL_iRadius sub J eq or <-SOL_iCellZ <-SOL_iRadius add J eq or if I J GetCellOccupiedCount ->SOL_iCount <-SOL_bSet if I J <-SOL_iCount 1 add SetCellOccupiedCount else I J <-SOL_iCount 1 sub SetCellOccupiedCount endif endif loop loop ===== Make a non-moving unit only buildable in void ===== This code will make it so a unit can be only built in void and not on terrain. It only works properly for stationary units. * This is a global script, so you have to add it in Package Manager -> Global Control -> Pre Update. * **Tick the Run when paused box**, then in one of the UNITGUID fields paste the GUID of one or more void only units. * The script does not work in editor, you can finalize the map, back out then open the finalized map and test there if it works. * The unit must have the "Can build/land anywhere" toggle enabled. $UnitGUID0:"" $UnitGUID1:"" $UnitGUID2:"" $UnitGUID3:"" $UnitGUID4:"" GetBuildUnit ->curBuildUnit #Only do something if state has changed <-curBuildUnit <-lastBuildUnit neq if FALSE ->buildUnitSelected FALSE ->buildUnitDeselected 5 0 do "UnitGUID" I concat <-! ->val <-val StringLength gt0 <-val <-curBuildUnit eq and if TRUE ->buildUnitSelected endif <-val StringLength gt0 <-val <-lastBuildUnit eq and if TRUE ->buildUnitDeselected endif loop #If a void only unit was selected, and the last selection was not a void only unit <-buildUnitSelected <-buildUnitDeselected not and if FALSE SetAllLegalUnitCells <-LEGAL_CELLS TRUE SetLegalUnitCells TRUE UseLegalUnitCells #If a non void only unit was selected, and the last selection was a void only unit else <-buildUnitSelected not <-buildUnitDeselected and if FALSE SetAllLegalUnitCells FALSE UseLegalUnitCells endif endif endif #Save current state <-curBuildUnit ->lastBuildUnit #Initialize :once GetBuildUnit ->lastBuildUnit @RebuildVoidCellsList :RebuildVoidCellsList #Build list of void cells CreateList ->LEGAL_CELLS GetMapSize ->sizeZ ->sizeX <-sizeZ 0 do <-sizeX 0 do #If terrain is void, add coordinates to list I J GetTerrain eq0 if <-LEGAL_CELLS I J V2 AppendToList endif loop loop ---- ===== Next Unit Script goes here ===== ---- ====== Useful Functions ====== ===== GetUnitsInPolygon() and IsPointInPoly() ===== Need to find units in a polygon or determine if a specific point is inside a convex/concave polygon (without holes)? These slightly inefficient and imperfect functions are for you - at least until something equivalent is available natively in 4RPL. Just put the functions at the bottom of your 4RPL script. # IsPointInPoly(point polyList) :IsPointInPoly ->ipip_polyList ->ipip_point false ->ipip_retval GetListCount(<-ipip_polyList) 1 - ->ipip_prev <-ipip_polyList 0 do <-ipip_polyList[I] ->ipip_linept1 <-ipip_polyList[<-ipip_prev] ->ipip_linept2 if (<-ipip_linept1.y <-ipip_point.y gt <-ipip_linept2.y <-ipip_point.y gt neq) <-ipip_linept2.x <-ipip_linept1.x - <-ipip_point.y <-ipip_linept1.y - * ->ipip_tempval <-ipip_tempval <-ipip_linept2.y <-ipip_linept1.y - / ->ipip_tempval <-ipip_tempval <-ipip_linept1.x + ->ipip_tempval if (<-ipip_point.x <-ipip_tempval lt) <-ipip_retval ! ->ipip_retval endif endif I ->ipip_prev loop <-ipip_retval # GetUnitsInPolygon(unitType polyList enemyState builtState imperviousState) :GetUnitsInPolygon ->guip_imperviousState ->guip_builtState ->guip_enemyState ->guip_polyList ->guip_unitType # Get all units on the map that match the non-position requirements. GetUnits(<-guip_unitType V3(128 128 128) 512 false false false <-guip_enemyState <-guip_builtState <-guip_imperviousState) ->guip_units CreateList ->guip_retval <-guip_units 0 do <-guip_units[I] ->guip_tempunit if (@IsPointInPoly(V2(GetUnitCell(<-guip_tempunit)) <-guip_polyList)) AppendToList(<-guip_retval <-guip_tempunit) endif loop <-guip_retval Example usage: # Creates a sparks effect on each friendly unit inside a triangle. List( V2(18 148) V2(138 102) V2(44 29) ) ->testPoly @GetUnitsInPolygon("" <-testPoly 2 0 1) ->units TraceAllSp("Num units " GetListCount(<-units)) <-units 0 do <-units[I] ->tempunit CreateEffect("sparks" GetUnitPosition(<-tempunit) V3(1 1 1)) loop ---- ===== Bresenham Line Algorithm ===== Calculates the points (usually map cells) in a line between a starting and ending point. @getLine(GetMapSize 0 0 ) ->LineList EditAddUndo(0) Do (GetListCount(<-linelist) 0) SetTerrain(ToCell(<-linelist[I]) 15) Loop #TraceAllSp (<-LineList) # Bresenham diagonal line # http://ericw.ca/notes/bresenhams-line-algorithm-in-csharp.html :getLine ->z1 ->x1 ->z0 ->x0 CreateList ->line If (Abs(<-z1 <-z0 - ) Abs(<-x1 <-x0 -) GT ) ->steep (true) ->t (<-x0) # swap x0 and z0 ->x0 (<-z0) ->z0 (<-t) ->t (<-x1) # swap x1 and z1 ->x1 (<-z1) ->z1 (<-t) else ->steep (false) endIf if (<-x0 GT (<-x1)) ->swap (true) ->t (<-x0) # swap x0 and x1 ->x0 (<-x1) ->x1 (<-t) ->t (<-z0) # swap z0 and z1 ->z0 (<-z1) ->z1 (<-t) else ->swap (false) endIf ->dx (<-x1 - (<-x0)) ->dz (Abs(<-z1 - (<-z0))) ->error (<-dx / (2)) if (<-z0 <-z1 LT) ->zstep (1) else ->zstep (-1) endif ->z (<-z0) Do(<-x1 <-x0) If (<-swap) if (<-steep) PrependToList(<-line FromCell(<-z I)) else PrependToList(<-line FromCell(I <-z)) endif else if (<-steep) PushList(<-line FromCell(<-z I)) else PushList(<-line FromCell(I <-z)) endif endIf ->error (<-error - (<-dz)) if (<-error LT0) ->z (<-z + (<-zstep)) ->error (<-error + (<-dx)) endIf Loop <-line #return ---- ===== Random X-Z position within the area of a circle ===== This is a simple piece of trigonometry that is able to pick a random position within the area of a 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 within the area of a circle with a definable radius.\\ Note: Floats will work for the radius.\\ \\ **Applying to a V3:** V3(cos(RandFloat 2.0 / RandInt(0 7) +) <-RADIUS * <-cellX + <-Y_COORDINANT RandInt(-15 16) + sin(RandFloat 2.0 / RandInt(0 7) +) <-RADIUS * <-cellZ +) ->position **Applying to a V2:** V2(cos(RandFloat 2.0 / RandInt(0 7) +) <-RADIUS * <-cellX + sin(RandFloat 2.0 / RandInt(0 7) +) <-RADIUS * <-cellZ +) ->cellLocation Example 1:\\ Descending down onto the map from an angle rather than only straight down.\\ (Remember this is just a snipit of what can use a random position on a circle, not an entire script). $FALL_HEIGHT:300.0 GetUnitPosition(self) ->pos if(<-pos.y lte(GetTerrain(<-pos.x <-pos.z))) @deploy else SetUnitPosition(self MoveTowards(<-pos V3(<-placementPos.x 0 <-placementPos.z) <-FALL_SPEED)) endif :Once #When a unit begins operating, choose a random location on a circle to relocate the unit and remember the destination position it was originally placed at. if(GetEditMode !) GetUnitPosition(self) ->placementPos SetUnitPosition(self V3(cos(RandFloat 2.0 / RandInt(0 7) +) 45 * <-placementPos.x + <-FALL_HEIGHT RandInt(-15 16) + sin(RandFloat 2.0 / RandInt(0 7) +) 45 * <-placementPos.z +)) endif ---- ===== MultiObjective ===== This script is designed to allow for victory conditions on objectives other than 'All Required' or 'Any'. By manipulating the weights, a much more varied set of victory conditions can be used in maps. The script uses the custom objective for this; setting the custom objective to the only required objective will allow for different victory conditions.\\ The script can also be used as an optional 'Complete All Objectives' timer for maps. # MultiObjective 2023-03-16 # Grants the custom objective if the player has completed enough of the other objectives. # Default use of this script is for the custom objective to be required, and all other objectives be optional. # This will make the map be completed when a certain number of objectives are completed. $ObjectivesRequired:2 $Nullify:1 # Set these to 0 to prevent them from counting $Totems:1 # Unused objectives do not need to have their parameters set to 0. $Reclaim:1 $Hold:1 $Collect:1 # Numbers other than 0 and 1 can be used to give different weights to the objectives. # As an example, setting Nullify to 2 would have nullify count as 2 objectives when completed. # If ObjectivesRequired is also set to 2 this would make the custom objective 'Nullify all or complete 2 objectives' GetMissionObjectiveState(0) <-Nullify mul GetMissionObjectiveState(1) <-Totems mul add GetMissionObjectiveState(2) <-Reclaim mul add max(GetMissionObjectiveState(3) 0) <-Hold mul add GetMissionObjectiveState(4) <-Collect mul add ->Points if(<-Points <-ObjectivesRequired gte) once AcquireMissionObjective(5 false) endonce endif ---- ===== Next UseFul Function goes here ===== ----