<-CW3 Home<- CRPL Home

EasyCRPL

Version 1.1
#=============================================
# ===== :EasyCRPL: Collection by Telanir =====
# --VERSION: 1.1
# CREDITS:
# Big thanks to Knuckle Cracker (knucracker) for an amazing game and lots of well commented source!
# Big thanks to Tyler21 for a contribution of Forcefield code using Field cells!
#
# Usage: Simply paste this script at the bottom of your
# project to get the full benefits of the pre-written code.
# You can also just copy-paste parts of this you need.
#
# EasyCRPL is a collection of scripts from various community
# projects, members, and maps that have been created by myself
# or modified to meet a more generic purpose.
#
# -----
# A List of all available functions and their notation:
# @FunctionName: order of input - order of output with last object on top of the stack
# x - X coordinate
# y - Y coordinate
# n - numerical value
# i - integer value
# f - float value
# b - boolean value
# s - string value
# u - unit id, integer value
# L - list
# * - any value
#
# NOTE: Non-pixel coords are flipped vertically
# with (0,0) being top-left, whereas pixel (0,0)
# is the bottom left.
#
# -- Special Code --
# This code can be used at any time if it is in your
# script, and generally comes in blocks of functions.
# Forcefield Manager: A custom forcefield manager that helps safely create functional forcefields.
# @SetForcefieldProperties: 
#
# List-Builder: Two simple functions that will safely create a list without touching the stack.
# @BeginList [* *...] @BuildList
# @DoesListContain: L1 * - b1
# 
# Pre-Built Beam: A beam with a customizable color, alpha, and width.
# @SetBeamProperties: s1 i1 i2 i3 i4 i5 i6 f1 -
# @AnimateBeam: -
# @SetBeamTarget: u -
# @TargetBeam: x y b1 -
# @DestroyBeam: -
#
# -- Various Convenience Methods --
#
# SPECIAL METHODS, READ COMMENTS ABOVE CODE
# --
# @GetClosestUnit: x y f1 L1 - u
# @GetAllUnits: x y f1 L1 - [u1 u2...]i1
# @CRPLMatchesValue: u s1 * - b1
# --
#
# @GetCreeperInRadius: x y f1 - f2
# @SetImageAlpha: s1 i1 -
# @TerrainAccessible: x y i1 - b1
# @AreaOccupied: x y i1 - b1
# @AddOccupy: x y i1 b1 -
# -- Cell-Related Code --
# @UnitCoords: u - x y
# @MovePosition: x y x2 y2 f1 f2 - x y
# @GetVector: f1 f2 - x y
# @NormalizeVector: x y - x y
# @GetLength: x y - f1
# @GetAngle: x y x2 y2 - f1
# @RotateToAngle: x y x2 y2 f1 f2 - b1 f1
# @IsPointOnMap: x y - b1
# -- Pixel-Related Code --
# @UnitPixelCoords: u - x y
# @SetPixelCoords: x y -
# @MovePixelPosition: x y x2 y2 f1 f2 - x y
# @GetPixelVector: f1 f2 - x y
# @GetPixelAngle: x y x2 y2 - f1
# @RotateToPixelAngle: x y x2 y2 f1 f2 - b1 f1
#
# Wiki CRPL Reference: http://knucklecracker.com/wiki/doku.php?id=crpl:crplreference
#=============================================
 
#======================================
#======== FORCEFIELD MANAGER ==========
#======================================
 
# Sets the Forcefield properties.
# Order goes: RANGE CPUSH ACPUSH
# The higher the PUSH values the more the
# forcefield repels from the epicenter.
# Values are in millionths per unit of creeper moved.
# Notation: f1 i1 i2 -
# 12.5 100000 100000 @SetForcefieldProperties
:SetForcefieldProperties
	-?FF_INITIALIZED not if
		->FF_ACPUSH
		->FF_CPUSH
		->FF_RANGE
		true ->FF_INITIALIZED
		-?FF_X or(-?FF_Y) not if
			CurrentCoords ->FF_Y ->FF_X
		endif
	endif
 
# Function :SetForcefieldCoords
# Sets the coordinates of future forcefields.
# If a previous forcefield was present it removes
# the old one and creates a new one.
# Notation: x y -
# e.g. CurrentCoords @SetForcefieldCoords
:SetForcefieldCoords
	-?FORCEFIELD_AVAILABLE if
		@WipeForcefield
		->FF_Y
		->FF_X
		@CreateForcefield
	else
		->FF_Y
		->FF_X
	endif
 
# Function :OverrideWipeForcefieldProperties
# By default initializing forcefield properties locks the
# method and helps prevent leftover forcefields.
:OverrideWipeForcefieldProperties
	@WipeForcefield
	0 ->FF_ACPUSH
	0 ->FF_CPUSH
	0 ->FF_RANGE
	--FF_INITIALIZED
 
# Function :WipeForcefield
# Safe method to wipe forcefield, if there is none it will be ignored.
# Notation: -
# e.g. @WipeForcefield
:WipeForcefield
	-?FF_INITIALIZED if
		-?FORCEFIELD_AVAILABLE if
			--FORCEFIELD_AVAILABLE
			@ModifyForcefield(<-FF_X <-FF_Y <-FF_RANGE <-FF_CPUSH neg <-FF_ACPUSH neg)
		endif
	endif
 
# Function :CreateForcefield
# Creates a Forcefield with the initialized parameters.
# Notation: -
# e.g. @CreateForcefield
:CreateForcefield
	-?FF_INITIALIZED if
		-?FORCEFIELD_AVAILABLE not if
			true ->FORCEFIELD_AVAILABLE
			@ModifyForcefield(<-FF_X <-FF_Y <-FF_RANGE <-FF_CPUSH <-FF_ACPUSH)
		endif
	endif
 
