PDA

View Full Version : ghost.zh - A header file for ghosted enemy scripts



Saffith
01-18-2010, 01:26 AM
Some functions for ghosted enemy scripts. Requires at least RC2.

ghost.zh and demo (http://www.purezc.net/index.php?page=scripts&id=246)
AutoGhost setup demo video (https://www.youtube.com/watch?v=CXVD2gIfE7w)

In the demo quest, the cheat codes are just 1, 2, 3, and 4.

Any bugs or suggestions, let me know.

pkmnfrk
01-18-2010, 02:32 AM
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 Link

The explosion on death travels slowly to the left... No idea why, since it clearly sets the explosion to step 0!

Saffith
01-18-2010, 08:16 AM
I think that one's a bug in ZC. Seems to do it no matter what.

Saffith
03-01-2010, 04:25 PM
Updated for builds 1174 and up. Handles stunning better now, and a couple of small bugs are fixed.

Saffith
04-15-2010, 12:15 AM
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.)

bigjoe
07-04-2010, 07:10 AM
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 ghosting

ffc 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

bigjoe
07-23-2010, 11:08 AM
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;

}

}

Saffith
11-21-2010, 06:00 PM
Updated. It needs build 1326+ now, so it's only for Linux and the debug build (http://armageddongames.net/showthread.php?45857-Let-s-do-some-debugging) 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 (http://www.youtube.com/watch?v=-QMv5woMiHw).

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.

Saffith
12-15-2010, 12:04 AM
So, I kind of screwed up the last update. The link was pointing to an older version. Sorry. Fixed now.

bigjoe
01-04-2011, 07:49 PM
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.

Saffith
01-05-2011, 03:51 PM
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?

bigjoe
01-07-2011, 08:31 PM
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.

Saffith
04-28-2011, 04:20 PM
Updated.

All ffc->Misc[] data has been moved to npc->Misc[]. Any scripts accessing these directly or using GotHit() will have to be updated.
npc->Misc[GHI_BASE_CSET] is no longer needed; npc->CSet is used instead.
Added SetCSet() and RevertCSet().
GhostInitWait2() has changed; the argument specifying which enemy to use is no longer needed. Instead, it finds the first one that's not already in use by another script. The old version of the function is deprecated, and the obsolete argument is ignored.
Added flags to make enemies ignore solid combos, water, and pits.
npc->TileWidth and npc->TileHeight are now set by the Init functions and Transform(), so shadows should be drawn in the right place in beta 18 and up.
Added DrawSpawnAnimation(), which lets you show the enemy spawning graphic that gets cut off by the Init functions.
If GH_BLANK_TILE is set and the enemy used has a frame rate of 0 (or 256, since ZC changes 0 to 256), the enemy's tile will be set to a clear one automatically. This way, you can give the enemy a visible tile so it shows up in the enemy panel in ZQuest's large mode.
Added an internal EWeapon flag that marks the weapon as having been spawned by ghost.zh. This allows other scripts to use all but one element of eweapon->Misc[] without causing confusion.
Reworked SpawnNPC() to increase the minimum distance from Link.
ghost_zh_readme.txt has been renamed ghost.txt to match other header files' documentation.

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.

Saffith
08-03-2011, 02:31 PM
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. :p

