Adaptive Emitter

Started by Molay, May 28, 2013, 09:31:13 AM

Previous topic - Next topic

Molay

Hello there, Creeplers!

First of all, apologies for the way my code is structured. I do probably break about every RPL convention that has ever been established, but this being my very first try at RPL, I think I can be forgiven.

What it does:
Creates a core that behaves like an emitter with a twist: The core is lazy and doesn't produce a lot of creeper. Barely any, actually. However, when units get close, he starts producing at a rather rapid rate.

Two things condition the "emitter" output: Amount of units in range, and distance between the core and the closest unit.
I don't think it possible to overrun this core with blasters. Instead, one ought to be sneaky and be bringing along no more than a shield and nullifier :)

Here's the code: (also available as download below)

# Adaptive_Emitter.crpl
# Created on: 5/28/2013 2:37:58 PM
# Creator: Molay
# ------------------------------------------
# Adaptive_Emitter.crpl
# Behaves like an emitter that becomes stronger the more unit are nearby, the closer they are.
# Weakens if not bothered by unit presence.
#
# This is my first ever code in a RPL language. Please comment.
#-----------------------------
#
#>>>MODIFY THESE TO CHANGE THE BEHAVIOR OF THE CORE>>>
once
$RANGE:30 # Will check within this range if player units are present
$MULTIPLIER:6 # Used to determine emitter strength depending on units in range
$MULTIBONUS:3 # Enhances the multiplier further depending on units in range
$RATE:12 # Used to determine emitter rate depending on closest unit in range and amount of units
endonce
#<<<CODE BELOW - MODIFY AT OWN RISK!<<<


#>>>VARIABLES>>>
once
$baseEmit:10
$baseRate:30
$rangeMin:30
$cpt:0
$unitCount:0
$currentUID:111
endonce
#<<<VARIABLES<<<

#>>>MAIN>>>
Delay(<-baseRate) #Emit Rate
@getUnitsWithRange #Gets amount of units nearby and the closest unit's range
->baseEmit(add(<-unitCount 1) mul(<-MULTIPLIER mul(add( 1 <-unitCount) <-MULTIBONUS)) )
->baseRate(div(mul(<-RATE <-rangeMin) add(<-unitCount 1)))
AddCreeper(CurrentCoords <-baseEmit)
#<<<MAIN<<<

#>>>FUNCTIONS>>>
:getUnitsWithRange
0 ->cpt #Initializes the counter (cpt) to 0
<-RANGE ->rangeMin
GetUnitsInRange(CurrentCoords <-RANGE) #Returns list of UID's with the amount of UIDs
->unitCount #Store the amount of UIDs. Pop that value?
while <-cpt <-unitCount lt #Repeats unless all UIDs checked.
repeat
->currentUID #Puts the current UID from the list in this variable
if(GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance lt <-RangeMin)
#Checks if the distance between the core and the UID is < than the current RangeMin value.
GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance ->RangeMin
#Assigns the new value to RangeMin
endif
add(<-cpt 1) ->cpt #Increases the counter by 1
endwhile
#<<<FUNCTIONS<<<


Overall, I'm satisfied with what the code does. But it seems overly long for what it is doing. I wonder where I can cut it down? Maybe I can't. Maybe I just got spoiled by more recent languages.
Either way, I'd appreciate any comments, suggestions and general feedback.

Thanks,
Molay

PS: Comments are not yet final and may be false or lacking, but I'm in a hurry and have to leave for class. Wanted to post it before, in hopes of having some suggestions when I come back :)

J

This script should do exactly the same.
Does this look more clear to you?
# Adaptive_Emitter.crpl
# Created on: 5/28/2013 2:37:58 PM
# Creator: Molay
# Adaptive_Emitter.crpl
# Behaves like an emitter that becomes stronger the more unit are nearby, the closer they are.
# Weakens if not bothered by unit presence.
# ------------------------------------------

