User Tools

Site Tools


prpl:prplsnippets

This is an old revision of the document!


<-PF Home <-PRPL Home

PRPL Code Segments

Here I go over some PRPL code segments, each achieving some particular task (like moving a units/ships, creating units/particles, showing images or text, discovering units/ships/particles, input handling etc.). Each segment isn't enough to make a fully functional unit, but most units consist of several such segemnts working together.

If you are looking for what is possible in PRPL or some inspiration for your own units, this is a good place to start, provided you know the fundamentals. Each segemnt will consist of the code doing the work, the explaination of the code and a little homework for you to modify it in some way.

(TODO: buildable/charable unit, creating a unit, work with images, work with text, communication betweeen scripts, work with ship inventory - this list is mostly for myself so I don't forget what I wanted to talk about)

Discovering units/ships/particles in a range

Let's start with “discovery” commands, since we will learn about cell and pixel coordinates, which will be usefull later. Let's start with GetAllShipsInRange # [int int float bool - list] (x y range square?). The first 2 arguments are the cell coordinates for the center of the search, next is the range of the search (in cells) and the final argument is the search mode (1=square, 0=circle). Let's look at a visual example:

50 50 20 0 GetAllShipsInRange ->list

Here, we are looking for all ships in a cirlce centered at cell (50,50) with radius 20. The search area is visualized exactly with the range of the energy source. As noted in the comment, the center of the command module has to be in the range for the ship to be considered in range.

The (50,50) detonate the coordinates of the cell at the center of the search. when wrtitng coordinates as a pair, X is always first and Y is always second. The bottom-left cell has coordinates (0,0), you can also see the coordinates of the cell where your mouse on the bottom of your screen (left of the mid-bottom panel).

Lastly, if the mode was set to square, the area would be a square with side length 2*length as shown in the image.

You can also discover units in the same way using GetAllUnitsInRange # [int int float bool - list], and there is also GetClosesShipInRange # [int int float bool - int] that works, simmilarly, but only returns the id of the closest ship in the search range, or -1 if no ship is found.

Particle discovery commands

Particle discovery works in a completely different way. First, GetParticlesInRange # [int int float bool bool - list] (x y range square? enemy?) takes additional parameter: wheter to look for enemy particles or friendly (1=enemy, 0=friendly).

Moreover, range is now the diameter rather than the radius of the search (the diameter of the circle, or the side length of the square, depending on the mode), but you can still use GetParticlesInRadius if you want to use radius.

Lastly the coordinates for the center of the search as well as the search diameter/radius are now in pixels. Pixels are like cells, except they form a denser grid. The easy conversion is that 1 cell = 4 pixels, so for example the pixel equivalent of cell (10,20) would be (40,80). Ecxept, that is not entirely the case.

Cell coordinates vs pixel coordinates

Cell coordinates are always integers, and a cell coordinate always refer to the cell as a whole. Pixel coordinates, however, are float and they refer to a specific point on the game map. For example, (4.2,5) and (4.3,5) are different pixel coordinates, since they are different points on the game map, however (4.2,5) and (4.3,5) are the same cell coordiantes since they are converted to the same cell (4,5).

So, when I say that “1 cell = 4 pixels isn't entirely accurate”, what I mean is that if you take a cell coordinate and just multiply both components by 4, you will get a pixel coordinate refering to the bottom-left corner of the cell. If you want a pixel coordinate representing the center of the cell, you need to multiply the cell coordinates by 4 and then add 2.

The homework

Write a script that will find all units and enemy particles withing 20 cells distance from itself. (Use CurrentCoords # [ - int int] and CurrentPixelCoords # [ - float float] to get the current pixel/cell position of the PRPL core running the script.)

Solution:

Click to display ⇲

Click to hide ⇱

CurrentCoords 20 0 GetAllUnitsInRange ->foundUnits
CurrentPixelCoords 80 0 1 GetParticlesInRadius ->foundParticles

Notes: the search type needs to be cirlce (0) to accurately check for range, and the radius needs to be 4 times larger for particles, since it is in pixels and one cell = 4 pixels when it comes to distance. Finally, CurrentPixelCoords already gives the pixel coordinates representing the center of the cell where the PRPL core is located (unless changed by SetUnitPixelCoords or course, but more on that later), so no need for further adjustments.

<span></span>

Moving a unit

Moving a unit to a target position isn't nearly as simple as you might think. Here is the full code:

$Speed:2.0 #Move $Speed pixels per frame
$TargetX:202 #Pixel X of the target position
$TargetY:82 #Pixel Y of the target postiion
 
CurrentPixelCoords ->y ->x #save current position
<-x <-y <-TargetX <-TargetY Distance ->distance #distance to target, in pixels
<-distance <-Speed lte if #if the target is withing the reach, teleport there outright
    Self <-TargetX 4 div <-TargetY 4 div SetUnitCoords
    Self <-TargetX <-TargetY SetUnitPixelCoords
    "Finished moving" Trace
else
    #move $Speed pixels closer to the target
    <-TargetX <-x sub <-distance div <-Speed mul <-x add ->x
    <-TargetY <-y sub <-distance div <-Speed mul <-y add ->y
    Self <-x 4 div <-y 4 div SetUnitCoords #set cell coordinates
    Self <-x <-y SetUnitPixelCoords #set pixel coordinates
endif

We start relatively simply by first saving the current pixel coordinates and then computing the distance t othe targer. If the target is close enough (closer than $Speed), it means that the unit can move to the targer in this frame, so we do just that.

The more interesting is how we move the units closer if it's far ways. First, <-TargetX <-x sub is the “X distance” from the current core to the target. If we were to add this to the X position, it would move to the target directly. So we divide this by distance. That way we only move 1 pixel closer to the target. Finally, we multiply by Speed to move Speed pixel closer to the target. … <-x add ->x Will then move the core by the specified amount.

Some image might be more helpful:

The image explains the expression <-TargetX <-x sub <-distance div <-Speed mul and why adding it to the X position (and same with Y) will move the unit Speed pixels closer to the target. Then, <-x add ->x simply increases the position by that amount, moving the unit closer to the target.

Few important notes:

  • SetUnitCoords will also set the pixel coordinates coresponding to that cell, so it's important to call this first.
  • SetUnitCoords will convert the cell coordinates as integers, so it's important to move the unit alongside the pixel coordinates instead.
  • Notice that the target is (202,82). This is very much on purpouse, as that pixel coordiantes are at the center of the cell (50,20).

Homework

Make a unit that accelerate towards the target point instead. Isntead of $Speed use $Accel and increase the speed by that amount every frame.

Solution:

Click to display ⇲

Click to hide ⇱

$Accel:0.1 #Increase the speed by $Accel every frame
$TargetX:202 #Pixel X of the target position
$TargetY:82 #Pixel Y of the target postiion
 
once
    0 ->speed
endonce
<-speed <-Accel add ->speed
 
CurrentPixelCoords ->y ->x #save current position
<-x <-y <-TargetX <-TargetY Distance ->distance #distance to target, in pixels
<-distance <-speed lte if #if the target is withing the reach, teleport there outright
    Self <-TargetX 4 div <-TargetY 4 div SetUnitCoords
    Self <-TargetX <-TargetY SetUnitPixelCoords
    "Finished moving" Trace
else
    #move speed pixels closer to the target
    <-TargetX <-x sub <-distance div <-speed mul <-x add ->x
    <-TargetY <-y sub <-distance div <-speed mul <-y add ->y
    Self <-x 4 div <-y 4 div SetUnitCoords #set cell coordinates
    Self <-x <-y SetUnitPixelCoords #set pixel coordinates
endif

Making a buildable & chargable unit

Here is a code for making a unit that first needs to be build with lathes and then once build accepts energy from nearby energym mines (or ships with energy port).

$MaxHealth:200
$MaxEnergy:100
 
once
    #healt
    Self <-MaxHealth SetUnitMaxHealth # lathes will heal 1 health per frame
    Self 0 SetUnitHasHealthBar # don't display healtb bar while it has only 1 health at start
    Self -1 SetUunitLatheDamageAmt # negative value - lathe heals the unit
    Self 1 SetUnitLatheTargets # make lathes target this unit
 
    #energy
    Self 0 SetUnitReceivesPackets # set 0 for now, so it doesn't accept enemy packets
    Self <-MaxEnergy SetUnitMaxEnergy 
    Self 3 SetUnitPacketDelay # accept 1 energy packet every X frames
    Self 0 SetUnitHasEnergyBar # hide energy bar for now
 
    #reset
    Self 1 SetUnitHealth # start at 1 health - it would destroy itself at 0
    Self 0 SetUnitEnergy # reset energy to 0
    Self 1 SetUnitIsEnemy # make the unit enemy, so that friendly lathes will target it and heal it
endonce
 
Self GetUnitIsEnemy if
    #while "constructing" with lathe
 
    #display health bar only if health > 1
    Self GetUnitHealth 1.5 lt if
        Self 1 SetUnitHealth # work around some wierd float calcualtion issues
    else
        Self 1 SetUnitHasHealthBar # show energy bar once it's being "constructed" by the lathes
    endif
 
    #flip to gather energy when built
    Self GetUnitHealth gte (Self GetUnitMaxHealth) if # check if at full health
        Self 0 SetUnitIsEnemy # make unit no longer enemy, so it can receive friendly energy
        Self 1 SetUnitReceivesPackets # receive friendly energy packets
        Self 0 SetUnitHasHealthBar # hide health bar once it's build
        Self 1 SetUnitHasEnergyBar # show energy bar isntead
        Self 0 SetUnitLatheTargets # so enemies don't destroy it once built
    endif
else
    # already build
    Self GetUnitEnergy gte (Self GetUnitMaxEnergy) if # check if at full energy
        "Full energy!" Trace #todo: actaully do something useful here
        Self 0 SetUnitEnergy #reset energy back to 0
    endif
endif

Now that's a lot of code, but the comments should explain it well enought. The only wierd bit might be hiding/showing the ehalth bar. It is hidden at first, because it would render a tiny red dot when the unit is at 1 health, which wouldn't look very nice, since the unit starts at 1 health even before yo ustart building it with lathes.

You can go and make it do something usefull once it cahrges with energy, or you can remove the nergy reset and have it provide a pasive bonus while at full energy, the possibilites are endless.

Homework

Change the unit to be symetrical for both the player and the enemy - once the player captures it with lathes, the enemy can capture it as well. When in enemy control, make the unit accept enemy energy packets and up build energy as well. Finally, make the health go from 100% to 0% when being captured by the lathes, as that would more suit a flipable unit like this.

Click to display ⇲

Click to hide ⇱

$MaxHealth:200
$MaxEnergy:100
$StartAsEnemy:1
 
once
    #healt
    Self <-MaxHealth SetUnitMaxHealth
    Self 0 SetUnitHasHealthBar # don't display health bar on 100% health
    Self 1 SetUunitLatheDamageAmt # positive value - lathe damages the unit
    Self 1 SetUnitLatheTargets # always stay a valid lathe target
 
    #energy
    Self 1 SetUnitReceivesPackets # always accept energy packets
    Self <-MaxEnergy SetUnitMaxEnergy
    Self 3 SetUnitPacketDelay #accept 1 energy packet every X frames
    Self 1 SetUnitHasEnergyBar # always show energy bar: it will be empty when at 0 energy
 
    #reset
    <-StartAsEnemy ->isEnemy
    Self Self GetUnitMaxHealth SetUnitHealth # fully heal
    Self 0 SetUnitEnergy # start at 0 energy
    Self <-isEnemy SetUnitIsEnemy
    @SetImageColor
endonce
 
#check health
Self GetUnitHealth 10 lt if # health is low enought to flip
    # flip from enemy to player or wise versa
    <-isEnemy neg ->isEnemy
    Self Self GetUnitMaxHealth SetUnitHealth # reset to full health
    Self 0 SetUnitEnergy # reset to 0 energy
    Self <-isEnemy SetUnitIsEnemy # set enemy to new value
    Self 0 SetUnitHasHealthBar # don't display health bar on 100% health
    @SetImageColor
    return # end script execution for this frame
endif
 
Self GetUnitMaxHealth sub (Self GetUnitHealth) lt (0.5) if #current health is at most 0.5 lett than max health = unit at full health
    Self Self GetUnitMaxHealth SetUnitHealth # force set unit to full health, fixes a wierd float issue
else
    Self 1 SetUnitHasHealthBar #unit has taken damage - display the health bar
endif
 
#flip to gather energy when built
Self GetUnitHealth gte (Self GetUnitMaxHealth) if
    Self 0 SetUnitIsEnemy
    Self 1 SetUnitReceivesPackets
    Self 0 SetUnitHasHealthBar
    Self 1 SetUnitHasEnergyBar
    Self 0 SetUnitLatheTargets # so enemies don't destroy it once built
endif
 
#check energy
Self GetUnitEnergy gte (Self GetUnitMaxEnergy) if
    <-isEnemy if
        "Full energy on enemy side, hue hue." Trace #todo: actaully do something useful here
    else
        "Full energy on friendly side, yatta!" Trace #todo: actaully do something useful here
    endif
    Self 0 SetUnitEnergy
endif
 
:SetImageColor #sets the image color to red/blue depending on isEnemy variable
if (<-isEnemy) 
    Self "main" 255 0 0 255 SetImageColor # R G B A
else
    Self "main" 0 0 255 255 SetImageColor # R G B A
endif

Again, most code is exaplined with comment. This time we don't show the health bar until the unit takes some damage. The “wierd float issue” was that the health would randomly decreace by a tiny amount (say, from `200` to `199.9999764`) so we fix that by forcably heling the unit when it's at full ehalth, just in case.

Aslo, when checking if the ehalth is low enought, `Self GetUnitHealth 10 lt if # health is low enought to flip` it is wise to choose a slightly larger number, like `10`, so that even when multiple lathes are targeting the unit, they will not destroy it before it can flip and fully heal itself.

prpl/prplsnippets.1513458026.txt.gz · Last modified: 2017/12/16 16:00 by kajacx