PDA

View Full Version : LWeapons and EWeapons



CJC
04-09-2009, 07:26 PM
I've been doing a lot of scripting lately, but I've hit a wall. I don't really understand how to code custom enemies from scratch, and neither do I know how to code custom weapons.


So here's the request. I need help coding a basic weapon (This thread) and a basic enemy (To come later) so that I can use that as a frame-of-reference to make other weapons and enemies.

For the weapons, I'd like to avoid using FFCs... if possible.


If you have the time, could you please:

1.) Code an Lweapon that persists so long as Link holds the button, moves with him, knocks back enemies it touches, and inflicts 1 heart damage?

2.)Code an Lweapon that extends like the hookshot, but only for two links, and then retracts.

2.) Code an Eweapon that extends two tiles in front of the enemy when it is called?



Any help at all is well appreciated. Thank you in advance.


P.S.: Please either heavily annotate your codes or use variables that are not one letter long.... so you can 'teach me how to fish', so to speak.

Joe123
04-10-2009, 07:50 AM
const int T_HOLDOUTWEAPON = 0; //Here we set the tile for the weapon. There should be 4 tiles on the tilesheet for it, arranged as the up sprite, then down, then left, then right

//We're going to use these two functions to check whether Link has the weapon equipped on A or B
//I'll let you work them out for yourself; if you don't want to you just have to use them, no need to understand them really
int StoreInput;
//Store whether Link pressed A or B to use an item
void StoreInput(){
if(Link->InputA && !Link->InputB) StoreInput = 1;
else if(Link->InputB && !Link->InputA) StoreInput = 2;
else if(Link->InputB && Link->InputA) StoreInput = 3;
else StoreInput = 0;
}
//Returns true if Link is holding down the button input we stored earlier
bool CheckInputs(){
return ((StoreInput == 1 && Link->InputA) || (StoreInput == 2 && Link->InputB)
|| (StoreInput == 3 && Link->InputA && Link->InputB));
}

bool HoldOutWeapon; //We use this to link to the global script

//I'm doing it as an item. If you don't like that, tough.
item script HoldOutLWeapon{
void run(){
HoldOutWeapon = true; //Tell the global script we've used the weapon
StoreInputs(); //Store which button Link has the weapon equipped on
}
}

//We're gonna need to link to the global script cause our item scripts run for only one frame
global script Slot2{
void run(){
HoldOutWeapon = false; //Make sure the weapon doesn't start when the game starts
while(true){
HoldOutWeapon(); //Call our function
Waitframe();
}
}
//The function to handle our weapon
void HoldOutLWeapon(){
if(!HoldOutWeapon || !CheckInputs()){//If we're not using the weapon, or if Link isn't holding down the button anymore, remove the weapon and return the function so the rest of the code doesn't run.
lweapon Remove = FindHeldWeapon(); //Load our weapon
if(Remove->IsValid()){ //Remember, the weapon might not actually be on the screen anyway, so in that case we don't need to get rid of it
Remove->DeadState = WDS_DEAD; //Remove it from the screen
}
return; //return the function so the rest of the code doesn't run
}

//Now for the part of the code that actually handles the weapon
lweapon HoldOut = FindHeldWeapon(); //First we check if we already made a weapon
if(!HoldOut->IsValid()){ //If we haven't made one yet, or if our weapon hit an enemy, we'll need to make a new one
HoldOut = Screen->CreateLWeapon(LW_SCRIPT1); //Actually make the weapon. Remember, when a weapon hits an enemy it's removed from the screen
HoldOut->Misc[0] = 1; //Here we mark our weapon so that we know later that it's the one we made, rather than another weapon
}
//We're going to use these variables in working out where the weapon should be positioned
int x; int y;
//I'll let you work out what this bit does, but basically it's my method for making sure the weapon is infront of Link
if(Link->Dir == DIR_UP || Link->Dir == DIR_DOWN) y = Link->Dir*32-16;
else x = (Link->Dir-2)*32-16;
HoldOut->X = Link->X+x; //Place the weapon infront of Link
HoldOut->Y = Link->Y+y;

HoldOut->Dir = Link->Dir; //This will make sure that the weapon pushes enemies back when it hits them
HoldOut->Damage = 8; //I think that's one hearts worth of damage, might not be though. I'll let you play around with that
HoldOut->Tile = T_HOLDOUTWEAPON+HoldOut->Dir; //Set the graphics for the weapon
}
//This function finds the weapon we made
lweapon FindHeldWeapon(){
for(int i=1;i<=Screen->NumLWeapons();i++){
//Check through all of the lweapons on the screen till we find the one we made earlier
lweapon l = Screen->LoadLWeapon(i);
//Our weapon is marked by setting it's Misc[0] value to 1, so we'll know if we've found it
if(l->Misc[0] == 1) return l;
}
//If our weapon's not on the screen, return an invalid pointer
return Screen->LoadLWeapon(0);
}
}