# Major props to Tyler, thanks for helping make forcefields work as intended.
# Function :ModifyForcefield
# Adds input creeper and anti-creeper to surrounding field cells with
# strength decreasing with distance form the epicenter.
# Negative values will modify the forces in opposite directions and 0 will change nothing.
# Notation: x y f1 i1 i2 -
# e.g. CurrentCoords 10 100000 -100000 @ModifyForcefield
:ModifyForcefield
neg ->SF_ACPUSH
neg ->SF_CPUSH
asfloat ->SF_RANGE # float-value is required for the div operations
->SF_Y
->SF_X
 
<-SF_RANGE 2 mul 1 add 0 
do
	<-SF_RANGE 2 mul 1 add 0 
	do
		<-SF_X J add <-SF_RANGE sub ->xCell
		<-SF_Y I add <-SF_RANGE sub ->yCell
 
		<-xCell neq(<-SF_X) or(<-yCell neq(<-SF_Y)) if
			<-SF_X <-SF_Y <-xCell <-yCell Distance ->SF_DIST
			<-SF_DIST lte(<-SF_RANGE) if
				<-SF_DIST neg add(<-SF_RANGE) div(<-SF_RANGE) mul(0.7) add(0.3) ->SF_MOD
				<-SF_ACPUSH mul(<-SF_MOD) asint ->SF_ACPUSHMOD
				<-SF_CPUSH mul(<-SF_MOD) asint ->SF_CPUSHMOD
				@GetVector(@GetAngle(<-xCell <-yCell <-SF_X <-SF_Y) <-SF_CPUSHMOD) ->cRLCell ->cUDCell
				@GetVector(@GetAngle(<-xCell <-yCell <-SF_X <-SF_Y) <-SF_ACPUSHMOD) ->acUDCell ->acRLCell
				GetFieldCell(<-xCell <-yCell) ->oacRLCell ->oacUDCell ->ocRLCell ->ocUDCell
				SetFieldCell(<-xCell <-yCell <-cUDCell add(<-ocUDCell) <-cRLCell add(<-ocRLCell) <-acUDCell add(<-oacUDCell) <-acRLCell add(<-oacRLCell)) # n1, n2, n3 and n4 are for creeper U(+)/D(-), creeper R(+)/L(-), AC U/D and AC R/L
			endif
		endif
	loop
loop
 
#======================================
#=========== LIST-BUILDER =============
#======================================
 
# How to use:
# If you ever need to quickly build a list of X units then
# you can use these two functions to do it in one line.
# You can use this in conjuction with @DoesListContain listed in the index.
# e.g. @BeginList "COLLECTOR" "RELAY" "COMMANDNODE" "PULSECANNON" "MORTAR" @BuildList ->newList
 
:BeginList
	StackSize ->BL_I
 
:BuildList
	StackSize ->BL_M
	CreateList ->BL_L
	<-BL_M <-BL_I do
		->BL_O
		<-BL_L <-BL_O PrependToList
	loop
	<-BL_L
 
# Function :DoesListContain
# This function an object, then a list and returns a boolean value.
# Notation: L1 n1 - b1
# eg. <-myList <-myValue @DoesListContain ->contains
:DoesListContain
	->dl_value
	->dl_list
	false ->dl_contained
 
	<-dl_list GetListCount 0 do
		<-dl_list I GetListElement eq(<-dl_value) if
			true ->dl_contained
			break
		endif
	loop
 
	<-dl_contained
 
#======================================
#=========== PRE-BUILT BEAM ===========
#======================================
# How to use:
# Copy the 4 beam methods into your code.
# @AnimateBeam needs to run /every/ script invocation.
# Use @SetBeamProperties at any time to change the
# appearence of the beam.
# Use @TargetBeam at any time to focus the beam on something,
# if it is moving, keep calling @TargetBeam.
# When finished, call @DestroyBeam and the beam will vanish!
#
# Warning: DO NOT USE @BeamInit!
 
# Function: :SetBeamProperties
# Sets the properties of the beam.
# Alpha, red, green, and blue range from 0 - 255 
# With 0 being transparent and 255 being opaque
# The string is the "Beam" item, it can be found here:
# http://knucklecracker.com/wiki/doku.php?id=crpl:custom_image_repository
# The two integers after the image name are the indexes for
# the damage icons. If your "Damage 0" is at "Custom1" then write 1,
# then if your "Damage 3" is at "Custom4" then write 4 after.
# Notation: s1 i1 i2 i3 i4 i5 i6 f1 -
# eg. "Custom0" 1 4 255 0 0 255 1.5 @SetBeamProperties
:SetBeamProperties
	->BM_SCALEY
	->BM_ALPHA
	->BM_BLUE
	->BM_GREEN
	->BM_RED
	->BM_DAMAGEINDEXEND
	->BM_DAMAGEINDEXSTART
	->BM_IMAGE
	true ->BM_SET
	-?BM_ACTIVE if
		@BeamInit
	endif
 
