monster_spawn function?

Discuss programming in the QuakeC language.
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

monster_spawn function?

Post by ceriux »

yes im making a wave based mod for my own use, but the problem iv come across is this. i cant get it to work! i tried doing if (wave == x) {monster_army} this obviously doesn't work. so iv been trying to use e = spawn(); and in monster_army i set entity = soldier; . but i think this just gave me an error when trying to call soldier = spawn(); from my monster_spawn function. what am i doing wrong? what do i need to do to fix it?
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: monster_spawn function?

Post by Baker »

DMSP is a mod that spawns monsters in deathmatch maps, which is why it is called DMSP. (Deathmatch single player). http://strlen.com/maps/dmsp/index.html

Back story done, continuing ... this mod spawns monsters out of thin air.

1) dmsp.qc thread: http://forums.inside3d.com/viewtopic.php?t=2587 where hondobondo shares dmsp.qc which I guess he got from Aardappel.
2) This .qc file is in the download here: http://www.moddb.com/members/hondobondo ... collection
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
andrewj
Posts: 133
Joined: Mon Aug 30, 2010 3:29 pm
Location: Australia

Re: monster_spawn function?

Post by andrewj »

I think it would go something like this:

Code: Select all

  self = spawn();
  self.origin = '0 0 0';  // put some calculation here!
  monster_army();
Ace12GA
Posts: 56
Joined: Sat Jan 28, 2012 12:08 am

Re: monster_spawn function?

Post by Ace12GA »

I wrote some code that semi randomizes the monster spawns for soldiers, dogs, and enforcers, in single player maps. Its in this thread: http://forums.inside3d.com/viewtopic.php?f=2&t=4776

Essentially I used some logic to determine where near an existing spawn I could spawn a monster, and randomly picked how many to spawn. It is straight forward enough, and effective.
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Re: monster_spawn function?

Post by ceriux »

thanks for the info guys ill be sure to look into it all.
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Re: monster_spawn function?

Post by ceriux »

so basically i have to make a selectspawnpoint for my monsters. similar to the one for the player. then dependent on the wave tell it to spawn one? thats okay, but what if i wanted to spawn 3 or 4 of one monster or 2 of one and 3 of another?
Ace12GA
Posts: 56
Joined: Sat Jan 28, 2012 12:08 am

Re: monster_spawn function?

Post by Ace12GA »

The trick is to spawn the monster you want.

Code: Select all

local entity new, tmp;
new = spawn();
new.classname = "monster_army";
new.angles = self.angles;
setorigin(new, neworigin);
tmp = self;
self = new;
monster_army_spawn();
new = self;
self = tmp;
spawncount = spawncount + 1;
So in this code which is called by the entity in the map, I create two entities, the new entity I am setting up, and the tmp one to hold onto self while I use it to pass off to the monster spawn code. So, in order:

1) Create the two new entities

2) Spawn the new entity

3) Set location, rotation, and importantly, classname.

4) Preserve self entity in the tmp entity

5) Set self to the new entity

6) Call the monster spawn function to complete setting up the monster and spin him off.

7) reset self back from the tmp entity.

At this point, rinse, reuse, repeat. This method should work in a loop with any monster, the trick is of course to spawn the correct monster type you want. So you might do something like this:

Code: Select all

local float randint, soldiers, dogs, ogres;
soldiers = 0;
dogs = 0;
ogres = 0;

randint = rint((random() * 3) + 1);
while (soldiers < randint)
{
    //Spawn Soldier Code here
    soldiers = soldiers + 1;
}

randint = rint((random() * 3) + 1);
while (dogs < randint)
{
    //Spawn Dog Code here
    dogs = dogs + 1;
}

randint = rint((random() * 3) + 1);
while (ogres < randint)
{
    //Spawn Dog Code here
    ogres = ogres + 1;
}
You would still need code to determine where to spawn the entities, and if you even can. So check for other monsters there, walls, etc...
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Re: monster_spawn function?

Post by ceriux »

so, creating a wave based monster spawner is seriously this indepth? it seems like it should be easier... ugh my qc is rusty again..

also im not sure if iv seen qc that uses while? and why do you need a random number?

also found this, going to look through it as i try to understand your code. just to see if its any similar so i can compare and learn something.

and does anyone think it would be easier to adapt something like this to my purpose? instead of spawning a bot that acts like a monster actually use it to spawn my monsters? http://quakewiki.net/archives/aicafe/tu ... tutor9.htm
Ace12GA
Posts: 56
Joined: Sat Jan 28, 2012 12:08 am

Re: monster_spawn function?

Post by Ace12GA »

