O Ilusionista
Captain 80K
As some may already know, there is a security lock in the engine (as I remember the devs talking about it) that prevents an entity from calling a copy of itself, correct?
Depending on the type of entity, either this copy does not appear on the screen, or it appears and has a completely strange behavior (such as appearing in a random position on the screen), such as TYPE ENEMY. And the PLAYER TYPE is a special type, where this doesn't really work.
It turns out that this is valid for all times... EXCEPT during the "ondeathscript"! @Kratus discovered this by looking at the engine source code.
When the "ondeathscript" is fired, the entity has "died" but has not been removed from the screen yet - "dying" and "being removed from the screen" are different things, it's like the difference between an entity "being dead" and "the entity no longer exists".
In other words, the entity is "dead", but it still exists and you can still refer to it, even though it is "dead".
"Ondeathscript" is triggered as soon as the entity dies (that is, it has no more hit points), however, before the entity is removed - for obvious reasons.
1 - You need to create a .c file that will be called in "ondeathscript" (in this example, I will use the "spawnclone.c" file inside the scripts/ondeath/ folder):
In order to optimize memory consumption, we use a redirector, so 2 files will be needed
Both need to be in the same folder.
Important: you need to point to the correct file, which will be the redirector that imports the correct file.
Add this line in your entity header:
Ondeathscript data/scripts/ondeath/spawnclone.c
This is the content of the file "spawnclone.c"
And this is the content of the file "spawnclone_actual.c"
note: the code above uses and old method of spawning entities with specific animations that isn't the best option, but I am copying here the code I use and I will edit it later.
As you can read above, the function will:
- Get the caller entity
- Get the caller entity model
- spawn a copy of the same entity, using the same map of the caller entity
- on the same X position, 30 pixels above and 1 pixel in front (z)
- using the FOLLOW98 anim
You can customize the position to your need, also the animation. I use always the same animation so I can resuse the code, hence why the big number FOLLOW98
Important: you won't be able to use such high number if you don't change your MAXFOLLOW setting.
2- Create the FOLLOW98 animation in your entity.
In my case, I'm using this code on an obstacle, to create pieces of it breaking, without the need to create clones of that obstacle. Therefore, it is important to add a line that destroys this piece of the obstacle at the end:
And that's it - when you destroy this obstacle, it creates a copy of itself, using the FOLLOW98 animation.
If you wanted it to have more "chunks" (or copies), you need to change the code and change the animations. For example, in entities that use 4 copies, I use the FOLLOW98, FOLLOW97, FOLLOW96 and FOLLOW95 animations. And I change it in the script.
On this example, using 4 pieces, You would need to change this line:
To this:
See it in action here, at 0:25 - the drum creates a clone of itself
Depending on the type of entity, either this copy does not appear on the screen, or it appears and has a completely strange behavior (such as appearing in a random position on the screen), such as TYPE ENEMY. And the PLAYER TYPE is a special type, where this doesn't really work.
It turns out that this is valid for all times... EXCEPT during the "ondeathscript"! @Kratus discovered this by looking at the engine source code.
How it works?
When the "ondeathscript" is fired, the entity has "died" but has not been removed from the screen yet - "dying" and "being removed from the screen" are different things, it's like the difference between an entity "being dead" and "the entity no longer exists".
In other words, the entity is "dead", but it still exists and you can still refer to it, even though it is "dead".
"Ondeathscript" is triggered as soon as the entity dies (that is, it has no more hit points), however, before the entity is removed - for obvious reasons.
How to do this
1 - You need to create a .c file that will be called in "ondeathscript" (in this example, I will use the "spawnclone.c" file inside the scripts/ondeath/ folder):
In order to optimize memory consumption, we use a redirector, so 2 files will be needed
spawnclone.c
spawnclone_actual.c
Both need to be in the same folder.
Important: you need to point to the correct file, which will be the redirector that imports the correct file.
Add this line in your entity header:
Ondeathscript data/scripts/ondeath/spawnclone.c
This is the content of the file "spawnclone.c"
C-like:
#import "data/scripts/ondeath/spawnclone_actual.c"
void main()
{
actual_main();
}
And this is the content of the file "spawnclone_actual.c"
C-like:
void actual_main(){
void self = getlocalvar("self"); // get caller entity
char Name = getentityproperty(self,"model"); // get caller entity model
spawnAniMap(Name,0, 30,1,"ANI_FOLLOW98",NULL(),NULL(),NULL()); // spawn a copy of the same entity on the same X position, 30 pixels above and 1 pixel in front (z), using the FOLLOW98 anim
}
void spawnAni(void vName, float fX, float fY, float fZ, int Ani, float Vx, float Vy, float Vz)
{
//spawnB (Generic spawner) + Specific animation + velocities
//Damon Vaughn Caskey + Douglas Baldan
//07/06/2007
//
//Spawns entity next to caller.
//
//vName: Model name of entity to be spawned in.
//fX: X location adjustment.
//fZ: Y location adjustment.
//fY: Z location adjustment.
void self = getlocalvar("self"); //Get calling entity.
void vSpawn; //Spawn object.
int iDirection = getentityproperty(self, "direction");
clearspawnentry(); //Clear current spawn entry.
setspawnentry("name", vName); //Acquire spawn entity by name.
if (iDirection == 0){ //Is entity facing left?
fX = -fX; //Reverse X direction to match facing.
Vx= -Vx; //Reverse X velocity to match facing.
}
fX = fX + getentityproperty(self, "x"); //Get X location and add adjustment.
fY = fY + getentityproperty(self, "a"); //Get Y location and add adjustment.
fZ = fZ + getentityproperty(self, "z"); //Get Z location and add adjustment.
vSpawn = spawn(); //Spawn in entity.
if (vSpawn){// safe check
changeentityproperty(vSpawn, "position", fX, fZ, fY); //Set spawn location.
changeentityproperty(vSpawn, "direction", iDirection); //Set direction.
performattack(vSpawn, openborconstant(Ani));
changeentityproperty(vSpawn, "velocity", Vx, Vy, Vz);
changeentityproperty(vSpawn, "parent", self); //Set parent.
return vSpawn; //Return spawn.
}
}
void spawnAniMap(void vName, float fX, float fY, float fZ, int Ani, float Vx, float Vy, float Vz)
{
//spawnB (Generic spawner) + Specific animation + velocities
//Damon Vaughn Caskey + Douglas Baldan
//07/06/2007
//
//Spawns entity next to caller.
//
//vName: Model name of entity to be spawned in.
//fX: X location adjustment.
//fZ: Y location adjustment.
//fY: Z location adjustment.
void self = getlocalvar("self"); //Get calling entity.
void vSpawn; //Spawn object.
int iDirection = getentityproperty(self, "direction");
int iMap = getentityproperty(self, "map"); // Get caller's remap.
clearspawnentry(); //Clear current spawn entry.
setspawnentry("name", vName); //Acquire spawn entity by name.
if (iDirection == 0){ //Is entity facing left?
fX = -fX; //Reverse X direction to match facing.
Vx= -Vx; //Reverse X velocity to match facing.
}
fX = fX + getentityproperty(self, "x"); //Get X location and add adjustment.
fY = fY + getentityproperty(self, "a"); //Get Y location and add adjustment.
fZ = fZ + getentityproperty(self, "z"); //Get Z location and add adjustment.
vSpawn = spawn(); //Spawn in entity.
if (vSpawn){// safe check
changeentityproperty(vSpawn, "map", iMap); //Set map.
changeentityproperty(vSpawn, "position", fX, fZ, fY); //Set spawn location.
changeentityproperty(vSpawn, "direction", iDirection); //Set direction.
performattack(vSpawn, openborconstant(Ani));
changeentityproperty(vSpawn, "velocity", Vx, Vy, Vz);
changeentityproperty(vSpawn, "parent", self); //Set parent.
return vSpawn; //Return spawn.
}
}
note: the code above uses and old method of spawning entities with specific animations that isn't the best option, but I am copying here the code I use and I will edit it later.
As you can read above, the function will:
- Get the caller entity
- Get the caller entity model
- spawn a copy of the same entity, using the same map of the caller entity
- on the same X position, 30 pixels above and 1 pixel in front (z)
- using the FOLLOW98 anim
You can customize the position to your need, also the animation. I use always the same animation so I can resuse the code, hence why the big number FOLLOW98
Important: you won't be able to use such high number if you don't change your MAXFOLLOW setting.
2- Create the FOLLOW98 animation in your entity.
In my case, I'm using this code on an obstacle, to create pieces of it breaking, without the need to create clones of that obstacle. Therefore, it is important to add a line that destroys this piece of the obstacle at the end:
anim follow98
loop 0
delay 2
offset 238 157
landframe 16
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
frame data/chars/misc/items/drum02f2.gif
frame data/chars/misc/empty.gif
@cmd killentity getlocalvar("self")
frame data/chars/empty.gif
And that's it - when you destroy this obstacle, it creates a copy of itself, using the FOLLOW98 animation.
If you wanted it to have more "chunks" (or copies), you need to change the code and change the animations. For example, in entities that use 4 copies, I use the FOLLOW98, FOLLOW97, FOLLOW96 and FOLLOW95 animations. And I change it in the script.
On this example, using 4 pieces, You would need to change this line:
C-like:
spawnAniMap(Name,0, 30,1,"ANI_FOLLOW98",NULL(),NULL(),NULL());
To this:
C-like:
spawnAniMap(Name,0, 30,1,"ANI_FOLLOW95",NULL(),NULL(),NULL());
spawnAniMap(Name,0, 30,1,"ANI_FOLLOW96",NULL(),NULL(),NULL());
spawnAniMap(Name,0, 30,1,"ANI_FOLLOW97",NULL(),NULL(),NULL());
spawnAniMap(Name,0, 30,1,"ANI_FOLLOW98",NULL(),NULL(),NULL());
See it in action here, at 0:25 - the drum creates a clone of itself