# Function: :AnimateBeam
# Animates the beam and the explosion animation.
# The boolean indicates whether it is a pixel position.
# Notation: -
# eg. @AnimateBeam
:AnimateBeam
	-?BM_ACTIVE and(-?BM_SET) if
		if(<-BM_TOG)
			<-BM_X <-BM_Y ->BM_PY ->BM_PX
		else
			<-BM_X <-BM_Y CellToPixel ->BM_PY ->BM_PX
		endif
 
		-?BM_T if
			GetUnitType(<-BM_T) ->BM_TT
			if(<-BM_TT eq("BOMBERAIR") or(<-BM_TT eq("STRAFERAIR")))
				<-BM_PY add(27) ->BM_PY
			endif
			if(<-BM_TT eq("GUPPYAIR"))
				<-BM_PY add(10) ->BM_PY
			endif
		endif
 
		self CONST_PIXELCOORDX GetUnitAttribute ->BM_SX
		self CONST_PIXELCOORDY GetUnitAttribute ->BM_SY
 
		<-BM_PX <-BM_SX sub ->BM_DX
		<-BM_PY <-BM_SY sub ->BM_DY
		<-BM_DY <-BM_DX atan2 ->BM_A
		<-BM_SX <-BM_SY <-BM_PX <-BM_PY Distance ->BM_D
		<-BM_D 24 div ->BM_SCX
 
		<-BM_DX 2 div ->BM_PPX
		<-BM_DY 2 div ->BM_PPY 
 
		SetImagePosition(self "beam" <-BM_PPX <-BM_PPY -0.1) 
		SetImageScaleX(self "beam" <-BM_SCX)
		SetImageRotation(self "beam" <-BM_A) 
 
		<-BM_DAMAGEINDEXSTART neq(-1) and(<-BM_DAMAGEINDEXEND neq(-1)) if
			<-B_IMG add(1) ->B_IMG
			<-B_IMG <-BM_DAMAGEINDEXSTART lt if <-BM_DAMAGEINDEXSTART ->B_IMG endif
			<-B_IMG <-BM_DAMAGEINDEXEND gt if <-BM_DAMAGEINDEXSTART ->B_IMG endif
			SetImage(self "damage" concat("Custom" <-B_IMG))
			SetImagePosition(self "damage" <-BM_DX <-BM_DY -0.1) 
		endif
	endif
 
# Function: :SetBeamTarget
# The purpose of this function is for the main
# @AnimateBeam function to be aware if the target
# is a certain type of unit. It will automatically
# make corrections for flying units.
# Use -1 if the target is a non-unit.
:SetBeamTarget
	->BT_T
	<-BT_T eq(-1) if
		--BM_T
	else
		<-BT_T ->BM_T
	endif
 
# Function: :TargetBeam
# Focuses the beam on a target location.
# If a beam does not exist, makes one.
# The boolean value indicates whether or not the
# new position is a pixel position.
# IF THE ENEMY IS AN 'AIR' SUFFIX ENEMY (aka STRAFERAIR)
# YOU MUST USE @SetBeamTarget TO CORRECT THE Y-OFFSET.
# Notation: x y b1 -
# eg. @UnitCoords(<-targetUnit) false @TargetBeam
# eg. 100 100 true @TargetBeam
:TargetBeam
	->BM_TOG
	->BM_Y
	->BM_X
	-?BM_SET not if
		# Set some default values.
		"Custom0" 1 4 255 0 0 255 1.5 @SetBeamProperties
	endif
	<-BM_ACTIVE not if
		true ->BM_ACTIVE
		@BeamInit
	endif
 
# Function: :DestroyBeam
# Removes the active beam, if there is one.
# Notation: -
# eg. @DestroyBeam
:DestroyBeam
	-?BM_ACTIVE if
		--BM_ACTIVE
		self "beam" "NONE" SetImage
		self "damage" "NONE" SetImage
	endif
 
# Function: :BeamInit
# Used by the Beam script to update Beam appearance.
:BeamInit
	self "beam" <-BM_IMAGE SetImage
	self "beam" <-BM_RED <-BM_GREEN <-BM_BLUE <-BM_ALPHA SetImageColor
	self "beam" <-BM_SCALEY SetImageScaleY
	self "beam" -0.04 SetImagePositionZ
 
#======================================
#==== VARIOUS CONVENIENCE METHODS =====
#======================================
 
# Function :GetClosestUnit
# WARNING: use this convenience function sparingly, tens of CRPL Cores
# running this all at once every frame will slow the game down significantly.
#
# This function uses a flexible algorithm to filter
# units within a certain distance and retrieve the one
# closest to the position given to the function.
# It requires a position, a maximum distance, and an
# instruction list.
#
# The instruction list can contain multiple objects (but at least one is needed).
# An instruction follows this format: "unittype:parameter1,parameter2,parameter3=value,parameter4=value..."
# All applicable unit types can be found on:
# https://knucklecracker.com/wiki/doku.php?id=crpl:docs:getunittype
#
# The following parameters are acceptable:
# LANDED BUILT RANK=value
# LANDED and BUILT can accept modifiers. * means __either__, - means NOT, no modifier means IS
# If the parameter is none of the three, and the unit type is defined as "CRPLCORE" the assumed
# parameter type is a CRPLCore variable which is defined like so: myVar=myVal
#
# The unit type can be substituted by ALL which will affect all instructions, ALL must be in the
# beginning of the list or it will be assumed to be a unit type. 
# ALL does not accept RANK or script variables as parameters.
# 
# == EXAMPLE ==
# @BeginList "ALL:BUILT" "COLLECTOR" "relay:*built,rank=0.5" "pulsecannon:-landed,rank=2" "crplcore:emit=3.5" @BuildList ->instructions
# CurrentCoords 50 <-instructions @GetClosestUnit ->unit
# 
# Explanation: All of the instructions within the list are acceptable. The only time the parameters are
# case sensitive is when defining script parameters. CONST_ISLANDED and CONST_ISBUILDING do not affect CRPLCores.
# According to these instructions, to get the closest unit within a range of 50 we will accept any built collector,
# any relay that is built or not but we give the a half the normal rank so the algorithm will treat them as if
# they are twice the distance away from anything else in the way. A collector and relay at the same distance will
# always return the collector. Next we prioritize any flying pulse cannon and override the ALL parameter, we
# then also look for any CRPLCores we made that had a script variable of "emit" that is equal to 3.5.
#
# Notation: x y f1 L1 - u
# e.g. CurrentCoords 50 <-instructionList @GetClosestUnit ->unit
:GetClosestUnit
	->un_list
	asfloat ->un_dist
	asfloat ->un_y
	asfloat ->un_x
 
	-1 ->un_unit
	-1 ->un_udist
 
	<-un_x <-un_y <-un_dist <-un_list @GetAllUnits ->un_count
	<-un_count neq0 if
		<-un_count 0 do
			->un_temp
			<-un_temp GetUnitType ->un_type
			# GET INSTRUCTION FOR THIS UNIT TYPE
			<-un_instructions GetListCount 0 do
				<-un_instructions GetListElement(I) ->un_inst
				# found correct instruction
				<-un_inst StartsWith(<-un_type) if
					<-un_inst Split(" ") ->un_ilist
					<-un_ilist GetListElement(3) asfloat ->une_rank
 
					# now we apply the priority and get the closest unit
					<-un_x <-un_y @UnitCoords(<-un_temp) Distance div(<-une_rank) ->un_tdist
					<-un_tdist lt(<-un_udist) or(<-un_udist eq(-1)) if
						<-un_tdist ->un_udist
						<-un_temp ->un_unit
					endif
					break
				endif
			loop
		loop
	endif
	<-un_unit
 