I used random just to create a certain degree of randomness to how many of each monster is spawned. It was purely an example. Yes, qc can use while loops. The id code does it, I do it, and it works quite nicely. It would be insanely hard to do a lot of things without a looping structure or two.
ajay
Posts: 559
Joined: Fri Oct 29, 2004 6:44 am
Location: Swindon, UK

Re: monster_spawn function?

Post by ajay »

My current mod, which is basically a wave gametype, cunningly hidden in a story, has avoided all this spawning malarky while I've been testing and just used teleporting monsters. It can't really stay like that, as it gets bigger and more complicated. This code is properly helpful, though, being a control freak with a 'story' to tell, I'll not be randomising
frag.machine
Posts: 2126
Joined: Sat Nov 25, 2006 1:49 pm

Re: monster_spawn function?

Post by frag.machine »

ceriux wrote:so, creating a wave based monster spawner is seriously this indepth? it seems like it should be easier... ugh my qc is rusty again..

also im not sure if iv seen qc that uses while? and why do you need a random number?

also found this, going to look through it as i try to understand your code. just to see if its any similar so i can compare and learn something.

and does anyone think it would be easier to adapt something like this to my purpose? instead of spawning a bot that acts like a monster actually use it to spawn my monsters? http://quakewiki.net/archives/aicafe/tu ... tutor9.htm

Spawning monsters out of thin air (or at least from a info_player_start) is not hard, provided you take some precautions.

First, if you just call the monster spawning function directly (monster_dog(), monster_army(), etc.), it will crash in most engines because it will try to precache all required media during runtime - a big no no, even in more robust engines like DP. So, to fix this, precache all monster stuff in the main() function at world.qc. Then, I use a small trick to not break hell loose with the precache_* functions. In defs.qc find this:

Code: Select all

string(string s) precache_sound        = #19;
string(string s) precache_model        = #20;
string(string s) precache_file     = #68;  
string(string s) precache_model2    = #75;      // registered version only
string(string s) precache_sound2    = #76;      // registered version only
string(string s) precache_file2     = #77;      // registered version only
and change to this:

Code: Select all

string(string s) _precache_sound        = #19;
string(string s) _precache_model        = #20;
string(string s) _precache_file     = #68;  // no effect except for -copy
string(string s) _precache_model2    = #75;      // registered version only
string(string s) _precache_sound2    = #76;      // registered version only
string(string s) _precache_file2     = #77;      // registered version only
after this, at the end of the file, add these wrapper functions:

Code: Select all

string(string s) precache_sound =
{
    if (time < 3)
    {
        return (_precache_sound (s));
    }

    return (string_null);
};

string(string s) precache_model =
{
    if (time < 3)
    {
        return (_precache_model (s));
    }

    return (string_null);
};

string(string s) precache_file =
{
    if (time < 3)
    {
        return (_precache_file (s));
    }

    return (string_null);
};

string(string s) precache_sound2 =
{
    if (time < 3)
    {
        return (_precache_sound2 (s));
    }

    return (string_null);
};

string(string s) precache_model2 =
{
    if (time < 3)
    {
        return (_precache_model2 (s));
    }

    return (string_null);
};

string(string s) precache_file2 =
{
    if (time < 3)
    {
        return (_precache_file2 (s));
    }

    return (string_null);
};
This way if any precache_* function is called in the first 3 seconds, it's treated by the regular builtins, otherwise they are silently ignored.
Now is safe to call the spawn functions to any monster. Let's try something insane to test, so add this to the top of weapons.qc:

Code: Select all

// spawns a dog over the player's head
// WARNING: not tested, probably wont work from start
void () spawn_dog_over_my_head =
local entity munster;

munster = spawn ();
setorigin (munster, self.origin + '0 0 64');
musnter.classname = "monster_dog";
munster.think = monster_dog;
munster.nextthink = time + 0.1;
};
To fire your new monster spawning spell, add a call to ImpulseCommands():

Code: Select all

void() ImpulseCommands =
{
    local entity p;

    if (self.impulse >= 1 && self.impulse <= 8)
        W_ChangeWeapon ();

    if (self.impulse == 9)
        CheatCommand ();
    if (self.impulse == 10)
        CycleWeaponCommand ();
    if (self.impulse == 11)
        ServerflagsCommand ();
    if (self.impulse == 12)
        CycleWeaponReverseCommand ();
    if (self.impulse == 255)
        QuadCheat ();
    if (self.impulse == 100)
    {
        spawn_dog_over_my_head();
    }

    self.impulse = 0;
};
Lastly, "bind space impulse 100" and make dogs rain over your head :D
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Re: monster_spawn function?

