Forum

Monsters vs. Monsters

Discuss programming in the QuakeC language.

Moderator: InsideQC Admins

Monsters vs. Monsters

Postby redrum » Sun Apr 26, 2009 12:33 am

Whats the easiest way to get monsters to fight each other?
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York

Postby ceriux » Sun Apr 26, 2009 1:38 am

classnames and targets?
User avatar
ceriux
 
Posts: 2223
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Postby Wazat » Sun Apr 26, 2009 2:03 pm

Well, monster infighting lets monsters fight each other when one hurts the other. You can make this more normal by enhancing the monster search-for-players function (can't remember what the blasted thing is called) so that it searches for other monsters too, that have a different classname or are of a monster type that this monster doesn't like (however you want to define that).

So monsters will prioritize killing the player, but if they don't find him they'll turn on each other. Makes the player's job easier -- nearly empty map soon after he spawns, and what's left is pretty hurt. :)
When my computer inevitably explodes and kills me, my cat inherits everything I own. He may be the only one capable of continuing my work.
Wazat
 
Posts: 771
Joined: Fri Oct 15, 2004 9:50 pm
Location: Middle 'o the desert, USA

Postby redrum » Sun Apr 26, 2009 9:37 pm

Here's the code:

float() FindTarget =
{
local entity client;
local float r;
local vector dist;
dist = self.enemy.origin - self.origin;

// if the first spawnflag bit is set, the monster will only wake up on
// really seeing the player, not another monster getting angry

// spawnflags & 3 is a big hack, because zombie crucified used the first
// spawn flag prior to the ambush flag, and I forgot about it, so the second
// spawn flag works as well


if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) )
{
client = sight_entity;
if (client.enemy == self.enemy)
return;
}
else
{
client = checkclient ();
if (!client)
return FALSE; // current check entity isn't in PVS
}


if (client == self.enemy)
return FALSE;

if (client.flags & FL_NOTARGET)
return FALSE;
if (client.items & IT_INVISIBILITY)
return FALSE;

r = range (client);
if (r == RANGE_FAR)
return FALSE;

if (!visible (client))
return FALSE;

if (r == RANGE_NEAR)
{
if (client.show_hostile < time && !infront (client))
return FALSE;
}
else if (r == RANGE_MID)
{
if ( /* client.show_hostile < time || */ !infront (client))
return FALSE;
}

self.enemy = client;
if (self.enemy.classname != "player")
{
self.enemy = self.enemy.enemy;
if (self.enemy.classname != "player")
{
self.enemy = world;
return FALSE;
}
}

FoundTarget ();

if (self.health <= 35) // make the blood
Bleeding1(self);

return TRUE;

if (self.watertype == CONTENT_LAVA) // do lava damage
T_Damage (self, world, world, 10*self.waterlevel);

if (self.watertype == CONTENT_SLIME) // do slime damage
T_Damage (self, world, world, 6*self.waterlevel);

if (self.watertype == CONTENT_WATER) // do water damage
T_Damage (self, world, world, 3*self.waterlevel);
};[code]

I'm an average coder, this seemingly simple task is driving me nutz!
I've tried numerous code changes to no avail.
Can someone please point me in the right direction. Thanks.
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York

Postby MauveBib » Sun Apr 26, 2009 9:50 pm

It's not quite as simple as it seems unfortunately. The function the monsters use to find players to attack is a built-in function of the quake engine called checkclient(), which returns a client entity, a different one each turn. There is no equivilent built-in function for finding monsters, so you have to make your own, something along these lines:

Code: Select all
entity() findmonster =
{
   local entity guy, found;
   local float closest;

   closest = 1000;

   guy = findradius(self.origin, closest);
   
   while(guy)
   {
      if ((guy.health > 0) && (guy != self))
      {
         if (vlen(guy.origin - self.origin < closest)
         {
            if ((visible(guy)) && (infront(guy)))
            {
               closest = vlen(guy.origin - self.origin);
               found = guy;
            }
         }
      }
      guy = guy.chain;
   }

   return found;
};


This function shouldreturn the nearest visible monster or player. It can easily be altered to just find monsters by changing this line:

if ((guy.health > 0) && (guy != self))

to this:

if ((guy.health > 0) && (guy != self) && (guy.flags & FL_MONSTER))


Do you want the monsters to only attack other monsters? Or particular other monsters? Or both monsters and the player? The answer to this question leads into how you use this function to achieve what you want.
Apathy Now!
User avatar
MauveBib
 
Posts: 634
Joined: Thu Nov 04, 2004 1:22 am

Postby redrum » Sun Apr 26, 2009 10:28 pm

ok, that clears things up a bit, thanks.

I would like that soldiers attack everything except other soldiers. Ogres attack everything except ogres.....

I'll give it a shot on my own. I'll reply back if I get into trouble!
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York

Postby Electro » Tue Apr 28, 2009 5:30 am

In that case, check to make sure they're not the same classname...

Code: Select all
entity() findmonster =
{
   local entity guy, found;
   local float closest;

   closest = 1000;

   guy = findradius(self.origin, closest);
   
   while(guy)
   {
      if ((guy.health > 0) && (guy != self))
      {
         if (vlen(guy.origin - self.origin < closest)
         {
            if ((visible(guy)) && (infront(guy)))
            {
               if (guy.classname != self.classname)
               {
                  closest = vlen(guy.origin - self.origin);
                  found = guy;
               }
            }
         }
      }
      guy = guy.chain;
   }

   return found;
};
Benjamin Darling
http://www.bendarling.net/

Reflex - In development competitive arena fps combining modern tech with the speed, precision and freedom of 90's shooters.
http://www.reflexfps.net/
Electro
 
Posts: 312
Joined: Wed Dec 29, 2004 11:25 pm
Location: Brisbane, Australia

Postby redrum » Wed Apr 29, 2009 4:29 pm

Thanks!
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York

Postby ajay » Wed Apr 29, 2009 6:42 pm

I played around with this while making the final levels of Lunkin's Journey. the plan was to have a huge mass battle between NPCs. I got them finding each other and attacking each other. My aged memory being what is, I can't remember exact details but there was a major problem with it in large groups; basically all of one group would attack just one of the other until that one died, rather than lots of different battles going on. I may be wrong, but I think they also then failed to find another from the other group to atatck, but I may ahve solved that.
User avatar
ajay
 
Posts: 559
Joined: Fri Oct 29, 2004 6:44 am
Location: Swindon, UK

Postby Wazat » Thu Apr 30, 2009 3:55 am

Well, if you are just taking the first target you find, or the closest target, then it's easy for a whole group to target one unit instead of breaking off against different targets. There are many ways to do something about that:

1) While looping through targets finding the best, closest one, add randomness. When you're comparing the distance to the current subject vs the best distance so far, multiply the distance by (1 + random()*.5). That randomly adds 50% distance and will jumble the targets a bit, causing monsters to not always pick the best target so that a group will have a better chance of splitting up their enemies. +25% random probably works just as well.

2) While looping through enemies and looking for the best target, factor the number of monsters attacking that enemy into the "best" rating. This increases the number of iterations in your loop (nxm instead of just n iterations), but it's another easy way to write the logic. If many monsters are attacking that enemy, it becomes less desirable to attack than the enemy that is a little farther away.