$RANGE:30
$MULTIPLIER:6
$MULTIBONUS:3
$RATE:12
$baseEmit:10
$baseRate:30
$rangeMin:30
$cpt:0
$unitCount:0
$currentUID:111

<-baseRate Delay
@getUnitsWithRange
<-unitCount 1 add <-MULTIPLIER  1 <-unitCount add <-MULTIBONUS mul mul ->baseEmit
<-RATE <-rangeMin mul <-unitCount 1 add div ->baseRate
CurrentCoords <-baseEmit AddCreeper

:getUnitsWithRange
<-RANGE ->rangeMin
CurrentCoords <-RANGE GetUnitsInRange
0 do
->currentUID
<-currentUID CONST_COORDX GetUnitAttribute <-currentUID CONST_COORDY GetUnitAttribute CurrentCoords Distance lt <-RangeMin if
<-currentUID CONST_COORDX GetUnitAttribute <-currentUID CONST_COORDY GetUnitAttribute CurrentCoords Distance ->RangeMin
endif
loop

I removed all comments, removed all parenthesis (and moved some commands) and changed the while loop into a do loop. I don't think you can make this even more efficient anymore.

Molay

Thanks for your reply, J!

To be frank, removing the warp operator makes it barely understandable to me. If I didn't write it myself, I would be terribly confused by how you calculate baseEmit without writing it down, filling an A3 paper (a bit of hyperbole here).

However, removing the comments, in retrospect, was a very good thing! I guess I went a bit over the top with the comments, looking back at it. I may want to keep that for tricky stuff only, and here I don't have any tricky stuff, really.

Using the do-while loop instead makes much sense. I didn't think about that. Saves me a variable, an "add" and an assignment. I'll make sure to use it more often in the future :)

Another quick question came to mind:
Is it possible to hide a variable from the "Input Variables" section in the map editor? So as to hide critical variables the novice user should in no case touch, leaving him only with the things he wants to change to affect the behavior, without breaking the logic?

Thanks,
Molay

PS: Thanks to you and Michionlion I could actually dabble in CRPL. I would have no idea where to start was it not for your tutorial! Amazing work!

J

Quote from: Molay on May 28, 2013, 04:07:36 PM
To be frank, removing the warp operator makes it barely understandable to me. If I didn't write it myself, I would be terribly confused by how you calculate baseEmit without writing it down, filling an A3 paper (a bit of hyperbole here).
I can barely read it with the warp operator =P
As for calculating baseEmit, it looks like you missed one 'mul' or 'add', there are numbers left on the stack after calculating it.
Quote
Another quick question came to mind:
Is it possible to hide a variable from the "Input Variables" section in the map editor? So as to hide critical variables the novice user should in no case touch, leaving him only with the things he wants to change to affect the behavior, without breaking the logic?
Nope
Quote
PS: Thanks to you and Michionlion I could actually dabble in CRPL. I would have no idea where to start was it not for your tutorial! Amazing work!
;D

Molay

Ah, thanks for the hint with my calculations of baseEmit. Yes, yes, earlier when I was trying to finish it in time I started to modify the formula quite a bit, then I forgot to remove a part. Fortunately, the part that stuck on the stack was of no use anyways^^
Anyways, it's fixed now!

I also noticed you forgot the ->unitCount when implementing the do-loop. If you're using the your modified script version, consider adding it back in, and pushing it back on the stack right afterwards for the do-loop.

In any case, here's the new version if anyone is interested: (Apologies, warp operators ahead)

# Adaptive_Emitter.crpl
# Created on: 5/28/2013 2:37:58 PM
# Creator: Molay
# ------------------------------------------
# Behaves like an emitter that becomes stronger the more unit are nearby, the closer they are.
# Weakens if not bothered by unit presence.
#-------------------------------------------
$RANGE:30
$MULTIPLIER:6
$MULTIBONUS:3
$RATE:12
$baseEmit:10
$baseRate:30
$rangeMin:30
$unitCount:0
$currentUID:111


