Jump to content

Photo

ZScript - Beginner/Intermediate Weapons Tutorial

Tutorial ZScript weapons weapon scripting script code coding beginner intermediate

  • Please log in to reply
4 replies to this topic

#1 Soga

Soga

    Secretly Alive

  • Members

Posted 21 May 2013 - 01:36 PM

Introduction

 

Weapons are a fundamental aspect of the Zelda Classic engine. Weapons drive the game's many interactions between Link and enemies. When you shoot an arrow, drop a bomb, cast the Wand's magic, or throw a boomerang, weapons are created which are graphically portrayed by the arrow tile, the bomb tile, the Wand Magic tile, and the boomerang tile. Likewise, when an enemy launches a projectile at you, that projectile's tile represents a weapon.

 

There are two types of weapons: Link Weapons, named LWeapons in ZScript, and Enemy Weapons, named EWeapons in ZScript. As the names suggest, weapons that come from Link and harm enemies are LWeapons. In contrast, the weapons that enemies use to harm Link are EWeapons. The difference lies in who the weapon collides with, knocks back, and deals damage to. A LWeapon will never collide with Link, but will collide with NPCs (enemies and Guys). An EWeapon, conversely, will never collide with NPCs but will collide with Link.

 

The different types of weapons can be referenced in ZScript and are found in std_constants.zh. The weapons are prefixed with LW_ and EW_, which stand for Link Weapon and Enemy Weapon, respectively.

 

FisherPrice: My First Weapons

 

Weapons can be spawned using ZScript and destroyed at will.

 

Weapons are used by pointers in ZScript. Pointers are beyond the level of this tutorial, so we won't get into that. But in ZScript, weapons are treated in a similar manner as Link, Screen, Game, FFC, and npc. That means that weapons are neither numbers nor true/false bool values. Weapons are treated as whole objects with a whole set of data that tells the game all about them, such as what tile to draw for them, what CSet to draw the tile using, the damage the weapon does if it hits, how fast it moves, how long it stays on the screen, and so on forth.

 

There are several ways in which weapons can be used with ZScript. The first is creating weapons out of thin air. The second way to manipulate weapons is to load a weapon that exists on the current screen. In either approaches, you must assign the created or loaded weapon to a weapon variable. Examples below:

weapon foo = Screen->CreateLWeapon(LW_ARROW); // Create an arrow friendly to Link and assign that arrow weapon to the weapon variable named "foo"
weapon bar = Screen->CreateEWeapon(EW_ROCK); // Create an Octorok rock projectile that will harm Link and assign it to the weapon variable named "bar"
 
weapon joe = Screen->LoadLWeapon(1); // Load the first Link Weapon on the screen and assign it to the weapon variable named "joe"
weapon bob = Screen->LoadEWeapon(1); // Load the first Enemy Weapon on the screen and assign it to the weapon variable named "bob"

If you create a weapon based on one of the default weapon types (see std_constants.zh) such as LW_ARROW, LW_BOMB, EW_BRANG, or EW_MAGIC, the game automatically generates the weapon and provides the weapon with all the attributes based on the type. In other words, the weapon gets Tile, CSet, Damage, Step (speed), Deadstate (lifespan in frames), hitbox offsets and sizes, and so on forth. An arrow, for instance, has different values for those attributes from those of a Wand magic beam. Those attributes can be viewed under the Weapon category in the zscript.txt file that comes packed with Zelda Classic.

 

You may have been looking at the example code above and wondering what the number in LoadLWeapon(1) and LoadEWeapon(1) were all about. That loads a weapon on the screen according to an identifier number for them based on the number of weapons on the screen. For example, if there was only one weapon on the screen, then the only weapon will be at index 1 - hence the 1 in the code above.

 

Birds of A Feather (or weapons of a... feather?)

 

In general, when you want to manipulate weapons that already exist on the screen (and were not necessarily created by script), you don't want to stick to one number when loading a weapon from the screen. For all you know, that weapon may be invalid in the next frame after you've done a Waitframe(). Weapon variables are only guaranteed to be valid in the SAME frame that they were assigned. In the next frame, anything could have happened. The weapon could have hit something and disappeared. It might have "timed out" (think candle flame stopping), or it might have been a boomerang returning to you. The bottom line is: when you write ZScript code, you can't just trust that dynamic things like weapons stay unchanged. So you have to be able to account for that dynamic nature of weapons. A nice way to do just that in the next frame is to check if it is still valid. Example:

lweapon coolWeapon = Screen->CreateLWeapon(LW_BOMBBLAST); // Create a bomb blast.
Waitframe(); // Wait a frame.
if (coolWeapon->isValid()) // Test if it is valid.
{
    // The weapon is still valid. Let's move it to the right one pixel.
    coolWeapon->X = coolWeapon->X + 1;
}

NOTE: When creating a weapon, don't forget to set its position! Created weapons will automatically start at the top left of the screen (screen position [0, 0]). So when you first create a weapon, it's a good idea to set its position to an appropriate position by tweaking its X and Y values (weapon->X and weapon->Y, for example).

 

Furthermore, it's simply bad practice to just load a weapon using whatever number you feel like using. You can't just use Screen->LoadLWeapon(1) every time and expect it to work the exact same way every time. In general, you want to scan all weapons on the screen by looping over weapons from 1 to Screen->NumLWeapons() or Screen->NumEWeapons(), depending on if you're processing Link Weapons or Enemy Weapons. You check the type (ID) of the weapon to make sure it is the weapon you, the scripter, are thinking of.

 

Let's do an exercise. Let's say you want to process all weapons of a certain type (e.g. arrow, bomb, magic, etc.) on the screen. You will want to scan all weapons on the screen and check if the current weapon you're looking at is the right type (the weapon has the right ID), then you do your thing to it. Let's take a look at an example where we scan all weapons for wand magic projectiles and do things to them:

lweapon tempWeapon; // Declare a lweapon variable.
// Loop over all weapons on the screen to find magic projectiles.
for (int i=1; i <= Screen->NumLWeapons(); i++)
{
    tempWeapon = Screen->LoadLWeapon(i); // Load the next weapon.
    if (tempWeapon->ID == LW_MAGIC) // Check if the weapon is a wand magic projectile.
    {
        // Do things to the magic projectile in here.
    }
}

Special Weapons

 

There are special types of weapons that have their own set of rules for usage in ZScript. There are certain weapons that cannot be created in script (but can still be loaded). They are the sword, the wand itself (not the magic projectile), the candle itself (not the fire that it shoots out), the hammer, the hookshot (only the "head", not the chain), and the Cane of Byrna. If you examine the std_constants.zh file, the LW_ constants for those have comments indicating that they cannot be created via ZScript. But, I remind you. They can still be loaded and manipulated. However, expect very odd behavior if you attempt to tamper with those weapons. They do not work like the other kinds of weapons at all. They have more hard-coded behaviors that are part of the game engine. There's simply nothing you can do about that.

 

Then there are dummy weapon types that exist solely for scripting purposes (LW_SCRIPT1 through LW_SCRIPT10 and EW_SCRIPT1 through EW_SCRIPT10). When created, those weapons are mostly uninitialized. That means the game doesn't automatically fill out the attributes for that weapon like the other predefined weapon types. You have to do it yourself. That means you want to provide the following at a minimum, referring to the zscript.txt file for documentation:

 

  • X - The X position of the weapon.
  • Y - The Y position of the weapon.
  • Dir - The direction of movement used by the weapon.
  • Tile - The tile to use to graphically represent the weapon on the screen.
  • CSet - The CSet to use. This defaults to 0.
  • Damage - The damage the weapon does.
  • Step - The speed of the weapon's movement, where a Step of 100 is one pixel per frame.
  • DeadState - How long the weapon stays active and if the weapon is controlled by the game engine or by you (read the comment about it in zscript.txt).
  • Flip - The graphical flip of the weapon tile (0 = no flip, 1 = horizontal flip, 2 = vertical flip, 3 = both, AKA 180 degrees rotation)
  • TileWidth - How many tiles wide the weapon graphically is (starting from the left).
  • TileHeight - How many tiles high the weapon graphically is (starting from the top).
  • HitWidth - The hitbox width of the weapon (starting from the left).
  • HitHeight - The hitbox height of the weapon (starting from the top).