# Function :GetAllUnits
# Grabs all units of specified instruction within a radius of
# a given point. Read :GetClosestUnit for more info.
# Notation: x y f1 L1 - [u1 u2...]i1
# e.g. 
# CurrentCoords 50 <-instructionList @GetAllUnits ->unitCount
# <-unitCount 0 do
# # code here
# loop
:GetAllUnits
	->un_list
	asfloat ->un_dist
	asfloat ->un_y
	asfloat ->un_x
 
	CreateList ->un_units
	--un_all_landed
	--un_all_built
 
	<-un_list GetListCount gt(0) if
		<-un_list GetListElement(0) ToUpper ->un_elm
		<-un_elm StartsWith("ALL") if
			<-un_elm StringReplace("ALL:" "") ->un_elm
			<-un_elm Split(",") ->un_values
			<-un_values GetListCount 0 do
				<-un_values GetListElement(I) ->un_elm
				<-un_elm ToUpper EndsWith("LANDED") if
					<-un_elm StartsWith("-") if
						false ->un_all_landed
					else
						true ->un_all_landed
					endif
				endif
				<-un_elm ToUpper EndsWith("BUILT") if
					<-un_elm StartsWith("-") if
						false ->un_all_built
					else
						true ->un_all_built
					endif
				endif
			loop
		endif
	endif
 
	CreateList ->un_instructions
	<-un_list GetListCount 0 do
		"" ->compile_task
		"" ->un_type
		1.0 ->rank
 
		--un_landed_req
		--un_built_req
 
		-?un_all_landed if <-un_all_landed ->un_landed_req endif
		-?un_all_built if <-un_all_built ->un_built_req endif
 
		<-un_list GetListElement(I) ->un_elm
		<-un_elm Split(":") ->un_lelm
 
		<-un_lelm GetListElement(0) ToUpper ->un_type
 
		<-un_type ->compile_task
		"" ->append_task
 
		<-un_lelm GetListCount gt(1) if
			<-un_lelm GetListElement(1) ->un_elm
			<-un_elm StringReplace(<-un_type concat(":") "") ->un_elm
			<-un_elm Split(",") ->un_values
			<-un_values GetListCount 0 do
				<-un_values GetListElement(I) ->un_elm
				true ->definingVariable
				<-un_elm ToUpper EndsWith("LANDED") if
					<-un_elm StartsWith("-") if
						false ->un_landed_req
					else
						true ->un_landed_req
					endif
					<-un_elm StartsWith("*") if
						--un_landed_req
					endif
					false ->definingVariable
				endif
				<-un_elm ToUpper EndsWith("BUILT") if
					<-un_elm StartsWith("-") if
						false ->un_built_req
					else
						true ->un_built_req
					endif
					<-un_elm StartsWith("*") if
						--un_built_req
					endif
					false ->definingVariable
				endif
				<-un_elm ToUpper StartsWith("RANK") if
					<-un_elm Split("=") ->un_rankl
					<-un_rankl GetListElement(1) asfloat ->rank
					false ->definingVariable
				endif
 
				<-definingVariable and(<-un_type eq("CRPLCORE")) if
					<-un_elm Split("=") ->un_varl
					<-append_task concat(" ") concat(<-un_varl GetListElement(0)) concat(" ") concat(<-un_varl GetListElement(1) StringReplace("true" "1") StringReplace("false" "0")) ->append_task
				endif
			loop
		endif
		# 2 means not applicable
		<-compile_task concat(" ") ->compile_task
		-?un_built_req if <-compile_task concat(<-un_built_req) else <-compile_task concat("2") endif ->compile_task
		<-compile_task concat(" ") ->compile_task
		-?un_landed_req if <-compile_task concat(<-un_landed_req) else <-compile_task concat("2") endif ->compile_task
		<-compile_task concat(" ") ->compile_task
		<-compile_task concat(<-rank) ->compile_task
		<-compile_task concat(<-append_task) ->un_instruction
		<-un_instructions AppendToList(<-un_instruction)
	loop
 
	<-un_x <-un_y <-un_dist false GetAllUnitsInRange ->un_count
	<-un_count neq0 if
		<-un_count 0 do
			->un_temp
			<-un_temp neq(self) if
				<-un_temp GetUnitType ->un_type
				# GET INSTRUCTION FOR THIS UNIT TYPE
				<-un_instructions GetListCount 0 do
					<-un_instructions GetListElement(I) ->un_inst
					<-un_inst StartsWith(<-un_type) if
						<-un_inst Split(" ") ->un_ilist
						--une_built
						--une_landed
						# 0 is the type
						<-un_ilist GetListElement(1) ->une_t <-une_t neq(2) if <-une_t ->une_built endif
						<-un_ilist GetListElement(2) ->une_t <-une_t neq(2) if <-une_t ->une_landed endif
						<-un_ilist GetListElement(3) asfloat ->une_rank
 
						false ->skip
						<-un_type eq("CRPLCORE") if
							4 ->unc_index
							while <-unc_index lt(<-un_ilist GetListCount) repeat
								<-un_ilist GetListElement(<-unc_index) ->unc_var
								<-unc_index add(1) ->unc_index
								<-un_ilist GetListElement(<-unc_index) ->unc_val
								<-unc_index add(1) ->unc_index
								<-un_temp <-unc_var <-unc_val @CRPLMatchesValue ->unc_match
								<-unc_match not if
									true ->skip
								endif
							endwhile
						endif
						not(<-skip) if
							# MATCH VALUES
							<-un_type @BuildApplies if
								-?une_built if GetUnitAttribute(<-un_temp CONST_ISBUILDING) eq(<-une_built) ->skip endif
							endif
							<-un_type @LandedApplies if
								-?une_landed and(not(<-skip)) if GetUnitAttribute(<-un_temp CONST_ISLANDED) neq(<-une_landed) ->skip endif
							endif
							not(<-skip) if
								<-un_units AppendToList(<-un_temp)
							endif
						endif
						break
					endif
				loop
			endif
		loop
	endif
	<-un_units GetListCount 0 do
		<-un_units GetListElement(I)
	loop
	<-un_units GetListCount
 