Delay(<-baseRate)
@getUnitsWithRange
->baseEmit(mul(<-MULTIPLIER mul(add( 1 <-unitCount) <-MULTIBONUS)))
->baseRate(div(mul(<-RATE <-rangeMin) add(<-unitCount 1)))
AddCreeper(CurrentCoords <-baseEmit)


:getUnitsWithRange
0 ->cpt
<-RANGE ->rangeMin
GetUnitsInRange(CurrentCoords <-RANGE)
->unitCount

do(<-unitCount 0)
->currentUID
if(GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance lt <-RangeMin)
GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance ->RangeMin
endif
loop


Next up will be some animations then, to show that something is actually happening when you move closer to it.

Molay

#5
UPDATE:

I've added 2 Custom Images (custom0 is mortarbarrel and custom1 is mortarbarrel2)
They gain in color intensity as the creeper production increases. They only become visible after a unit enters the range.
The mortarbarrel2 does also rotate.

Here's the new code:

# Adaptive_Emitter.crpl
# Created on: 5/28/2013 2:37:58 PM
# Creator: Molay
# ------------------------------------------
# Behaves like an emitter that becomes stronger the more unit are nearby, the closer they are.
# Weakens if not bothered by unit presence.
# The activity level is shown by the color intensity of the core's addons.
#-------------------------------------------
$RANGE:30
$MULTIPLIER:7
$MULTIBONUS:3.6
$RATE:15
$baseEmit:10
$baseRate:30
$rangeMin:30
$unitCount:0
$currentUID:111

$bulb:"bulb"
$pikes:"pikes"
$bulbAlpha:0
$pikesAlpha:0
$pikesRotation:0
$BLINK:30

once
SetImage(self <-bulb "Custom0")
SetImage(self <-pikes "Custom1")
SetImagePositionZ(self <-bulb -0.01)
SetImagePositionZ(self <-pikes -0.01)
SetTimer0(<-baseRate)
SetTimer1(3)
endonce

#GRAPHICAL MANIPULATIONS BELOW
SetImageColor(self <-bulb 0 0 0 0)  #Edit: Redundant, found out by re-reading the post.
SetImageColor(self <-pikes 0 0 0 0) #Edit: Redundant, found out be re-reading the post.

15 ->bulbAlpha
if(<-baseEmit <-BLINK gt)
mul(<-baseEmit 2.2) ->bulbAlpha
endif
SetImageColor(self <-bulb 255 0 0 <-bulbAlpha)

15 ->pikesAlpha
if(<-baseEmit <-BLINK gt)
mul(<-baseEmit 1.5) ->pikesAlpha
add(<-pikesRotation 0.04) ->pikesRotation
endif
SetImageRotation(self <-pikes <-pikesRotation)
SetImageColor(self <-pikes 200 255 0 <-pikesAlpha)

#CREEPER SPAWN LOGIC BELOW
if(GetTimer1 eq0)
@getUnitsWithRange
->baseEmit(mul(<-MULTIPLIER mul(add( 1 <-unitCount) <-MULTIBONUS)))
->baseRate(div(mul(<-RATE <-rangeMin) add(<-unitCount 1)))
SetTimer1(3)
endif

if(GetTimer0 eq0)
AddCreeper(CurrentCoords <-baseEmit)
SetTimer0(<-baseRate)
endif


:getUnitsWithRange
0 ->cpt
<-RANGE ->rangeMin
GetUnitsInRange(CurrentCoords <-RANGE)
->unitCount

do(<-unitCount 0)
->currentUID
if(GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance lt <-RangeMin)
GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance ->RangeMin
endif
loop


