Hello there! First time programming crpl
I have created a custom tower that transfers creeper from itself to its slave (or vice versa) to work as a portal.
I have two scripts: portalMaster and portalSlave, both of which have a once that is supposed to find their partner and save the partner's UID as a variable
I want destroying one to also destroy the other, so I wrote this little :destroyed function:
Quote
:destroyed
<-partner 2 Destroy
It seems too simple to get wrong, but somehow I did. UID is correct (had them both print self and partner to trace).
The complete scripts:
Master:
Spoiler
# portalMaster.crpl
# Created on: 1/24/2014 10:33:22 AM
# Author: LordBiscuit
# ------------------------------------------
$portalID:0
$multiplier:0.5
$interval:0
once
ClearTraceLog
HideTraceLog
"portalID" Dup <-! GetCoresWithVar
->numOfCoresWithSamePortalID
<-numOfCoresWithSamePortalID 2 neq if
ShowTraceLog
"ERROR: number of cores with same portalID != 2"
Trace
<-numOfCoresWithSamePortalID Trace
endif
<-numOfCoresWithSamePortalID 0 do
->unitUID
<-unitUID self neq if
<-unitUID ->partner
endif
loop
<-partner CONST_COORDX GetUnitAttribute ->partnerX
<-partner CONST_COORDY GetUnitAttribute ->partnerY
endonce
CurrentCoords GetCreeper
<-partnerX <-partnerY GetCreeper
Sub # C(self) - C(partner)
<-multiplier Mul
->amount # amount to transfer from p1 to p2
CurrentCoords 0 <-amount Sub AddCreeper
<-partnerX <-partnerY <-amount AddCreeper
<-interval Delay
:destroyed
<-partner 2 Destroy
Slave:
Spoiler
# portalSlave.crpl
# Created on: 1/24/2014 10:33:26 AM
# Author: LordBiscuit
# ------------------------------------------
$portalID:0
once
ClearTraceLog
HideTraceLog
"portalID" Dup <-! GetCoresWithVar
->numOfCoresWithSamePortalID
<-numOfCoresWithSamePortalID 2 neq if
ShowTraceLog
"ERROR: number of cores with same portalID != 2"
Trace
<-numOfCoresWithSamePortalID Trace
endif
<-numOfCoresWithSamePortalID 0 do
->unitUID
<-unitUID self neq if
<-unitUID ->partner
endif
loop
<-partner CONST_COORDX GetUnitAttribute ->partnerX
<-partner CONST_COORDY GetUnitAttribute ->partnerY
endonce
:destroyed
<-partner 2 Destroy
Running on Ubuntu 12.04 LTS
Thanks in advance!
Looks like a bug to me. I assume the second core to go down tries to destroy the first one, which is already dead. Usually, calling 'Destroy' on a dead unit does nothing. I'd suggest you place a bug report with a link to this thread in the support forum (or have a mod move the entire thing there).
In the mean time, you can work around the issue by replacing the :destroyed function with something like this:
<-partner CONST_ISDESTROYED GetUnitAttribute if
Self 2 Destroy
endif
Attach a sample map with the scripts - the same map that cause your game to crash - to this thread.
Also, if you run it again and it crashes again, please look in Creeperworld3 directory (where keydata.dat is kept - not the game code or the maps, another location), for log.txt and attach it to a post here.
Yeah, that definitely creates an infinite call loop. The :Destroyed function gets called before the destroyed state is set. Both the master and slave try to destroy their partner. So master dies, which kills the slave, which kills the master, which kills the slave... etc. (Until there is a stack overflow and the party crashes to a halt).
I'll see what I can do to prevent this...
First of all, thanks eduran for helping me work around this. My tower now works as intended :D
I tried running the map from Windows 7 on another machine.
It doesn't crash, but I can neither land on, build on, nor terraform the area where the cores used to be
Attached:
The .cw3 of my sample map
A screenshot of the weird thing I described on Windows 7
I could not find the log.txt file, tried to search within both of these:
~/Games/CreeperWorld3
~/Documents/CreeperWorld3
The workaround doesn't actually work either... you are still getting a stack overflow problem which is creating a large number of stacked power zones (that's why they look funny in your screen shot, there are a gazillion of them stacked).
The only way to hack around this problem right now in your current build is to have each tower remember if it has already had its :destroyed called and to do nothing if it gets called again.
So in your :Destroyed function do something like this (untested, but this might work).
:destroyed
if (<-destroying) return
TRUE ->destroying
#the rest of your destroy function
Or maybe don't call Destroy on your partner if it's already flagged as destroyed:
:destroyed
<-partner CONST_ISDESTROYED GetUnitAttribute not if
<-partner 2 Destroy
endif
Quote from: Grayzzur on January 24, 2014, 12:26:14 PM
Or maybe don't call Destroy on your partner if it's already flagged as destroyed:
:destroyed
<-partner CONST_ISDESTROYED GetUnitAttribute not if
<-partner 2 Destroy
endif
I don't think this would work either.
Quote from: virgilw on January 24, 2014, 10:31:31 AM
The :Destroyed function gets called before the destroyed state is set.
So Core 1 dies, sees that its partner is alive, says "I'll fix that!", and destroys it. Trigger Core 2 dying, and it would likely read that Core 1 is still alive, since it hasn't had time to set itself to destroyed yet. Queue infinite loop yet again.
What if you built in a delay? If it gives itself time to change over to Destroyed status, it should work. And if the delay is small enough, it shouldn't interfere too much with the aesthetic. I just...don't know the right place to put said delay.
It will get destroyed after the function has finished doing whatever it does.
In my experience, that last tick in which it exists (and runs its :destroyed function), CONST_ISDESTROYED is set to true. Next frame, it doesn't exist at all.
Quote from: virgilw on January 24, 2014, 11:26:40 AM
The workaround doesn't actually work either... you are still getting a stack overflow problem which is creating a large number of stacked power zones (that's why they look funny in your screen shot, there are a gazillion of them stacked).
The only way to hack around this problem right now in your current build is to have each tower remember if it has already had its :destroyed called and to do nothing if it gets called again.
So in your :Destroyed function do something like this (untested, but this might work).
:destroyed
if (<-destroying) return
TRUE ->destroying
#the rest of your destroy function
I didn't exactly use eduran's suggestion. What I did use is as follows:
The slave polls the master to see if it's destroyed, and destroys itself if it is. Then in its destruction function it checks if the master is still alive and kills it if it is. If the master dying triggered the slave to self destruct, the ISDESTROYED flag on the master would be true and it won't get destroyed again. If the slave died first, it would kill the master but won't continue to poll, so it won't self destruct again and cause the loop.
So code wise, the master deals only with transferring creep between the portals, and the slave only with making them both go down together.
Btw, you shouldn't have to do anything special in the latest beta build. You original solution should work fine.