How do you make player units indestructible? (may be picking units wrong)

Started by StardustAlpha, July 10, 2018, 01:16:22 PM

Previous topic - Next topic

StardustAlpha

What I want: health of pulse cannon, mortar, sheild, collector, ect. to never go down. EVER.
My current code:

while 1 0 gt
repeat
->unit
<-unit CONST_HEALTH 999999999 SetUnitAttribute
endwhile

I think my code might not be actually finding any units.

Grabz

Your code reads as follows:

While 1 is larger than 0 (i.e. always), pull a unit from the stack (which doesn't work, because nothing has been added to the stack so it's empty). As your while loop never ends, this only doesn't crash the game because it runs into the opcode limit and is halted.

I think you want something more like this:


0 0 9999 GetUnitsInRange 0 do
->unit

<-unit CONST_ISBUILDING GetUnitAttribute not if
<-unit CONST_HEALTH <-unit CONST_MAXHEALTH GetUnitAttribute SetUnitAttribute
endif
loop

This script will make regular units impervious, except for units that are destroyed in one hit - these cannot unfortunately be made impervious to creeper with CRPL.

I'll explain how it works below. However, if you don't know what a stack is, you should give this a read first: https://knucklecracker.com/wiki/doku.php?id=crpl:overview

The function GetUnitsInRange will find all player units in a specified range originating from specific coordinates. It accepts three arguments from the stack - X, Y and range. Then, it pushes all the unit ID's that it finds to the stack, followed by the amount of units that were found. We feed the function the 0,0 X,Y coordinates and 9999 as range, which is our way of saying we want to basically find everything.

Let's say the function has found four units. After calling GetUnitsInRange, the stack will look something like this

345 196 67 741 4


Now, the reason this function outputs the amount of units that were found as the last argument to the stack, is to make it more convenient to use with loops.

A do loop has the following syntax:

4 0 do
   
loop


the do function takes two arguments from the stack - the iteration index to stop at, and the iteration index to start at. Then it will start iterating the code between the do function call and the loop function call, in our case, four times.

Now, when we say this:

0 0 9999 GetUnitsInRange 0 do

loop

0 0 9999 GetUnitsInRange executes and pushes all the items into the stack, then we push 0 to the stack, and then we call the do loop. The do loop now sees the stack like this:
345 196 67 741 4 0
It takes the 4 and the 0 for its own use (end index, start index) and then begins iteration. Now the stack only contains unit ID's.

To save an item from the stack to a variable, we use ->

ClearTraceLog
ShowTraceLog
0 0 9999 GetUnitsInRange 0 do
    ->unit

    <-unit Trace
loop

Now we save an item from the stack each iteration to a unit variable, and we can perform operations on it. In this case I'm just logging the ID's to the console.


<-unit CONST_ISBUILDING GetUnitAttribute not if
<-unit CONST_HEALTH <-unit CONST_MAXHEALTH GetUnitAttribute SetUnitAttribute
endif

What I'm doing here first is ignoring every unit if it's under construction. For some reason if I don't do that, units never build - presumably because a unit's CONST_MAXHEALTH increases as the unit builds, but at the beginning it's 0 so we keep setting health to 0 so it never builds. If it isn't building, I'm calling <-unit CONST_MAXHEALTH GetUnitAttribute to get the max health, and feeding it as an argument to <-unit CONST_HEALTH health_here SetUnitAttribute.

However, if a unit has so little health that it will get destroyed by Creeper in one frame, it will explode before we have a chance to increase its health back up, and there's nothing we can do about that.

StardustAlpha

Quote from: Grabz on July 10, 2018, 02:07:11 PM
Well explained code
If I also added some code to increase all unit's max health before the 'set unitHP to maxHP' bit, would that make them less likely to be one-shotted by the creeper? Also, is it possible to change how much damage creeper does?

GoodMorning

You would have to replicate the Creeper damage behaviour, or deal damage yourself.

Unit max HP is not settable except for CRPL units.
A narrative is a lightly-marked path to another reality.

Grabz

Quote from: StardustAlpha on July 11, 2018, 01:07:35 AM
If I also added some code to increase all unit's max health before the 'set unitHP to maxHP' bit, would that make them less likely to be one-shotted by the creeper?
As GoodMorning says, you cannot change default units' max health. The game generally prevents you from changing many attributes of default units - presumably so that players are not surprised by their armament acting differently.

Quote from: StardustAlpha on July 11, 2018, 01:07:35 AM
Also, is it possible to change how much damage creeper does?
Not easily - this functionality is hardcoded into the game against default player units, and you cannot use it for your own CRPL core units, so you have to write your own.

It would be possible to store all of the units found by GetUnitsInRange in a list as well as their current health, then on the next frame compare their current health to the health in the list. This way we know how much damage the unit took, and we can apply a modifier to that to effectively change how much damage Creeper would do. I can write this for you if you're interested, although it may or may not be very fast on bigger maps with many units.

Builder17

Click [Select] button and then copy that code, do you know basics?
https://knucklecracker.com/wiki/doku.php?id=crpl:crpltutorial:interactive:importing

