Finished this around December with just plain directions, and recently changed it to angles.
The reason I started this was to allow a change of the speed and range of a projectile. The only thing it really doesn't have now is z manipulation.
Dusty style comments alright!
Here's the actual projectile class, which should be named "projectile". I would suggest you not change anything except for
this.playercatch and
this.npccatch in the
onCreated function, and if you want your own defined width/height you could change
this.width and
this.height in the
onMove function to your desired width and height.
PHP Code:
/*
BEFORE JOINING TO THE CLASS, MAKE SURE THESE ARE SET:
npc.angle - ANGLE OF THE PROJECTILE IN RADIANS
npc.speed - TILES PER 0.05 SECONDS
npc.range - MAX TILES THE PROJECTILE CAN GO WITHOUT HITTING A WALL
npc.owner - SET TO THE PLAYER THAT YOU DON'T WANT TO BE HIT (PLAYER THAT SHOT THE PROJECTILE?/NULL FOR UNNEEDED)
npc.showCharacter() - CREATES A WIDTH/HEIGHT
npc.ani - PROJECTILE'S GANI
npc.aniparams - PROJECTILE'S GANI PARAMS AS A STRING SEPARATED BY ","'S
npc.params - PARAMS TO BE READ BY THE OBJECT THAT WAS HIT AS AN ARRAY
*/
function onCreated() {
//CHANGE TO THE PLAYER HIT WEAPON NPC (NEEDED FOR PLAYERS)
this.playercatch = "-System/Projectiles";
//CHANGE TO THE HIT EVENT onEventname (NEEDED FOR NPCS)
this.npccatch = "CustomProjectile";
//SET ON CREATION
this.startx = this.x;
this.starty = this.y;
this.oldx = this.x;
this.oldy = this.y;
dontBlock();
onMove();
}
function onMove() {
//CHECK IF PROJECTILE IS OVER THE RANGE
if (((this.x-this.startx)^2+(this.y-this.starty)^2)^0.5 > this.range) {
onProjectileDestroy();
}
//CHECK IF THE PROJECTILE HASN'T MOVED
if (this.oldx == this.y && this.oldy = this.y) {
onProjectileDestroy();
}
this.oldx = this.x;
this.oldy = this.y;
//FULL TIME TO MOVE
temp.time = 0.1;
//FULL DELTA X AND Y TO MOVE
temp.movex = cos(this.angle)*this.speed*2;
temp.movey = -sin(this.angle)*this.speed*2;
//CHANGE DIR IF NECESSARY
this.dir = (this.angle in |0, pi*0.25|?3:(this.angle in |pi*0.25, pi*0.75|?0:(this.angle in |pi*0.75, pi*1.25|?1:(this.angle in |pi*1.25, pi*1.75|?2:3))));
//PLAYERS, NPCS, AND WALLS
for (temp.i=0; i<this.speed*16*2; i++) {
//PLAYERS
for (temp.pl : findNearestPlayers(this.x, this.y)) {
//CHECKS IF THE PLAYER IS READY TO BE HIT
if (!(temp.pl in this.playerhit) && temp.pl.account != this.owner) {
//CHECKS IF THE PLAYER IS IN VALID X/Y COORDINATES
if ((temp.pl.x+0.5 <= this.x+temp.movex*(i/16) && temp.pl.x+2.5 >= this.x+temp.movex*(i/16)+this.width) || (temp.pl.x+0.5 >= this.x+temp.movex*(i/16) && temp.pl.x+2.5 <= this.x+temp.movex*(i/16)+this.width) || (temp.pl.x+0.5 <= this.x+temp.movex*(i/16) && temp.pl.x+2.5 >= this.x+temp.movex*(i/16)) || (temp.pl.x+0.5 <= this.x+temp.movex*(i/16)+this.width && temp.pl.x+2.5 >= this.x+temp.movex*(i/16)+this.width)) {
if ((temp.pl.y+1 <= this.y+temp.movey*(i/16) && temp.pl.y+3 >= this.y+temp.movey*(i/16)+this.height) || (temp.pl.y+1 >= this.y+temp.movey*(i/16) && temp.pl.y+3 <= this.y+temp.movey*(i/16)+this.height) || (temp.pl.y+1 <= this.y+temp.movey*(i/16) && temp.pl.y+3 >= this.y+temp.movey*(i/16)) || (temp.pl.y+1 <= this.y+temp.movey*(i/16)+this.height && temp.pl.y+3 >= this.y+temp.movey*(i/16)+this.height)) {
//GIVES TIME FOR THE PLAYER TO NOT RECEIVE EXCESSIVE HITS WHILE PASSING THROUGH
this.playerhit.add(temp.pl);
scheduleEvent(3, "PlayerUnhit", temp.pl);
//TRIGGERS THE CONTROL WEAPON
temp.pl.triggerClient("gui", this.playercatch, this.params);
}
}
}
}
//NPCS
for (temp.npc : findAreaNpcs(this.x+temp.movex*(i/16), this.y+temp.movey*(i/16), this.width, this.height)) {
//CHECK IF THE NPC IS READY TO BE HIT
if (!(temp.npc in this.npchit)) {
//GIVE TIME FOR THE NPC TO NOT RECEIVE EXCESSIVE HITS WHILE PASSING THROUGH
this.npchit.add(temp.npc);
scheduleEvent(3, "NPCUnhit", temp.npc);
//TRIGGERS THE NPC'S PROJECTILE CATCH
temp.npc.trigger(this.npccatch, this.params);
}
}
//WALLS
if (onWall2(this.x+temp.movex*(i/16), this.y+temp.movey*(i/16), this.width, this.height)) {
//TIME LEFT TO MOVE
temp.time = this.speed/i/16;
//DELTA X AND Y LEFT TO MOVE
temp.movex = temp.movex*(i/16);
temp.movey = temp.movey*(i/16);
//SETS TO BLOCKED
temp.blocked = true;
break;
}
}
//SCHEDULES WHEN TO DESTROY THE PROJECTILE IF BLOCKED
if (temp.blocked) {
//MAKE SURE THERE'S ENOUGH TIME TO DESTROY
if (temp.time-0.05 <= 0) {
onProjectileDestroy();
}
else {
scheduleEvent(temp.time-0.05, "ProjectileDestroy", null);
}
}
//DO MOVE
move(temp.movex, temp.movey, temp.time, 0);
//SCHEDULE NEW MOVE
scheduleEvent(0.05, "Move");
}
//ALLOWS PLAYER TO GET HIT AGAIN
function onPlayerUnhit(pl) {
this.playerhit.remove(pl);
}
//ALLOWS NPC TO GET HIT AGAIN
function onNPCUnhit(npc) {
this.npchit.remove(npc);
}
//DESTROYS THE PROJECTILE
function onProjectileDestroy() {
this.destroy();
}
Note: The projectile's direction (animation direction, that is) is always being set as it goes along based on it's angle, so if you decide to change it's angle while it's going it will face the right direction.
Here's the class with a
shoot2(x, y, angle, speed, range, owner, ani, aniparams, params) serverside function (explanations on them at the top of the projectile class) for if you don't need to keep the projectile object in scope. I would name it "projectilesmaker", and join an NPC to use shoot2().
PHP Code:
function shoot2(sx, sy, ag, sp, ra, ow, ai, aip, pa) {
temp.npc = putNpc2(sx, sy, "");
temp.npc.showCharacter();
temp.npc.ani = ai;
temp.npc.aniparams = aip;
temp.npc.speed = sp;
temp.npc.angle = ag;
temp.npc.range = ra;
temp.npc.owner = ow;
temp.npc.params = pa;
temp.npc.join("switch_projectiles");
}
Here's the player's catch (weapon), and all player catches will be done in here. The default name for it is "-System/Projectiles". Just add if checks and whatnot. Use
temp.param[0,1,...] instead of
params[0,1,...] since the param array has to be sent as an array (given
params[0] spot only).
PHP Code:
//#CLIENTSIDE
function onActionClientside() {
temp.param = params[0];
//EXAMPLE CHECK AS {cookies", "yogurt"}
if (temp.param[0] == "cookies") {
player.chat = temp.param[1]; //PLAYER SAYS "yogurt"
}
}
Now, in a level NPC, you need to add your own catch function (serverside). The event name would be the projectile system's NPC catch with "on" stuck before it. An example could be like below. Also, notice that you can use the default
params[0,1,...] for this. Not sure why this is, but it is.
PHP Code:
...
//STILL USING {"cookies", "yogurt"}
function onCustomProjectile() {
if (params[0] == "cookies") {
with (findNearestPlayer(this.x, this.y)) {
player.chat = params[1]; //NEAREST PLAYER SAYS "yogurt"
}
}
}
...
So with all that, here's examples of the two ways you can go about doing this (easy weapon NPC example).
PHP Code:
//USING shoot2()
function onCreated() {
this.join("projectilesmaker");
}
function onActionServerSide() {
temp.sx = player.x+1.5+vecx(player.dir)-(player.dir == 1?3:0)-(player.dir in {0, 2}?1.5:0); //x
temp.sy = player.y+2+vecy(player.dir)-(player.dir == 0?3:0)-(player.dir in {1, 3}?1.5:0); //y
temp.ag = getangle(vecx(player.dir), vecy(player.dir)); //angle
temp.sp = 1.5; //speed
temp.ra = 64; //range
temp.ow = player.account; //owner
temp.ai = "switch_projectiles"; //ani
temp.aip = "switch_projectiles.png"; //aniparams
temp.pa = {"cookies", "yogurt"}; //params
shoot2(temp.sx, temp.sy, temp.ag, temp.sp, temp.ra, temp.ow, temp.ai, temp.aip, temp.pa);
}
//#CLIENTSIDE
function onWeaponFired() {
triggerServer("gui", this.name, null);
}
---
//KEEPING THE PROJECTILE IN SCOPE
function onActionServerSide() {
temp.sx = player.x+1.5+vecx(player.dir)-(player.dir == 1?3:0)-(player.dir in {0, 2}?1.5:0); //x
temp.sy = player.y+2+vecy(player.dir)-(player.dir == 0?3:0)-(player.dir in {1, 3}?1.5:0); //y
temp.npc = putnpc2(temp.sx, temp.sy, "");
temp.npc.angle = getangle(vecx(player.dir), vecy(player.dir));
temp.npc.speed = 1.5;
temp.npc.range = 64;
temp.npc.owner = player.account;
temp.npc.ani = "switch_projectiles";
temp.npc.aniparams = "switch_projectiles.png";
temp.npc.params = {"cookies", "yogurt"};
temp.npc.join("projectiles");
//USE temp.npc OBJECT FOR OTHER THINGS, ETC.
}
//#CLIENTSIDE
function onWeaponFired() {
triggerServer("gui", this.name, null);
}
Attached is a testing image and gani you can use with the example weapon NPCs I provided.