3) If you don't parse through all possible targets and select the best one by distance (but instead just select the first target you find that you can see, the way monsters do with players now), this is an option. Instead of starting the loop with the first entity in the list, remember the last entity that any monster searched for and start with the one after that one. This is similar to how the player selects a deathmatch spawn point.

4) Combine one of the methods above with this one: Occasionally monsters re-search for a target and switch to the new one found if it doesn't match their old one.
When my computer inevitably explodes and kills me, my cat inherits everything I own. He may be the only one capable of continuing my work.
Wazat
 
Posts: 771
Joined: Fri Oct 15, 2004 9:50 pm
Location: Middle 'o the desert, USA

Postby redrum » Mon May 04, 2009 9:45 pm

How do I implement the new code?

I added it to this code:
Code: Select all
void(float dist) ai_walk =
{
   movedist = dist;
   
   if (self.classname == "monster_dragon")
   {
      movetogoal (dist);
      return;
   }

   if (FindMonster ())                                                                       // check for noticing a monster
      return;

   if (FindTarget ())                                                                       // check for noticing a player
      return;

   movetogoal (dist);

        if (self.health <= 35)                                                                    //make the blood
           Bleeding1(self);

   if (self.watertype == CONTENT_LAVA)                                                       // do lava damage
      T_Damage (self, world, world, 10*self.waterlevel);

   if (self.watertype == CONTENT_SLIME)                                                      // do slime damage
      T_Damage (self, world, world, 6*self.waterlevel);

   if (self.watertype == CONTENT_WATER)                                                      // do water damage
      T_Damage (self, world, world, 3*self.waterlevel);
};


Now they just walk in place. I'm using QW if that makes a difference.
I noticed that this code is entity() instead of void(). Can someone explain in laymans terms what is actually happening here?
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York

Postby MauveBib » Mon May 04, 2009 10:00 pm

the key difference between entity() and void() is what is returned by the function.

A void() function returns nothing, but an entity() function (or a float() or vector() function) returns a value (be it an entity, float or vector as appropriate.

Therefore, in order to use the function you'll need to do something along the lines of:

Code: Select all
self.enemy = FindMonster();
if (self.enemy)
     return;


The code you've written is what would be appropriate if FindMonster were a float().
Apathy Now!
User avatar
MauveBib
 
Posts: 634
Joined: Thu Nov 04, 2004 1:22 am

Postby redrum » Mon May 04, 2009 10:25 pm

I tried this:
Code: Select all
void(float dist) ai_walk =
{
   movedist = dist;
   
   if (self.classname == "monster_dragon")
   {
      movetogoal (dist);
      return;
   }

        self.enemy = FindMonster();

        if (FindMonster ())
           return;

   if (FindTarget ())                                                                       // check for noticing a player
      return;

   movetogoal (dist);

        if (self.health <= 35)                                                                    //make the blood
           Bleeding1(self);

   if (self.watertype == CONTENT_LAVA)                                                       // do lava damage
      T_Damage (self, world, world, 10*self.waterlevel);

   if (self.watertype == CONTENT_SLIME)                                                      // do slime damage
      T_Damage (self, world, world, 6*self.waterlevel);

   if (self.watertype == CONTENT_WATER)                                                      // do water damage
      T_Damage (self, world, world, 3*self.waterlevel);
};

Still not working  :(

Maybe a QW thing?
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York

Postby MauveBib » Mon May 04, 2009 10:47 pm

No. The code you've used won't work, as you're not telling it what to do once it has an enemy. It has to change think functions and whatnot, which is done by the FoundTarget() function.

Try this for a failsafe:

Code: Select all
if (!self.enemy)
     self.enemy = FindMonster();

if (self.enemy)
{
     FoundTarget();
     return;
}
Apathy Now!
User avatar
MauveBib
 
Posts: 634
Joined: Thu Nov 04, 2004 1:22 am

Postby redrum » Tue May 05, 2009 12:01 am

ok, thanks for your help. It's working now. Just gotta make some tweaks.
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
User avatar
redrum
 
Posts: 410
Joined: Wed Mar 28, 2007 11:35 pm
Location: Long Island, New York


Return to QuakeC Programming

Who is online

Users browsing this forum: No registered users and 1 guest