News | Forum | People | FAQ | Links | Search | Register | Log in
Coding Help
This is a counterpart to the "Mapping Help" thread. If you need help with QuakeC coding, or questions about how to do some engine modification, this is the place for you! We've got a few coders here on the forum and hopefully someone knows the answer.
First | Previous | Next | Last
Application 
Your MonsterCheckEnemy needs to be running in T_Damage to have an effect on infighting. And there’s nothing in your FindTarget that ever makes “client” a monster, because checkclient only yields players. So there’s no chance of fighting breaking out spontaneously. 
@Preach 
"Your MonsterCheckEnemy needs to be running in T_Damage to have an effect on infighting. "

"And there’s nothing in your FindTarget that ever makes “client” a monster, because checkclient only yields players. So there’s no chance of fighting breaking out spontaneously. "

Can you explain these 2 points in more detail? or provide code snippets? I'm still new to this 
T_Damage 
nvm I got the prevent faction infighting in - based on the area that you mentioned before in combat.qc/T_Damage 
FindTarget 
I tried adding this , and different monsters in different factions do fight, but strangely only when they first took damage from the player

//>>> If no player found, look for monsters
if (!client)
{
//client = find(world, flags, FL_MONSTER);
client = find(world, classname, "monster_*");
while (client)
{
if (client != self && MonsterCheckEnemy(client))
{
r = range(client);
if (r != RANGE_FAR && visible(client) &&
(r == RANGE_NEAR || infront(client)))
{
break;
}
}
//client = find(client, flags, FL_MONSTER);
client = find(world, classname, "monster_*");
}
No Wildcards 
I had to wait for a chance to research this. Can now confirm that “find” does NOT support wildcards, so your code is still not ever finding monsters. Unsure how you are ever getting spontaneous fights but it isn’t via the planned route. Here’s a function that will help, it’s a hand-rolled search for a flag that fits your commented out line of code.

entity (entity start) findNextMonster = {
local entity e;
e = start;
while ( (e = nextent(e)) ) {
if( e.flags & FL_MONSTER) {
return e;
}
return world;
}
}

It might be better to build a bit more structure around it if you want something akin to checkclient, like a global that stores the candidate monster, which is rotated once a frame in StartFrame(). And don’t forget that checkclient does a visibility check which is up to you to handle on the monster side. 
Thanks! It Is Working Now! 
But sadly i'm getting a very bizarre unrelated bug?

Host_Error:PR_ExecuteProgram: Null Function

I even restored the original unedited monster code and it's still having the error? 
Nvm I Figured It Out It Was An Properly Coded Animation 
 
Regarding Monster AI 
I was wondering how to improve it?

like even simple pathfinding? (funnily enough the Vore bomb has better cornering than the actual vore/monsters)

or being able to go up and down reasonable heights?

and how to change the angle before the monster can shoot? because seems grunt can shoot the player before his barrel is even pointed straight at the player?

and how to change the max & min "tilt" angles for monsters? because want some monsters to not be able to shoot at enemies at are at too steep angles



(this is what i have so far but honestly i don't really know what i am doing - very new at this)
---
float CheckMonsterJump(entity self)
{
vector start, end;

// Check ground below
start = self.origin;
end = start;
end_z = end_z - 64; // Adjust drop height check

traceline(start, end, TRUE, self);

if (trace_fraction == 1)
return FALSE; // Too high to drop

if (trace_fraction < 1)
{
// Safe to drop - within acceptable height
if ((start_z - trace_endpos_z) <= 64)
{
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.velocity_z = -300; // Adjust drop speed
return TRUE;
}
}

// Check for jumpable height
start = self.origin;
end = start;
end_z = end_z + 32; // Jump check height

traceline(start, end, TRUE, self);

if (trace_fraction == 1)
{
self.velocity_z = 300; // Jump velocity
return TRUE;
}

return FALSE;
}

float TryAlternativePath(entity self)
{
vector dir, right, left, start, end;
float right_clear, left_clear;

// Get perpendicular directions
dir = normalize(self.enemy.origin - self.origin);
right = '0 0 0';
right_x = dir_y;
right_y = -dir_x;
left = -right;

// Check right path
start = self.origin;
end = start + (right * 128); // Adjust side check distance
traceline(start, end, TRUE, self);
right_clear = trace_fraction;

// Check left path
end = start + (left * 128);
traceline(start, end, TRUE, self);
left_clear = trace_fraction;

if (right_clear > left_clear)
{
self.ideal_yaw = vectoyaw(right);
return TRUE;
}
else if (left_clear > right_clear)
{
self.ideal_yaw = vectoyaw(left);
return TRUE;
}

return FALSE;
AI Tutorials 
The gold standard for quake AI tutorials are found at the “AI cafe”.

https://web.archive.org/web/20080603081043/http://minion.planetquake.gamespy.com:80/tutorial/main.htm

There are several tutorials about navigation. But bear in mind there’s no magic wand to switch perfect navigation on. Most modern games get better navigation by tagging their levels with lots of metadata. In Quake levels that don’t have any, you’ll never get the same results.

Also tutorials there about aiming. But bear in mind that the current aiming for grunts is
1 aim exactly at the player
2 “correct” the aim to slightly behind where they are now

The point I’m circling is that fun AI needs to be imperfect. Making them shoot the way they are facing is a simple change. The hard part is making them rotate in such a way that they are still fun to fight when they fire straight. 
Example Of Grunt's Aiming Leeway 
https://imgur.com/a/DMMkpxO

if you sneakup behind grunt and he shoots, seems there's a 60d arc where it's considered "pointing the gun at you"

This also applies to enforcers and such

trying to reduce this to 5d or less 
^It's FacingIdeal In AI.qc 
delta 
Bug Where Monster Turns Into Solid BSP But Is Still Alive 
I was following one of the tutorials to add strafing to monsters,and i think it might have caused this bug - but i am not familiar with

nothing looks wrong?

have any ideas?

if it helps i vaguely remember reading that if a fiend kills a nuclear box, it took becomes treated as BSP event though it's still alive? 
Nvm I Found The Cuase, It's FoundTarget ... I Can't See What's Wrong? 
void () FoundTarget = //causes invincible bug??
{
local entity head; //new
local float alertRadius; //new

if ( (self.enemy.flags & FL_MONSTER) || (self.enemy.classname == "player") || (self.enemy.classname == "hologram") || (self.enemy.classname == "cbot") || (self.enemy.classname == "CBot_Create")) //new
{
// let other monsters see this monster for a while
sight_entity = self;
sight_entity_time = time;
}
self.show_hostile = time + 2.00; // was 1 wake up other monsters
alertRadius = 512; // Large radius for alert spread (default was usually 1000)?


// Alert all monsters in radius
head = findradius(self.origin, alertRadius);
while(head)
{
if (head.flags & FL_MONSTER)
{
if (head != self) // Don't alert self
{
// Make other monsters aggressive
head.show_hostile = time + 2;
head.enemy = self.enemy;
head.think = head.th_run;
head.nextthink = time + random() * 0.5; // Stagger response times

// Chance to alert even more distant monsters
//if (random() < 0.3) // 30% chance
// {
// head.noise_time = time + 2; // Make noise to attract others
// }
}
}
head = head.chain;
}
//end new


SightSound ();
HuntTarget ();
}; 
Clarification 
I can't see anything in the code you posted that looks relevant. Are you getting a error message like "MOVETYPE_PUSH with a non bsp model" or are you just seeing a monster that's frozen solid and invulnerable?

If it's the latter, it can be helpful to get a snapshot of the entity data of the frozen monster. One way to do that without much fuss is to save the game once you've reproduced the error, then open the save file in a text editor. Then after a bit of searching for the classname you should find the stuck entity. Easier to do in a test map with a small number of monsters! 
 
the monster is still alive and attacking, but when you hit with an axe, has the "plink" sound like you are hitting a wall & invincible...

also is "MOVETYPE_PUSH with a non bsp model" is this fixable? 
Error 
If you had the "MOVETYPE_PUSH with a non bsp model" error you would know it - the game crashes to console with that text.

My guess is that your oddball monsters come from the following sequence of events:
1) A monster is killed and starts its death animation
2) Another monster wakes up nearby and runs FoundTarget
3) FoundTarget finds the dying monster and changes its animation from dying to running