Post by ceriux »

well, when i get my code bug free. ill post the src up.
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Re: monster_spawn function?

Post by ceriux »

thanks for the replys so far... i have it working kinda. cept i have a few problems...


here's my code.

Code: Select all

/*
============
SelectSpawnPoint

Returns the entity to spawn at
============
*/
entity() MonsterSpawnPoint =
{
	local	entity spot;
	local	entity thing;
	local	float  mcount;
	
// testinfo_player_start is only found in regioned levels
	spot = find (world, classname, "testplayerstart");
	if (spot)
		return spot;
		
// choose a info_player_deathmatch point
	if (coop)
	{
		lastspawn = find(lastspawn, classname, "monster_spawner");
		if (lastspawn == world)
			lastspawn = find (lastspawn, classname, "monster_spawner");
		if (lastspawn != world)
			return lastspawn;
	}
	else if (deathmatch)
	{
		spot = lastspawn;
		while (1)
		{
			spot = find(spot, classname, "monster_spawner");
			if (spot != world)
			{
				if (spot == lastspawn)
					return lastspawn;
				mcount = 0;
				thing = findradius(spot.origin, 32);
				while(thing)
				{
					if (thing.classname == "monster")
						mcount = mcount + 1;
					thing = thing.chain;
				}
				if (mcount == 0)
				{
					lastspawn = spot;
					return spot;
				}
			}
		}
	}

	if (serverflags)
	{	// return with a rune to start
		spot = find (world, classname, "monster_spawner");
		if (spot)
			return spot;
	}
	
	spot = find (world, classname, "monster_spawner");
	if (!spot)
		error ("WARNING: no monster_spawner on level");
	
	return spot;
		
	
};

Code: Select all

// ------------------------------------------------
void() create_armybot =
// ------------------------------------------------
{

local entity bot, spawn_spot;

// start entity and place it in world 
	bot = spawn();
	spawn_spot = MonsterSpawnPoint ();
	bot.origin = spawn_spot.origin + '0 0 1';
	bot.angles = spawn_spot.angles;
	bot.fixangle = TRUE;	
	spawn_tfog (bot.origin);
	spawn_tdeath (bot.origin, bot);
	

// set size and shape
	setsize (bot, VEC_HULL2_MIN, VEC_HULL2_MAX);
	bot.solid = SOLID_SLIDEBOX;
	bot.movetype = MOVETYPE_STEP;
	setmodel(bot, "progs/soldier.mdl");
	bot.flags = bot.flags | FL_MONSTER;
	bot.takedamage = DAMAGE_AIM;

// define his animation
	bot.th_stand = army_stand1;
	bot.th_walk = army_walk1;
	bot.th_run = army_run1;
	bot.th_die = army_die;
	//bot.th_melee = ogre_melee;
	bot.th_missile = army_atk1;
	bot.th_pain = army_pain;
	bot.health = 30;

// polish him up
	bot.classname = "monster";
	bot.ideal_yaw = bot.angles * '0 1 0';
	bot.yaw_speed = 120;
	bot.view_ofs = '0 0 22';
	//bprint("an ogre joins the game\n");

// begin his thinking
	bot.nextthink = time + 0.1 + random();
	bot.think = bot.th_walk;
	
};

Code: Select all

/*
==============================================================================

gamerules

==============================================================================
*/

void () waverules =
{
	if (waves == 0 && mkills == 1)
		waves = 1;
		mkills = 0;
	if (waves == 1 && mkills == 2)
		waves = 2;
		mkills = 0;
		
};

void () monster_spawner =
{
	local float soldiers;
	
	self.classname = "monster_spawner";
	
	
	while (waves == 0)
	
		while (soldiers < 1)
		{
		create_armybot();
			soldiers = soldiers +1;
				return;
		}
	
	
		while (waves == 1)
		
		return;
		
		
			while (waves == 2)
				
				return;
				
				
		};
okay... what this currently does is create a bot that behaves like a monster (monster_army) , the problem that im having is that 1 its moving MY spawn location to that of the monster_spawner, and instead of just spawning 1 monster_army, its spawning one in each location that there is a monster_spawner...

ps: i just fixed the problem with it switching my spawn spot!

lmao i had it like 99% fixed then this happened xD

Image

okay back to 99% fixed. i'm thinking i have to remove the corpses. cause monsters arnt spawning while corpses remain. if they gib... they continue to respawn (i think) so for a temp fix for the moment. ill be gibbing bodies on death. just to see if it works. nope...

