Read-only archive of the Armageddon Games Forums Archive (2000–2026) · About this archive

ghost.zh - A header file for ghosted enemy scripts

41 replies · 99,672 views · Started 18 January 2010

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.

You just made my day. Thanks Saffith.
[video=youtube;_J6-3l3hCm0]https://www.youtube.com/watch?v=_J6-3l3hCm0[/video]

edit: I use this meme way too much. XD
[MENTION=38582]Saffith[/MENTION]
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.

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.

[MENTION=38582]Saffith[/MENTION]

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?

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.

[C]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);
}
}[/C]

example quest

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!

[MENTION=38582]Saffith[/MENTION]
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.

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?

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.