:BuildApplies
	->ba_type
	# list of what DOESN'T
	-?ba_applies not if
		@BeginList
			"GUPPYAIR" "BOMBERAIR" "STRAFERAIR" "CRPLCORE" "EMITTER" "SPORETOWER" "RUNNER" "RUNNERNEST"
			"AETOWER" "INHIBITOR" "POWERZONE" "OREDEPOSIT" "RESOURCEPACK" "TOTEM" "AOO" "SHIELDKEY"
			"MESSAGEARTIFACT"
		@BuildList ->ba_applies
	endif
	not(<-applies @DoesListContain(<-ba_type))
 
:LandedApplies
	->la_type
	# list of what DOES
	-?la_applies not if
		@BeginList
			"TERP" "SHIELD" "PULSECANNON" "MORTAR" "SPRAYER" "BEAM" "SNIPER"
		@BuildList ->la_applies
	endif
	<-la_applies @DoesListContain(<-la_type)
 
# Function :CRPLMatchesValue
# Takes a unit, a value and a variable and determines
# without a given script whether or not the CRPLCore
# has a variable with that value.
# Notation: u s1 * - b1
# e.g. self "myVar" 5 @CRPLMatchesValue ->matchesValue
:CRPLMatchesValue
	->uv_val
	->uv_var
	->uv_unit
	false ->uv_found
 
	<-uv_var <-uv_val GetCoresWithVar ->uv_count
	<-uv_count neq(0) if
		<-uv_count 0 do
			->uv_temp
			<-uv_temp eq(<-uv_unit) if
				true ->uv_found
			endif
		loop
	endif
	<-uv_found
 
# Function :GetCreeperInRadius
# Returns the SUM of all creeper in a certain square
# around an origin. If there is anti-creeper present this
# value may go into the negatives.
# Notation: x y f1 - f2
# eg. CurrentCoords 1 @GetCreeperInRadius ->sumcreepergrid3x3
:GetCreeperInRadius
	->CR_RAD
	->CR_Y
	->CR_X
	0 ->CR_C
	<-CR_RAD mul(2) add(1) ->CR_LENGTH
 
	<-CR_LENGTH 0 do
		<-CR_LENGTH 0 do
			<-CR_Y I add <-CR_RAD sub ->CR_FY
			<-CR_X J add <-CR_RAD sub ->CR_FX
			<-CR_FX <-CR_FY GetCreeper add(<-CR_C) ->CR_C
		loop
	loop
 
	<-CR_C
 
# Function: :SetImageAlpha
# Sets the alpha of the specified image for self.
# Alpha ranges from 0 - 255 with 0 transparent and 255 opaque
# Notation: s1 i1 -
# eg. "main" 100 @SetImageAlpha
:SetImageAlpha
asint ->si_alpha_new
->si_image
self <-si_image GetImageColor ->si_alpha ->si_blue ->si_green ->si_red
self <-si_image <-si_red <-si_green <-si_blue <-si_alpha_new SetImageColor
 
# Function :TerrainAccessible
# Returns whether or not there is smooth terrain available
# of equivalent height in a certain area of specified radius.
# Radius is measured in cells.
# Area of radius 0 is 1 cell, area of radius 1 is 9 cells, etc.
# Notation: x y i1 - b1
# eg. 10 10 5 @TerrainAccessible ->accessible
:TerrainAccessible
	->ta_rad
	->ta_y
	->ta_x
 
	-1 ->ta_fr
	<-ta_rad mul(2) add(1) ->ta_length
	<-ta_rad ->ta_mid
 
	<-ta_length 0 do
		<-ta_length 0 do
			<-ta_y I add <-ta_mid sub ->ta_aoy
			<-ta_x J add <-ta_mid sub ->ta_aox
			<-ta_aox <-ta_aoy GetTerrain ->ta_terr
			<-ta_terr 0 lt if
				false
				return
			else
				<-ta_fr -1 eq if
					<-ta_terr ->ta_fr
				else
					<-ta_fr <-ta_terr neq if
						false
						return
					else
					endif
				endif
			endif
		loop
	loop
	true
 