Right well.
I perhaps went a bit over the top there, but that should be quite a nice way of doing it.
If you don't get any of it, let me know and I'll explain it some more.

It will only work in builds 982 and 984 currently, but it should also work in the next build that we get. It won't work in 987.

For number 2, you can just make a new hookshot weapon with the item editor and make it only extend for 2 combos.
I'd rather not do the enemy weapon one, I'm not too keen on working with enemies.


Oh yeah, and I haven't actually tested that code so it might not compile quite right.
It'll just be a couple of semi-colons or brackets at the end of functions missed or something though, so I'm sure you can fix that.

pkmnfrk
04-10-2009, 01:16 PM
Right off the bat, I know it's not going to compile. But, I'm going to go through it, so gimme a minute.

Joe123
04-10-2009, 01:22 PM
import "std.zh"

const int T_HOLDOUTWEAPON = 0; //Here we set the tile for the weapon. There should be 4 tiles on the tilesheet for it, arranged as the up sprite, then down, then left, then right

//We're going to use these two functions to check whether Link has the weapon equipped on A or B
//I'll let you work them out for yourself; if you don't want to you just have to use them, no need to understand them really
int StoreInput;
//Store whether Link pressed A or B to use an item
void StoreInput(){
if(Link->InputA && !Link->InputB) StoreInput = 1;
else if(Link->InputB && !Link->InputA) StoreInput = 2;
else if(Link->InputB && Link->InputA) StoreInput = 3;
else StoreInput = 0;
}
//Returns true if Link is holding down the button input we stored earlier
bool CheckInputs(){
return ((StoreInput == 1 && Link->InputA) || (StoreInput == 2 && Link->InputB)
|| (StoreInput == 3 && Link->InputA && Link->InputB));
}

bool HoldOutWeapon; //We use this to link to the global script

//I'm doing it as an item. If you don't like that, tough.
item script HoldOutLWeapon{
void run(){
HoldOutWeapon = true; //Tell the global script we've used the weapon
StoreInput(); //Store which button Link has the weapon equipped on
}
}

