User Tools

Site Tools


cw4:4rpl_tools

Contributors to this page is encouraged to attribute their contributions1).

Editors

Note: to get a default editor associated with the .4rpl filetype, please read about 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 Notepad++ Guide on how to install highlighters if you don't know how.

Highlighter by Sanian.

Comes with basic spellcheck by underlining offending text in red. A Standard and Dark version are available.

Highlighter by Alophox.

Does not include spellcheck. This highlighter can be used with multiple themes.

VSCode

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 Console in the Mods section of the 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.)

Click here for the source code.

Click here for the source code.

CopyPaste.4rpl
# 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.

Click here for the source code.

Click here for the source code.

Map Export.4rpl
# 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.

Click here for the source code

Click here for the source code

Sound Player.4rpl
# 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.

Click here for the source code

Click here for the source code

looping Sound Player.4rpl
# 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.

Sample Output

Sample Output

 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 

click here for source code

click here for source code

file MapInfo.4rpl
#"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.

click here for source code

click here for source code

Execution Time.4rpl
$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.

Click here for source code

Click here for source code

Symmetrifier.4rpl
# 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

Click here for controls list

Click here for controls list

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

click here for source code

click here for source code

ResetMesh.4rpl
# 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.

click here for source code

click here for source code

CellRange.4rpl
# 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.

click here for source code

click here for source code

file name.4rpl
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 PAC2) 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.

Click here for source code.

Click here for source code.

Fully Build Selected Units.4rpl
# 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.

click here for source code

click here for source code

MIssionControlStats.4rpl
# 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 "<size=100%><color=red> Exit" LF " " concat concat concat <-red "ClickExit" "X")
  	SetMSGButton(1 true LF "<size=100%><color=yellow> mcs.dat is not backed up" LF " " concat concat concat <-yellow "ClickBackup" "N")
	SetMSGButton(2 FALSE " " " " " " " ")
 	SetMSGButton(3 False " " " " " " " ")
  	SetMSGButton(4 true LF "<size=100%><color=green> mcs.dat is backed up" LF " " concat concat concat <-green "ClickContinue" "Y")
 