# Function :AreaOccupied
# Returns whether or not a certain area is occupied by one or
# more other units. Radius is measured in cells.
# Notation: x y i1 - b1
# eg. 10 10 5 @AreaOccupied ->occupied
:AreaOccupied
	->ao_rad
	->ao_y
	->ao_x
 
	<-ao_rad mul(2) add(1) ->ao_length
	<-ao_rad ->ao_mid
 
	<-ao_length 0 do
		<-ao_length 0 do
			<-ao_y I add <-ao_mid sub ->ao_aoy
			<-ao_x J add <-ao_mid sub ->ao_aox
			<-ao_aox <-ao_aoy GetCellOccupiedCount 0 gt if
				true
				return
			endif
		loop
	loop
	false
 
# Function :OccupyArea
# Safety net for @AddOccupy to prevent bad areas.
# Notation: x y f1 -
# eg. <-targetX <-targetY 1 @OccupyArea # this would occupy an area of land
# #in a 3x3 grid around the central point, useful for flying units looking to land
:OccupyArea
	-?OA_OCCUPIED not if
		# First occupation is legal.
		->OA_RAD
		->OA_Y
		->OA_X
 
		<-OA_X <-OA_Y <-OA_RAD true @AddOccupy
		# Does occupy, is initialized.
		true ->OA_OCCUPIED
	else
		<-OA_OCCUPIED if
			# If occupying land already, disable occupation for re-assigning.
			<-OA_X <-OA_Y <-OA_RAD false @AddOccupy
		endif
		->OA_RAD
		->OA_Y
		->OA_X
		<-OA_OCCUPY if
			<-OA_X <-OA_Y <-OA_RAD true @AddOccupy
		endif
		<-OA_OCCUPY ->OA_OCCUPIED
	endif
 
:ReleaseOccupiedArea
	<-OA_OCCUPIED if
		# If occupying land already, disable occupation.
		<-OA_X <-OA_Y <-OA_RAD false @AddOccupy
		false ->OA_OCCUPIED
	endif
 
# Function :AddOccupy
# Returns whether or not a certain area is occupied by one or
# more other units. Radius is measured in cells.
# Notation: x y f1 b1 -
# eg. CurrentCoords 1 false @AddOccupy #this would allow another unit to land
# #in a 3x3 grid around the central point.
:AddOccupy
	->OC_ADD
	->OC_RAD
	->OC_Y
	->OC_X
 
	<-OC_RAD mul(2) add(1) ->OC_LENGTH
	<-OC_RAD ->OC_MID
 
	<-OC_LENGTH 0 do
		<-OC_LENGTH 0 do
			<-OC_Y I add <-OC_MID sub ->OC_AOY
			<-OC_X J add <-OC_MID sub ->OC_AOX
			<-OC_AOX <-OC_AOY GetCellOccupiedCount ->OC_COUNT
			<-OC_ADD if
				<-OC_AOX <-OC_AOY <-OC_COUNT add(1) SetCellOccupiedCount
			else
				<-OC_AOX <-OC_AOY <-OC_COUNT sub(1) SetCellOccupiedCount
			endif
		loop
	loop
 
#======================================
#========= CELL RELATED CODE ==========
#======================================
 
# Function :UnitCoords
# Simple convenience method. Takes a unit and returns the coordinates.
# 'CurrentCoords' and 'self @UnitCoords' are equivalent.
# Notation: n1 - x y
# eg. <-myUnitID @UnitCoords ->unitX ->unitY
:UnitCoords
	asint ->uc_u
	<-uc_u CONST_COORDX GetUnitAttribute
	<-uc_u CONST_COORDY GetUnitAttribute
 
# Function :SetUnitCoords
# Simple convenience method. Sets current units pixel coordinates.
# Notation: x y -
# eg. x y u @SetPixelCoords
:SetUnitCoords
	->sp_unit
	asfloat ->sp_y
	asfloat ->sp_x
	SetUnitAttribute(<-sp_unit CONST_COORDX <-sp_x)
	SetUnitAttribute(<-sp_unit CONST_COORDY <-sp_y)
 
# Function :MovePosition
# Uses the given position, angle, and speed and returns a new position.
# The target position is used for correctional purposes so that it doesn't overstep.
# If you want to ignore the correctional functionality set the target coordinates to -5000 -5000.
# Notation: x y x2 y2 f1 f2 - x y
# eg. CurrentCoords <-targetX <-targetY <-angle 1.5 @MovePosition ->y ->x
:MovePosition
	asfloat ->mp_ms
	asfloat ->mp_an
	asfloat ->mp_tY
	asfloat ->mp_tX
	asfloat ->mp_pY
	asfloat ->mp_pX
 
	<-mp_an <-mp_ms @GetVector ->mp_vY ->mp_vX
 
	<-mp_pX <-mp_tX sub <-mp_pY <-mp_tY sub @GetLength <-mp_ms lt if
		<-mp_tX
		<-mp_tY
	else
		<-mp_pX <-mp_vX add
		<-mp_pY <-mp_vY add
	endif
 
