Bad guys

Decided to script a typical baddy.

Features :

  • Highlight customizable settings
  • Sword and Bow attacks
  • Solid wall detection
  • Goes ‘agro’ on anyone near him swinging his sword if he happens to be hit(AKA cheap ‘chase player that hit me detection’)
  • Cuts down bushes in his path to try to reach you

Also taking any suggestions to improve him!

[php]
i// NPC made by Shiny
if (created) {
showcharacter;
setcharprop #n,Bad Guy; // Name
setcharprop #1,sword1.png; // Sword image
setcharprop #2,shield1.png; // Shield image
setcharprop #3,head1212.png; // Head image
setcharprop #8,body.png; // Body image
setcharprop #C0,black; // Skin color
setcharprop #C1,black; // Coat color
setcharprop #C2,black; // Sleeve color
setcharprop #C3,black; // Shoe color
setcharprop #C4,black; // Belt color
swordpower = 1; // Sword power
dir = 2; // Starting direction
hearts = 3; // Heart count
darts = 5; // Dart count
this.speed = .45; // Chase speed
this.sight = 15; // How far it can see
this.bowcooldown = 2; // How long between shooting
this.bowfreezetime = .5; // Freeze time from shooting
this.swordfreezetime = .35; // Freeze time from swinging sword
this.hurtrecoil = .35; // How long they recoil from a hit
this.hurtpause = .75; // How long they freeze from a hit
this.patience = 5; // How long they will wait until giving up when they can’t get to player

setstring this.animations,idle,walk,walk,sword2,shoot,hurt,dead;
this.orghearts = hearts;
this.orgdarts = darts;
this.mode = 0;
this.freezetimer = 0;
hurtdx = 0;
hurtdy = 0;
this.bushtiles = {
0x2, 0x3,
0x12, 0x13,
0x35E,0x35F,
0x36E,0x36F,
0xA02,0xA03,
0xA12,0xA13,
};
this.bushreplace = {
0x2A5,0x2A6,
0x2B5,0x2B6,
0x388,0x389,
0x389,0x388,
0xC90,0xC91,
0xCA0,0xCA1,
};

timeout = 0.05;
}

if (playerenters) {
if (hearts <= 0) {
hearts = this.orghearts;
hurtdx = 0;
hurtdy = 0;
this.target = -1;
darts = this.orgdarts;
this.freezetimer = 0;
this.mode = 0;
}
timeout = 0.05;
}

