Jump to content

Photo

Basic Boss Scripts - Learning to Script

Basic

  • Please log in to reply
8 replies to this topic

#1 isilmo

isilmo

    Recipient of Ways

  • Members

Posted 09 August 2016 - 09:05 PM

Hello,

 

I am learning to script and while some videos have helped me get the basics, as I move towards enemies I figured some movement/eweapon basics would help. I am requesting the following:

 

1. Predictable Movement Boss, which moves down, then up, left and/or right similar to a ghoma. Perhaps with a random feature. For eWeapon something swinging and physical, like a sword or axe (sprite dependent).

 

2. Circle Moving Boss, stopping at random points (diamond if easier), with a stabbing eWeapon.

 

3. A 4-way moving boss with a 'breakable' shield, I am thinking 3-4 'phases' where after the player drains a benchmark of enemy hp it 'damages' the armor with each phase. Magic eWeapon.

 

Any additional info in advance would help as I will use these to try and figure out the script and logic.

 

Thank you in advance.

 

 



#2 Lejes

Lejes

    Seeker of Runes

  • Members
  • Location:Flying High Above Monsteropolis

Posted 10 August 2016 - 12:08 AM

Stabbing Weapons Are Much Harder To Make Than You'd Think


You might notice I'm using ghost.zh for all of these. It's a very useful header for making any type of custom enemy. You can find a tutorial I made for it here. Sorry about the complete lack of comments, I kind of busted these out quickly. Tell me if you need an example quest for how to set them up, or questions about how any of these scripts work.

EDIT: Added script comments.

Edited by Lejes, 10 August 2016 - 04:16 PM.

  • isilmo likes this

#3 isilmo

isilmo

    Recipient of Ways

  • Members

Posted 12 August 2016 - 08:53 PM

Thank you for this, it helps!



#4 isilmo

isilmo

    Recipient of Ways

  • Members

Posted 10 September 2016 - 07:08 PM

Good Evening.

 

I finally had some free time to mess around with this using the firebird tileset. I used the ffc script IsilmoPredictable script to try and make a Horsehead like boss. The issue I am seeing are background tiles.

 

As you can see from the top (green link, red horsehead) there is background tiles (starting with tile 260) and upon link's death or the boss's death, those tiles are visible (blue link below).I am not sure where the program is pulling the use of these tiles from. I used Saffith's Autoghost video for setup help.

 

 

example.png

 

For the script I have it as:

const int SPRITE_RIGHTSWORD = 104; // a right facing sword sprite, with no frames of animation

