View previous topic :: View next topic |
Author |
Message |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Sun Apr 26, 2009 12:33 am Post subject: Monsters vs. Monsters |
|
|
Whats the easiest way to get monsters to fight each other? _________________ Welcome to the Overlook Hotel 69.113.123.178:27500 |
|
Back to top |
|
 |
ceriux

Joined: 06 Sep 2008 Posts: 969 Location: Florida, USA
|
|
Back to top |
|
 |
Wazat
Joined: 15 Oct 2004 Posts: 732 Location: Middle 'o the desert, USA
|
Posted: Sun Apr 26, 2009 2:03 pm Post subject: |
|
|
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. |
|
Back to top |
|
 |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Sun Apr 26, 2009 9:37 pm Post subject: |
|
|
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 69.113.123.178:27500 |
|
Back to top |
|
 |
MauveBib

Joined: 04 Nov 2004 Posts: 602
|
Posted: Sun Apr 26, 2009 9:50 pm Post subject: |
|
|
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: |
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! |
|
Back to top |
|
 |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Sun Apr 26, 2009 10:28 pm Post subject: |
|
|
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 69.113.123.178:27500 |
|
Back to top |
|
 |
Electro
Joined: 29 Dec 2004 Posts: 241 Location: Brisbane, Australia
|
Posted: Tue Apr 28, 2009 5:30 am Post subject: |
|
|
In that case, check to make sure they're not the same classname...
Code: |
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;
};
|
_________________ Unit reporting!
http://www.bendarling.net/ |
|
Back to top |
|
 |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Wed Apr 29, 2009 4:29 pm Post subject: |
|
|
Thanks! _________________ Welcome to the Overlook Hotel 69.113.123.178:27500 |
|
Back to top |
|
 |
ajay

Joined: 29 Oct 2004 Posts: 295 Location: Swindon, UK
|
Posted: Wed Apr 29, 2009 6:42 pm Post subject: |
|
|
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. _________________ my site |
|
Back to top |
|
 |
Wazat
Joined: 15 Oct 2004 Posts: 732 Location: Middle 'o the desert, USA
|
Posted: Thu Apr 30, 2009 3:55 am Post subject: |
|
|
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. |
|
Back to top |
|
 |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Mon May 04, 2009 9:45 pm Post subject: |
|
|
How do I implement the new code?
I added it to this code:
Code: | 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 69.113.123.178:27500 |
|
Back to top |
|
 |
MauveBib

Joined: 04 Nov 2004 Posts: 602
|
Posted: Mon May 04, 2009 10:00 pm Post subject: |
|
|
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: |
self.enemy = FindMonster();
if (self.enemy)
return;
|
The code you've written is what would be appropriate if FindMonster were a float(). _________________ Apathy Now! |
|
Back to top |
|
 |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Mon May 04, 2009 10:25 pm Post subject: |
|
|
I tried this:
Code: | 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 69.113.123.178:27500 |
|
Back to top |
|
 |
MauveBib

Joined: 04 Nov 2004 Posts: 602
|
Posted: Mon May 04, 2009 10:47 pm Post subject: |
|
|
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: |
if (!self.enemy)
self.enemy = FindMonster();
if (self.enemy)
{
FoundTarget();
return;
}
|
_________________ Apathy Now! |
|
Back to top |
|
 |
redrum

Joined: 28 Mar 2007 Posts: 367 Location: Long Island, New York
|
Posted: Tue May 05, 2009 12:01 am Post subject: |
|
|
ok, thanks for your help. It's working now. Just gotta make some tweaks. _________________ Welcome to the Overlook Hotel 69.113.123.178:27500 |
|
Back to top |
|
 |
|