(Untested, please tell if any problems occur.)
# This script rebuilds any lost units and keeps existing ones alive

# Defaults and initial setup
$name:"Abraxis"

# These are the units that will get captured:
# 1 means it is a normal unit. 0 means it will not be captured (typically done for the large units that take a long time to die). Any units not listed here will not be captured.
$COLLECTOR:1
$RELAY:1
$REACTOR:1
$OREMINE:1
$SIPHON:1
$GUPPY:1
$SHIELD:1
$STRAFER:1
$BOMBER:1
$TERP:1
$PULSECANNON:1
$MORTAR:1
$SPRAYER:1
$BEAM:1
$SNIPER:1
$FORGE:1
$BERTHA:1
$COMMANDNODE:0

once
@Awake
ShowTraceLog
ClearTraceLog
#SetImage(Self "Main" "None")
SetUnitAttribute(Self CONST_COUNTSFORVICTORY FALSE)
SetUnitAttribute(Self CONST_CREATEPZ FALSE)
SetUnitAttribute(Self CONST_TAKEMAPSPACE FALSE)
SetUnitAttribute(Self CONST_NULLIFIERDAMAGES FALSE)
SetUnitAttribute(Self CONST_SUPPORTSDIGITALIS FALSE)
#SetUnitAttribute(Self CONST_COORDX 1) # Moves to coordinates (1,1)
#SetUnitAttribute(Self CONST_COORDY 1)
CreateList ->UnitX
CreateList ->UnitY
CreateList ->UnitType # Type of unit such as RELAY, PULSECANNON, BEAM, etc...
endonce

GetTimer3 eq0 if
0 ->Units # Keep track of how many units are on the map (used to add unit information into lists and when cycling through the units to rebuild

# Loop through each unit on the map and store its information
GetUnitsInRange(0 0 9999) 0 do # This finds all units on map and loops through them
->UnitID # Capture the unit's ID for use later
GetUnitType(<-UnitID) ->Type # Get the unit type
if(<-Type <-!) # Look up the unit type in the list of units that will be captured. If 1 proceed, otherwise skip
GetUnitAttribute(<-UnitID CONST_COORDX) ->X # Get the X coordinate of the unit
GetUnitAttribute(<-UnitID CONST_COORDY) ->Y # Get the Y coordinate of the unit
<-Type <-X <-Y @AddUnit
<-UnitID CONST_ISBUILDING GetUnitAttribute not if
<-UnitID CONST_HEALTH <-UnitID CONST_MAXHEALTH GetUnitAttribute SetUnitAttribute
endif
else
<-UnitID CONST_ISBUILDING GetUnitAttribute not if
<-UnitID CONST_HEALTH <-UnitID CONST_MAXHEALTH GetUnitAttribute SetUnitAttribute
endif
endif
loop
2 SetTimer3 endif

# Run checks to rebuild units
<-Units 0 do
# First, check the current unit index. If it is over the unit count, move it back to the start of the list (0)
if(<-Index <-Units gte)
0 ->Index
endif

# Get unit information
GetListElement(<-UnitX <-Index) ->X
GetListElement(<-UnitY <-Index) ->Y
GetListElement(<-UnitType <-Index) ->Type

if(GetCellOccupiedCount(<-X <-Y) eq0)
if(GetCreeper(<-X <-Y) 0 lte)
"NoCreeper" Trace
if(GetDigitalis(<-X <-Y) eq0)
"yes" ->Rebuild # Assume the unit will be rebuilt unless determined otherwise

# Rebuild the unit
if(<-Rebuild "yes" eq)
# Show this to make sure units are being rebuilt
"Rebuilding unit " <-Index concat " - " concat <-Type concat " at " concat <-X concat ", " concat <-Y concat trace
PauseGame
CreateUnit(<-Type <-X <-Y) ->UnitID
<-UnitID CONST_ISBUILDING FALSE SetUnitAttribute
<-UnitID CONST_HEALTH <-UnitID CONST_MAXHEALTH GetUnitAttribute SetUnitAttribute
UnpauseGame
endif
endif
endif
endif

# Increment the index number by 1
<-Index 1 add ->Index
loop

:AddUnit # Adds a unit to the rebuild list
# Unit Type, X coordinate, Y coordinate pulls from the stack and is assigned to the corresponding list index
->Y ->X ->Type
SetListElement(<-UnitX <-Units <-X)
SetListElement(<-UnitY <-Units <-Y)
SetListElement(<-UnitType <-Units <-Type)

# Increment the unit count
<-Units 1 add ->Units

# Uncomment the following line to show the results of the unit capture (This should only be done during testing. Make sure these are commented so it doesn't show in the final map)
#ShowTraceLog <-Type " (" concat <-X concat "," concat <-Y concat ")" concat trace

:Awake
TRUE OperateWhilePaused



Grabz

Well, while you can rebuild, rebuilding is not ideal with thousands of info messages popping up and explosion noises...

Builder17

Hmm, should OP tell more about if he wants it to rebuild or just keep existing ones alive?

Edit: Script in previous message updated.