ffc script Horsehead
{
	// Enemy ID is an argument of the run() function, Autoghost will handle automatically. No need to provide specific number.
	void run(int enemyID)
	{
		// NPC pointer declaration and ghost init
		npc ghost = Ghost_InitAutoGhost(this, enemyID);
		// Increasing enemy size, important to use ghost variables when available. Hitbox handled automatically with tile changes.
		ghost->Extend = 1;
		//ghost->TileWidth = 2;
		//ghost->TileHeight = 3;
		Ghost_TileWidth = 2;
		Ghost_TileHeight = 3;
		// Various ghost flags exist, this one is set so walls don't get in the way of Gohma-like movement.
		Ghost_SetFlag(GHF_IGNORE_ALL_TERRAIN);
		// Set its starting position directly instead of relying on random spawn or enemy flags.
		Ghost_X = 112;
		Ghost_Y = 64;
		
		// Randomly choose left or right from the start.
		int dir = Choose(DIR_LEFT, DIR_RIGHT);
		// Variable to keep track of left/right, so it can resume after down/up walk cycle.
		int prev_dir;
		// Timer for down/up walk cycle.
		int timer_walkdown = 30;
		// Timer for sword attack.
		int timer_attack = Rand(40, 120);
		// Eweapons function like other variables, they have to be declared first.
		eweapon sword;
		
		while (true)
		{
			// Turn around four tiles from the edge.
			if (Ghost_X <= 64 && dir == DIR_LEFT)
			{
				dir = DIR_RIGHT;
			}
			// Same as going left, but enemy width is factored in since X/Y are always the enemy's top left corner.
			if (Ghost_X + (Ghost_TileWidth * 16) >= 192 && dir == DIR_RIGHT)
			{
				dir = DIR_LEFT;
			}
			// Start process of going down when this timer hits 0, and reset the timer to some random value.
			if (timer_walkdown <= 0)
			{
				prev_dir = dir;
				dir = DIR_DOWN;
				timer_walkdown = Rand(120, 240);
			}
			// Decrement timer.
			if (timer_walkdown > 0 && dir != DIR_DOWN && dir != DIR_UP)
			{
				timer_walkdown--;
			}
			// Start going back up four tiles from bottom screen edge.
			if (dir == DIR_DOWN && Ghost_Y + (Ghost_TileHeight * 16) >= 128)
			{
				dir = DIR_UP;
			}
			// Resume going left/right four tiles from screen top edge.
			if (dir == DIR_UP && Ghost_Y <= 64)
			{
				dir = prev_dir;
			}
			
			// Decrement attack timer.
			if (timer_attack)
			{
				timer_attack--;
			}
			// Big attack loop starts here.
			if (timer_attack <= 0)
			{
				// Wait a full second so player has time to react.
				for (int i = 0; i < 60; i++)
				{
					Ghost_Waitframe(this, ghost);
				}
				// Play a single sound effect for attack.
				Game->PlaySound(SFX_SWORD);
				// Using angle in for loop here, instead of generic "i" counter variable.
				for (int angle = 135; angle > 45; angle -= 3)
				{
					// Stabbing weapons are hard. A new sword is produced every frame, each with 1 frame lifespan. This makes unfortunate double sword graphical error, potentially fixed
					// by making actual weapon invisible and using DrawTile to draw sword instead.
					//Screen->DrawTile(int layer, int x, int y, int tile, int blockw, int blockh, int cset, int xscale, int yscale, int rx, int ry, int rangle, int flip, bool transparency, int opacity);
					// Actual sword is created here. Center of sprite - 8 will center a 16 by 16 weapon on an enemy, and the Cos and Sin factor in the rotation, at a radius of 16 pixels.
					// ZScript angles start pointing right, and go clockwise from there, hence the need for right facing sword to get properly rotated weapon graphic.
					// All ghost eweapon flags are entered as one argument, ORed together with the bar symbol "|".
					sword = FireEWeapon(EW_SCRIPT1, (Ghost_X + (0.5 * (Ghost_TileWidth * 16)) - 8) + (16 * Cos(angle)), (Ghost_Y + (0.5 * (Ghost_TileHeight * 16)) - 8) + (16 * Sin(angle)), DegtoRad(angle), 0, ghost->WeaponDamage, SPRITE_RIGHTSWORD, 0, EWF_UNBLOCKABLE | EWF_ROTATE_360);
					// Lifespan and death effects, see ghost.txt for these functions.
					SetEWeaponLifespan(sword, EWL_TIMER, 1);
					SetEWeaponDeathEffect(sword, EWD_VANISH, 0);
					// Need a waitframe here so the attack doesn't get crammed into 1 frame. Takes 30 frames with given loop parameters.
					Ghost_Waitframe(this, ghost);
				}
				// Set timer to attack again to a random value, between 3 and 6 seconds.
				timer_attack = Rand(40, 120);
			}
			
			// Actual movement function. Needs a pixels per frame step speed argument, so ZQuest step speed is divided by 100. Enemy stops moving when this function isn't called,
			// hence why enemy no longer moves during attack loop.
			Ghost_Move(dir, ghost->Step * 0.01, 2);
			// Ghost_Waitframe() is very important. CANNOT use normal Waitframe() in ghosted enemy scripts. Large number of variants, see ghost.txt.
			Ghost_Waitframe(this, ghost);
		}
	}
}

The quest file is at:

http://northeasternc.../files/Boss.qst

 

I am not sure what I am doing wrong. 



#5 Jamian

Jamian

    ZC enthusiast

  • Members

Posted 11 September 2016 - 05:16 AM

Did you set up ghost.zh?

 

It's a text file you can edit. A lot of graphical problems with ghost stem from the shadows, blank tiles, etc. not being set up properly there.



