Multiply / clone enemies

Discuss programming in the QuakeC language.
Post Reply
Seven
Posts: 301
Joined: Sat Oct 06, 2007 8:49 pm
Location: Germany

Multiply / clone enemies

Post by Seven »

Dear all,

I was thinking about a way to bring some more variation into Quake.
So I looked for a way to vary their positions a little bit.
Of course, their positions are declared by the map makers and most of the time, the positions have a reason.
Usually the monster shall suprise you (from a hidden place) :)

I did not want to spoil this, but tweak it a bit. I stumbled over this very interesting function in Mission pack 1 source:
gremlin_split (); (it clones the gremlin)

So I had the idea to make every monster "clone-able" in Quake.

You now have the possibility to multiply the monsters inside a map via auto_cvar (if your engine supports it) and give it a random position around the original monster within a specific radius.
You can choose how many times the monster shall be cloned via auto_cvar. According to this, the total amount of monster per map will be doubeld, trippled, quadtrippeld, and so on.
If your engine does not support auto_cvars, you can still use the "free" original engine cvars, or use a fixed multiplier inside qc directly.

The new monster will only be placed if a suitable position is found in that specific radius. So it will not be stuck in a wall for example. The cloned monsters have the exact same properties like the orirginal. Map or ent file must not be edited.

I extended the gremlin_split (); function to be usable for all monsters, added crandom() vector positions for more positioning variation. It further also detects now if the random position is inside lava/slime. Also flying/swimming monster can now be cloned.

This short clip shows you some maps with doubled enemies:
Quake multiply enemies - YouTube

============================

This is the code:

Start the clone-ing with this call right after "walkmonster_start();" in the monsters .qc file:
For walking monsters:

Code: Select all

do_the_cloning();
For flying/swimming monsters:

Code: Select all

do_the_cloning__fly_swim ();
This is the code if you want to use a auto_cvar to enable/disable it and select how many times they shall be cloned:
First declare a new .float:

Code: Select all

.float clone_me;
And use this code:

Code: Select all

/////// clone monsters:
var float autocvar_clonemonsters = 0;	// set cvar 'clonemonsters' default to 0.  0= disabled   sets how many times monsters shall be cloned. "1" = will have 2 times as much monsters as original. "2" = will have 3 times as much monsters as original.  ...etc.

// code for walking monsters:
void() Clone =
{
   local entity clone, head;
   local float done, c, proceed;
   local vector ang, pos;

   done = 0;
   c = 0;
   ang = self.angles;
   while (done == 0)
      {
      makevectors(ang);
      pos = self.origin + (60 * v_forward) + (crandom() * 33 *v_right);		// randomize the spawn position !!
      head = findradius(pos, 35);
      proceed = 1;
      while (head)
         {
         if ((head.health > 0) && (head.flags & (FL_MONSTER | FL_CLIENT)))
            proceed = 0;
         head = head.chain;
         }
      traceline(self.origin,pos,FALSE,self);
      if (trace_fraction == 1 && (proceed == 1))
         {
         traceline(self.origin,(pos-'40 40 0'),FALSE,self);
         if (trace_fraction == 1)
            {
            traceline(self.origin,(pos+'40 40 0'),FALSE,self);
            if (trace_fraction == 1)
               {
               traceline(self.origin,(pos + '0 0 64'),FALSE,self);
               if (trace_fraction == 1)
                  {
                  traceline(self.origin,(pos - '0 0 64'),FALSE,self);
                  if (trace_fraction != 1)
                     {
                     traceline(pos,(pos - '0 0 64'),FALSE,self);
		     local vector checkforlavaslime;
		     checkforlavaslime = trace_endpos; 
		     if ((pointcontents(checkforlavaslime) != CONTENT_SLIME) && (pointcontents(checkforlavaslime) != CONTENT_LAVA))
			{
	                done = 1;
			}
                     }
                  }
               }
            }
         }
      if (done == 0)
         {
         ang_y = ang_y + 36;
         c = c + 1;
         if (c==10)
            {
            return;
            }
         }
      }
clone = spawn();
copyentity (self,clone);
total_monsters = total_monsters + 1;
setorigin(clone, pos);
};


void() do_the_cloning =
{
if (autocvar_clonemonsters <= self.clone_me)
	return;
else
	Clone(); 

self.clone_me = self.clone_me + 1;
do_the_cloning();
};


// ===================

// code for flying/swimming monsters:
void() Clone_fly_swim =
{
   local entity clone, head;
   local float done, c, proceed;
   local vector ang, pos;

   done = 0;
   c = 0;
   ang = self.angles;
   while (done == 0)
      {
      makevectors(ang);
      pos = self.origin + (60 * v_forward) + (crandom() * 33 *v_right) + (crandom() * 25 *v_up);	// randomize the spawn position !!
      head = findradius(pos, 35);
      proceed = 1;
      while (head)
         {
         if ((head.health > 0) && (head.flags & (FL_MONSTER | FL_CLIENT)))
            proceed = 0;
         head = head.chain;
         }
      traceline(self.origin,pos,FALSE,self);
      if (trace_fraction == 1 && (proceed == 1))
         {
         traceline(self.origin,(pos-'40 40 0'),FALSE,self);
         if (trace_fraction == 1)
            {
            traceline(self.origin,(pos+'40 40 0'),FALSE,self);
            if (trace_fraction == 1)
               {
                  done = 1;
               }
            }
         }
      if (done == 0)
         {
         ang_y = ang_y + 36;
         c = c + 1;
         if (c==10)
            {
            return;
            }
         }
      }
clone = spawn();
copyentity (self,clone);
total_monsters = total_monsters + 1;
setorigin(clone, pos);
};