I do need some help however. I'm, frankly, bad at mathematics. I never paid too much attention and now I'm sometimes missing some useful notions.
I rotate by increasing the radian value by 0.04 per frame. I'd like to adjust the speed depending on the amount of creeper produced.
Is there some mathematical function that allows the following:
An input of about 10-500, giving me an output of about 0.01-0.1, where a smaller input gives me a smaller output?
i.e. 10 -> 0.01 and 500 -> 0.1?
I really don't know how to do that, and as most programmers are often good at maths (I'm the exception I'm afraid), chances are someone has an idea :)
Just telling me what I need to look at might be enough, I can read through it myself^^

Thanks!
Molay

PS: Testing map is attached, so the images are preset. You can play around a bit by moving more/less units into range of the emitter. Due to the elevation, it's easy to beat though. Just for testing purposes :)

hoodwink

Quote from: Molay on May 29, 2013, 10:58:37 AM
Is there some mathematical function that allows the following:
An input of about 10-500, giving me an output of about 0.01-0.1, where a smaller input gives me a smaller output?
i.e. 10 -> 0.01 and 500 -> 0.1?
Couldn't you simply divide your input by a constant, say 1000, and use that?
That way, 10 -> 0.01 and 500 -> 0.5
A smaller number divided by a constant will always result in a smaller number.
If you wanted a smaller number from a bigger input, you would simply divide the constant by the number.
Stare not into the abyss, or it has hasten in its approach.
~ Hoodwink (thesmish, smish777 or sigil)

Molay

I tried it.
I was afraid of it, because I thought it would result in too quick a rotation once more than 7-8 units got close.
I got to say, it looks pretty neat this way!
It pretty much did the job perfectly, so no need to worry how to get those values I was initially looking for :)
Thanks for the suggestion :)

Now off to the blinking of the center piece, and the first script is done :)

Molay

#8
Done.

Here what it does:
Creates a very small amount of creeper.
If units approach, increases creeper production rapidly. The more units, the more creeper. The closer the units, the more creeper!
Shows the level of creeper production graphically (blinking light, rate indicative of production level and rotor spin, rate indicative of production level)
When it detects a nullifier, the emitter goes into desperation mode, drastically increasing the creeper production. This is signalled by a sound asset.
As to variables you may want to change:
MULTIPLIER (higher values produce more creeper)
MULTIBONUS (higher values produce more creeper)
RATE (lower values increase the creeper production rate)
Changing any might make the tower unbeatable or too easy to defeat. As of now, with guppies it's fairly easy to take care of (as guppies are not recognized by the emitter).
You can do it using a normal network and a couple units, too, but it's more difficult.
Height advantage is crucial to take it out easily.

# Adaptive_Emitter.crpl
# Created on: 5/28/2013 2:37:58 PM
# Creator: Molay
# ------------------------------------------
# Behaves like an emitter that becomes stronger the more unit are nearby, the closer they are.
# Weakens if not bothered by unit presence.
# The activity level is shown by the color intensity of the core's addons.
#-------------------------------------------
$RANGE:30
$MULTIPLIER:7
$MULTIBONUS:3.6
$RATE:15
$baseEmit:10
$baseRate:30
$rangeMin:30
$unitCount:0
$currentUID:111

$bulb:"bulb"
$pikes:"pikes"
$bulbAlpha:20
$pikesAlpha:0
$pikesRotation:0
$BLINK:30
$bulbUp:1
$nullDetected:0

once
SetImage(self <-bulb "Custom0")
SetImage(self <-pikes "Custom1")
SetImagePositionZ(self <-bulb -0.01)
SetImagePositionZ(self <-pikes -0.01)
SetTimer0(<-baseRate)
SetTimer1(3)
SetTimer2(0)
SetTimer3(0)
endonce

#GRAPHICAL MANIPULATIONS BELOW
if(<-baseEmit <-BLINK gt)
if(<-bulbUp)
add(<-bulbAlpha div(<-baseEmit 100)) ->bulbAlpha
if(<-bulbAlpha 255 gte)
255 ->bulbAlpha
0 ->bulbUp
endif
else
sub(<-bulbAlpha div(<-baseEmit 100)) ->bulbAlpha
if(<-bulbAlpha mul(<-unitCount 11) lte)
mul(<-unitCount 11) ->bulbAlpha
1 ->bulbUp
endif
endif
else
0 ->bulbAlpha
endif
# mul(<-baseEmit 2.2) ->bulbAlpha #For a constant light
SetImageColor(self <-bulb 255 0 0 <-bulbAlpha)

