Train won't move and gets instantly destroyed by nullifiers

Started by Warpsing, December 15, 2013, 02:30:48 PM

Previous topic - Next topic

Warpsing

What i have here is the head of my "Train Core", I haven't made the part where it makes other parts behind it.
I'm planning for the train to be able to move, take extra damage from nullifiers, and take a ton of sniper shots.
once

Spoiler
once

   GetTerrain(CurrentX CurrentY) ->targetTerrainHeight




   -1 ->lastCellX

   -1 ->lastCellY

   

   SetUnitAttribute(Self CONST_CREATEPZ FALSE)

   SetUnitAttribute(Self CONST_TAKEMAPSPACE FALSE)

   SetUnitAttribute(Self CONST_SUPPORTSDIGITALIS FALSE)

   SetUnitAttribute(Self CONST_NULLIFIERDAMAGES TRUE)

   SetUnitAttribute(Self CONST_COUNTSFORVICTORY FALSE)

   SetUnitAttribute(Self CONST_SNIPERTARGET TRUE)

   SetUnitAttribute(Self CONST_DESTROYMODE 1)

   1.5 ->moveSpeed

   SetUnitAttribute(Self CONST_HEALRATE 0.1)

   SetUnitAttribute(Self CONST_MAXHEALTH 3000)

   SetUnitAttribute(Self CONST_HEALTH 3000)

endonce

#If we aren't moving, choose a new location.  The location will be a neighboring cell.

@CheckHealth

if (GetQueuedMoveCount eq0)



   if (@ChooseNewCell)

      QueueMove(<-chosenX <-chosenY <-SPEED)

      CurrentX ->lastCellX

      CurrentY ->lastCellY

   endif

endif





:ChooseNewCell

   -1 ->chosenX

   -1 ->chosenY



   # Get a list of the neighbors that we can move to.

   # This call returns the coordinate pairs on the stack.

   @GetPossibleCells



   # Choose a random location within the list

   RandInt(0 <-count)  ->randCellNumber



   # Go through the list and pop tall of the coordinates off the stack

   # As we pass the coordinates that we chose in our random number above, remember

them.

   # Those are the coordinates we will be returning.

   do (<-count 0)

      ->y

      ->x

      if (I eq(<-randCellNumber))

         <-x ->chosenX

         <-y ->chosenY

      endif

   loop

   # Return if we chose a new location

   <-chosenX neq(-1)





:GetPossibleCells

   #Check the four neighboring cells to see if they are the same terrain height.

   0 ->count

   CurrentX 1 add ->cx CurrentY ->cy @CheckCell      #Right

   CurrentX ->cx CurrentY 1 sub ->cy @CheckCell      #Up

   CurrentX 1 sub ->cx CurrentY ->cy @CheckCell      #Left

   CurrentX ->cx CurrentY 1 add ->cy @CheckCell      #Down



   # By default, we won't return the last cell coordinates.  This is so the

patrolling unit

   # doesn't return back to where it came from immediately.  But, if the only

choice is to return

   # to the previous cell, then that is what we have to do.

   if (<-count eq0)

      <-lastCellX

      <-lastCellY

      1 ->count

   endif





:CheckCell

   #Check to see if the cell we are looking at is the last cell, if so ignore.

   if (<-cx <-lastCellX neq <-cy <-lastCellY neq or)

      # Check if the target cell is at our target terrain height.  If so, push

the

      # coordinates to the stack and increment count.

      if (GetTerrain(<-cx <-cy) eq (<-targetTerrainHeight))

         <-cx

         <-cy

         <-count 1 add ->count

      endif

   endif





:Destroyed

   if (<-base neq0)

      <-base "Base.crpl" "patrolCount" GetScriptVar ->patrolCount

      <-patrolCount 1 sub ->patrolCount

      <-base "Base.crpl" "patrolCount" <-patrolCount SetScriptVar

   endif

   PlaySound("Weapons15")


:CheckHealth

   GetUnitAttribute(Self CONST_HEALTH) ->health

   GetUnitAttribute(Self CONST_MAXHEALTH) ->maxHealth



   if (<-health lte (0.1))

      0 ->health

      $targetX:0

      Destroy(self 2)


   endif   

[close]

thepenguin

I think that the default is for CRPLCOREs to be completely destroyed by nullifiers.  I also don't think there's any way to change that behavior directly.  However it is possible to create another CRPLCORE that locks its location directly on top of the "Train Core" and when it is destroyed (nullified), it subtracts health from the real train and creates another copy of itself in the same location.  I'm not sure what to do about that not moving, though.
We have become the creeper...