#6 isilmo

isilmo

    Recipient of Ways

  • Members

Posted 11 September 2016 - 12:58 PM

Did you set up ghost.zh?

 

It's a text file you can edit. A lot of graphical problems with ghost stem from the shadows, blank tiles, etc. not being set up properly there.

 

 

Not sure which file it could be pulling from. I searched for the string 'tile' for reference. My shadows seem correct.

// ghost.zh
// Version 2.8.0

// See ghost.txt for documentation.

// Standard settings -----------------------------------------------------------

// Small (1x1) shadow settings
const int GH_SHADOW_TILE = 28320;
const int GH_SHADOW_CSET = 2;
const int GH_SHADOW_FRAMES = 0;
const int GH_SHADOW_ANIM_SPEED = 0;
const int GH_SHADOW_TRANSLUCENT = 1; // 0 = No, 1 = Yes
const int GH_SHADOW_FLICKER = 0; // 0 = No, 1 = Yes

// Large (2x2) shadow settings
// If GH_LARGE_SHADOW_TILE is 0, large shadows will be disabled
const int GH_LARGE_SHADOW_TILE = 28874; // Top-left corner
const int GH_LARGE_SHADOW_CSET = 2;
const int GH_LARGE_SHADOW_FRAMES = 0;
const int GH_LARGE_SHADOW_ANIM_SPEED = 0;
const int GH_LARGE_SHADOW_MIN_WIDTH = 2;  // Enemies must be at least this wide
const int GH_LARGE_SHADOW_MIN_HEIGHT = 2; // and this high to use large shadows

// AutoGhost settings
const int AUTOGHOST_MIN_FFC = 1; // Min: 1, Max: 32
const int AUTOGHOST_MAX_FFC = 32; // Min: 1, Max: 32
const int AUTOGHOST_MIN_ENEMY_ID = 20; // Min: 20, Max: 511
const int AUTOGHOST_MAX_ENEMY_ID = 511; // Min: 20, Max: 511

// Other settings
const int GH_DRAW_OVER_THRESHOLD = 32;
const float GH_GRAVITY = 0.16;
const float GH_TERMINAL_VELOCITY = 3.2;
const int GH_SPAWN_SPRITE = 22; // Min: 0, Max: 255, Default: 22
const int GH_FAKE_Z = 0; // 0 = No, 1 = Yes
const int __GH_FAKE_EWEAPON_Z = 0; // 0 = No, 1 = Yes
const int GH_ENEMIES_FLICKER = 0; // 0 = No, 1 = Yes
const int GH_PREFER_GHOST_ZH_SHADOWS = 0; // 0 = No, 1 = Yes

// Top-left corner of a 4x4 block of blank tiles
const int GH_BLANK_TILE = 64480; // Min: 0, Max: 65456

// Invisible combo with no properties set
const int GH_INVISIBLE_COMBO = 2; // Min: 1, Max: 65279

// Always read script name and combo from the enemy's name,
// freeing up attributes 11 and 12
const int __GH_ALWAYS_USE_NAME = 0;

// End standard settings -------------------------------------------------------



// Advanced settings -----------------------------------------------------------

// AutoGhost will read a script name from the enemy's name if attribute 12
// is set to this. Must be a negative number.
const int AUTOGHOST_READ_NAME = -1;

// When reading a script from the enemy name, this character marks the
// beginning of the script name.
// Default: 64 ( @ )
const int AUTOGHOST_DELIMITER = 64;

// Misc. attribute 11 can be set to this instead of GH_INVISIBLE_COMBO.
// Must be a negative number.
const int __GH_INVISIBLE_ALT = -1;

// This will use the invisible combo, but also set npc->Extend to 3 or 4,
// hiding the initial spawn puff. Must be a negative number.
const int __GH_INVISIBLE_EXTEND = -2;

// If enabled, the FFC will be invisible, and Screen->DrawCombo will be used
// to display enemies.
const int __GH_USE_DRAWCOMBO = 1;

// Enemies flash or flicker for this many frames when hit. This does not
// affect enemies that use the invisible combo.
// Default: 32
const int __GH_FLASH_TIME = 32;