# Function :GetIntermediatePosition
# Uses two points and a distance to find a position that is
# between the two points, but X distance away from the target.
# Negative distances will go beyond the target.
# Notation: x y x2 y2 f1 - x y
# eg. CurrentPosition @GetUnitCoords(<-targetUnit) 10 @GetIntermediatePosition QueueMove
:GetIntermediatePosition
	asfloat ->gip_dist
	asfloat ->gip_ty
	asfloat ->gip_tx
	asfloat ->gip_y
	asfloat ->gip_x
 
	<-gip_tx <-gip_ty <-gip_x <-gip_y @GetAngle ->gip_a
	<-gip_a <-gip_dist @GetVector ->gip_vy ->gip_vx
	<-gip_tx <-gip_vx add
	<-gip_ty <-gip_vy add
 
# Function :GetVector
# Uses an angle, and speed and returns a vector
# You can add the vector to a position to get a moving effect
# Notation: f1 f2 - x y
# eg. <-angle 1.5 @GetVector ->y ->x
:GetVector
	asfloat ->gv_vrsm
	asfloat ->gv_vrAngle
 
	<-gv_vrAngle sin ->gv_vrY
	<-gv_vrAngle cos ->gv_vrX
	<-gv_vrX <-gv_vrY @NormalizeVector ->gv_vrY ->gv_vrX
	<-gv_vrY <-gv_vrsm mul ->gv_vrY
	<-gv_vrX <-gv_vrsm mul ->gv_vrX
	<-gv_vrX
	<-gv_vrY neg
 
# Function :NormalizeVector
# Takes an (x,y) coordinate and returns it in terms of 0 to 1,
# with 1 being the maximum length (traveling directly up/down or left/right)
# and 0 being the minimum.
# Notation: x y - x y
# eg. <-vectorX <-vectorY @NormalizeVector ->vectorY ->vectorX
:NormalizeVector
	asfloat ->nv_theY
	asfloat ->nv_theX
	<-nv_theX <-nv_theY @GetLength ->nv_length
	<-nv_theY <-nv_length div ->nv_ny
	<-nv_theX <-nv_length div ->nv_nx
	<-nv_nx
	<-nv_ny
 
# Function :GetLength
# Takes a width and height and calculates a length.
# Notation: x y - f1
# eg. 4 3 @GetLength ->length #(will return 5)
:GetLength
	asfloat ->gl_theY
	asfloat ->gl_theX
	<-gl_theX 2.0 pow <-gl_theY 2.0 pow add sqrt
 
# Function :GetAngle
# Takes an initial position and a target position,
# then calculates an angle to face the target.
# Value returned in radians.
# Notation: x y x2 y2 - f1
# eg. CurrentCoords <-targetUnit @UnitCoords @GetAngle ->angle
:GetAngle
	asfloat ->ga_targetY
	asfloat ->ga_targetX
	asfloat ->ga_curY
	asfloat ->ga_curX
 
	<-ga_targetX <-ga_curX sub ->ga_deltaX
	<-ga_curY <-ga_targetY sub ->ga_deltaY
	<-ga_deltaY <-ga_deltaX atan2 ->ga_desiredAngle
	<-ga_desiredAngle
 
# Function :RotateToAngle
# Takes an initial position and a target position,
# calculates an angle, and then returns an angle
# restricted by the specified speed and angle.
# This function can allow for slow rotations.
# It will also return a boolean value which is
# true if the angle directly faces the target.
# Notation: x y x2 y2 f1 f2 - b1 f1
# eg. CurrentCoords <-targetUnit @UnitCoords <-angle 0.03 @RotateToAngle ->newAngle ->isOnTarget
:RotateToAngle
	asfloat ->rta_maxRot
	asfloat ->rta_curAngle
	asfloat ->rta_targetY
	asfloat ->rta_targetX
	asfloat ->rta_curY
	asfloat ->rta_curX
 
	<-rta_targetX <-rta_curX sub ->rta_deltaX
	#(vertical 0 is TOP-left) 
	#cell-y is flipped so we instead subtract target-y from current-y
	<-rta_curY <-rta_targetY sub ->rta_deltaY
	<-rta_deltaY <-rta_deltaX atan2 ->rta_desiredAngle
 
	<-rta_curAngle <-rta_desiredAngle ShortestAngle ->rta_rot
 
	<-rta_rot <-rta_maxRot gt if
		<-rta_maxRot ->rta_rot
	else
		<-rta_rot <-rta_maxRot neg lt if
			<-rta_maxRot neg ->rta_rot
		endif
	endif
 
	<-rta_rot abs 0.0001 lt if
		1 ->rta_onTarget
	else
		0 ->rta_onTarget
	endif
 
	<-rta_curAngle <-rta_rot add ->rta_rot
 
	<-rta_onTarget
	<-rta_rot
 
# Function :IsPointOnMap
# Takes a set of cell-coordinates and returns true
# if the coordinates are on the map.
# Notation: x y - b1
# eg. -1 -1 @IsPointOnMap ->onMap #(will return false)
:IsPointOnMap
	->pom_pointY
	->pom_pointX
	1 ->pom_onMap
	<-pom_pointY MapHeight gt if
		0 ->pom_onMap
	endif
	<-pom_pointY 0 lt if
		0 ->pom_onMap
	endif
	<-pom_pointX MapWidth gt if
		0 ->pom_onMap
	endif
	<-pom_pointX 0 lt if
		0 ->pom_onMap
	endif
	<-pom_onMap
 
#======================================
#========= PIXEL RELATED CODE =========
#======================================
 
# Function :UnitPixelCoords
# Simple convenience method. Takes a unit and returns the pixel coordinates.
# 'CurrentPixelCoords' and 'self @UnitPixelCoords' are equivalent.
# Notation: u - x y
# eg. <-myUnitID @UnitCoords ->unitY ->unitX
:UnitPixelCoords
	asint ->uc_u
	<-uc_u CONST_PIXELCOORDX GetUnitAttribute
	<-uc_u CONST_PIXELCOORDY GetUnitAttribute
 