so now that i have things fixed-ish.. on the first wave 1 monster spawns as expected.. on the 2nd wave 2 spawns just as expected... on the 3rd wave... only 1 spawns ? why?

code update : http://pastebin.com/TMjUfNJg

I BELIEVE I HAVE IT FIXED! If anyone wants src for what i have for your own use let me know! right now it only supports 1 monster, but others should be easy changes!
ceriux
Posts: 2230
Joined: Sat Sep 06, 2008 3:30 pm
Location: Indiana, USA

Re: monster_spawn function?

Post by ceriux »

okay in attempting to make my wave coding easier, i tried setting soldiers back to 0 at the beginning of a new round, which causes a problem like the one above where craploads of monsters keep spawn and continuously explode... how can i do this with out this problem?
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Re: monster_spawn function?

Post by goldenboy »

Just for the record, RMQ also has monster summoning code where monsters are spawned "from thin air" at runtime. I probably got the most important parts from Drake, a mod that doesn't get enough credit by the way, and perhaps some from Hipnotic or so, I forget.

Code: Select all

// gb, spawns a monster - mostly for summoning spells
// Spawn must have an external precache function (blah_cache)
// which the summoner must call >> in his own spawn function! <<
// to avoid "precaches can only be done in spawn functions"
void(vector org, void() spawnfunc, string spawnclass) SpawnMonster =
{
        local entity oldself;
        oldself = self;

        // gb, send totalmonsters update because most clients only check once when connecting

        total_monsters = total_monsters + 1;

        WriteByte (MSG_ALL, SVC_UPDATESTAT);
        WriteByte (MSG_ALL, STAT_TOTALMONSTERS);
        WriteLong (MSG_ALL, total_monsters);

        self = spawn();

        self.flags = 0;
        self.spawnflags = 0;

        self.enemy = oldself.enemy;     // FoundTarget() called for summons in blahmonster_start

        org_z = org_z + 64;     // lift it up a bit, prevent flymonsters banging on the floor
        setorigin (self, org);

        self.spawned = TRUE;    // bypass its precaches (summoner must call those)

        self.classname = spawnclass;
        self.angles = oldself.angles + '0 180 0';       // face summoner and hopefully enemy

        spawnfunc();    // sneaky, huh?

        // switch back
        self = oldself;

        // thanks to PM, wouldn't have figured this out myself
};
as stated, this requires external precaches to avoid an error

Code: Select all

void() monster_shalrath_go =
{
        self.solid = SOLID_SLIDEBOX;
        self.movetype = MOVETYPE_STEP;

        setmodel (self, "progs/shalrath.mdl");
        setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX);

        if (self.spawned)
                self.health = self.max_health = MonsterHealth(300);             //ijed Spawned vores are weaker
        else
                self.health = self.max_health = MonsterHealth(400);

        self.th_stand = shal_stand;
        self.th_walk = shal_walk1;
        self.th_run = shal_run1;
        self.th_die = shalrath_die;
        self.th_pain = shalrath_pain;
        self.th_missile = shal_attack1;
        self.th_submerged = CastPnakoticDefensive;

        Gyro_Object_Activate (self, 100000);

        if (self.spawned)
                self.caster_level = 0;  // something was badass enough to summon us, so let's join the party ijed But without magic...

        self.magic_done = 1;

        walkmonster_start ();
};

// support summoning
void () shalrath_cache =
{
        precache_model2 ("progs/shalrath.mdl");
        precache_model2 ("progs/h_shal.mdl");
        precache_model2 ("progs/v_spike.mdl");
        precache_model2 ("progs/deathslither.mdl");     //new spell

        precache_sound2 ("magic/deathslither_hit.wav");
        precache_sound2 ("shalrath/attack.wav");
        precache_sound2 ("shalrath/attack2.wav");
        precache_sound2 ("shalrath/death.wav");
        precache_sound2 ("shalrath/idle.wav");
        precache_sound2 ("shalrath/pain.wav");
        precache_sound2 ("shalrath/sight.wav");

        // Shn'tkk, AAahton
        precache_sound2 ("shalrath/shalstep1.wav");
        precache_sound2 ("shalrath/shalstep2.wav");
        precache_sound2 ("shalrath/shalstep3.wav");
        precache_sound2 ("shalrath/shalstep4.wav");
        precache_sound2 ("shalrath/shalstep5.wav");
        precache_sound2 ("shalrath/shalstep6.wav");
        precache_sound2 ("shalrath/shalstep7.wav");
        precache_sound2 ("shalrath/shalstep8.wav");
};