In step 3 you've quite literally reanimated the dead! When a monster dies, it turns non-damageable immediately, but usually becomes non-solid at some point midway through the death animation. So if you hit the right timing you'd get the behaviour you report. Fix is to exclude dead monsters from the process. 
About Pushable Models 
"MOVETYPE_PUSH with a non bsp model" is this fixable?

So if I make monster corpses pushable it will cause this error?

I know shubnigurrath caused this sometimes... 
Who Is Pushing 
I think you misunderstand MOVETYPE_PUSH. It's not to make a thing be pushable, it's designed for things like doors and platforms that do the pushing. Don't make corpses MOVETYPE_PUSH, or anything else that isn't a BSP object for that matter.

Yes, the teleport train is MOVETYPE_PUSH without being a BSP object, but that's why it sometimes crashes! It was a bad idea, probably done in a hurry to get Quake out of the door.

You can read some more on the issue here, but bear in mind that the fix discussed on the page is an engine-level fix. If you're writing a mod that doesn't prescribe which engine to use, you can't benefit from this.
https://www.quake-info-pool.net/q1/qfix.htm#movetype_push 
Proximity Mine Help (based Off SoA Prox Launcher Code) 
Basically it's overall working but
the key issues are
it never seems to stick on world (it otherwise behaves properly more or less), and thus the arming and stick sounds never play. Also even if it doesn't stick the arming sound never plays