# Function :SetUnitPixelCoords
# Simple convenience method. Sets current units pixel coordinates.
# Notation: u x y -
# eg. <-unit 10 10 @SetPixelCoords
:SetUnitPixelCoords
	asfloat ->sp_y
	asfloat ->sp_x
	->sp_unit
	SetUnitAttribute(<-sp_unit CONST_PIXELCOORDX <-sp_x)
	SetUnitAttribute(<-sp_unit CONST_PIXELCOORDY <-sp_y)
 
# Function :SetPixelCoords
# Simple convenience method. Sets current units pixel coordinates.
# Notation: x y -
# eg. x y @SetPixelCoords
:SetPixelCoords
	asfloat ->sp_y
	asfloat ->sp_x
	SetUnitAttribute(self CONST_PIXELCOORDX <-sp_x)
	SetUnitAttribute(self CONST_PIXELCOORDY <-sp_y)
 
# Function :MovePixelPosition
# Uses the given position, angle, and speed and returns a new position.
# The target position is used for correctional purposes so that it doesn't overstep.
# Notation: x y x2 y2 f1 f2 - x y
# eg. CurrentPixelCoords <-targetX <-targetY <-angle 1.5 @MovePixelPosition ->y ->x
:MovePixelPosition
	asfloat ->mp_ms
	asfloat ->mp_an
	asfloat ->mp_tY
	asfloat ->mp_tX
	asfloat ->mp_pY
	asfloat ->mp_pX
 
	<-mp_an <-mp_ms @GetPixelVector ->mp_vY ->mp_vX
 
	<-mp_pX <-mp_tX sub <-mp_pY <-mp_tY sub @GetLength <-mp_ms lt if
		<-mp_tX
		<-mp_tY
	else
		<-mp_pX <-mp_vX add
		<-mp_pY <-mp_vY add
	endif
 
# Function :GetIntermediatePixelPosition
# Uses two points and a distance to find a position that is
# between the two points, but X distance away from the target.
# Notation: x y x2 y2 f1 - x y
# eg. CurrentPixelPosition @GetUnitPixelCoords(<-targetUnit) 10 @GetIntermediatePosition QueueMove
:GetIntermediatePixelPosition
	asfloat ->gip_dist
	asfloat ->gip_ty
	asfloat ->gip_tx
	asfloat ->gip_y
	asfloat ->gip_x
 
	<-gip_tx <-gip_ty <-gip_x <-gip_y @GetPixelAngle ->gip_a
	<-gip_a <-gip_dist @GetPixelVector ->gip_vy ->gip_vx
	<-gip_ty <-gip_vy add
	<-gip_tx <-gip_vx add
 
# Function :GetPixelVector
# Uses an angle, and speed and returns a vector
# You can add the vector to a position to get a moving effect
# Notation: f1 f2 - x y
# eg. <-angle 1.5 @GetPixelVector ->y ->x
:GetPixelVector
	asfloat ->gv_vrsm
	asfloat ->gv_vrAngle
 
	<-gv_vrAngle sin ->gv_vrY
	<-gv_vrAngle cos ->gv_vrX
	<-gv_vrX <-gv_vrY @NormalizeVector ->gv_vrY ->gv_vrX
	<-gv_vrY <-gv_vrsm mul ->gv_vrY
	<-gv_vrX <-gv_vrsm mul ->gv_vrX
	<-gv_vrX
	<-gv_vrY
 
# Function :GetPixelAngle
# Takes an initial position and a target position,
# then calculates an angle to face the target.
# Value returned in radians.
# Notation: x y x2 y2 - f1
# eg. CurrentPixelCoords <-targetUnit @UnitPixelCoords @GetPixelAngle ->angle
:GetPixelAngle
	asfloat ->ga_targetY
	asfloat ->ga_targetX
	asfloat ->ga_curY
	asfloat ->ga_curX
 
	<-ga_targetX <-ga_curX sub ->ga_deltaX
	<-ga_targetY <-ga_curY sub ->ga_deltaY
	<-ga_deltaY <-ga_deltaX atan2 ->ga_desiredAngle
	<-ga_desiredAngle
 
# Function :RotateToPixelAngle
# Takes an initial position and a target position,
# calculates an angle, and then returns an angle
# restricted by the specified speed and angle.
# This function can allow for slow rotations.
# It will also return a boolean value which is
# true if the angle directly faces the target.
# Notation: x y x2 y2 f1 f2 - b1 f1
# eg. CurrentPixelCoords <-targetUnit @UnitPixelCoords <-angle 0.03 @RotateToPixelAngle ->newAngle ->isOnTarget
:RotateToPixelAngle
	asfloat ->rta_maxRot
	asfloat ->rta_curAngle
	asfloat ->rta_targetY
	asfloat ->rta_targetX
	asfloat ->rta_curY
	asfloat ->rta_curX
 
	<-rta_targetX <-rta_curX sub ->rta_deltaX
	<-rta_targetY <-rta_curY sub ->rta_deltaY
	<-rta_deltaY <-rta_deltaX atan2 ->rta_desiredAngle
 
	<-rta_curAngle <-rta_desiredAngle ShortestAngle ->rta_rot
 
	<-rta_rot <-rta_maxRot gt if
		<-rta_maxRot ->rta_rot
	else
		<-rta_rot <-rta_maxRot neg lt if
			<-rta_maxRot neg ->rta_rot
		endif
	endif
 
	<-rta_rot abs 0.0001 lt if
		1 ->rta_onTarget
	else
		0 ->rta_onTarget
	endif
 
	<-rta_curAngle <-rta_rot add ->rta_rot
 
	<-rta_onTarget
	<-rta_rot