// Enemies will be knocked back for this many frames when hit.
// Default: 16
// Max: 4095
const int __GH_KNOCKBACK_TIME = 16;

// The speed at which enemies are knocked back, in pixels per frame.
// Default: 4
const int __GH_KNOCKBACK_STEP = 4;

// The imprecision setting used when a movement function is called internally
// (except for walking functions).
const int __GH_DEFAULT_IMPRECISION = 2;

// npc->Misc[] index
// Set this so it doesn't conflict with other scripts. Legal values are 0-15.
const int __GHI_NPC_DATA = 15;

// eweapon->Misc[] indices
// These must be unique numbers between 0 and 15.
const int __EWI_FLAGS          = 15; // Every index but this one can be used by non-ghost.zh EWeapons
const int __EWI_ID             = 3;
const int __EWI_XPOS           = 4;
const int __EWI_YPOS           = 5;
const int __EWI_WORK           = 6;
const int __EWI_WORK_2         = 7; // Only used by a few movement types
const int __EWI_MOVEMENT       = 8;
const int __EWI_MOVEMENT_ARG   = 9;
const int __EWI_MOVEMENT_ARG_2 = 10;
const int __EWI_LIFESPAN       = 11;
const int __EWI_LIFESPAN_ARG   = 12;
const int __EWI_ON_DEATH       = 13;
const int __EWI_ON_DEATH_ARG   = 14;

// These are only used by dummy EWeapons;
// they can use the same values as __EWI_XPOS and __EWI_YPOS
const int __EWI_DUMMY_SOUND    = 2;
const int __EWI_DUMMY_STEP     = 4;
const int __EWI_DUMMY_SPRITE   = 5;

// Returns true if the given combo should be considered a pit.
bool __IsPit(int combo)
{
    return IsPit(combo); // std.zh implementation by default
}

// Returns true if enemies are visible on screens with the
// "All Enemies Are Invisible" flag enabled
bool __HaveAmulet()
{
    return Link->Item[I_AMULET1] || Link->Item[I_AMULET2];
}

// End advanced settings -------------------------------------------------------


import "ghost_zh/2.8/common.zh"
import "ghost_zh/2.8/deprecated.zh"
import "ghost_zh/2.8/drawing.zh"
import "ghost_zh/2.8/eweapon.zh"
import "ghost_zh/2.8/eweaponDeath.zh"
import "ghost_zh/2.8/eweaponMovement.zh"
import "ghost_zh/2.8/flags.zh"
import "ghost_zh/2.8/global.zh"
import "ghost_zh/2.8/init.zh"
import "ghost_zh/2.8/modification.zh"
import "ghost_zh/2.8/movement.zh"
import "ghost_zh/2.8/other.zh"
import "ghost_zh/2.8/update.zh"

import "ghost_zh/2.8/scripts.z"



Edited by isilmo, 11 September 2016 - 12:59 PM.


#7 Saffith

Saffith

    IPv7 user

  • ZC Developers

Posted 11 September 2016 - 01:18 PM

Change the tile used by combo 2. It should be the top-left of a block of at least 4x4 blank tiles.
  • isilmo likes this

#8 isilmo

isilmo

    Recipient of Ways

  • Members

Posted 11 September 2016 - 07:22 PM

Change the tile used by combo 2. It should be the top-left of a block of at least 4x4 blank tiles.

That worked, though I am seeing a second combo(?) pop up upon death:

 

example2.png



#9 Saffith

Saffith

    IPv7 user

  • ZC Developers

Posted 13 September 2016 - 04:51 PM

I think that's because you set ghost->Extend to 1. That's fixed at 1x2. The "TH" is the end of the "DEATH" label above the sprite's tiles.
Extend is set automatically in initialization, so you shouldn't need to do anything with it. The hitbox behaves differently in that case, so you'll probably need to use Ghost_SetHitOffsets() to adjust it. Also, setting the tile sizes directly can reset the hitbox, so you should use Ghost_SetSize() instead.
  • isilmo likes this



Also tagged with one or more of these keywords: Basic

1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users