0 ->pikesAlpha
if(<-baseEmit <-BLINK gt)
mul(<-baseEmit 1.5) ->pikesAlpha
endif
add(<-pikesRotation div(<-baseEmit 1000)) ->pikesRotation
SetImageRotation(self <-pikes <-pikesRotation)
SetImageColor(self <-pikes 200 255 0 <-pikesAlpha)
if(GetTimer3 eq0)
if(<-nullDetected 1 eq)
PlaySound("Misc18")
SetTimer3(39)
endif
endif
#CREEPER SPAWN LOGIC BELOW
if(GetTimer1 eq0)
@getUnitsWithRange
->baseEmit(mul(<-MULTIPLIER mul(add( 1 <-unitCount) <-MULTIBONUS)))
->baseRate(div(mul(<-RATE <-rangeMin) add(<-unitCount 1)))
SetTimer1(3)
endif

if(<-nullDetected 1 eq)
mul(<-baseEmit 1.5) ->baseEmit
div(<-baseRate 1.35) ->baseRate
endif

if(GetTimer0 0 lte)
AddCreeper(CurrentCoords <-baseEmit)
SetTimer0(<-baseRate)
endif


:getUnitsWithRange
0 ->cpt
<-RANGE ->rangeMin
GetUnitsInRange(CurrentCoords <-RANGE)
->unitCount

do(<-unitCount 0)
->currentUID
if(GetTimer2 eq0)
0 ->nullDetected
if(GetUnitType(<-currentUID) "NULLIFIER" eq)
1 ->nullDetected
SetTimer2(210)
endif
endif

if(GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance lt <-RangeMin)
GetUnitAttribute(<-currentUID CONST_COORDX) GetUnitAttribute(<-currentUID CONST_COORDY) CurrentCoords Distance ->RangeMin
endif
loop


If you have suggestions, don't hesitate. I'm eager to learn ways to improve the script.
If you think the script should do something else, let me know as well. Once started, CRPL is a very fun thing!

Molay

Edit: And of course the testing map as an attachment below

Azraile

I liked this enough I made my own (more clasic creep collored and looking) graphics based off what you had...

Annonymus

Actually, there IS a way to hide the variables, just define them by using "value ->var" instead of "$var:value" or use ->var (value), if you like that more.
If a topic started by me is in the wrong place feel free to move it at anytime.

Grayzzur

Quote from: Molay on May 28, 2013, 04:07:36 PM
Another quick question came to mind:
Is it possible to hide a variable from the "Input Variables" section in the map editor? So as to hide critical variables the novice user should in no case touch, leaving him only with the things he wants to change to affect the behavior, without breaking the logic?

Input Variables, no. You can, however, initialize variables inside a ONCE block instead of making them input variables.

# This is a visible input variable
$SomeInputVariable:10
once
  # This is just another variable in the script, not editable via the editor properties dialogs.
  11 ->SomeOtherVariable
endonce


The downside is if you need the value to be different for two different units, you need two scripts. If it's a global setting for a particular type of unit, then it works better doing it that way.

I wouldn't worry too much about hiding things from other map makers, though. Everything you post to Colonial Space is 100% available to other map makers.
"Fate. It protects fools, little children, and ships named 'Enterprise.'" -William T. Riker

Dook

Awesome script. It made for a challenging game. One thing i noticed is it would beep if i placed a nullifier close to it. which is cool but sometimes it would continue to beep even if the nullifier failed. with no units in proximity.

Courtesy

I really like this script. The way it reacts to nullifiers shares a basic concept with a type of unit I'm working on currently. You wouldn't mind if I snagged this, would you?