eduran

Quote from: Warpsing on December 15, 2013, 02:30:48 PM
1.5 ->moveSpeed
[...]
QueueMove(<-chosenX <-chosenY <-SPEED)

As far as I can see you never assign a value to 'SPEED'. That is most likely the reason why the train does not move.

Clean0nion

At the end, add a :Destroyed function. This is called like any other @DoSomething command, except it is called automatically by the game upon the CRPLcore being destroyed. So add something like this to the end of your script:

:Destroyed
  "CRPLCORE" CurrentCoords CreateUnit ->newTrain
  <-newTrain "TrainScript.crpl" AddScriptToUnit
   <-newTrain "TrainScript.crpl" "health" <-health 1 sub SetScriptVar

Grayzzur

The downside of simply creating a new train on destruction to take multiple nullifer hits is that you lose all your variables, queued moves, etc. As thepenguin says, you can have a helper, or ghost, core follow your main core around. The main core is immune to nullifiers, the ghost core is invisible, and can be killed with nullifiers. It appears that the nullifier is hitting the main unit, and that it takes multiple hits. When the ghost core is destroyed, the main core increments it's counter and spawns another one, until it decides it's been destroyed.

This sample doesn't have your other movement logic, but it illustrates how to use the ghost cores to take multiple hits, and have the ghost track your main unit's position.

Sample code:

TrainHead.crpl (put this on a core)
Spoiler

# TrainHead.crpl
# Created on: 12/15/2013 3:18:25 PM
# ------------------------------------------

$MaxNulliferShots:3

once
   -1 ->HitsTaken #Initial creation of helper increments to zero
   0 ->HelperID
   
   self CONST_CREATEPZ false SetUnitAttribute
   self CONST_NULLIFIERDAMAGES false SetUnitAttribute
   self CONST_SNIPERTARGET false SetUnitAttribute

   TRUE SetPopupTextAlwaysVisible #Display hits taken as popup text for example
endonce

#If HelperID is zero, spawn a new helper
<-HelperID eq0 if
   "CRPLCore" CurrentCoords CreateUnit ->HelperID
   <-HelperID "TrainHeadHelper.crpl" AddScriptToUnit
   <-HelperID "TrainHeadHelper.crpl" "TrainHeadID" Self SetScriptVar
   
   #Record hit
   <-HitsTaken 1 add ->HitsTaken
endif


#Damage Check
<-HitsTaken <-MaxNulliferShots gte if
   Self 2 Destroy
endif

#Display hits taken
<-HitsTaken "/" concat <-MaxNulliferShots concat SetPopupText

#Movement
#Make core move in a small square, just to show the helper core follows
GetQueuedMoveCount eq0 if
   60 50 1 QueueMove
   75 50 1 QueueMove
   75 65 1 QueueMove
   60 65 1 QueueMove
endif
[close]