//We're gonna need to link to the global script cause our item scripts run for only one frame
global script Slot2{
void run(){
HoldOutWeapon = false; //Make sure the weapon doesn't start when the game starts
while(true){
HoldOutLWeapon(); //Call our function
Waitframe();
}
}
//The function to handle our weapon
void HoldOutLWeapon(){
if(!HoldOutWeapon || !CheckInputs()){//If we're not using the weapon, or if Link isn't holding down the button anymore, remove the weapon and return the function so the rest of the code doesn't run.
lweapon Remove = FindHeldWeapon(); //Load our weapon
if(Remove->isValid()){ //Remember, the weapon might not actually be on the screen anyway, so in that case we don't need to get rid of it
Remove->DeadState = WDS_DEAD; //Remove it from the screen
}
return; //return the function so the rest of the code doesn't run
}

//Now for the part of the code that actually handles the weapon
lweapon HoldOut = FindHeldWeapon(); //First we check if we already made a weapon
if(!HoldOut->isValid()){ //If we haven't made one yet, or if our weapon hit an enemy, we'll need to make a new one
HoldOut = Screen->CreateLWeapon(LW_SCRIPT1); //Actually make the weapon. Remember, when a weapon hits an enemy it's removed from the screen
HoldOut->Misc[0] = 1; //Here we mark our weapon so that we know later that it's the one we made, rather than another weapon
}
//We're going to use these variables in working out where the weapon should be positioned
int x; int y;
//I'll let you work out what this bit does, but basically it's my method for making sure the weapon is infront of Link
if(Link->Dir == DIR_UP || Link->Dir == DIR_DOWN) y = Link->Dir*32-16;
else x = (Link->Dir-2)*32-16;
HoldOut->X = Link->X+x; //Place the weapon infront of Link
HoldOut->Y = Link->Y+y;

HoldOut->Dir = Link->Dir; //This will make sure that the weapon pushes enemies back when it hits them
HoldOut->Damage = 8; //I think that's one hearts worth of damage, might not be though. I'll let you play around with that
HoldOut->Tile = T_HOLDOUTWEAPON+HoldOut->Dir; //Set the graphics for the weapon
}
//This function finds the weapon we made
lweapon FindHeldWeapon(){
for(int i=1;i<=Screen->NumLWeapons();i++){
//Check through all of the lweapons on the screen till we find the one we made earlier
lweapon l = Screen->LoadLWeapon(i);
//Our weapon is marked by setting it's Misc[0] value to 1, so we'll know if we've found it
if(l->Misc[0] == 1) return l;
}
//If our weapon's not on the screen, return an invalid pointer
return Screen->LoadLWeapon(0);
}
}

Well it does now...
I'm sure he could've sorted it out, it was only a couple of capitalisation issues and I missed an S and an L out of the names of two of the functions.


Oh, and I forgot to add Link->X and Link->Y to the weapon's coordinates.
I've tested it now and it works fine though.

pkmnfrk
04-10-2009, 01:51 PM
import "std.zh"

//We're going to use these two functions to check whether Link has the weapon equipped on A or B
//I'll let you work them out for yourself; if you don't want to you just have to use them, no need to understand them really
int StoreInput;
//Store whether Link pressed A or B to use an item
void StoreInput(){
if(Link->InputA && !Link->InputB) StoreInput = 1;
else if(Link->InputB && !Link->InputA) StoreInput = 2;
else if(Link->InputB && Link->InputA) StoreInput = 3;
else StoreInput = 0;
}
//Returns true if Link is holding down the button input we stored earlier
bool CheckInputs(){
return (
((StoreInput & 1) != 0 && Link->InputA)
||
((StoreInput & 2) != 0 && Link->InputB)
);
}

bool HoldOutWeapon = false; //We use this to link to the global script
int HoldOutWeaponTile; //and this
int HoldOutWeaponDamage; //and this

//I'm doing it as an item. If you don't like that, tough.
item script HoldOutLWeapon{
void run(int tile, int damage){
HoldOutWeapon = true; //Tell the global script we've used the weapon
HoldOutWeaponTile = tile;
HoldOutWeaponDamage = damage;
StoreInput(); //Store which button Link has the weapon equipped on
}
}