if (timeout) {
if (this.mode != 6) {
if (this.freezetimer =< 0){
if (this.mode == 0) {
this.target = -1;
FindPlayer();
} else if (this.mode == 2) {
if (this.target == -1 || players[this.target].hearts <= 0) this.mode = 0;
dx = (players[this.target].x+1.5) - (x+1.5);
dy = (players[this.target].y+2) - (y+2);
dist = (dx^2 + dy^2)^.5;
dir = getdir(dx,dy);

    if (dist =< 4 || (dist < 5 && strequals(#m(this.target),sword2))) {
      CheckBushes();
      this.mode = 3;
      this.freezetimer = this.swordfreezetime;
    }

    this.cansee = 1;
    for (i=1;i<((dist < this.sight) ? int(dist) : this.sight);i++) {
      if (onwall(x+1.5+(dx/dist)*i+.5,y+2+(dy/dist)*i+.5)) this.cansee = 0;
    }
    if (this.cansee == 0) this.findtolerance += 0.05;
    else this.findtolerance = 0;

    if (this.findtolerance > this.patience) this.mode = 0;

    if (dist > 3) {
      if (dist > this.sight) {
        if (this.shootcooldown <= 0 && darts>0 && ((dir in {0,2} && abs(dx) < 2) || (dir in {1,3} && abs(dy) < 2))) {
          shootarrow dir;
          darts--;
          this.mode = 4;
          this.freezetimer = this.bowfreezetime;
          this.shootcooldown = this.bowcooldown;
        }
        this.farchase += 0.05;
      } else {
        this.shootcooldown = 0;
        this.farchase = 0;
      }
      if (this.farchase > this.patience) this.mode = 0;
      if (!onwall2(x+.5+(1/16)+vecx(dx>0?3:1)*this.speed,y+1+(1/16),2-2/16,2-2/16)) x += (dx/dist)*this.speed;
      else x = int(x)+.5;
      if (!onwall2(x+.5+(1/16)+,y+1+(1/16)+vecy(dy>0?2:0)*this.speed,2-2/16,2-2/16)) y += (dy/dist)*this.speed;
      else y = int(y+.5);

      CheckBushes();
    }
  } else if (this.mode in {3,4,5}) {
    if (this.freezetimer == 0) this.mode = 2;
  }

  if(!strequals(#m(-1),#I(this.animations,this.mode))) setcharani #I(this.animations,this.mode),;
} else if (this.mode == 5) {
  if (!onwall2(x+.5+vecx(hurtdx>0?3:1),y+1,2,2)) x += hurtdx;
  else x = int(x)+.5;
  if (!onwall2(x+.5,y+1+vecy(hurtdy>0?2:0),2,2)) y += hurtdy;
  else y = int(y+.5);
}
if (this.shootcooldown > 0) this.shootcooldown -= 0.05;
if (this.freezetimer > 0) this.freezetimer -= 0.05;
if (this.hurtcounter > 0) this.hurtcounter -= 0.05;
else if (this.mode == 5) this.mode = 2;

}

timeout = 0.05;
}

if (washit && this.mode!=5 && hearts > 0) {
hearts -= playerswordpower/2;
if (hearts <= 0) {
this.mode = 6;
setcharani dead,;
dontblock;
} else {
dx = (x+1.5)-(playerx+1.5);
dy = (y+2)-(playery+2);
dist = (dx^2 + dy^2)^.5;
hurtdx = dx/dist;
hurtdy = dy/dist;
this.mode = 5;
this.freezetimer = this.hurtpause;
this.hurtcounter = this.hurtrecoil;
setcharani hurt,;
if (this.target == -1) FindPlayer();
}
}

function FindPlayer() {
for (i=0;i<playerscount;i++) {
if (players[i].hearts>0 && players[i].id>=0) {
dx = (players[i].x+1.5) - (x+1.5);
dy = (players[i].y+2) - (y+2);
dist = (dx^2 + dy^2)^.5;
checkdir = getdir(dx,dy);
this.agro = this.mode == 5 && this.target == -1;
if (this.agro == 1) {
if (strequals(#m(i),sword2)) {
if (dist < this.sight) this.target = i;
}
} else {
if (dist < this.sight && checkdir == dir) {
this.cansee = 1;
for (j=1;j<((dist < this.sight) ? int(dist) : this.sight);j++) {
if (onwall(x+1.5+(dx/dist)*j+.5,y+2+(dy/dist)*j+.5)) this.cansee = 0;
}
if (this.cansee == 1) {
this.target = i;
this.mode = 2;
break;
}
}
}
}
}
}

function CheckBushes() {
bushtype = -1;
px = int(x+(dir==1? -0.5: (dir==3? 3.5: 1.5)));
py = int(y+(dir==0? 0: (dir==2? 4: 2)));
for (i=0; i<(arraylen(this.bushtiles)-1)/4; i++) {
for (j=0;j<4;j++) {
if (tiles[px,py] == this.bushtiles[(i4)+j]) {
bushtype = i;
break;
}
}
}
if (bushtype >= 0) {
this.mode = 3;
this.freezetimer = this.swordfreezetime;
for (i=0;i<4;i++) {
if (tiles[px-1+(i%2),py-1+int(i/2)] == this.bushtiles[bushtype
4]) {
px = px-1+(i%2);
py = py-1+int(i/2);
break;
}
}
replacebush = true;
for (i=0;i<4;i++) {
if (tiles[px+i%2,py+int(i/2)] != this.bushtiles[(bushtype4)+i]) {
replacebush = false;
break;
}
}
if (replacebush == true) {
for (i=0;i<4;i++) {
tiles[px+i%2,py+int(i/2)] = this.bushreplace[(bushtype
4)+i];
}
putleaps 0,px,py;
updateboard px,py,2,2;
}
}
}
[/php]

I may also be posting more enemies here that function differently from your typical character baddy.

Interesting tidbit: Did you know the custom NPC baddies(placed with the editor) have some sort of makeshift, minimal pathfinding detection? I’ve been playing with them in my test level and they can do a decent job of navigating around obstacles, regardless of where I am. They almost ‘follow’ my path around things. It’s interesting, and I never really noticed it.

http://www.newgrounds.com/portal/view/340494
Bad Guys :smiley:

Also their path finding method is to move back and forth when blocked by a wall infront.

Edit: Path Finding ;D


Beware of Dangerless’ Audio.

Nah, I’m not talking about baddies, I’m talking about the scripted NPCs(that you can customize, can be carried, hurt by sword, ect…). It’s hard to explain, it’s not pretty by any means, but it surprised the hell out of me.

Also, pathfinding is always cool when it manages to be pulled off. How’s the CPU usage on it? I’ve only seen one other successful pathfinding script on Graal, and it wasn’t that fine-tuned, and I remember it taking a while to calculate steps. Yours is A*?

So Nalin, if I uploaded this script online, there would be some problems with it functioning correctly? I always have a hard time managing stuff with online because of the complications…

Works fine on Waffles. I would know, I’ve been killed by them.

Now cuts down bushes if they happen to be in his way. Updated original post with the new version.

Slightly A*, its a bit “greedy” if you notice around
It was originally slow as hell to calculate but I managed to increase the calculation speed by, hell, x100? Only other attempt at path finding I seen on graal was in the NewWorld project, which yes, was slow as hell, nearly to the point of crashing. Since this version of graal cannot have multi-dimensional arrays, its running mostly off of one array.

As for CPU intensive, not that I can notice, was fairly bad for it in the beginning. It goes slowly because it exceeds it’s search cap which I had to manually put it; Graal hates very large loops, and will automatically cap them to prevent overflows, and when it doesn’t catch them, generally the client closes out immediately. lol

If I plan to make the path finding script a little more common place, I’ll limit it’s search range so it’ll be much faster (and be disabled, for long distances).
Reason I made it was because I was bored, and craving RTS.

A spark. These guys from Zelda cling to walls – their only way to navigate. Destroy what they’re clinging to and they’ll just move on to find another wall. Currently hits for half a heart and 10 mp. No way to kill(since in Zelda only the boomerang could).

[php]// NPC made by Shiny
if (created) {
this.speed = .5; // I suggest .5, or .25… others cause bugs!
setcharani shiny_spark,;

dir = 0;
for (i=0;i<4;i++) {
if (onwall(x+1+vecx(i)*1.5,y+1+vecy(i)*1.5)) {
dir = (i - 1)%4;
break;
}
}
this.checks = {-1/16,0,0,2+1/16,2+1/16,0,0,-1/16};
dontblock;

timeout=0.05;
}

if (timeout) {
x += vecx(dir)*this.speed;
y += vecy(dir)*this.speed;

if (this.attached == 0) {
if (onwall2(x + (dir==3 ? 2 : 0),y + (dir == 2 ? 2 : 0),dir in {1,3} ? 1/16 : 2,dir in {0,2} ? 1/16 : 2)) dir = (dir-1)%4;
} else {
checkx = x + this.checks[dir2];
checky = y + this.checks[dir
2+1];
if (!onwall2(checkx,checky,dir in {1,3} ? 2 : 1/16,dir in {0,2} ? 2 : 1/16)) {
dir = (dir+1)%4;
x = int(x+.5);
y = int(y+.5);
} else if (onwall2(x + (dir==3 ? 2 : 0),y + (dir == 2 ? 2 : 0),dir in {1,3} ? 1/16 : 2,dir in {0,2} ? 1/16 : 2)) {
dir = (dir-1)%4;
x = int(x+.5);
y = int(y+.5);
}
}
this.attached = onwall2(x-1/16,y-1/16,2+2/16,2+2/16);

for (i=0;i<playerscount;i++) {
dx = (players[i].x+1.5)-(x+1);
dy = (players[i].y+2)-(y+1);
if (abs(dx) < 1.5 && abs(dy) < 1.5) {
hitplayer i,1,x+1,y+1;
players[i].mp -= 10;
}
}

timeout=0.05;
}[/php]

When I plop that into 2.17, I get an unexpected format error on all of the if (onwall) lines.

Oh… I think 2.17 was before onwall2… :frowning:

Damn.

Its because on Onwall2, which is technically an NPC-Server related command O_o

If it’s called clientside in the newer clients it gives you an area check.

onwall2 X,Y,Width,height;

Other fun things to note between 2.171 & 2.220 ;D

Client 2.171, does not support “-1” (self) as an index for #string(index); Eg: #m(-1)

Receiving Projectile Co-ordinates is different between the two clients.
2.171: “if(actionprojectile) { } #p(0) #p(1)” (co-ords are removed if it hits the player. All params are pushed up two indexes otherwise.)
2.220: if(actionpojectile2) { } #p(0) #p(1)"

Showani’s including “BODY” sprites on layer 4 and up (GUI) are placed incorrectly in 2.171, this was fixed in 2.220

The newest version being worked on right now is 2.220, though, right? Which means anything I make in the editor will then work?

Just about. Though the newer sets of GServers that allow 2.2.2.0, allow 2.1.7.1 as well by default (it can be changed in the settings.)

Just remember, 13 kilobytes ;D (fuck)

lol I actually converted your GS2 version to GS1 last year.

Hey Shiny, would you mind and fix your baddy script? I’ve run into two problems so far:

  1. The baddy actually can’t hurt you.
  2. Sometimes when you strike it when it doesn’t agro, it starts tweaking out and repeatingly slashes its sword.

Knock yourself out :slight_smile:

No I mean, if you can. I can’t even understand half of the script myself.

Oh…

Well, if you comment on each function I COULD be able to figure it out.

___Merged doublepost__________________

Nah, I would it work it out but through a very inefficient way. Trust me, I’ve done this kind of work before.