ghost.zh (http://saffith.homestead.com/files/zc/scripts/ghost_zh.zip)
Demo quest and sample scripts (http://www.mediafire.com/?ed3q7egd4x3cfd8)
AutoGhost enemies (http://saffith.homestead.com/files/zc/scripts/AutoGhost.zip)
AutoGhost setup demo video (http://www.youtube.com/watch?v=CXVD2gIfE7w)

Saffith
08-14-2011, 12:15 PM
Another update.
Added script versions of some of ZC's built-in movement functions
Added EWM_DRIFT and EWM_DRIFT_WAIT
The hitbox adjustments made by Ghost_SetHitOffsets() are now taken into account by Ghost_CanMove()

Edit: Quickly followed by a bugfix update. :p

Imzogelmo
08-20-2011, 04:49 PM
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.

Saffith
09-05-2011, 10:11 PM
Blargh. All right, shouldn't be hard to fix.

Saffith
02-28-2012, 09:25 PM
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?

Saffith
05-20-2012, 04:29 PM
Another update.


There's now built-in support for multi-FFC enemies like Iflyte.
It's now possible to use Screen->DrawCombo rather than making the FFC itself visible. There are three advantages to this:
Enemies will correctly be drawn above layers 2 and 4 rather than 1 and 3 (currently, FFCs do appear above layers 2 and 4, but that's a bug).
Enemies disappear when the screen is scrolling.
Multi-FFC enemies don't actually require multiple FFCs.
Enemy blocking combos and flags are now respected.
Added GHF_FLYING_ENEMY, which controls whether ground/jumping or flying enemy blockers are checked.
Ghost_CanMove and Ghost_CanMovePixel now take an optional argument indicating whether the enemy is in the air; this defaults to Ghost_Z>0.
Added flags to restrict an enemies movement to water, including or excluding shallow water.
Added a setting to make all Z-axis movement actually use the Y axis.
Added a setting to make enemies flicker instead of flash.
You can now use an alternate number (-1 by default) instead of GH_INVISIBLE_COMBO to make enemies use the invisible combo.
GHF_ flags can no longer be ORed together; they must be set individually. Unfortunate, but it can't be helped; I ran out of bits.
Ghost_SetFlags and the init functions with a flags argument are deprecated.
Ghost_WaitframeLight now takes ffc and npc arguments. The old version is deprecated and may not work correctly with existing scripts if DrawCombo is enabled.

Saffith
12-09-2012, 07:16 PM
Updated.

For the sake of maintainability, ghost.zh has been split into multiple files. You still only need to import ghost.zh itself; it imports all the others.
Drawing has been moved to the global script, so enemies won't disappear when the screen freezes.
The multi-FFC functions have been replaced. I kind of hate to do that, but they sucked, and no one was using them, anyway.
Directions 8-15 should be handled correctly now.
Ghost_Explode has been replaced by Ghost_DeathAnimation. This will allow scripts in which the animation is determined by a misc. attribute to support new animations as they're added.
Added functions to suspend and resume ghost.zh scripts.
Added functions to find random spawn points.
Added functions to simplify reading attributes.
Shadows can now be animated.
The "Is Flashing" and "Is Flickering" enemy flags work now.


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.

XMuppetSB
12-09-2012, 10:10 PM
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?

Saffith
12-09-2012, 10:17 PM
They should all still work. I do plan to update them soon anyway, though.

XMuppetSB
12-09-2012, 10:21 PM
Thing is, though, some of them have the old ghost_explode variable, which has been replaced with ghost_deathanimation.

Saffith
12-09-2012, 10:42 PM
It hasn't been removed, it's just deprecated.

Edit: Sorry. Left a bug in one of the FindSpawnPoint functions. Fixed now.

Saffith
12-21-2012, 12:22 PM
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.

Imzogelmo
12-21-2012, 04:16 PM
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.

Saffith
12-21-2012, 10:26 PM
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. :p

Saffith
02-27-2013, 12:33 AM
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.

It's finally possible to read and change ghosted enemies' properties from other scripts using the functions GetEnemyProperty() and SetEnemyProperty().
SetEWeaponMovement() now takes another argument. Some similar weapon movement types have been merged with the differing functionality now controlled by the second argument.
The movement functions have been rewritten to be more accurate and faster.
The FireEWeapon functions can now take -1 for the sprite and sound arguments to use defaults for the weapon type.
2x2 shadows are now used for enemies 3x3 and larger. (Only applies if GH_FAKE_Z or GHF_FAKE_Z is used)
Added GHF_STATIC_SHADOW for enemies whose shadows should never animate. (Only applies if GH_FAKE_Z or GHF_FAKE_Z is used)

Saffith
11-21-2013, 12:14 PM
Finally, another update. It's mostly internal changes and some bug fixes, but there's some new stuff.
This will break existing save files.

Added Ghost_FloaterWalk(), the movement type used by peahats and keese
Added EWF_ROTATE_360
Added settings to change how big an enemy must be to use large shadows
Added an option to use scripted shadows rather than built-in ones when possible
Added new Ghost_Waitframe() variants to simplify usage; you can now leave out quitOnDeath and clearOnDeath (they'll both be true) or specify a death animation
The global script functions have been consolidated into three; you'll need to update the global script, but it should never be necessary for future updates

Saffith
08-20-2014, 11:57 AM
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

Saffith
10-06-2015, 01:25 PM
Hey, I'm actually updating it. How about that.

The biggest change this time is the __GH_ALWAYS_USE_NAME setting. If it's enabled, AutoGhost() will always read an enemy's name to get the script and combo number. Misc. attributes 11 and 12 will be free for other scripts to use.
The script and combo number both go after @ in the name. Order and spacing aren't important, and other characters are allowed afterward. Either of these will work:
Goriya (LttP, Green) [@Goriya_LttP, @25804]
Goriya (LttP, Green) @25804@Goriya_LttP
You have to provide the script name, but if you don't specify a combo, it'll default to GH_INVISIBLE_COMBO. This will work fine:
Armos @Armos_LttP

The downside to this is that enemy scripts can no longer assume misc. attribute 11 specifies the base combo. I don't think that was a common thing to do, anyway.

Other notable changes:
The files in the ghost_zh directory have been moved to a numbered subdirectory. This will allow multiple versions to coexist.
Ghost_TileWidth and Ghost_TileHeight should now be considered read-only. Ghost_SetSize() should be used to set them.
You can now control what constitutes a pit by editing the __IsPit() function in the main file.
There's also a __HaveAmulet() function, used to determine if enemies are visible when the "All Enemies Are Invisible" screen flag is used.
Weapons can now have fake Z movement, both individually and globally.
Added a new death animation, as well as constants to use as arguments to Ghost_DeathAnimation() and Ghost_Waitframe().

There haven't been any changes to global data, so this version should be compatible with existing saves using 2.7.x. Still, be careful.

Tamamo
10-06-2015, 01:48 PM
You just made my day. Thanks Saffith.

https://www.youtube.com/watch?v=_J6-3l3hCm0

edit: I use this meme way too much. XD
Saffith
I am laughing so hard right now. Burt the Bashful anyone?

* GHD_SHRINK
* The enemy grows slightly and then shrinks away to nothing. This does not
* work with enemies that use additional combos.

ZoriaRPG
10-12-2015, 04:24 PM
Nice one. Are you adding a backward-compatibility setting, as a constant for this?



const int GHOST_USE_ENEMY_NAMES_FOR_SCRIPTS = 1;

if ( GHOST_USE_ENEMY_NAMES_FOR_SCRIPTS ) gohst_use_at_sign_routines();
else ghost_use_misc11_misc12();


Something like that would be good. Just set the constant in the main header file, and use it as needed. That will prevent conflicts with games that are built on the old system.

Saffith
10-13-2015, 10:37 AM
__GH_ALWAYS_USE_NAME. It's disabled by default.

ZoriaRPG
10-14-2015, 03:37 PM
__GH_ALWAYS_USE_NAME. It's disabled by default.

Gorgeous. :D

Tamamo
10-19-2015, 01:54 PM
Saffith

Have an issue with sideview shenanigans "Sideview Gravity ugh..."
So just ghost.zh take care of it on its' own as far Ghost_Z is concerned?

Saffith
10-19-2015, 05:55 PM
What problem are you having?

Hm, I thought Ghost_Z affected ghost->Y in sideview, but it looks like it's not as consistent as it should be. It's likely to be a bit messy at best, but I could probably improve on it.

Tamamo
10-19-2015, 08:04 PM
That may be my problem then.
I'm setting Ghost_Y to 160 each frame. "It's Barba"

Tamamo
10-27-2015, 03:30 PM
const int BARBA_QUAKE_TIME = 80; //Time in frames to shake the screen for.
const int BARBA_QUAKE_SFX = 61; //This should loop on it's own. LTTP has a good one. DO NOT SET TO BARBA'S DEFAULT ROAR SFX! IT WILL BREAK EVERYTHING!
const int BARBA_WATER_OKAY = 1; //Whether he will surface from water.
const int BARBA_PITS_OKAY = 1; //Whether he will emerge from pits.

ffc script Barba
{
void run(int enemyID)
{
// Init
npc head = Ghost_InitAutoGhost(this,enemyID);
npc body = Screen->CreateNPC(enemyID);
head->DrawXOffset+=1000;
body->DrawXOffset+=1000;
Ghost_MarkAsInUse(body);
Ghost_SetAllDefenses(body, NPCDT_IGNORE);

// Position, Resize, and store Ghost_Data
Ghost_X=256; Ghost_Y=176; //Spawn off screen;
Ghost_Transform(this,head,-1,-1,2,1);
int baseCombo=Ghost_Data;

// Flags
Ghost_SetFlag(GHF_4WAY);
Ghost_SetFlag(GHF_NO_FALL);

// Movement
int movestate;
int counter;
int basePos; //Combo
int ZVelocity=Ghost_GetAttribute(head,0,160)/100;
int segmentCount=Ghost_GetAttribute(head,1,3,1,6);
int segmentLag=Ghost_GetAttribute(head,2,10);
int segmentCombo=Ghost_Data+12;
int xoffsets[] = {-1,0,1,0,-1,0,1,0};
// int movehistory[128]; //removed as their is no use now.

// Attacking
int attackTimer;
int wDamage=head->WeaponDamage;
int wSprite=Ghost_GetAttribute(head,3,-1);
int wSound=Ghost_GetAttribute(head,4,-1);

// Misc
int dSound=head->SFX;
int frame;
int defenses[18];
Ghost_StoreDefenses(head, defenses);

// Barba Time!
do
{
if(movestate==0) //Quake
{
if(head->SFX!=BARBA_QUAKE_SFX) BarbaQuake(head);
else if(Screen->Quake==0)
{
movestate=1;
counter=0;
head->SFX=dSound;
Ghost_SetDefenses(head,defenses);
basePos = FindSpawnPoint(false,false,BARBA_WATER_OKAY,BARBA_ PITS_OKAY);
BarbaHitOffsets(head);
Ghost_X=ComboX(basePos)-8;
Ghost_Y=ComboY(basePos);
}
}
else if(movestate==1) //Surfacing
{
Ghost_Data=baseCombo;
counter++;
Ghost_Z+=ZVelocity;
if(counter==segmentCount*segmentLag)
{
Ghost_Z=(segmentCount*16);
movestate=2;
counter=0;
attackTimer=0;
}
SegmentDrawing(head, basePos, counter, segmentLag, segmentCombo, xoffsets);
}
else if(movestate==2) //Attacking
{
Ghost_Data=baseCombo+4;
if(counter==32)
{

Ghost_Data=baseCombo+8;
if(attackTimer==0)
attackTimer=Rand(50,99);
if(attackTimer%8==0)
{
float x=BarbaMouthX();
float y=head->Y+InFrontY(Ghost_Dir,3);
eweapon e=FireAimedEWeapon(EW_FIREBALL, x, y, 0, 300, wDamage,wSprite,wSound,NULL); //|EWF_UNBLOCKABLE); No... and never again rapid fire unblockable is a bad idea.
}
attackTimer--;
}
else if(counter==48)
{
movestate=3;
counter=segmentCount*segmentLag;
}
if(attackTimer==0)
counter++;
SegmentDrawing(head, basePos, segmentCount*segmentLag, segmentLag, segmentCombo, xoffsets);
}
else if(movestate==3) //Submerging
{
Ghost_Data=baseCombo;
counter--;
Ghost_Z-=ZVelocity;
if(counter<=0)
{
movestate=0;
counter=0;
Ghost_Z=0;
Ghost_X=256+Ghost_TileWidth;
}
SegmentDrawing(head, basePos, counter, segmentLag, segmentCombo, xoffsets);
}
frame++;
if(frame%8==0)
{
//pushfront and loop front to back
int front = xoffsets[0];
memmove(xoffsets, 0, xoffsets, 1, 7);
xoffsets[7]=front;
frame=0;
}
BarbaWaitframe(this,head,body,basePos);
} while(Ghost_HP>0)
if(movestate==2)
counter=segmentCount*segmentLag;
BarbaExplode(this, head, body, counter, segmentLag, segmentCombo, xoffsets, basePos);
}
void BarbaQuake(npc head)
{
Screen->Quake=BARBA_QUAKE_TIME;
head->SFX=BARBA_QUAKE_SFX;
Ghost_SetAllDefenses(head,NPCDT_IGNORE);
}

int BarbaMouthX()
{
int aimx=Ghost_X+8;
return aimx+InFrontX(Ghost_Dir, 3);
}
void BarbaHitOffsets(npc head)
{
if(Ghost_Dir==DIR_LEFT)
Ghost_SetHitOffsets(head,0,0,0,.25);
else if(Ghost_Dir==DIR_RIGHT)
Ghost_SetHitOffsets(head,0,0,.25,0);
else // if(Ghost_Dir==DIR_UP || Ghost_Dir==DIR_DOWN);
Ghost_SetHitOffsets(head,0,0,.25,.25);
}
void BarbaWaitframe(ffc this, npc head, npc body, int basePos)
{
body->X=ComboX(basePos);
if(IsSideview())
{
body->Y=160;
body->Jump=0; //keep it from falling do to sideview gravity
if(Ghost_X+8>=Link->X) Ghost_Dir=DIR_LEFT;
else Ghost_Dir=DIR_RIGHT;
}
else
{
body->Y=ComboY(basePos);
Ghost_Dir=RadianAngleDir4(ArcTan(Link->X-(Ghost_X+8),Link->Y-Ghost_Y));
}
body->HitZHeight=Ghost_Z;
Ghost_Waitframe(this,head,false,false);
}
void BarbaExplode(ffc this, npc head, npc body, int counter, int segmentLag, int segmentCombo, int xoffsets, int basePos) //Custom Death cause why not? o_O
{
//Miscellaneous Crap that has to be done first.
body->ItemSet=IS_NONE;
body->HP=HP_SILENT;
body->X=1024;
__DeathAnimStart(this, head);
__DeathAnimSFX(head->ID, head->X);
__Ghost_FlashCounter=10000; //Should last long enough.
int baseX=ComboX(basePos);
int baseY=ComboY(basePos);
int segmentCount = Div(counter,segmentLag);
int layer=3; int x; int y; int x2; int y2;
int timer;
int j=0;
if(!IsSideview())
{
head->DrawXOffset-=1000;
Ghost_Y-=Ghost_Z;
Ghost_Z=0;
}
lweapon explosion;

while(j<segmentCount)
{

for(int i=j; i < segmentCount; i++)
{
x = baseX+xoffsets[i];
y = Cond(IsSideview(),160,baseY)-16*i;
y -= Ghost_Y%16;
if(i==j)
{
x2=x;
y2=y;
}
Screen->FastCombo(layer,x,y-2,segmentCombo+Ghost_Dir,Ghost_CSet,128);
}
timer++;
if(timer%16==0)
{
explosion=Screen->CreateLWeapon(LW_BOMBBLAST);
explosion->X=x2;
explosion->Y=y2;
explosion->CollDetection=false;
j++;
}
Ghost_SetPosition(this, head); // Make sure it doesn't wander off
__Ghost_UpdateFlashing(this, head);
Ghost_WaitframeLight(this,head);
}

baseX=Ghost_X+head->DrawXOffset;
baseY=Ghost_Y+head->DrawYOffset;

// One explosion every 16 frames, 15 times
for(int i=0; i<15; i++)
{
explosion=Screen->CreateLWeapon(LW_BOMBBLAST);
explosion->X=baseX+Rand(16*Ghost_TileWidth)-8;
explosion->Y=baseY+Rand(16*Ghost_TileHeight)-8;
explosion->CollDetection=false;

for(int j=0; j<16; j++)
{
Ghost_SetPosition(this, head); // Make sure it doesn't wander off
__Ghost_UpdateFlashing(this, head);
Ghost_WaitframeLight(this, head);
}
}

__DeathAnimEnd(this, head);
}
void SegmentDrawing(npc head, int basePos, int counter, int segmentLag, int segmentCombo, int xoffsets)
{
int baseX=ComboX(basePos);
int baseY=Cond(IsSideview(),160,ComboY(basePos));
int segmentCount = Div(counter,segmentLag);
int layer=2;
int x; int y;
for(int i; i < segmentCount; i++)
{
x = baseX+xoffsets[i];
y = baseY-(16*i);
if(!IsSideview() && (i*16) >= GH_DRAW_OVER_THRESHOLD) //additional segments have a drawover threshold.
{
layer=4;
}
y-=Ghost_Z%16;
Screen->FastCombo(layer,x,y-2,segmentCombo+Ghost_Dir,Ghost_CSet,128);
}
int w=1;
if(Ghost_Dir==DIR_LEFT||Ghost_Dir==DIR_RIGHT) w=2;
x-=8;
y=baseY-Ghost_Z;
layer=Cond(Ghost_Z>=GH_DRAW_OVER_THRESHOLD,4,2);
Screen->DrawCombo(4,x,y-2,Ghost_Data+Ghost_Dir,w,1,Ghost_CSet,-1,-1,0,0,0,0,0,true,128);
}
}

example quest (https://www.dropbox.com/s/hh5mvsxnfcfn8wz/Barba.qst?dl=0)

Lot of issues here, best to turn on collision hitbox cheat and see for yourself what's happening. Should reproduce the battle in sideview perfectly, but it does not.

edit: Fixed!

Tamamo
11-12-2015, 02:27 PM
Saffith
I have a few suggestions.

GHF_NO_SHADOW
Unless there's a way to disable the shadow that I don't know of.

EWM_VEER_ANGLE
EWM_DRIFT_ANGLE
EWM_DRIFT_WAIT_ANGLE
Same as normal except they move towards an angle.

EWD_FIRETRAIL.
For the pyro in all of us.

Tamamo
11-29-2015, 10:57 AM
I have a question this time.


bool Ghost_WasFrozen()
* Returns true if the enemy was stunned or frozen by a clock in the last frame.
* This only works if GHF_STUN or GHF_CLOCK is used.

Ghost_Waitframe() does not return if these are in use, does that mean these are only checked when the stun/freezing wears off?

Saffith
09-16-2016, 02:24 PM
Sorry, totally missed the question before. Yes, Ghost_WasFrozen() tells you if the enemy just recovered from being stunned.


Updated, and now in the database: http://www.purezc.net/index.php?page=scripts&id=246

* Fixed a bug in positioning enemies after knockback in Ghost_HaltingWalk4() and Ghost_ConstantWalk4().
* Fixed an error in validating spawn locations in FindSpawnPoint().
* Fixed some weird behavior in weapons with EWD_AIM_AT_LINK spinning while waiting to aim.
* EWM_THROW with an argument of -1 (land at Link's position) is now much more accurate at long distances.
* Added ghostZHChangelog.txt.