//The function to handle our weapon
void HoldOutLWeapon(){
if(!HoldOutWeapon || !CheckInputs()){//If we're not using the weapon, or if Link isn't holding down the button anymore, remove the weapon and return the function so the rest of the code doesn't run.
lweapon Remove = FindHeldWeapon(); //Load our weapon
if(Remove->isValid()){ //Remember, the weapon might not actually be on the screen anyway, so in that case we don't need to get rid of it
Remove->DeadState = WDS_DEAD; //Remove it from the screen
}
HoldOutWeapon = false;
return; //return the function so the rest of the code doesn't run
}

//Now for the part of the code that actually handles the weapon
lweapon HoldOut = FindHeldWeapon(); //First we check if we already made a weapon

if(!HoldOut->isValid()){ //If we haven't made one yet, or if our weapon hit an enemy, we'll need to make a new one
HoldOut = Screen->CreateLWeapon(LW_SCRIPT1); //Actually make the weapon. Remember, when a weapon hits an enemy it's removed from the screen
HoldOut->Misc[0] = 1; //Here we mark our weapon so that we know later that it's the one we made, rather than another weapon

HoldOut->Damage = HoldOutWeaponDamage; //Set the damage
}

//We're going to use these variables in working out where the weapon should be positioned
int x = Link->X;
int y = Link->Y;

//I'll let you work out what this bit does, but basically it's my method for making sure the weapon is infront of Link
if(Link->Dir == DIR_UP || Link->Dir == DIR_DOWN)
y += Link->Dir*32-16;
else
x += (Link->Dir-2)*32-16;

HoldOut->X = Link->X+x; //Place the weapon infront of Link
HoldOut->Y = Link->Y+y;

HoldOut->Dir = Link->Dir; //This will make sure that the weapon pushes enemies back when it hits them
HoldOut->Tile = HoldOutWeaponTile+HoldOut->Dir; //Set the graphics for the weapon
}
//This function finds the weapon we made
lweapon FindHeldWeapon(){
for(int i=1; i<=Screen->NumLWeapons(); i++) {
//Check through all of the lweapons on the screen till we find the one we made earlier
lweapon l = Screen->LoadLWeapon(i);
//Our weapon is marked by setting it's Misc[0] value to 1, so we'll know if we've found it
if(l->Misc[0] == 1) return l;
}
//If our weapon's not on the screen, return an invalid pointer
return Screen->LoadLWeapon(0);
}


global script Slot2 {
void run() {
while(true) {
HoldOutLWeapon();
Waitframe();
}
}
}

I've optimized it a tiny bit, and added the ability to have more than one of this item (Level 1, Level 2), by supplying D0 (tile) and D1 (damage) to the script.

Joe123
04-10-2009, 04:04 PM
Do the changes you made to the checkinputs accomodate for if storeinput == 3?
I'm guessing they do?

Did you do anything else apart from move the damage part into the weapon spawning code?
I realised I could've done that, but I was lazy.
There should be a line setting the CSet in those braces too really.

And why did you move my functions out of the global script?
It looks much neater with them in the script.

CJC
04-10-2009, 04:18 PM
Having it in an item is fine. In fact, it's preferable, because I can just use that code in the items I want to have this effect (Mainly, my shield item and my dash item).

Plus, I can probably extract it to a global scale now that I have a general idea of how it works (Based on the notations).

My biggest fear was dealing with item validity, but one of your functions seems to handle that perfectly. I can implement other items simply by using different miscellaneous values, right?


Oh, and what kind of Lweapon does this spawn... for the purpose of triggering flags? I suppose I could set up a custom flag for it, but I need more practice in forcing screen secrets.
Actually... that'd be a good excercise, so I can work on triggering secrets from different screens (I know it uses global variables, I just don't know how to do it in ZQuest. So... if Zquest fails, ZScript!)

Joe123
04-10-2009, 05:36 PM
You don't have to do it exactly as I did, but the principles work quite nicely.
It's a good idea not to cycle through all of the weapons on the screen more times than you need to every frame though, so calling the FindHeldWeapon function lots of times per frame wouldn't be the best way of doing things.

It spawns an 'LW_SCRIPT1' type LWeapon, but that can be changed easily.
On the 'Screen->CreateLWeapon' line.

CJC
04-10-2009, 06:36 PM
Ah. Should have read the script more thoroughly before posting my reply.

So I take it if I change the weapon type off scripted, I'll have to specify a deadstate, right? Like, if I changed it to a sword-type LWeapon, I'd need to set the deadstate to -2 so that it doesn't continually vanish and reappear. Gotcha.