And, voila! You have a scripted weapon that can be used like an actual weapon. Just remember, a LW_ weapon hits enemies (unless it's a bomb blast or candle flame, assuming the rules for those harming Link are enabled). An EW_ weapon hits Link.

 

There is also more that can be done with weapons, but those would be better reserved as a topic for more advanced scripting techniques.


Edited by Soga, 24 May 2013 - 12:32 AM.

  • ShadowTiger and Gleeok like this

#2 makarthekorok

makarthekorok

    the korokish korok

  • Members

Posted 23 May 2013 - 07:51 PM

Actually what about weapon classess is it possible to have one script affect all weapons in a class if so how, or do I have to make a script for each one ?


Edited by makarthekorok, 23 May 2013 - 07:52 PM.


#3 Russ

Russ

    Caelan, the Encouraging

  • Administrators
  • Location:Washington

Posted 23 May 2013 - 09:30 PM



Actually what about weapon classess is it possible to have one script affect all weapons in a class if so how, or do I have to make a script for each one ?

Yes, and in fact, it's super easy.  Here's the code that Soga used in the tutorial.

lweapon tempWeapon; // Declare a lweapon variable.
// Loop over all weapons on the screen to find magic projectiles.
for (int i=1; i <= Screen->NumLWeapons(); i++)
{
    tempWeapon = Screen->LoadLWeapon(i); // Load the next weapon.
    if (tempWeapon->ID == LW_MAGIC) // Check if the weapon is a wand magic projectile.
    {
        // Do things to the magic projectile in here.
    }
}

While you could, in theory, be trying to just grab one weapon with this code, it will actually affect EVERY Magic LWeapon on screen. Now, unless you scripted a new wand or something like that, there can only ever be one Magic LWeapon on screen at a time, because of the wand's limited rate of fire.  However, if you just tweak the script a bit...

eweapon tempWeapon; // Declare a eweapon variable.
// Loop over all weapons on the screen to find magic projectiles.
for (int i=1; i <= Screen->NumEWeapons(); i++)
{
    tempWeapon = Screen->LoadEWeapon(i); // Load the next weapon.
    if (tempWeapon->ID == EW_MAGIC) // Check if the weapon is a enemy magic projectile.
    {
        // Do things to the magic projectile in here.
    }
}

Now, instead of finding Link's magic, the script will look for Wizzrobe's magic attacks (or any custom enemy that uses a magic weapon). If you were to tell the script to, I dunno, replace the eweapon with a bomb, every single magic attack on the screen would turn into a bomb. Of course, we can take it even a step further.

eweapon tempWeapon; // Declare a eweapon variable.
// Loop over all weapons on the screen to find magic projectiles.
for (int i=1; i <= Screen->NumEWeapons(); i++)
{
    tempWeapon = Screen->LoadEWeapon(i); // Load the next weapon.
        // Do things to the magic projectile in here.
}

Now, the code will affect EVERY single eweapon on the screen. Wizzrobe magic, Octorock rocks, Moblin spears, everything. If you wanted to be more selective with the weapons you're grabbing, there are ways to do that, but I'm guessing Soga's planning that for his advanced tutorial, and I certainly don't want to steal his thunder.

 

Edit: Actually Soga...

 



A LWeapon will never collide with Link, but will collide with NPCs (enemies and Guys).

 

This isn't entirely true. Depending on what quest rules you have checked, Bomb and Fire LWeapons can hurt Link (since he can be hurt by using the built in bomb and candle).



#4 Gleeok

Gleeok

    It's dangerous to dough alone, bake this.

  • Members
  • Real Name:Pillsbury
  • Location:Magical Land of Dough

Posted 24 May 2013 - 12:19 AM

Great stuff Soga. Thanks for doing this. :) For the intermediate section I think some simple working examples would go a long way. For example starting off with something easy but cool like simulating a firerobe attack. (At least I always liked functional examples when I was learning things)

#5 Soga

Soga

    Secretly Alive

  • Members

Posted 24 May 2013 - 12:31 AM

This isn't entirely true. Depending on what quest rules you have checked, Bomb and Fire LWeapons can hurt Link (since he can be hurt by using the built in bomb and candle).

 

Thanks for the correction. I'll edit that now.

 

 

 

Great stuff Soga. Thanks for doing this. :) For the intermediate section I think some simple working examples would go a long way. For example starting off with something easy but cool like simulating a firerobe attack. (At least I always liked functional examples when I was learning things)

 

Thanks, Gleeok! I agree, examples are a very great way to learn new things.


Edited by Soga, 24 May 2013 - 12:33 AM.




Also tagged with one or more of these keywords: Tutorial, ZScript, weapons, weapon, scripting, script, code, coding, beginner, intermediate

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users