void() do_the_cloning__fly_swim =
{
if (autocvar_clonemonsters <= self.clone_me)
	return;
else
	Clone_fly_swim(); 

self.clone_me = self.clone_me + 1;
do_the_cloning__fly_swim ();
};
Have fun,
Seven.
ooppee
Posts: 70
Joined: Thu Oct 28, 2010 2:57 am

Re: Multiply / clone enemies

Post by ooppee »

Will have to give this a try sometime.

You should have all enemies take damage at the start of the function too (both the spawner and the spawned). Even make it 0 damage. This would make them run around and make the game even more chaotic.
frag.machine
Posts: 2126
Joined: Sat Nov 25, 2006 1:49 pm

Re: Multiply / clone enemies

Post by frag.machine »

There are a number of ways to"improve replay value" in Quake in theory. None of them usually results in good gameplay tho.

Example: Some time ago me and Entar tried to add some "unpredictability" to existing maps, just making monsters "walk away" from their start point after some time. No big deal in terms of gameplay balance, one would think. This leaded to funny situations, usually monsters facing walls in a pathetic way. Unfortunately, most of time the net result wasn't better than original.

I still believe random monster roaming has a big potential to add replay value in a map, but the map must be projected to support it. Most id maps don't fall on this category, so it's not just a case of QuakeC work IMHO.

The first big problem with simple monster cloning is to break the resource x obstacle balance: place fewer monsters and the map loses a lot of challenge; place too many monsters, and it becomes impossible to play.

Another alternative would be to replace monsters with others more or less in the same difficulty scale: a dog for a soldier, an ogre for a hell knight. Problem is, this quickly becomes complicated, because one monster usually won't replace another directly (it's not only a matter of "how many hp does the monster have x how many ammo do I have" - an ogre is basically a melee monster, a hell knight is more challenging at ranged combat, etc.).

As I said, it's quite a challenge. But is an interesting one. I'd like to hear about other experiments and/or theories...
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Re: Multiply / clone enemies

Post by goldenboy »

You could write a wrapper for something like Quoth that replaces id1 monsters in strategic spots with Quoth monsters. I once did something like that for RMQ, simply by intercepting the spawn functions, grabbing some monster's origin (can be found via GPL maps) and bending the spawn function towards an RMQ monster.

This allows you to fine tune where what replacement is spawned without changing it in the actual maps, and without upsetting the balance.

Theoretically you could even insert Lardarse's randomizer in the same way and get truly randomized monsters in id1 maps, again without upsetting the balance.

Unfortunately there is no Quoth source. 8) Ouch.
Spirit
Posts: 1065
Joined: Sat Nov 20, 2004 9:00 pm
Contact:

Re: Multiply / clone enemies

Post by Spirit »

Keep in mind that randomising monsters on each map launch is drastically different from the vanilla game. At least for me learning a map and trying again and again is a huge part of the fun nowadays (not on my first playthrough obviously, there I used quickloading). "Volatile" monster placements make the game much more random and unpredictable. I am not saying that it would be bad at all, just very different.

edit: Of course different monster placement affects the balance. When I map I try to think of the ways a player takes until he encounters specific monsters and place items accordingly.
Improve Quaddicted, send me a pull request: https://github.com/SpiritQuaddicted/Quaddicted-reviews
Seven
Posts: 301
Joined: Sat Oct 06, 2007 8:49 pm
Location: Germany

Re: Multiply / clone enemies

Post by Seven »

Thank you very much for your comments.

A real monster replacement (in terms of exchanging monster types) will indeed most probably lead to a gameplay change in that map.
It will amplify the variation, but you will have to pay a price for it.
I think this is what gb and spirit are saying. I fully agree.

That was the reason why I wanted to keep it subtle and as close to original map-makers idea as possible.
Cloning the same monster and place it near the original is maybe a good compromise...
The backpack throwing is also enabled for the clones, so the ammo resupply should be sufficient (at least in ID1 maps)

The cloning itself works also fairly well in fan-made maps. The probelm with ammo supply gets serious though,
due to high difficulty, which is unfortunately common in fan-made maps (even on easy)... (at least for me :roll: )


@ frag.machine

I am not sure if I understood your last sentence correctly.
If you asked for more ideas to make the gameplay more varied, I also tried these:
- add semi-transparent monsters (which brings back the shocking moments which we all had when playing Quake the 1st time)
- multimodel and multiskin support for each monster-type (every monster is spawned with randomly use of external replacement skins/models)
That brings some variation into the game, but keeping the original feel.


@ OoPpEe

I made something similar by accident and the result was that the monsters walked in chaotic ways. Also where they should not walk, lol.
If a map has no waypath declared for them, an ai_walk(x) ing monster is dumber than dumb, hehe.

Kind regards,
Seven
wjbr
Posts: 2
Joined: Sat Oct 06, 2012 12:57 am

Re: Multiply / clone enemies

Post by wjbr »

You could try controlling the cloning with some AI.

Following the Left for Dead example.

If you have lots of health and ammo, spawn more monsters.


I've been playing Wolfire's Receiver recently and the random monsters are great fun.
Post Reply