/*QUAKED monster_shalrath (1 0 0) (-32 -32 -24) (32 32 48) Ambush
*/
void() monster_shalrath =
{
        if (deathmatch)
        {
                remove(self);
                return;
        }

        if (!self.spawned)      // This has to be done like this to avoid a crash it if summons itself
        {
                shalrath_cache();       // precaches
                proto_cache();          // can summon proto-shalrath at caster_level 2, includes magic precaches
                wizard_cache();         // summons hordes of wizards
        }

        self.go = monster_shalrath_go;
        if(!CheckGroup()) self.go();
};

And a bit in blahmonster_start to make it go to FoundTarget() and deal with the monstercount

Code: Select all

void() walkmonster_start_go =
{
local string	stemp;
local entity	etemp;

	self.origin_z = self.origin_z + 1;	// raise off floor a bit
	if (!(self.spawnflags & 64))		// SPAWNED, gb
	{
		droptofloor ();
		if (!walkmove (0, 0))
		{
			dprint ("walkmonster in wall at: ");
			dprint (vtos (self.origin));
			dprint ("\n");
		}
	}
	self.takedamage = DAMAGE_AIM;

	/*
	// DRS: Monster Combat think
	// Supa, argh, don't do this, it makes Darkplaces cry! >:
	if(!self.monsterthink)
		self.monsterthink = SUB_Null;
	*/

	self.ideal_yaw = self.angles * '0 1 0';
	if (!self.yaw_speed)
		self.yaw_speed = 20;
	self.view_ofs = '0 0 25';
	self.use = monster_use;

//	self.flags = self.flags | FL_MONSTER;

	// Abort if monster got telefragged upon level placement.
	if (self.health <= 0)
	{
		print_self ("walkmonster", "killed");
		return;
	}

	if (self.spawned)	// gb, summoned...
	{
		self.nextthink = time + 0.1;
		self.think = FoundTarget;
		return;
	}

	if (self.target)
	{
		self.goalentity = self.movetarget = find(world, targetname, self.target);
		
		if (self.h2olevel < 3)	// not submerged
			self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);

		if (!self.movetarget)
			print_self ("walkmonster", "can't find target");

// this used to be an objerror
		if (self.movetarget.classname == "path_corner")
			self.th_walk ();
		else
		{
			self.pausetime = 99999999;
			self.th_stand ();
		}
	}
	else
	{
		self.pausetime = 99999999;
		self.th_stand ();
	}

	// spread think times so they don't all happen at same time
	self.nextthink = self.nextthink + random()*0.5;
	
	// gb, func_hordespawn
        if (self.awake == 1 && !(self.enemy.items & IT_INVISIBILITY) && !(self.enemy.flags & FL_NOTARGET) && self.enemy.classname == "player")
        {
               self.nextthink = time + 0.1;
               self.think = FoundTarget;
        }
};

void () walkmonster_start =
{
	if ((self.spawnflags & 3) || self.targetname)
		self.goalentity = self; //ijed Hack that stops monsters from wandering whilst asleep or ambush 
	self.flags = self.flags | FL_MONSTER;	// Supa, if it's stupid and it works..
		
	if ((self.spawnflags & 64))		// SPAWNED, gb
	{
		if (self.targetname == string_null)  //ijed Alerts mapper to lost monsters caused by broken spawns
		{
			dprint ("walkmonster at ");
			dprint (vtos (self.origin));
			dprint ("is set as spawned but has no targetname\n");
		}
		self.wad = self.model;
		self.pos1 = self.mins;
		self.pos2 = self.maxs;
		self.walkframe = self.solid;
		self.fly_sound = self.movetype;
		
		if (!self.statue)	// gb
		{
			self.model = "";
			self.solid = SOLID_NOT;
		}
		
		self.movetype = MOVETYPE_NONE;
		self.use = monster_teleport;
		self.think1 = walkmonster_start_go;
		
		if (self.statue)
		{
			self.takedamage = DAMAGE_AIM;	// this makes them awaken when shot at
			droptofloor();
		}

		if (!(self.specialflags & GOOD)		)	// Supa, friendly NPCs don't show up on killcount
		if (!(self.specialflags & IMMORTAL)	)	// + immortals too
			total_monsters = total_monsters + 1;

		return;
	}
	self.nextthink =  time + (random() * 0.5);
	self.think = walkmonster_start_go;
	
	if (!(self.specialflags & GOOD)		)	// Supa, friendly NPCs don't show up on killcount
	if (!(self.specialflags & IMMORTAL)	)	// + immortals too
	if (!(self.spawned))	// gb, summon spell sends totalmonsters update itself
		total_monsters = total_monsters + 1;
};
Post Reply