Some functions for ghosted enemy scripts. Requires at least RC2.
ghost.zh and demo
AutoGhost setup demo video
In the demo quest, the cheat codes are just 1, 2, 3, and 4.
Any bugs or suggestions, let me know.
Some functions for ghosted enemy scripts. Requires at least RC2.
ghost.zh and demo
AutoGhost setup demo video
In the demo quest, the cheat codes are just 1, 2, 3, and 4.
Any bugs or suggestions, let me know.
Oooh, very neat. This should make boss writing slightly easier!
Edit: Hmm, if you do this:
eweapon bomb = FireAimedEWeapon(EW_BOMB, this->X + 8, this->Y + 8, 0, 100, 4, false, 7, false, 38);
SetEWeaponMovement(bomb, EWM_THROW, 5);
SetEWeaponDeathEffect(bomb, EWD_EXPLODE, 4);
bomb->CollDetection = false; //so that the bomb itself doesn't hit LinkThe explosion on death travels slowly to the left... No idea why, since it clearly sets the explosion to step 0!
I think that one's a bug in ZC. Seems to do it no matter what.
Updated for builds 1174 and up. Handles stunning better now, and a couple of small bugs are fixed.
Another update. Added a new init method, improvements to stunning and thrown eweapons.
Does anyone besides me actually use this, or have any interest in doing so? I could write up a tutorial, if it'd be helpful.
(Edit: Small update: fixed a minor issue in the demo and a significant mistake in the documentation.)
(Another edit: Gah, I broke knockback last time. Fixed now. Added a couple more functions while I was at it.)
This header seems pretty useful..
Here's something I made with it
//This script creates an enemy that walks left to right between solid combos,
//similar to a Goomba from Super Mario Bros.
//Set D0 to 0 to start right and 1 to start left.
//D1 = Enemy type to use for ghosting
ffc script goomba_left_right
{ void run(bool startleft, int enemtype)
{
npc goomba;
int timer;
bool left;
bool right;
if(startleft){left = true; right = false;} else {right = true; left = false;}
goomba=GhostInitCreate(this, enemtype);
SetFlags(this, GHF_NORMAL);
timer = 2;
for(int i=0; true; i++)
{
if(i==timer)
{
i=0;
if(!right && left && CanMove(this,goomba,2,1,0)) Move(this,goomba,-1,0,0); else right = true;
if(right && CanMove(this,goomba,3,1,0))Move(this,goomba,1,0,0); else { left = true; right = false;}
}
GhostWaitframeF(this, goomba, true, true);
}
}
}
And the up/down one for good measure.
//This script creates an enemy that walks down and up between solid combos,
//Set D0 to 0 to start down and 1 to start up.
//D1 = Enemy type to use for ghostingffc script goomba_up_down
{
void run(bool startup, int enemtype)
{
npc goomba;
int timer;
bool up;
bool down;
if(startup){up = true; down = false;} else {down = true; up = false;}
goomba=GhostInitCreate(this, enemtype);
SetFlags(this, GHF_NORMAL);
timer = 2;
for(int i=0; true; i++)
{
if(i==timer)
{
i=0;
if(!down && up && CanMove(this,goomba,0,1,0)) Move(this,goomba,0,-1,0); else down = true;
if(down && CanMove(this,goomba,1,1,0))Move(this,goomba,0,1,0); else { up = true; down = false;}
}
GhostWaitframeF(this, goomba, true, true);
}
}
}
If you're going to use these, be sure to import "ghost.zh"!EDIT: I am a very poor scripter, so a tutorial on how to put together a custom boss with this would be very nice
First boss I made with it:
// Zora King// Will work best with a Freeform Combo set to Combo W: 32, Combo H: 32, Tile W: 2, Tile H: 2, with provided graphics.
// NOTE: Using a SkipX setting of 1 on the combo will allow
// you to animate using 2x2 tile chunks.
// D0 - The number pointing to another Freeform Combo
// Used to display the diving, as well as house more variables.
// (Check below for details)
// D1 - The total duration the zora will stay on the screen.
// D2 - Enemy Type to use for ghosting
// D3 - Total hit points
// D4 - Fire every this amount of frames when above water;
// D5 - Damage of projectile;
// D6 - Fireballs Per Shot (set to an odd number up to 9. Even numbers may function but are not recommended)
// D7 - Sprite to use for fireball
// As mentioned above, further customization is allowed by
// By using the D variables stored in the FFC called by D0
// D0 = Projectile movement pattern(0 for normal shot)
// D1 = Projectile movement argument (0 for none)
// See ghost.zh for its EWM constants for what to use as patterns
// See ghost_zh_readme.txt for what to use as argument.
// Use ZRB_CHECKPOINT Floats to specify in percentages when the Zora will be submerged, rise, appear, and sink.
const float ZRB_CHECKPOINT1 = 0.30;// Time From Phase 1 to 2. Decimal indicates a percentage.
const float ZRB_CHECKPOINT2 = 0.45;// Time From Phase 2 to 3. Decimal indicates a percentage.
const float ZRB_CHECKPOINT3 = 0.60;// Time from Phase 3 to 4. Decimal indicates a percentage.
const float ZRB_CHECKPOINT4 = 0.75;// Time from Phase 4 to end(staytime). Decimal indicates a percentage.
const int ZRB_PRJ_X_OFFSET = 8; // Offset of projectile from origin in X
const int ZRB_PRJ_Y_OFFSET = 16; // Offset of projectile from origin in Y
ffc script zora_king
{
//Running part of script
void run(int diveffc_s, int staytime, int enemtype, int enemhp, int fireat, int firedmg, int fireperarc, int firesprite)
{
npc zoraboss;
eweapon zora_fire;
//Loads an FFC
ffc diveffc = Screen->LoadFFC(diveffc_s);
int fire_mtype = diveffc->InitD[0];
int fire_arg = diveffc->InitD[1];
TraceNL();
Trace(fire_mtype);
TraceNL();
Trace(fire_arg);
int time_check = 0;
int water_check = 1;
int checkpoint_1 = (staytime*ZRB_CHECKPOINT1);
int checkpoint_2 = (staytime*ZRB_CHECKPOINT2);
int checkpoint_3 = (staytime*ZRB_CHECKPOINT3);
int checkpoint_4 = (staytime*ZRB_CHECKPOINT4);
int pop_up_x = 16 * Rand(15);
int pop_up_y = 16 * Rand(10);
zoraboss = GhostInitCreate(this,enemtype);
SetHP(this,zoraboss,enemhp);
while(true){
if(water_check == 1){pop_up_x = 16 * Rand(15);pop_up_y = 16 * Rand(10); water_check = 0;}
if(IsWater(ComboAt(pop_up_x,pop_up_y)) && IsWater(ComboAt(pop_up_x + 16,pop_up_y)) && IsWater(ComboAt(pop_up_x,pop_up_y + 16)) && IsWater(ComboAt(pop_up_x + 16,pop_up_y + 16))){} else {pop_up_x = 16 * Rand(15);pop_up_y = 16 * Rand(10);}
if(time_check < staytime) time_check++;
if(time_check <= checkpoint_1){
change_x_y_2(this, 256, 176, diveffc, 256, 176);
}
if(time_check > checkpoint_1 && time_check <= checkpoint_2){
change_x_y_2(this, 256, 176, diveffc, pop_up_x, pop_up_y);
}
if(time_check > checkpoint_2 && time_check <= checkpoint_3){
change_x_y_2(this, pop_up_x, pop_up_y, diveffc, 256, 176);
if(time_check % fireat == 0){
if (fireperarc >= 1){
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, 0, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0) SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
}
if (fireperarc >= 3){
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, -0.1, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, 0.1, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
}
if (fireperarc >= 5){
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, -0.2, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, 0.2, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
}
if (fireperarc >= 7){
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, -0.3, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, 0.3, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
}
if (fireperarc >= 9){
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, -0.4, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, 0.4, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
}
if (fireperarc >= 11){
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, -0.5, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
zora_fire=FireAimedEWeapon(129, this->X + ZRB_PRJ_X_OFFSET, this->Y + ZRB_PRJ_Y_OFFSET, 0.5, 200, firedmg,false, firesprite, false, 0);
if(fire_mtype > 0)SetEWeaponMovement(zora_fire, fire_mtype, fire_arg);
}
}
}
if(time_check > checkpoint_3 && time_check <= checkpoint_4){
change_x_y_2(this, pop_up_x, pop_up_y, diveffc, 256, 176);
}
if(time_check > checkpoint_4 && time_check <= staytime){
change_x_y_2(this, 256, 176, diveffc, pop_up_x, pop_up_y);
}
if(time_check >= staytime){water_check = 1; time_check = 0;}
GhostWaitframeF(this, zoraboss, true, true);
}
}
void change_x_y_2(ffc f1, int x1, int y1, ffc f2, int x2, int y2)
{
f1->X = x1;
f1->Y = y1;
f2->X = x2;
f2->Y = y2;
}
}
Updated. It needs build 1326+ now, so it's only for Linux and the debug build at the moment.
- Fixed a couple of small bugs.
- Enemies can now have combos set automatically based on direction.
- Added a function to adjust the enemy's hitbox.
- Eweapons can now flicker and cast shadows, and there's now a falling movement type.
- Added functions to create eweapons larger than 1x1.
- The SetFlags() and all three FireEWeapon functions have changed; the old versions are deprecated.
I've also added on to the demo. Do try it, I'm rather proud of it. If you can't/won't play it, I also made a video of it.
Also, bigjoe, you'd suggested adding functions for lweapons and items? Not a bad idea, but I think that'd be a different file. I probably will do that in the future.
So, I kind of screwed up the last update. The link was pointing to an older version. Sorry. Fixed now.
I have a suggestion for a function:
GhostWaitframeR
This would be the same as GhostWaitframeN but it would allow you to specify a location relative to the NPC. (Negative or positive integers.)
The reason I suggest this is that when I try to do that manually using GhostWaitframeM, both npc and ffc fly diagonally until they disappear. I guess its because they both have to be the same in the function.
I was thinking about adding something like that at one point, but I couldn't come up with anything I liked. The idea being that you might not want the hitbox to match the enemy's appearance, which ended up as SetOffsets() instead. Do you have a different purpose in mind?
Another possibility is that it could acknowledge the npc->DrawOffset variables and position the FFC where the enemy would normally be drawn. What do you think of that?
Edit: Actually, come to think of it, could you just use the npc->HitOffsets?
I think I've found a pretty reasonable workaround.. but what I was trying to do was make the FFC follow a position relative to the NPC. The collision would still be the same as the FFC, just the position would be different. This is to make a bigger enemy that behaves exactly like the small one(but has the larger collision)
My workaround will use two separate npcs.
Updated.
Edit: Sorry, quick update. I screwed up the arguments to SetCSet() and RevertCSet(). They worked, but they were inconsistent with other functions, and I'm trying to avoid that. Anyway, it's fixed now.
All right, big update. Complete overhaul, in fact. Here are the biggest changes this version:
Most enemy properties are now handled by global variables. There are only two standard Waitframe functions, Ghost_Waitframe and Ghost_Waitframe2. There's also a Ghost_WaitframeLight, but that'll generally be used only when the enemy is inactive. A lot of things that used to require a function can now be done just be setting one of the global variables. You can, for instance, set Ghost_HP to set the enemy's HP, set Ghost_CSet to change its CSet, or set Ghost_Data to change its combo, even if GHF_4WAY is set.
There are two major new eweapon death options. First, you can create a dummy eweapon with whatever properties you like, including its own scripted movement, and have an eweapon spawn copies of it on death. Second, you can set up an FFC to run a script where the eweapon died, so you can do whatever you like there.
Finally, the biggest new addition, AutoGhost. With appropriately written enemy scripts, the global script can set up FFCs automatically when enemies appear. That means scripted enemies can be used as easily as built-in ones, with no manual FFC setup at all. This requires at least RC2.
This version is not backward compatible with existing scripts. However, there is a separate file, ghost_legacy.zh, included for this purpose.
Also, thanks to Blaman/SpacemanDan for putting up with me through this. I know I can get a bit impatient at times. 😛
ghost.zh
Demo quest and sample scripts
AutoGhost enemies
AutoGhost setup demo video
Another update.
Edit: Quickly followed by a bugfix update. 😛
I recommend this be stickied.
EDIT:
I found a... well, an oddity let's say.
When using Ghost_CanMove with the diagonal directions, it's possible to get wrong results.
The reason is that it simply composes the two other directions. For example, say you're doing this:
Ghost_CanMove(DIR_LEFTUP,1,0);
The result will be essentially (Ghost_CanMove(DIR_LEFT,1,0) && Ghost_CanMove(DIR_UP,1,0));
However, if the enemy found itself in a scenario like this:
|X|
___ *
Then both LEFT and UP are true, but LEFTUP is not.
Thus, oddity.
Blargh. All right, shouldn't be hard to fix.
A long overdue update.
- Finally added handling for sideview screens
- Improved movement functions a bit (in particular, 4-way walking should work much better now)
- Added GHF_FULL_TILE_MOVEMENT, which makes the enemy consider a combo completely solid if any quadrant of it is
- Various bug fixes
I'm still only so happy with the movement functions. I was thinking about rewriting them completely, but that's proven rather tricky, so it'll have to wait for the next version.
There's no handling yet for the No Ground Enemies combo and flag. I might just have GHF_NO_FALL control that, or maybe it could depend on the enemy type being Other (Floating). Any other ideas?
Another update.
Updated.
Global scripts will need updated. See the "global" section of ghost.txt or ghost_zh/scripts.zh for the current set of functions.
I was going to redo movement this time, but it's taking longer than I expected. I'll be sure to get that next time.
Is this going to affect the ghosted enemy scripts I'm using in my quest? Because if so, would you mind updating the ones you included in your AutoGhost Enemies Pack?
They should all still work. I do plan to update them soon anyway, though.
Thing is, though, some of them have the old ghost_explode variable, which has been replaced with ghost_deathanimation.
It hasn't been removed, it's just deprecated.
Edit: Sorry. Left a bug in one of the FindSpawnPoint functions. Fixed now.
Another small update, requiring 2.50. I added another way to set up AutoGhost enemies. Set misc. attribute 12 to -1 and put the script name in the enemy's name after @, for instance:
Goriya (LttP, green) @Goriya_LttP
Also, I added constants to control flashing and knockback, so now you can fling enemies clear across the screen if you like.
Oh wow, I see you've been busy. I haven't played with ghost.zh in a while (ever since the Spark.h and HomingFireballShooter.h scripts), but I want to get back into it and see what I can make happen... I still have an enemy that moves in an epicycloid that got left on the cutting room floor, so I need to work out its kinks to get it ready for prime time.
Speaking of which, I never did update the scripts I've got linked... But I was thinking I should just post them here instead, anyway. I'll probably do that this weekend.
Anyway, another small update because I messed up flickering. 😛
Another update. This will invalidate existing saved games. (Generally the case, but I thought I'd start mentioning it.)
There's a particularly high chance of new bugs this time, so be ready for that.
Finally, another update. It's mostly internal changes and some bug fixes, but there's some new stuff.
This will break existing save files.
Another update, finally. Just some bug fixes.
- AutoGhost() now avoids using changers
- Fixed issues with diagonal movement in 8-way walking functions
- Corrected flashing CSets and timing
- The exploding and flashing death animation works correctly now
- SuspendGhostZHScripts() affects legacy Waitframe functions
Also, it appears I failed to post about the previous update here. Here are the changes from that one:
- AutoGhost no longer runs when ghost.zh is suspended
- Large enemies should no longer try to walk into walls when using four-way walking functions
- FindUnusedFFC is aware of GH_INVISIBLE_COMBO now
- The exploding death animation should no longer cause errors if the enemy fails to stay alive for the duration of the animation
- And it no longer stops the enemy from dropping an item