besides that it properly triggers on enemies, bounces off doors & enemies etc, and works more or less...

I've been working on this for 9+h and can't figure it out!

void() ProximityBomb =
{
local entity head;
local float blowup;

if (vlen(self.spawnmaster.velocity)>0)
{
ProximityGrenadeExplode();
self.think();
return;
}
if (time > self.delay)
{
mine_disarm();
self.think();
return;
}


self.touch = SUB_Null; //new
self.owner = world;
self.takedamage = DAMAGE_YES;
head = findradius(self.origin, PROX_MINE_RADIUS); //sensor radius
blowup = 0;

while (head) //what to look for
{

// Don't detonate on players or other mines
if ((head != self) && (head.health > 0) && (head.flags & (FL_CLIENT|FL_MONSTER)) && (head.classname != self.classname) && (head != self.lastvictim)) //(head.classname != "player"))
{
blowup = 1;
}


traceline(self.origin,head.origin,TRUE,self);
if (trace_fraction != 1.0)
blowup = 0;


if (blowup==1)
{

ProximityGrenadeExplode();
self.nextthink = time + 0.5;
return;
}
head = head.chain;
}
self.nextthink = time + 0.25;
}; 
 
void() ProximityArmed = //new
{
sound(self, CHAN_WEAPON, "weapons/proxarmed.wav", 1, ATTN_NORM);
self.think = ProximityBomb;
self.nextthink = time + 0.25;
};



void() ProximityGrenadeTouch =
{
if (other == self || other == self.owner) //|| other == self.owner
{
sound (self, CHAN_WEAPON, "weapons/proxbounce.wav", 1, ATTN_NORM); // bounce sound
return;
}

if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
// Don't collide with other mines
if (other.classname == self.classname)
{
sound (self, CHAN_WEAPON, "weapons/proxbounce.wav", 1, ATTN_NORM); // bounce sound
self.movetype = MOVETYPE_BOUNCE; // Add this line
return;
}


// Check if it's a brush entity (like doors)
if (other.movetype == MOVETYPE_PUSH) //|| other.solid == SOLID_BSP
{
sound(self, CHAN_WEAPON, "weapons/proxbounce.wav", 1, ATTN_NORM); // bounce sound
self.movetype = MOVETYPE_BOUNCE; // Add this line
return;
}

if (other.flags & FL_CLIENT) //user disarm mine
{
//self.solid = SOLID_NOT;
mine_disarm();
//remove(self);
return;
}

if ((other.takedamage == DAMAGE_AIM || other.solid == SOLID_TRIGGER || other.flags & FL_MONSTER )) //don't det on monsters || other.flags & FL_CLIENT
{
sound(self, CHAN_WEAPON, "weapons/proxbounce.wav", 1, ATTN_NORM);
//self.movetype = MOVETYPE_BOUNCE;
T_Damage(other, self, self.owner, 1);
//self.think();
return;
}


self.movetype = MOVETYPE_TOSS;
if (self.state == 1)
return;

if (vlen(other.velocity) > 0)
{
ProximityGrenadeExplode();
self.think();
return;
}

else
{

sound (self, CHAN_WEAPON, "weapons/proxstick.wav", 1, ATTN_NORM); // bounce sound
self.movetype = MOVETYPE_NONE;
setsize (self, '-5 -5 -5', '5 5 5');
self.state = 1;
self.spawnmaster = other;

self.velocity = '0 0 0';

// Start arming sequence
self.think = ProximityArmed;
self.nextthink = time + 2;

self.touch = SUB_Null; //new
self.solid = SOLID_NOT; //new
}
}; 
 