TrainHeadHelper.crpl (just have this in the map project, don't attach it to a core directly)
Spoiler

# TrainHeadHelper.crpl
# Created on: 12/15/2013 3:18:44 PM
# ------------------------------------------

once
   self "main" "NONE" SetImage

   self CONST_CELLHEIGHT 0 SetUnitAttribute
   self CONST_CELLWIDTH 0 SetUnitAttribute
   self CONST_CREATEPZ false SetUnitAttribute
   self CONST_DESTROYMODE 0 SetUnitAttribute
   self CONST_DESTROYONDAMAGE false SetUnitAttribute
   self CONST_SNIPERTARGET false SetUnitAttribute
   self CONST_SUPPORTSDIGITALIS false SetUnitAttribute
   self CONST_TAKEMAPSPACE false SetUnitAttribute
   self CONST_THORTARGET false SetUnitAttribute

endonce

#Check for abandonment, and destroy self
#Usually happens when recompiling scripts while editing a map
<-TrainHeadID eq0 if
   Self 0 Destroy
endif

#Follow parent, use pixel coordinates as the parent may not be at the center of a map cell
Self CONST_PIXELCOORDX <-TrainHeadID CONST_PIXELCOORDX GetUnitAttribute SetUnitAttribute
Self CONST_PIXELCOORDY <-TrainHeadID CONST_PIXELCOORDY GetUnitAttribute SetUnitAttribute

return

#On destruction, set parent TrainHead's HelperID to 0
:destroyed
<-TrainHeadID "TrainHead.crpl" "HelperID" 0 SetScriptVar
[close]
"Fate. It protects fools, little children, and ships named 'Enterprise.'" -William T. Riker

Warpsing

Thanks for helping, eduran, I realy wanted a way to get it to move, and it was that simple.

And I'm actually going to give up on having the nullifiers destroying the train thing.

knucracker

Apologies as I only just skimmed the previous messages... but if one of the issues is that a nullifier destroys a core, you can turn that off.  The easiest way for a core you manually create is to just select the core, then scroll down in the list of attributes.  One of them is "Nullifier Damages".  Just uncheck that and nullifiers won't target your core.

If it is a dynamically created core you don't want nullifiers targeting, then set a unit attribute to false using this constant: CONST_NULLIFIERDAMAGES

J

I thought the problem was that he wanted the core to take multiple hits instead of being destroyed after the first hit.

knucracker

Ah...
In that case the current build provides no easy options.  But, I just added this constant for the upcoming major update (late this week if all goes well).
CONST_NULLIFIERDAMAGEAMT

In the upcoming build you can set the amount of damage that a nullifier does to a unit using that unit attribute constant.
once
   SetUnitAttribute(Self CONST_NULLIFIERDAMAGEAMT 0.1)
endonce

This will make it so that a nullifier does 0.1 damage to a unit rather than destroying it.  Assuming the unit has a health more than 0.1, this means multiple nullifiers would be required to destroy the unit.

The default for nullifierdamageamt is 0.  This means that the unit should be destroyed (the normal and default behavior).

Grauniad

A goodnight to all and to all a good night - Goodnight Moon

knucracker

I made the constant apply to all units (so you can change an emitter for instance) and the health value of different units can vary (one might be set to 10 for instance).  Also, I wanted to maintain smooth backwards compatibility, so "0" because the default which means total destruction. 

And before anyone asks, yes you can do negative damage using this API (meaning a nullifier would make the core recover health).  Why you might want to do that... I'll leave that to your imagination.

Grauniad

I'm sorry, but this is complicated. Take the case of two units.

One has a health of 5.1, one has a health of 1.

Before this update,  regardless of health, a single nullifier could destroy both units? But other units that could, for instance, fire at it would take more than 5 times as much effort to destroy the 5.1 health unit?

Now, after this update, if the constant is specified as .1 for both units, then it would take 51 nullifiers to destroy the former and 10 nullifiers to destroy the latter?
A goodnight to all and to all a good night - Goodnight Moon

knucracker

If the map author set 0.1 for both units, that would be correct.  By default nothing changes.... a nullifier will destroy anything it fires at regardless of health.  All this new setting does is allow a map author to say for a given unit whether he wants the unit to be destroyed by the nullifer or whether he wants the unit to instead take X damage.

Note that the setting isn't global or per nullifier, it is per unit.  So you could have a boss on the map that you want to require 10 nullifiers to take out.  Everything else you want with the default behavior.  So the boss might say he wants nullifierdamage of 0.1 and sets his health to 1.  He might also leave on some small amount of unit healing, so you have to fire off the nullifiers near in time with each other.  For everything else on the map (all of the emitters, for instance) a nullifier would destroy when it fires.

Grauniad

While I fully understand the issue with backwards compatibility, it just seems foreign that the health curve is so weird.

-ve increases health
0 complete destruction
.1 removes .1 health
1 removes 1 health.

So in essence, 0 does more damage than any other value.
A goodnight to all and to all a good night - Goodnight Moon

knucracker

The alternative would be for 0 to do 0 damage, which is already covered by the alternate setting NULLIFIERDAMAGES (you can turn off nullifier targeting of a unit).  If for some reason somebody really wanted nullifiers to fire at a unit but do effectively no damage, they could set NULLIFIERDAMAGEAMT to something like 0.0000001.

At the other end of the spectrum, the map developer needs a way to specify that a nullfier destroys a unit rather than just damaging it (having previously set some damage amount, for instabce).  Granted, setting NULLIFIERDAMAGEAMT to some really large value will do it, but that also means I have to return max_int as the default value for a unit if somebody calls GetUnitAttribute(Self CONST_NULLIFIERDAMAGEAMT) on a unit.

In other words, it's easier to remember that "0" means "default behavior / ignore" than that 2147483647 is the default NULLIFIERDAMANGEAMT on all units.

One other perspective is that lots of times you will see things like "specify a limit" in a setting for software.  But "0" will mean "unlimited".  The reasoning is the same... when 'unlimited' is a common setting the user needs a way to specify that easily, especially when 0 has limited or no intrinsic value.

--edit--
Actually... I misspoke.  The value you specify is a float not an int, so the max value is 3.40282e+038 :)