I've got another question.
I've sort of hardcoded the sword to my B button with that buttonshuffle script (I posted it in the script showcase). So it doesn't really matter which button was pressed to use the item, since it's always technically going to be the "B" button (I know that probably doesn't make much sense to you, but it does to me).
I considered removing the CheckInput Bool, but then noticed it was a required portion of the "If" clause. I'm sure I could get around that by crafting a while-loop, but I don't want to muck around too much with a working code (So I don't break it).

What I wanted to ask was this... Do you think it makes a difference if I just leave the CheckInput bool in there? It won't be used, but something tells me it isn't massive enough to slow the game if I leave it in.

Joe123
04-10-2009, 07:11 PM
You can't spawn sword LWeapons.

Don't put in a while loop, then the rest of your global functions won't work properly.
It shouldn't make a difference if you leave in the checkinput bool.
Won't slow your game down, don't worry.

pkmnfrk
04-10-2009, 07:14 PM
Do the changes you made to the checkinputs accomodate for if storeinput == 3?
I'm guessing they do?

Yes. & is a bitwise operator. You set the "1" bit if A is pressed, and the "2" bit if B is pressed. If both are pressed, it's 1 + 2, right?


Did you do anything else apart from move the damage part into the weapon spawning code?
I realised I could've done that, but I was lazy.
There should be a line setting the CSet in those braces too really.

I did have a part to specify animation, but I removed it when I realized it wouldn't work with the built in animation. I plan to add that later.


And why did you move my functions out of the global script?
It looks much neater with them in the script.

I moved them out so I could easily add the script to my own global script. And... for another reason I'm not at liberty to discuss right now. But... well, you'll understand when it happens...

</foreshadowing>

Joe123
04-11-2009, 12:39 PM
I know what & does. I understand the bitwise operators.
The usefulness of left and right bitshifts I don't get, but I understand how they work.
My thought process was 'why is he doing it like that' 'oh it probably accounts for the third condition' 'I'm too lazy to think about how it works so I'll just ask'.

I was going to add something about animation, but then I decided I couldn't be bothered.
After all, I'm sure Cjc's capable of checking up what properties weapon objects have in zscript.txt.

I'm interested now =(
Why can't I know?

CJC
04-11-2009, 04:14 PM
If I wanted it to animate, I would just use Screen->DrawCombo over the weapon (I don't want this particular thing to animate, though, so that's fine).

I forgot you can't spawn swords... I'll just use something more generic, like an Arrow LWeapon, to get the job done. No worries.



Hmm... I bet I could use DrawCombo with Link too to get some savings on sprite pages... if I set him to blank tiles for heavily animated actions, I could animate over him with a combo and it would work perfectly. I wouldn't even have to specify his direction, I could just add it to the combo number like you did with the weapon tile. Awesome!

Joe123
04-11-2009, 07:34 PM
That's a very bad way of making it animate.

If you want to make it animate, just use the built in weapon->ASpeed and weapon->NumFrames values.

And using drawcombo for Link wouldn't be a good idea either - it wouldn't show up as you're crossing between screens, or if a message string is playing.

pkmnfrk
04-12-2009, 12:16 PM
I don't think we can use the built in animation, since we're setting tile directly. We would need to emulate it with a separate global variable to keep track of what frame we're on, and to update the tile-setting formula as such:

tile + Direction * frames

Joe123
04-12-2009, 12:55 PM
Oh, yeah.
You wouldn't need a global variable to do it like that though, that's exactly the sort of thing I added the Misc values for ^_^

Although it doesn't mean you can't use the built in animation though.

HoldOut->NumFrames = whatever;
HoldOut->ASpeed = whatever;
HoldOut->Dir = Link->Dir;
HoldOut->OriginalTile = T_HOLDOUT+HoldOut->Dir*HoldOut->NumFrames;
That'll do it.


EDIT:
Oh actually, are you saying that you'd do what I said or what?

pkmnfrk
04-12-2009, 01:06 PM
Hmm, actually, that might do it. That's not what I was thinking of, but that's probably best.