:ButtonBackup
 	SetMSGButton(0 true LF "<size=100%><color=red> 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 "<color=red> Exit" LF " " concat concat concat <-red "ClickExit" "X")
	SetMSGButton(1 FALSE " " " " " " " ")
	SetMSGButton(2 FALSE " " " " " " " ")
	SetMSGButton(3 true LF "<color=green> List " <-completedCount " Completed maps" LF " " concat concat concat concat concat <-green "ClickList" "C")
 	SetMSGButton(4 true LF "<color=yellow> 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 "<color=red> Exit" LF " " concat concat concat <-red "ClickExit" "X")
	SetMSGButton(1 FALSE " " " " " " " ")
	SetMSGButton(2 FALSE " " " " " " " ")
	SetMSGButton(3 true LF "<color=yellow> Select maps to remove " LF " " concat concat concat  <-green "ClickMode" "S")
 	SetMSGButton(4 true LF "<color=yellow> 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 "<size=100%><color=red> 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 "<color=red> 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 "<alpha=#00><color=#000000>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 "<alpha=#00><color=#000000>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

[Click to view]

[Click to view]

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.
Manipulate Selected Unit Positions
#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.

click here for source code

click here for source code

file name.4rpl
# AutoTowerGrid
# by Kalli
# Script is to run continuously in the console!
 
$pacMode:0 # Makes all units built by the pilot unselectable and undeletable by the player
 
# Quick guide to the variables: 
# 1) largeSkip makes the script less accurate, but it saves on computation time.
# 2) optimalCellSelection will keep looking for the cell with the most connections. Or until optimalTowerTarget is met.
# 3) Multiply optimalCellSelection with largeSkip to get an idea of what cellrange will be checked. Higher = more triangles even when square + more towers close to minimum distance.
# 4) Good settings for a well connected tower grid with low average computation times: mintowerdistance=0; gosquare=1; optimalselection=3; largeSkip=8.
 
$maxTowerDistance:12 # LT to exclude 12. Mostly max is 12, but sometimes 2 towers at an exact distance of 12 will not connect. And other units with a different connecting height, might not connect at all.
$minTowerDistance:0 # Set to 0 to let the script calc the min distance as a function of the max distance.
$disLOS:1 # Check LOS when checking for towers within the minimum distance. It makes it easier to climb walls or cliffs, but it becomes messier.
 
$contaminentSlot:8 # Towers shouldn't build on contaminated terrain, but the mapmaker could move the slots around in the editor.
$creeperRange:3 # Towers will be build up to this distance from creep. Closer creep will make it a "blocked" tower.
 
$goSQUARE:1 # The script will either work angular or perpendicular.
$optimalCellSelection:3 # after finding a suitable cell, the script will loop through x more cells to maybe find a better connected one.
$largeSkip:8 # When a computation heavy check fails, skip a few cells. Works well with lowering optimalCellSelection.
$smallSkip:1 # Only used for avoiding void and map limits atm. 
$optimalTowerTarget:2 # The loop will stop if the cell has this many towers at a good distance. Minimum 2.
 
$$minTowerCellDistance:1 # Could lower execution time, but will make it harder to scale some cliffs. If this is increased, decrease the $optimalCellSelection variable. Test per map.
$$maxTowerCellDistance:12 # LTE. something to play with. Cells further away than the maxTowerDistance are also automatically removed.
$$connectionHeight:2.8 # 2.9 is very rarely too high for the LOS check along slope edges.
$debugPrint:0
 
"Everything" @startChrono
<-debugPrint if elapsedtime ->starttime endif
# GetEnergyStore ->energyStored
# GetEnergyUse GetEnergyGeneration 1 max div ->energyUsage
GetTimer3 ->timer3
GetTimer0 ->timer0
switch
	# "Nodes" @startChrono
	case(<-timer0 eq0)
		151 Settimer0
		@findOtherNodes
	endcase
	# "Towers" @startChrono
	case(<-timer0 100 eq)
		@findOtherTowers
	endcase
	# "Cleaning" @startChrono
	case(<-timer0 50 eq)
		@cleanOldNodeList
	endcase
	# "Planned" @startChrono
	case(<-timer3 0 eq)
		11 Settimer3
		@buildPlannedTower
		@monitorTowers
	endcase
	# "Blocked" @startChrono
	case(<-timer3 10 eq)
		@buildBlockedTower
		@monitorTowers
	endcase
	# # "Supply" @startChrono
	# case(<-energystored eq0)
		# # The cases above don't need much energy, the cases below do. Only start large scale expansion when there is energy stored.
		# 0 @supplyNonEssentialInfrastructure
	# endcase
	# "Planned" @startChrono
	case(<-timer3 9 eq)
		@buildPlannedTower
		@monitorTowers
	endcase
	# "Blocked" @startChrono
	case(<-timer3 8 eq)
		@buildBlockedTower
		@monitorTowers
	endcase
	# case(<-energyUsage 1.0 gt <-energyStored 20 lt and)
		# # Try to prevent overbuilding again.
	# endcase
	# "Planned" @startChrono
	case(<-timer3 7 eq)
		@buildPlannedTower
		@monitorTowers
	endcase
	# "Blocked" @startChrono
	case(<-timer3 6 eq)
		@buildBlockedTower
		@monitorTowers
	endcase
	# "Planned" @startChrono
	case(<-timer3 5 eq)
		@buildPlannedTower
		@monitorTowers
	endcase
	# "Blocked" @startChrono
	case(<-timer3 4 eq)
		@buildBlockedTower
		@monitorTowers
	endcase
	# case(<-energyUsage 1.0 gt <-energyStored 40 lt or)
		# # Try to prevent overbuilding.
	# endcase
	# "PlannedS" @startChrono
	case(<-timer3 3 eq)
		@buildPlannedTower
		# 1 @supplyNonEssentialInfrastructure #turn on supply again for non essentials if there are enough supplies
	endcase
	# "Blocked" @startChrono
	case(<-timer3 2 eq)
		@buildBlockedTower
		@monitorTowers
	endcase
	# "Planned" @startChrono
	case(<-timer3 1 eq)
		@buildPlannedTower
		@monitorTowers
	endcase
endswitch
@stopChrono
 
:startChrono
	# the ID is left on the stack, swap to send it.
	# <-debugPrint if "StartElapsedMSG" swap sendmsg endif
	"StartElapsedMSG" swap sendmsg
 
:stopChrono
	# <-debugPrint if "EndElapsedMSG" "" sendmsg endif
	"EndElapsedMSG" "" sendmsg
 
:Once	
	createlist ->uid_BlockedTower # use indexBl to cycle through these between frames
	createlist ->dir_BlockedTower # not the coordinates, but the direction
 
	createlist ->uid_PlannedTower # use indexPl to cycle through these between frames
	createlist ->dir_PlannedTower # not the coordinates, but the direction
 
	createlist ->uid_MonitorTower # use indexMT to cycle through these between frames
	createlist ->dir_MonitorTower # not the coordinates, but the direction
	createlist ->uid_MonitoredTower # The actually build towers, whose eventual destruction has to be monitored.
 
	# Use existing or playerbuild nodes (microrifts, pylons, pods and towers) to plan towers around
	createlist ->oldNodeList 
	createlist ->oldNodeList # riftlab, pylons, pods and microrifts. Gets purged overtime.
 
	# If PAC mode is enabled, make friendly pre existing units unselectable or vice versa
	do(9999 0)
		if(GetUnitCreeperDamages(I))
			I @SetSelectable
		endif
	loop
 
	# The minimum distance is to block placing a tower within a perfect square of other towers. The absolute minimum ratio is roughly 7.25 / 12.
	if(<-minTowerDistance eq0)
		<-maxTowerDistance 7.25 mul 12 div ->minTowerDistance
	endif	
	# The following 2 variables are used in finding other towers at an "optimal" range
	<-maxTowerDistance 1.0 mul ->maxOptimalRange
	<-maxTowerDistance 0.85 mul <-minTowerDistance max ->minOptimalRange
 
	# 4 DIRECTIONS
	createlist ->relativePositionList1
	createlist ->relativePositionList2
	createlist ->relativePositionList3
	createlist ->relativePositionList4
 
	# The below cell lists + list sorting were created in excel.
	<-goSQUARE if
		list( v2(12 0) v2(11 0) v2(11 1) v2(11 -1) v2(11 2) v2(11 -2) v2(11 3) v2(11 -3) v2(11 4) v2(11 -4) v2(10 0) v2(10 1) v2(10 -1) v2(10 2) v2(10 -2) v2(10 3) v2(10 -3) v2(10 4) v2(10 -4) v2(10 5) v2(10 -5) v2(10 6) v2(9 0) v2(9 1) v2(9 -1) v2(10 -6) v2(9 2) v2(9 -2) v2(9 3) v2(9 -3) v2(9 4) v2(9 -4) v2(9 5) v2(9 -5) v2(9 6) v2(9 -6) v2(9 7) v2(9 -7) v2(8 0) v2(8 1) v2(8 -1) v2(8 2) v2(8 -2) v2(8 3) v2(8 4) v2(8 -3) v2(8 5) v2(8 -4) v2(8 6) v2(8 -5) v2(8 7) v2(8 -6) v2(8 8) v2(8 -7) v2(7 7) v2(7 6) v2(7 5) v2(7 -6) v2(7 -5) v2(7 4) v2(7 -4) v2(7 3) v2(7 -3) v2(7 2) v2(7 -2) v2(7 1) v2(7 -1) v2(7 0) v2(6 6) v2(6 5) v2(6 -5) v2(6 4) v2(6 -4) v2(6 3) v2(6 -3) v2(6 2) v2(6 -2) v2(6 1) v2(6 -1) v2(6 0) v2(5 5) v2(5 4) v2(5 -4) v2(5 3) v2(5 -3) v2(5 2) v2(5 -2) v2(5 1) v2(5 -1) v2(5 0) v2(4 4) v2(4 3) v2(4 -3) v2(4 2) v2(4 -2) v2(4 1) v2(4 -1) v2(4 0) v2(3 3) v2(3 2) v2(3 -2) v2(3 1) v2(3 -1) v2(3 0) v2(2 2) v2(2 1) v2(2 -1) v2(2 0) v2(1 1) v2(1 0) ) ->relativePositionList1 # loop through this reversed list in normal order
	else	
		list( v2(10 6) v2(6 10) v2(9 7) v2(7 9) v2(8 8) v2(11 4) v2(4 11) v2(10 5) v2(5 10) v2(9 6) v2(6 9) v2(8 7) v2(7 8) v2(11 3) v2(3 11) v2(10 4) v2(4 10) v2(12 0) v2(11 2) v2(9 5) v2(2 11) v2(5 9) v2(8 6) v2(6 8) v2(7 7) v2(10 3) v2(11 1) v2(3 10) v2(1 11) v2(9 4) v2(11 0) v2(4 9) v2(10 2) v2(2 10) v2(8 5) v2(5 8) v2(7 6) v2(6 7) v2(10 1) v2(9 3) v2(3 9) v2(1 10) v2(10 0) v2(8 4) v2(4 8) v2(9 2) v2(2 9) v2(7 5) v2(5 7) v2(6 6) v2(9 1) v2(8 3) v2(1 9) v2(3 8) v2(9 0) v2(7 4) v2(4 7) v2(8 2) v2(6 5) v2(2 8) v2(5 6) v2(8 1) v2(1 8) v2(7 3) v2(3 7) v2(8 0) v2(6 4) v2(4 6) v2(5 5) v2(7 2) v2(2 7) v2(7 1) v2(1 7) v2(6 3) v2(3 6) v2(7 0) v2(5 4) v2(4 5) v2(6 2) v2(2 6) v2(6 1) v2(5 3) v2(1 6) v2(3 5) v2(4 4) v2(6 0) v2(5 2) v2(2 5) v2(4 3) v2(3 4) v2(5 1) v2(1 5) v2(5 0) v2(4 2) v2(2 4) v2(3 3) v2(4 1) v2(1 4) v2(4 0) v2(3 2) v2(2 3) v2(3 1) v2(1 3) v2(3 0) v2(2 2) v2(2 1) v2(1 2) v2(2 0) v2(1 1) v2(1 0) ) ->relativePositionList1 # loop through this reversed list in normal order
	endif	
 
	# throw out the tower positions that are too close or too far.
	<-maxTowerDistance <-maxTowerCellDistance lt if <-maxTowerDistance ->maxTowerCellDistance endif
	do(-1 <-relativePositionList1 getlistcount 1 sub)
		if (distancecell(0 0 <-relativePositionList1[i] ev2) dup <-minTowerCellDistance lt swap <-maxTowerCellDistance gte or)
			<-relativePositionList1 i removelistelement
		endif
	loop
	<-relativePositionList1 getlistcount ->relativePositionsCount
 
	# Find back roughly the center cell of the position list and add it to index [0], so that a pre-emptive tower check can be ran against it.
	<-goSQUARE if
		do(<-relativePositionsCount 0)
			<-relativePositionList1[i] ev2 eq0 if # ->x
				<-maxX max ->maxX
			else pop
			endif
		loop
		<-maxX 2 div 1 add floor ->centralX
		<-relativePositionList1 <-centralX 0 v2 prependtolist
		<-maxX 2 div 1 add ceil <-minTowerDistance max ->quickRangeCheck # the range of the pre-emptive tower check
	else
		do(<-relativePositionsCount 0)
			<-relativePositionList1[i] ev2 dup2 eq if # ->z ->x
				pop <-maxX max ->maxX
			else pop pop
			endif
		loop
		<-maxX 2 div 0.7 add floor dup ->centralX ->centralZ
		<-relativePositionList1 <-centralX <-centralZ v2 prependtolist
		<-maxX 2 div 0.7 add 2 sqrt mul ceil <-minTowerDistance max ->quickRangeCheck # the range of the pre-emptive tower check
	endif
	<-relativePositionsCount 1 add ->relativePositionsCount
 
	# Generate the 3 other relative position lists by rotating the 1 1 list.
	<-relativePositionsCount 0 do
		<-relativePositionList1[i] dup dup	ev2 -1 mul swap v2 ->relativePositionList2[i]
											ev2 -1 mul swap -1 mul swap v2 ->relativePositionList3[i]
											ev2 swap -1 mul v2 ->relativePositionList4[i]
	loop
	# <-debugPrint if <-relativePositionList1 <-relativePositionList2 <-relativePositionList3 <-relativePositionList4 printallsp endif
 
:SetSelectable
	<-pacMode ! SetUnitSelectable
 
:supplyNonEssentialInfrastructure
->supplyON
	"porter" <-supplyON @supplyUnitType
	"runway" <-supplyON @supplyUnitType
	"greenarrefinery" <-supplyON @supplyUnitType
 
	"rocketpad" <-supplyON @constructUnitType
 
:supplyUnitType
->supplyON ->unitType
 
	<-unitType 0 GetUnitsByType dup ->list getlistcount ->listCount
 
	<-supplyON if
		<-listCount 0 do 
			<-list[i] dup getunitenabled not if 
				1 setunitenabled 
				return 
			endif
		loop
	else
		-1 <-listCount 1 sub do
			<-list[i] dup getunitenabled if 
				0 setunitenabled 
				return 
			endif
		loop
	endif
 
:constructUnitType
# the same functiion as supplyunittype, only aimed at infrastructure which does not consume energy once build.
->supplyON ->unitType
	<-supplyON if
		# supply should be turned on for all units of this type, since construction could have finished with the packets that were already underway.
		<-unitType 0 GetUnitsByType dup ->list getlistcount ->listCount
	else
		<-unitType 2 GetUnitsByType dup ->list getlistcount ->listCount
	endif
 
	<-supplyON if
		<-listCount 0 do 
			<-list[i] dup getunitenabled not if 
				1 setunitenabled 
				return 
			endif
		loop
	else
		-1 <-listCount 1 sub do
			<-list[i] dup getunitenabled if 
				0 setunitenabled 
				return 
			endif
		loop
	endif
 
:findOtherNodes
	"pylon" 1 getunitsbytype ->pylonList
	"microrift" 1 getunitsbytype ->microriftList
	"pod" 1 getunitsbytype ->podList
	do(<-podList 0)
		<-podList[i] ->UID
		# only add pods that are on the ground + containing energy
		<-UID GetUnitOccupiesLand if
			<-UID getunitsettings "Resource" gettableelement eq0 if
				<-oldNodeList <-UID listcontains not if
					<-UID @plan4Towers
				endif
			endif
		endif
	loop
	do(<-microriftList 0)
		<-microriftList[i] ->UID
		<-oldNodeList <-UID listcontains not if
			<-UID @plan4Towers
		endif
	loop
	do(<-pylonList 0)
		<-pylonList[i] ->UID
		<-oldNodeList <-UID listcontains not if
			<-UID @plan4Towers
		endif
	loop
	getriftlab ->UID @plan4Towers
	<-oldNodeList <-UID listcontains not if
		<-UID @plan4Towers
	endif
 
:findOtherTowers # A part of the findOtherNodes script, but separated to run during a different frame
	"tower" 1 getunitsbytype ->*towerList
	do(<-*towerList 0)
		<-*towerList[i] ->UID
		<-oldNodeList <-UID listcontains not if
			<-UID @plan4Towers
		endif
	loop
 
:findOtherTowers2 # Less effective, but better computation wise. Spam this when out of planned towers.
	# The below code requires less compute time, but is somehow less effective than the code above. How is getunitsbytype ordered?
	"tower" 1 getunitsbytype dup ->*towerList getlistcount ->listCount
	<-*towerList <-lastTower getlistindex 0 max ->listStart
	do(<-listCount <-listStart)
		<-*towerList[i] ->UID
		<-oldNodeList <-UID listcontains not if
			<-UID @plan4Towers
		endif
	loop
	<-*towerList[<-listCount 1 sub] ->lastTower
 
:plan4Towers
->UID
	<-UID getunitcell pop eq0 if return endif
	clearstack <-UID dup dup2 <-uid_PlannedTower PrependStackToList
	1 2 3 4 <-dir_PlannedTower PrependStackToList
	<-oldNodeList <-UID appendtolist
 
:cleanOldNodeList
	# remove destroyed nodes from the list
	do(-1 <-oldNodeList 1 sub)
		<-oldNodeList[i] ->UID
		<-UID getunitcell pop eq0 if
			<-oldNodeList i removelistelement
		endif
	loop
	# surviving old nodes need to be removed from the old node list periodically, because if they are unknown in the monitor, then no towers will be rebuild around them. So a constant slow purge is needed. Only remove 1 per 3 cycles.
	if(<-cycleCounter eq0)
		3 ->cycleCounter
		do(<-oldNodeList 0)
			<-uid_MonitorTower <-oldNodeList[i] listcontains not if
				<-oldNodeList i removelistelement
				return
			endif
		loop
	else
		<-cycleCounter 1 sub ->cycleCounter
	endif
 
:buildPlannedTower
	# randint(0 GetListCount(<-uid_PlannedTower)) ->indexPl # Builds faster in multiple directions.
	<-uid_PlannedTower getlistcount 1 sub ->indexPl # Builds more systematic, but can leave a front waiting if it's expanding on multiple sides.
	<-indexPl -1 eq if
		@findOtherTowers2
	else
		<-uid_PlannedTower[<-indexPl] ->UID <-dir_PlannedTower[<-indexPl] ->direction
		<-uid_PlannedTower <-indexPl RemoveListElement
		<-dir_PlannedTower <-indexPl RemoveListElement
		<-UID <-direction @buildOneTower
	endif
 
:buildBlockedTower
	# randint(0 GetListCount(<-uid_BlockedTower)) ->indexBl
	<-uid_BlockedTower getlistcount 1 sub ->indexBl
	<-indexBl -1 eq if
		@buildPlannedTower
	else
		<-uid_BlockedTower[<-indexBl] ->UID <-dir_BlockedTower[<-indexBl] ->direction
		<-uid_BlockedTower <-indexBl RemoveListElement
		<-dir_BlockedTower <-indexBl RemoveListElement
		<-UID <-direction @buildOneTower
	endif
 
:monitorTowers
	if(<-indexMT 0 lte)
		GetListCount(<-uid_MonitoredTower) 1 sub ->indexMT
	else
		<-indexMT 1 sub ->indexMT
	endif
	if(GetUnitCell(<-uid_MonitoredTower[<-indexMT]) pop eq0)
		if(GetUnitCell(<-uid_MonitorTower[<-indexMT]) pop neq0)		
			<-uid_BlockedTower <-uid_MonitorTower[<-indexMT] PrependToList
			<-dir_BlockedTower <-dir_MonitorTower[<-indexMT] PrependToList
		endif		
		<-uid_MonitoredTower <-indexMT RemoveListElement
		<-uid_MonitorTower <-indexMT RemoveListElement
		<-dir_MonitorTower <-indexMT RemoveListElement
	# else
		# # Some randomized tower building, which might unstuck tower building
		# <-uid_MonitorTower[<-indexMT] @buildOneRandomTower
	endif
 
:buildOneRandomTower
->UID
	randint(1 5) ->rand1_4
	<-UID <-rand1_4 @buildOneTower
 
:buildOneTower
->direction ->UID
	if(GetUnitCell(<-UID) pop eq0)
		return
	endif
 
	GetUnitPosition(<-UID) ->UIDPos
	<-UIDPos.y <-connectionHeight add ->UIDPos.y
 
	GetUnitType(<-UID) ->unitType
 
	<-debugPrint if 
		elapsedtime ->startTime
		<-UID " " <-direction concat3 " " <-UIDPos concat3 ->debugString
	endif
 
	switch
		case(<-direction 1 eq)
			<-relativePositionList1 ->relativeList
		endcase
		case(<-direction 2 eq)
			<-relativePositionList2 ->relativeList
		endcase
		case(<-direction 3 eq)
			<-relativePositionList3 ->relativeList
		endcase
		case(<-direction 4 eq)
			<-relativePositionList4 ->relativeList
		endcase
	endswitch
 
	# Do a pre-emptive tower check and abort if another tower is found in the middle of the positions list
	<-relativeList[0] ev2 <-UIDPos.z add ->testPos.z <-UIDPos.x add ->testPos.x
	GetTerrain(<-testPos.x <-testPos.z) ->testPos.y
	<-unitType "tower" eq if 1 ->check else 0 ->check endif
	GetUnits("tower" <-testPos <-quickRangeCheck 0 1 0 0 0 0) getlistcount <-check gt if 
		return 
		# <-debugPrint if <-debugString " " "QuickRangeCheck" concat3 print endif
	endif
 
	# Do a pre-emptive creeper check and classify it as blocked + abort if there is creeper in the middle of the positions list
	<-quickRangeCheck <-creeperRange sub <-creeperRange max ->checkRange
	GetCreeperInRange(<-testPos.x <-testPos.z <-checkRange 0 1 0) neq0 if
		<-uid_BlockedTower <-UID PrependToList
		<-dir_BlockedTower <-direction PrependToList
		return
		# <-debugPrint if <-debugString " " "CreeperRangeCheck" concat3 print endif
	endif
 
	# Check if the chosen direction is (partly) outside the map
	GetTerrain(<-testPos.x <-testPos.z) lt0 if return endif
 
	<-optimalCellSelection ->cellsToCheck
	-1 ->towersInRange # Even if no optimal towers are found nearby, construction still has to go ahead.
	0 ->skipI
	do(<-relativePositionsCount 1) # Index 0 is used for pre-emptive checks.
		switch
			case(<-cellsToCheck lte0)
				<-debugPrint if <-debugString " " 1 concat3 ->debugString endif
				break
			endcase
			case(<-skipI) # The find units in range are the most taxing. If one of those conditions is true, skip the next few i indices.
				<-skipI 1 sub ->skipI
				<-debugPrint if <-debugString " " 2 concat3 ->debugString endif
			endcase	
			if(<-towersInRange -1 gt)
				<-cellsToCheck 1 sub ->cellsToCheck
			endif
			<-relativeList[i] ev2 <-UIDPos.z add ->testPos.z <-UIDPos.x add ->testPos.x
			GetTerrain(<-testPos.x <-testPos.z) ->testPos.y
			case(<-testPos.y 0 lte)
				# cells outside the map will have a terrain height of -1
				<-debugPrint if <-debugString " " 3 concat3 ->debugString endif
				# <-smallSkip ->skipI # Avoid using this, it degrades the grid.
			endcase
			case(GetCellOccupiedCount(<-testPos.x <-testPos.z))
				<-debugPrint if <-debugString " " 4 concat3 ->debugString endif
			endcase
			# case(IsV3InMap(<-testPos) not)
				# <-debugPrint if <-debugString " " 5 concat3 ->debugString endif
			# endcase
			case(GetTerrainSpecial(<-testPos.x <-testPos.z) <-contaminentSlot eq)
				<-debugPrint if <-debugString " " 6 concat3 ->debugString endif
			endcase
			<-testPos.y <-connectionHeight add ->testPos.y # the next 3 checks are done slightly below the height of the connections. Slightly below because it was sometimes not getting terrainLOS right.
			distance(<-testPos <-UIDPos) ->testDist
			case(<-testDist <-maxTowerDistance gte)
				<-debugPrint if <-debugString " " 7 concat3 ->debugString endif
				# Do NOT use skipI here.
				# Sometimes 2 towers at an exact distance of 12 will not connect, so gte and not gt? This was a problem in version 1, also in 2?
			endcase
			case(<-testDist <-minTowerDistance lt <-disLOS not and)
				<-debugPrint if <-debugString " " 8 concat3 ->debugString endif
				<-largeSkip ->skipI
				<-cellsToCheck 1 sub ->cellsToCheck # be less picky
				if(<-towersInRange -1 gt) break endif
			endcase
			GetTerrainLOS(<-testPos <-UIDPos 0) ->losPos
			<-testPos.y <-UIDPos.y min ->connPosMinY
			<-testPos.y <-UIDPos.y max ->connPosMaxY
			case(<-losPos.y <-connPosMinY gte <-losPos.y <-connPosMaxY lte and)
				# <-cellsToCheck 1 sub ->cellsToCheck # be less picky
				if(<-towersInRange -1 gt) break endif
				<-debugPrint if <-debugString " " 9 concat3 ->debugString endif
			endcase
			<-disLOS if 
				GetUnits("tower" <-testPos <-minTowerDistance 0 1 1 0 0 0) getlistcount ->amtCloseTowers 
				<-testPos.y <-connectionHeight sub ->testPos.y # With LOS, check from an elevated position to get better viezing angles.
			else 
				<-testPos.y <-connectionHeight sub ->testPos.y
				GetUnits("tower" <-testPos <-minTowerDistance 0 1 0 0 0 0) getlistcount ->amtCloseTowers
			endif
			case(<-amtCloseTowers) # are there other towers too close by.
				<-debugPrint if <-debugString " " 10 concat3 ->debugString endif
				<-largeSkip ->skipI
				<-cellsToCheck 1 sub ->cellsToCheck # be less picky
				if(<-towersInRange -1 gt) break endif
			endcase
			# check the next few cells to look for more towers at a near optimal distance, to form extra connections.
			case(<-cellsToCheck)
				<-debugPrint if <-debugString " " 11 concat3 ->debugString endif
				0 ->goodTowersNearCell # gt(-1) atleast one suitable cell has been found, so the coordinates should be set.
				GetUnits("tower" <-testPos <-maxTowerDistance 0 1 0 0 0 0) getlistcount ->goodTowersNearCell
				if(<-goodTowersNearCell <-towersInRange lt) break endif	
				if(<-goodTowersNearCell <-towersInRange gt)
					<-goodTowersNearCell ->towersInRange
					<-testPos.x ->optimalX
					<-testPos.z ->optimalZ
				endif
				if(<-goodTowersNearCell <-optimalTowerTarget gte) break endif
				<-largeSkip ->skipI
			endcase
			<-debugPrint if <-debugString " " 12 concat3 ->debugString endif
			break # break the loop and build (or plan) the optimal tower outside of the loop
		endswitch
	loop
 
	if(<-towersInRange -1 gt)
		switch
			case(GetMeshHealth(<-optimalX <-optimalZ) 0 gt)
				<-uid_BlockedTower <-UID PrependToList
				<-dir_BlockedTower <-direction PrependToList
			endcase
			case(GetCreeperInRange(<-optimalX <-optimalZ <-creeperRange 0 1 0) neq0)
				<-uid_BlockedTower <-UID PrependToList
				<-dir_BlockedTower <-direction PrependToList
			endcase
			CreateUnitOnTerrain("tower" <-optimalX <-optimalZ 0) ->newTower
			<-debugPrint if <-newTower <-UID "-" <-direction concat3 setunitdebugtext endif
			# Start monitoring of that one tower
			<-uid_MonitoredTower <-newTower PrependToList
			<-uid_MonitorTower <-UID PrependToList
			<-dir_MonitorTower <-direction PrependToList
			<-indexMT 1 add ->indexMT
 
			<-newTower 999 constructunit
		endswitch
	endif
 
	<-debugPrint if 
		elapsedtime ->endTime 
		<-endTime <-startTime sub ->spentTime
		if(<-spentTime 10 gt)
			getgameupdatecount " " <-spentTime concat3 " " <-debugString concat3 printallsp
		endif
	endif

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 : Kalli - SnappingTool


Next Utility goes here

click here for source code

click here for source code


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 9×9 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.

Click here for the source code.

Click here for the source code.

CellOccupiedShape.4rpl
: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.

click here for the code

click here for the code

$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

click here for source code

click here for source code


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.

Click here for the source code

Click here for the source code

polygon_functions.4rpl
# 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:

Click to show example usage

Click to show example usage

polygon_functions_example.4rpl
	# 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.

click here for source code

click here for source code

BresenhamLine.4rpl
@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

click here for source code

click here for source code

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 RandInt and 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

click here for source code

click here for source code

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.4rpl
# 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

click here for source code

click here for source code


1)
Invitation issued on November 11th, 2022
2)
Play As Creeper
cw4/4rpl_tools.txt · Last modified: 2024/03/16 19:53 by LiteralNoob