Monsters vs. Monsters
Moderator: InsideQC Admins
15 posts
• Page 1 of 1
Monsters vs. Monsters
Whats the easiest way to get monsters to fight each other?
Welcome to the Overlook Hotel: The-Overlook-Hotel.game-server.cc
-

redrum - Posts: 410
- Joined: Wed Mar 28, 2007 11:35 pm
- Location: Long Island, New York
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.
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
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.
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
-

redrum - Posts: 410
- Joined: Wed Mar 28, 2007 11:35 pm
- Location: Long Island, New York
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:
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.
- 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!
-

MauveBib - Posts: 634
- Joined: Thu Nov 04, 2004 1:22 am
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!
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
-

redrum - Posts: 410
- Joined: Wed Mar 28, 2007 11:35 pm
- Location: Long Island, New York
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/
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
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.
-

ajay - Posts: 559
- Joined: Fri Oct 29, 2004 6:44 am
- Location: Swindon, UK
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.
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
How do I implement the new code?
I added it to this code:
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?
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
-

redrum - Posts: 410
- Joined: Wed Mar 28, 2007 11:35 pm
- Location: Long Island, New York
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:
The code you've written is what would be appropriate if FindMonster were a float().
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!
-

MauveBib - Posts: 634
- Joined: Thu Nov 04, 2004 1:22 am
I tried this:
Maybe a QW thing?
- 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
-

redrum - Posts: 410
- Joined: Wed Mar 28, 2007 11:35 pm
- Location: Long Island, New York
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:
Try this for a failsafe:
- Code: Select all
if (!self.enemy)
self.enemy = FindMonster();
if (self.enemy)
{
FoundTarget();
return;
}
Apathy Now!
-

MauveBib - Posts: 634
- Joined: Thu Nov 04, 2004 1:22 am
15 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 1 guest
