So I was reading
this thread here and I was thinking if I could write a step-by-step example of how to do some basic stuff in ZScript that is straightforward enough for non-programmers with some DECORATE experience to understand immediately but still clearly capable of showing how deep you can get into the workings of how actors are spawned and moved. Based largely on my own experience working with projectiles and explosions these past 2 months.
Two points to keep in mind about this tutorial:
1.
It looks stupidly long because I'm documenting every step and re-posting the entire modified code each time. It is intentionally repetitive.
2.
I do not intend anyone to use this code as a precedent. There are numerous decisions here that are actually suboptimal and could be changed to make it more efficient or maintainable, but are left this way since it follows the flow of the tutorial itself and I really don't want to get into coding best practices or whatever because that would be the blind person with the poor spatial memory and Dunning-Kruger leading the blind.
Premise: are you tired of having to walk all the way over to a zombieman's corpse to pick up his clip every time you kill one? With this mod you can double your efficiency having the clips spawn only half as far away!
First, create a plaintext file called "zscript.txt" and copypaste the following:
Code: Select all
version "3.3" // you must enter this (or the appropriate number)
// or you will be limited to features from 2.3.0
class LazyZom:Zombieman replaces Zombieman{}
(That's "class", not "actor", because there are things that can get this treatment that aren't actors (and "actor" as a declaration is used for something else as we'll get to below). When converting existing projects, it's also advisable to do a string search immediately after for "damagefclass", etc. because that error
will happen at least once.)
Now when you run it it should make no difference whatsoever (except breaking a few dynamic light definitions). Not very exciting! So let's start working on this for real...
The first real change we need is to get rid of the default dropitem so at the end of the day we should only have the one that drops at the closer distance. Unlike DECORATE, all flags and properties must be under a "default" section, and properties (not flags!) must end in a semicolon:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
}
Now we need to spawn the actual clip. Since we're
really lazy and don't even want to wait for those extra 5 tics, we'll just call the event at the very start of the animation and use goto for the rest:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
}
(There's a better way to do this without having to redefine both states but let's not get too far ahead. Read up on "virtual functions" in the wiki for that.)
Note the different syntax: All state lines must end in semicolons and all function calls must end with () even if they don't normally take any parameters.
Now obviously we haven't actually defined A_LazyDrop so the above won't run. Just to make sure it runs at all and we don't have any stray brackets, let's just put in an empty function:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<Actor> todrop = "Clip"){}
}
Now we've defined a function, A_LazyDrop, that is available to LazyZom and any and all descendants. It takes one parameter, which if left undefined defaults to "Clip".
At this point you should just get a normal zombieman that drops nothing. Now for the fun part - making it drop the thing!
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
Spawn(todrop, self.pos, ALLOW_REPLACE);
}
}
This is quite primitive. Now when a zombieman dies, a clip - not marked as dropped, perfectly still, giving the full 10 when picked up, replaced by any actor that "replaces Clip" - will instantly appear at its feet. Not exactly an improvement.
So let's at least mark it as dropped and give it something that
kinda looks like the drop movement:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
}
}
To break that down: instead of just calling Spawn, we define a variable "a" which points to the thing that we thereby spawn. Then we can tell ZDoom that this actor "a" has to be marked as +DROPPED (all the flag variable names start with "b" where the +- sign would otherwise be) and should have a random XY velocity and a Z velocity of 3.
Note how all this stuff - velocity, flags, etc. - needs to be defined explicitly, unlike A_SpawnItemEx. This might seem like more work, but once you're used to it it sure beats trying to remember all 29+ A_SpawnItemEx flags, the assumptions they're intended to override and what side effects each one has!
[
2017-12-28 performance/optimization note: For more advanced or complex uses you might actually want to go
back to A_SpawnItemEx, since every step you're putting through the ZScript virtual machine makes your code run that much slower. Sometimes I find it useful to use the approach in this tutorial to begin with and then change it to a Decorate command that relies on native functions that don't put as much load on the VM.]
Now getting the amount right is a bit trickier. ZScript won't recognize the "amount" property right off the bat since it's not found in all actors, just inventory and ammo, so you need to use the magic word "let" to define a variable that ZScript knows for sure is a kind of ammo and thus will definitely have that property to set:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
}
}
So now that's done and you've got something that more or less behaves just like a dropped zombieman clip. Just for fun, let's also make this starting velocity
relative to the dying zom by adding its velocity to the mix!
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
a.vel += self.vel;
}
}
Since it's the zombieman calling this, "self" means the zombieman. It does not have to be explicitly written (except in a few situations we're not getting into now), but it's left in here to make it clear which actor's velocity we're talking about.
This is, of course, doing the exact opposite of what we were advertising: go punch a zombie while berserk and the clip will end up even
further away than it otherwise would!
So let's make that velocity inheritance contingent... and finally do that halfway spawning thing!:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
if(target && target is "PlayerPawn"){
a.SetXYZ( (target.pos + a.pos) * 0.5 );
}else a.vel += self.vel;
}
}
(As a side note, generally computers multiply faster than they divide, so never use /2 when *0.5 will do.)
Now if the zombie has a target (gotta check this first to make sure the computer isn't trying to work with a null pointer) and that target is a player, it will set the clip's position to halfway between the zom and the target player
instead of adding the zom's velocity.
In other words, if you shoot a zombie at 20 paces, the zombie will drop dead where it is while a clip will pop up out of thin air only 10 paces away! Have fun!
...except there's a bug!
The way it's written now, sometimes the clip will hit a stair and get stuck in midair and unable to be picked up. It seems that calling SetXYZ right in the middle of setting everything up like that might have some side effects involving the engine trying to figure out which sector it's in.
To get around that, we'll just rewrite the conditional so that the initial spawning point is already the point that we want:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a;
if(target && target is "PlayerPawn"){
a = Spawn(todrop, (target.pos + self.pos) * 0.5, ALLOW_REPLACE);
}else{
a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.vel += self.vel;
}
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
}
}
No more stair-based weirdness! (I still have no real idea why it does this, though... and this will be something you'll learn to live with the more of this stuff you do until more of that coding stuff sinks in.)
Bonus: You can also give the poor zombieman a parting shot on the same principles:
Code: Select all
version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a;
if(target && target is "PlayerPawn"){
a = Spawn(todrop, (target.pos + self.pos) * 0.5, ALLOW_REPLACE);
self.bsolid = false; //since this is happening before A_NoBlocking()
actor b = Spawn("RevenantTracer", pos + (0,0,32), ALLOW_REPLACE);
b.tracer = target;
b.target = self;
b.A_FaceTracer(0,0);
b.A_PlaySound("skeleton/attack");
b.vel.x = cos(angle) * cos(pitch) * 10;
b.vel.y = sin(angle) * cos(pitch) * 10;
b.vel.z = -sin(pitch) * 10;
}else{
a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.vel += self.vel;
}
a.bdropped = true;
a.vel.x += random(-1,1);
a.vel.y += random(-1,1);
a.vel.z += 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
}
}
(The mnemonic I use when I need something to move forwards: X should be cosine of both angle and pitch, all horizontals are cosine of pitch, angles are irrelevant to Z, and don't forget that negative pitch means positive velocity.)
And, just for kicks, a rocket that inherits your momentum just as it's finished entering the playsim:
Code: Select all
version "3.3"
class AwkwardRocket:Rocket replaces Rocket{
override void PostBeginPlay(){
super.PostBeginPlay(); //does all the parent actor's initialization stuff
if(target) vel += target.vel;
}
}
All done in a fraction of the amount of text it would take to... well, I don't even want to think about the DECORATE code I used to need for this.
And, of course, if you really want to feel the full sense of initiation:
Code: Select all
version "3.3"
class HelloRocket:Rocket replaces Rocket{
override void PostBeginPlay(){
super.PostBeginPlay();
if(target){
vel += target.vel;
if(target is "PlayerPawn") target.A_Log("Hello world!",true);
}
}
}
So I was reading [url=https://forum.zdoom.org/viewtopic.php?f=4&t=55293]this thread here[/url] and I was thinking if I could write a step-by-step example of how to do some basic stuff in ZScript that is straightforward enough for non-programmers with some DECORATE experience to understand immediately but still clearly capable of showing how deep you can get into the workings of how actors are spawned and moved. Based largely on my own experience working with projectiles and explosions these past 2 months.
Two points to keep in mind about this tutorial:
1. [b]It looks stupidly long[/b] because I'm documenting every step and re-posting the entire modified code each time. It is intentionally repetitive.
2. [b]I do not intend anyone to use this code as a precedent.[/b] There are numerous decisions here that are actually suboptimal and could be changed to make it more efficient or maintainable, but are left this way since it follows the flow of the tutorial itself and I really don't want to get into coding best practices or whatever because that would be the blind person with the poor spatial memory and Dunning-Kruger leading the blind.
Premise: are you tired of having to walk all the way over to a zombieman's corpse to pick up his clip every time you kill one? With this mod you can double your efficiency having the clips spawn only half as far away!
First, create a plaintext file called "zscript.txt" and copypaste the following:
[code=php]version "3.3" // you must enter this (or the appropriate number)
// or you will be limited to features from 2.3.0
class LazyZom:Zombieman replaces Zombieman{} [/code]
(That's "class", not "actor", because there are things that can get this treatment that aren't actors (and "actor" as a declaration is used for something else as we'll get to below). When converting existing projects, it's also advisable to do a string search immediately after for "damagefclass", etc. because that error [i]will[/i] happen at least once.)
Now when you run it it should make no difference whatsoever (except breaking a few dynamic light definitions). Not very exciting! So let's start working on this for real...
The first real change we need is to get rid of the default dropitem so at the end of the day we should only have the one that drops at the closer distance. Unlike DECORATE, all flags and properties must be under a "default" section, and properties (not flags!) must end in a semicolon:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
} [/code]
Now we need to spawn the actual clip. Since we're [i]really[/i] lazy and don't even want to wait for those extra 5 tics, we'll just call the event at the very start of the animation and use goto for the rest:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
} [/code]
(There's a better way to do this without having to redefine both states but let's not get too far ahead. Read up on "virtual functions" in the wiki for that.)
Note the different syntax: All state lines must end in semicolons and all function calls must end with () even if they don't normally take any parameters.
Now obviously we haven't actually defined A_LazyDrop so the above won't run. Just to make sure it runs at all and we don't have any stray brackets, let's just put in an empty function:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<Actor> todrop = "Clip"){}
} [/code]
Now we've defined a function, A_LazyDrop, that is available to LazyZom and any and all descendants. It takes one parameter, which if left undefined defaults to "Clip".
At this point you should just get a normal zombieman that drops nothing. Now for the fun part - making it drop the thing!
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
Spawn(todrop, self.pos, ALLOW_REPLACE);
}
} [/code]
This is quite primitive. Now when a zombieman dies, a clip - not marked as dropped, perfectly still, giving the full 10 when picked up, replaced by any actor that "replaces Clip" - will instantly appear at its feet. Not exactly an improvement.
So let's at least mark it as dropped and give it something that [i]kinda looks like[/i] the drop movement:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
}
} [/code]
To break that down: instead of just calling Spawn, we define a variable "a" which points to the thing that we thereby spawn. Then we can tell ZDoom that this actor "a" has to be marked as +DROPPED (all the flag variable names start with "b" where the +- sign would otherwise be) and should have a random XY velocity and a Z velocity of 3.
Note how all this stuff - velocity, flags, etc. - needs to be defined explicitly, unlike A_SpawnItemEx. This might seem like more work, but once you're used to it it sure beats trying to remember all 29+ A_SpawnItemEx flags, the assumptions they're intended to override and what side effects each one has!
[[i]2017-12-28 performance/optimization note:[/i] For more advanced or complex uses you might actually want to go [i]back[/i] to A_SpawnItemEx, since every step you're putting through the ZScript virtual machine makes your code run that much slower. Sometimes I find it useful to use the approach in this tutorial to begin with and then change it to a Decorate command that relies on native functions that don't put as much load on the VM.]
Now getting the amount right is a bit trickier. ZScript won't recognize the "amount" property right off the bat since it's not found in all actors, just inventory and ammo, so you need to use the magic word "let" to define a variable that ZScript knows for sure is a kind of ammo and thus will definitely have that property to set:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
}
} [/code]
So now that's done and you've got something that more or less behaves just like a dropped zombieman clip. Just for fun, let's also make this starting velocity [i]relative[/i] to the dying zom by adding its velocity to the mix!
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
a.vel += self.vel;
}
} [/code]
Since it's the zombieman calling this, "self" means the zombieman. It does not have to be explicitly written (except in a few situations we're not getting into now), but it's left in here to make it clear which actor's velocity we're talking about.
This is, of course, doing the exact opposite of what we were advertising: go punch a zombie while berserk and the clip will end up even [i]further[/i] away than it otherwise would!
So let's make that velocity inheritance contingent... and finally do that halfway spawning thing!:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
if(target && target is "PlayerPawn"){
a.SetXYZ( (target.pos + a.pos) * 0.5 );
}else a.vel += self.vel;
}
} [/code]
(As a side note, generally computers multiply faster than they divide, so never use /2 when *0.5 will do.)
Now if the zombie has a target (gotta check this first to make sure the computer isn't trying to work with a null pointer) and that target is a player, it will set the clip's position to halfway between the zom and the target player [i]instead of[/i] adding the zom's velocity.
In other words, if you shoot a zombie at 20 paces, the zombie will drop dead where it is while a clip will pop up out of thin air only 10 paces away! Have fun!
...except there's a bug!
The way it's written now, sometimes the clip will hit a stair and get stuck in midair and unable to be picked up. It seems that calling SetXYZ right in the middle of setting everything up like that might have some side effects involving the engine trying to figure out which sector it's in.
To get around that, we'll just rewrite the conditional so that the initial spawning point is already the point that we want:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a;
if(target && target is "PlayerPawn"){
a = Spawn(todrop, (target.pos + self.pos) * 0.5, ALLOW_REPLACE);
}else{
a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.vel += self.vel;
}
a.bdropped = true;
a.vel.x = random(-1,1);
a.vel.y = random(-1,1);
a.vel.z = 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
}
} [/code]
No more stair-based weirdness! (I still have no real idea why it does this, though... and this will be something you'll learn to live with the more of this stuff you do until more of that coding stuff sinks in.)
Bonus: You can also give the poor zombieman a parting shot on the same principles:
[code=php]version "3.3"
class LazyZom:Zombieman replaces Zombieman{
default{
dropitem "none";
}
states{
death:
---- A 0 A_LazyDrop("Clip");
goto super::death;
xdeath:
---- A 0 A_LazyDrop("Clip");
goto super::xdeath;
}
void A_LazyDrop(class<actor> todrop = "Clip"){
actor a;
if(target && target is "PlayerPawn"){
a = Spawn(todrop, (target.pos + self.pos) * 0.5, ALLOW_REPLACE);
self.bsolid = false; //since this is happening before A_NoBlocking()
actor b = Spawn("RevenantTracer", pos + (0,0,32), ALLOW_REPLACE);
b.tracer = target;
b.target = self;
b.A_FaceTracer(0,0);
b.A_PlaySound("skeleton/attack");
b.vel.x = cos(angle) * cos(pitch) * 10;
b.vel.y = sin(angle) * cos(pitch) * 10;
b.vel.z = -sin(pitch) * 10;
}else{
a = Spawn(todrop, self.pos, ALLOW_REPLACE);
a.vel += self.vel;
}
a.bdropped = true;
a.vel.x += random(-1,1);
a.vel.y += random(-1,1);
a.vel.z += 8;
let aa = Ammo(a);
if(aa) aa.amount *= 0.5;
}
} [/code]
(The mnemonic I use when I need something to move forwards: X should be cosine of both angle and pitch, all horizontals are cosine of pitch, angles are irrelevant to Z, and don't forget that negative pitch means positive velocity.)
And, just for kicks, a rocket that inherits your momentum just as it's finished entering the playsim:
[code=php]version "3.3"
class AwkwardRocket:Rocket replaces Rocket{
override void PostBeginPlay(){
super.PostBeginPlay(); //does all the parent actor's initialization stuff
if(target) vel += target.vel;
}
} [/code]
All done in a fraction of the amount of text it would take to... well, I don't even want to think about the DECORATE code I used to need for this.
And, of course, if you really want to feel the full sense of initiation:
[code=php]version "3.3"
class HelloRocket:Rocket replaces Rocket{
override void PostBeginPlay(){
super.PostBeginPlay();
if(target){
vel += target.vel;
if(target is "PlayerPawn") target.A_Log("Hello world!",true);
}
}
} [/code]