void() ThrowProxMine =
{
float x;

// Check if enough time has passed since last throw
if (self.last_mine_time > time - 1.5)
return;

// Check if player has enough ammo
if (self.ammo_rockets2 < 1)



local entity missile; //, mpuff;

self.currentammo = self.ammo_rockets2 = self.ammo_rockets2 - 1;
self.last_mine_time = time; // Update last throw time

sound (self, CHAN_BODY, "zombie/z_shot1.wav", 0.8, ATTN_STATIC);

self.punchangle_x = -2;

missile = spawn ();
missile.owner = self;
missile.lastvictim = self;
missile.movetype = MOVETYPE_TOSS;// MOVETYPE_BOUNCE;
missile.solid = SOLID_BBOX;
missile.classname = "proximity_mine";
missile.takedamage = DAMAGE_YES; //DAMAGE_NO;
missile.health = 2;
missile.state = 0; //not armed

// set missile speed
makevectors (self.v_angle);


if (self.v_angle_x)
missile.velocity = v_forward*400 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10;
else
{
missile.velocity = aim(self, 10000);
missile.velocity = missile.velocity * 400;
missile.velocity_z = 200;
}
missile.avelocity = '100 400 100';


missile.angles = vectoangles(missile.velocity);

missile.touch = ProximityGrenadeTouch;
// set missile duration
missile.nextthink = time + 2; //min time until mine can explode from enemy
missile.think = ProximityBomb; //arm
missile.th_die = ProximityGrenadeExplode;
missile.delay = time + 90; // time until mine auto disarms

setmodel (missile, "progs/proxmine.mdl");
setorigin (missile, (self.origin) + (v_forward * 0) + (v_right * -17) + '0 0 10');

setsize (missile, '-4 -4 -4', '4 4 4');

}; 
Debugging 
The difficult part with your ProximityGrenadeTouch function is there are so many return statements which MIGHT end the function, and the bit of code that makes it stick to the wall is at the very end.

I would recommend you add some dprint statements to the function after each "checkpoint" - each place where the function has avoided a branch that contains a return. Then you can check how far execution gets through the function when you throw one against a wall, and deduce which exit is being taken unexpectedly. Two tips
a) pull down the console as soon as the bomb hits a wall for the first time, as you'll get a huge pile of debug messages if you let it bounce all over the shop first
b) remove the debug statements once you have solved the mystery, or they will be a pain in future 
I See, Thanks! 
Another issue is... is it possible to dynamically spawn monsters real time via impulse? (like half life's impulse 76)

I tried adding the monster's model precache to void () worldspawn, void () StartFrame , but i keep getting "host error pf precache can only be done in spawn functions?" 
StartFrame Timing 
StartFrame runs every frame! If you put a precache in there, you will hit the error for a late precache just about as fast as it's possible to hit it. Stick to worldspawn and you'll have a better time.

But as long as you time the precaches correctly (always on frame 0, and never afterwards), you can spawn monsters dynamically, in essentially the same way as creating a rocket or grenade. One small catch is that you can't update the total_monsters count after the first frame - so your monster kills can go over 100% unless you ensure that dynamically spawned monsters don't count for the kill count.

Another tricky wrinkle to deal with is where to place the monster so that it isn't stuck. Usually that's the responsibility of the mapper! One thing's for sure, you'll have to come up a compromise, and here's the proof: imagine that we make a map that's just a box 8 units larger than the player. It's physically impossible to successfully spawn a monster into that map. 
Fixed Precaches 
One other thing to warn you - don't ever randomise your precaches! Imagine you had some plan to randomly select one monster which your impulse spawns for the duration of the map, and you make the selection in frame 0 then precache just the assets for the selected monster. This exposes you to an interesting bug!

The bug arises when you save and load the game. In particular when you load the game, the engine:
1) Runs the first few frames of the map as if you had started from scratch
2) Reads the entity states from the save file and shuffles everything around to match it
The point of 1) is mainly to precache everything again. But second time round you might roll a different number on the random monster table and precache different models. Things may start showing up with the wrong models, or if you are less lucky the whole thing just crashes.

The only way to avoid this problem is to make the precache sequence entirely deterministic. If you're married to the random spawnable monster idea, you might have to precache the models for all the options, even if only one is ever used at a time.

And there are other more subtle ways to hit this error. Imagine you have an entity which you only allow a 50% chance of manifesting, randomised on each map load. Even if you have so many of this entity that one spawns every time, random variation might change the order in which models are first precached, as the first manifestation may occur before or after other fixed entities. Changing the order of precache messes up which models appear after a save is reloaded. 
First | Previous | Next | Last
You must be logged in to post in this thread.
Website copyright © 2002-2025 John Fitzgibbons. All posts are copyright their respective authors.