Hi Ranger
#3484 posted by Preach on 2024/11/21 16:20:01
Gonna take a whole post over answering each of these, and I'm gonna start with "prevent specified team's monsters from infighting (while other team's monsters may still do)".
Did you know that there's already an exception to the normal infighting rules? Two Grunts will infight even though they are the same class of monster. It's the opposite way round to what you're trying to do because it causes MORE infighting. But locating the code where this exception appears is a good way to learn how to set up the rules we want.
if ( (self.flags & FL_MONSTER) && attacker != world)
{
// get mad unless of the same class (except for soldiers)
if (self != attacker && attacker != self.enemy)
{
if ( (self.classname != attacker.classname)
|| (self.classname == "monster_army" ) )
{
if (self.enemy.classname == "player")
self.oldenemy = self.enemy;
self.enemy = attacker;
FoundTarget ();
}
}
}
This code is found in combat.qc, and it applies to all monsters, so you can make a change centrally and create the team functionality.
We need a way to record which team things belong to. We could call it .float monster_team, with the idea that monster_team = 0 represents membership on no teams and each other team gets a unique number. Then we can change the test to
if ((self.classname != attacker.classname && (self.monster_team = 0 || self.monster_team != attacker.monster_team)) || self.classname == "monster_army")
That should make it so that monsters assigned the same team will forgive friendly fire from teammates rather than retaliate (although I've left the exception in place for Grunts for flavour). If you had something a bit different in mind - e.g. a "team" of drones who forgive all friendly fire and focus exclusively on the player, that's possible too. Just write a different rule for which pairs of self and attacker cause self to target attacker.
Next time I'll return to question 1, a more thorny problem...
Appendix
#3485 posted by Preach on 2024/11/22 00:04:07
The test is getting a bit complicated, so it would probably be better to refactor it into its own function
float(entity monster, entity attacker) IsAttackerFairTarget
{
if(monster.classname == "monster_army")
return TRUE; //grunts will attack anything
if(monster.monster_team > 0 && monster.monster_team == attacker.monster_team)
return FALSE; //if we belong to a team, don't attack teammates
return monster.classname != attacker.classname;
}
Preach Is Da Man!
Gotta love it when Preach "preaches"! So much information in a couple hundred words.
Monster Vs Monster
#3487 posted by Preach on 2024/11/23 00:51:42
Coming back to
1 - specific monsters are on another "team" and attack other monsters/players who are not on that "team"
In the first post we've adjusted the rules for REACTION in combat so that our teams don't fight amongst themselves. But getting them to pro-actively attack the other team on their own initiative is another story. I see two big problems
Problem 1: In regular quake, the task of searching for a client who the monster might be able to see and ruling them out if they are in a part of the map not visible from the monster's position is entirely offloaded to the engine. The function is called "checkclient" and it does the whole thing in one go.
Because it's in the engine, your mod can't change how it functions. So you'd have to write something new from scratch in QuakeC to replace it. The engine has access to additional data that QuakeC can't get at, and it exploits some of that data in checkclient - so you can't even create an exact copy in QuakeC as a baseline. It's a very tall order.
Supposing you solve problem 1, problem 2 is a more practical one. You create your map full of rooms, each populated by squads of monsters from team A facing a squad from team B. The player spawns in, and your new AI kicks in. Across the map, team A fights with team B, even when the player is nowhere nearby. A victorious team emerges in each fight, and for the player the only evidence of all this groundbreaking AI is a pile of corpses from team A, and some half-health monsters from team B.
This is probably not what you had in mind - more likely you were wanting the player to be present each time the teams fight so they can see and engage in the spectacle.
That's why I would recommend you CHEAT instead. Rather than creating brilliant AI, just script the fights a bit. Have an entity who looks for monsters with a specific targetname, and assigns them an enemy with the same targetname from the other team. Make sure you trigger this entity when the player is approaching the battlefield. And when a monster kills a monster with the same targetname, make it retrigger to pick them another target.
Attenuation
#3488 posted by Preach on 2024/11/24 22:14:26
Also how can I edit ATTN_NONE ATTN_NORM ATTN_IDLE etc?
The good news is that while the values of
ATTN_NONE/ATTN_NORM/ATTN_IDLE/ATTN_STATIC make it look like there are just discrete values for attenuation, actually the final parameter to sound is a float, and you can supply any decimal starting at 0.0 and going up to (but not including) 4.0.
One of the confusing things is getting a handle on exactly how the scale works. It's actually a backwards scale - the higher the number, the smaller the distance that the sound travels. ATTN_NONE is the absence of attenuation, which means that the sound transmits across the entire level at full volume. If you still want a drop off, but slower than normal sounds, why not try a value of 0.5 instead of ATTN_NORM?
Tiny detail: ATTN_NORM and volume of 1.0 do have a magic benefit compared to other values, although it's a fairly minor one. They actually require less network bandwidth to transmit in a packet, because they are regarded as the "baseline" values, and the protocol only transmits variation from the baseline. In single player scenarios this doesn't make a difference, but it's an interesting quirk to note.
Thx Preach
#3489 posted by ranger on 2024/11/30 16:53:17
For Monster Vs Monster teams I believe the Qreate mod added monster teams, and a few other misc mod did too?
"Across the map, team A fights with team B, even when the player is nowhere nearby. A victorious team emerges in each fight, and for the player the only evidence of all this groundbreaking AI is a pile of corpses from team A, and some half-health monsters from team B."
actually this is perfectly fine for my mod!
Attenuation
#3490 posted by ranger on 2024/11/30 16:58:51
I presume the values are?
ATTN_NONE 0.0
ATTN_NORM 1.0
ATTN_IDLE 2.0
ATTN_STATIC 3.0
and is there a chart for how far Attenuation value affects sound range?
-
so to make sure
sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1.00, ATTN_NORM);
1.00 is the volume & I can change ATTN_NORM to a number?
so
sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 0.50, 0.1);
means half volume but 10x the sound range?
and I can also use 0.01 for 100x the range?
Teams Also
#3491 posted by ranger on 2024/11/30 17:22:32
Do I have to add .float monster_team to defs.qc?
and/or add monster.monster_team to each monster in the mod?
Sounds Right
#3492 posted by Preach on 2024/11/30 21:22:42
Your code looks correct. I can't quantify exactly how much further the sound goes when you set attenuation to 0.1, but it will be a long way. Give it a try, and you can always adjust it later.
I'd recommend downloading the source code for Qreate and see how they did it. I mentioned that the built-in function in the Quake engine is called "checkclient", and the function which calls it in QuakeC is called "FindClient" found in the ai.qc file. If you look for that in the mod source, you should get some idea how they did it.
Adding field definitions to the bottom of defs.qc is a good choice. You should only define a field once. It can be used from that point onwards in the compilation. defs.qc is a particularly good choice because it's usually the FIRST file to be compiled, so all your code will be able to see the definition.
Compiler For QC?
#3493 posted by ranger on 2024/12/01 08:05:31
noob question, but how do i compile the QC code to progs.dat?
Resources
#3494 posted by Preach on 2024/12/02 21:14:57
The short version:
https://quakewiki.org/wiki/Compiling_QuakeC
The slightly longer version with an example to try out and check you got it working:
https://www.quaddicted.com/webarchive/minion.planetquake.gamespy.com/tutorial/tutor7.htm
The two differ in which compiler they recommend. I agree with the recommendation in at the first link, FTEQCC is the better of the options.
I'm So Confused With The Monster Team/faction Code
#3495 posted by ranger on 2024/12/14 06:42:12
this is what I have/did - in monsters.qc (and does this also solve infighting?)
void() AssignFaction =
{
if (self.classname == "monster_ogre" || self.classname == "monster_fiend") {
self.faction = "Goliath";
} else if (self.classname == "monster_knight" || self.classname == "monster_dog" || self.classname == "monster_hell_knight") {
self.faction = "Juggernaut";
} else if (self.classname == "monster_shambler" || self.classname == "monster_wizard" || self.classname == "monster_zombie") {
self.faction = "Atlantean";
} else if (self.classname == "monster_enforcer" || self.classname == "monster_soldier1") {
self.faction = "Goliath"; // Adding Enforcer and Soldier to Goliath
} else if (self.classname == "monster_demon1" || self.classname == "monster_scrag") {
self.faction = "Juggernaut"; // Adding Demon1 and Scrag to Juggernaut
} else if (self.classname == "monster_tarbaby" || self.classname == "monster_shalrath2") {
self.faction = "Atlantean";
}
// If a monster isn't listed, it won't have a faction.
// Default faction if none assigned above (optional)
if (self.faction == "")
self.faction = "Neutral"; // Or choose a default team
};
void () monster_use =
{
// ... (Existing code for health and target checks)
// Team Check
if (activator.classname != "player" && activator.faction != self.faction && activator.faction != "Neutral" && self.faction != "Neutral")
{
self.enemy = activator;
self.nextthink = time + 0.1;
self.think = FoundTarget; // Make sure FoundTarget is defined
return;
}
// ... (Existing code for player attack)
};
Also How Does Dynamic Light Work?
#3496 posted by ranger on 2024/12/14 12:20:42
I can't find how the rocket emits light?
(trying to use as a reference to see how to attach dynamic light to objects using qc)
but there's no reference to any "light" in weapons.qc?
@ranger
#3497 posted by metlslime on 2024/12/14 23:16:37
That one is actually hard-coded in the engine, any model file with EF_ROCKET flag will get a rocket smoke trail and have a dynamic light attached to it. The flag can be set in model editors like qME.
But a glow can also be added in QC, for example enforcer lasers are implemented that way. Check out LaunchLaser in enforcer.qc for an example.
@metlslime
#3498 posted by ranger on 2024/12/15 18:52:32
seems I cannot find the setting in qc for the weapons/r_exp3.wav?
there's only a BecomeExplosion, but I don't see any sound attached.....
@ranger
#3499 posted by metlslime on 2024/12/17 11:56:21
weapons/r_exp3.wav seems to be directly called by some explosions in QC. But for rockets, it's hard-coded in the engine as part of TE_EXPLOSION
@metlslime
#3500 posted by ranger on 2024/12/17 18:29:36
oh so cannot adjust attn for explosion sound?
wanted explosion sound range to go further
Dynamically Spawn/replace Items?
#3501 posted by ranger on 2024/12/17 18:34:29
I saw some mods do this IIRC but it seems complex because quake has a "compiler order" it seems?
I can't have health kits spawn nearby enemies because it would say monster_zombie() is unknown but i cann't move monster_zombie.qc higher up in the "compiler order" because it then has multiple errors?
basically I want the game to have a chance of dynamically spawning nearby zombies at health kits - and it should by realtime/so works with any map
Forward Declaration
#3502 posted by Preach on 2024/12/19 16:41:40
What you need is a forward declaration of void monster_zombie().
https://en.wikipedia.org/wiki/Forward_declaration
Then you can keep zombie.qc lower in the compiler order and use it with healthkits.
For your explosion, you could maybe opt into the mission pack features and use TE_EXPLOSION2 which I think is silent so you can pair it with your own sound. Otherwise there's a hack posted by a scoundrel called Preach in the previous pages of this thread
https://www.celephais.net/board/view_thread.php?id=60097&start=1911&end=1935
@Preach
#3503 posted by ranger on 2024/12/22 11:58:49
is TE_EXPLOSION2 and such values, hardcoded?
got it, I see "Burst Of Particles"
is there a code number guide for palette colors? or I gotta count them manually?
--
also something is up with ATTN_STATIC and such for sounds? I tried setting the attn value for a sound to like 5 or 10 but seems no effect? the sound seems to still travel very far? but if i set it to ATTN_STATIC the range is reduced
Also This Is A Persistant Bug I'm Getting
#3504 posted by ranger on 2024/12/22 15:58:31
(trying to spawn new items/models dynamically in maps)
host error
pf precache can only be done in spawn functions?
Palette And More
#3505 posted by Preach on 2024/12/22 18:53:40
https://quakewiki.org/wiki/Quake_palette
Attenuation values wrap around when you hit the limit, so 1 = 5 = 9 etc. Don’t go above 4.
You can only precache at the first frame. You might need to rewrite the spawn function as multiple functions. One function to just precache things, one to actually create the entity without precache. The latter can be run at any time. But you must run the precache function on the initial frame somehow any time you might run the spawn function.
About Factions Revisted
#3506 posted by ranger on 2024/12/22 19:56:15
my quakec code not working? seems no effect ingame - all monsters still are all teams with each other against the player. (I even added AssignFaction() to every monster and confirmed the monsters are on the correct teams ingame)
in monsters.qc
void () monster_use =
{
if (self.enemy)
return;
if (self.health <= 0)
return;
if (activator.flags & FL_NOTARGET)
return;
//if (activator.classname != "player")
// return;
if (activator.classname != "player" &&
activator.faction != self.faction &&
activator.faction != FACTION_NEUTRAL &&
self.faction != FACTION_NEUTRAL)
{
self.enemy = activator;
self.nextthink = time + 0.1;
self.think = FoundTarget;
return;
}
self.enemy = activator;
self.nextthink = time + 0.1;
self.think = FoundTarget;
};
void() AssignFaction =
{
if (self.classname == "monster_shambler" )
{
self.faction = FACTION_A; //1
}
else if (self.classname == "monster_army" )
{
self.faction = FACTION_B; //2
}
else if (self.classname == "monster_tarbaby")
{
self.faction = FACTION_C; //3
}
else if (self.classname == "monster_dragon")
{
self.faction = FACTION_D; //4
}
// Default faction if none assigned above
if (!self.faction)
self.faction = FACTION_NEUTRAL; //0
}
in ai.qc
float(entity targ) MonsterCheckEnemy =
{
// Invalid or dead targets
if (!targ || targ.health <= 0)
return FALSE;
// Always target players
if (targ.classname == "player")
return TRUE;
// Don't target same faction
if (targ.faction == self.faction)
return FALSE;
// Neutral faction doesn't fight anyone except players
if (targ.faction == FACTION_NEUTRAL || self.faction == FACTION_NEUTRAL)
return FALSE;
// Different factions will fight
return TRUE;
};
float () FindTarget =
{
local entity client;
local float r;
if ((sight_entity_time >= (time - 0.10)) && !(self.spawnflags & 3))
{
client = sight_entity;
if (client.enemy == self.enemy)
return (FALSE);
}
else
{
if ((active_probes != FALSE) && (random () < 0.50))
{
beef = find (world, classname, "hologram");
if ((beef != world) && (beef.holo_owner.flags != (beef.holo_owner.flags | FL_NOTARGET)))
client = beef;
else
client = checkclient ();
}
else
client = checkclient ();
if (!client)
return (FALSE);
}
if (client == self.enemy)
return (FALSE);
if (client.flags & FL_NOTARGET)
return (FALSE);
// Add faction check
//if (client.faction == self.faction || client.faction == FACTION_NEUTRAL || self.faction == FACTION_NEUTRAL)
// return (FALSE);
if (!MonsterCheckEnemy(client))
return FALSE;
r = range (client);
if ((r == RANGE_FAR)) //out of range
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) //combat range max
{
if (!infront (client))
return (FALSE);
}
self.enemy = client;
if ((self.enemy.classname != "player") && (self.enemy.classname != "hologram"))
{
self.enemy = self.enemy.enemy;
if (self.enemy.classname != "player")
{
self.enemy = world;
return (FALSE);
}
}
FoundTarget ();
return (TRUE);
};
#3507 posted by ranger on 2024/12/22 19:59:56
To clarify all that code doesn't seem to work...
ingame monsters on the same side will infight if accidentally hit by teammates,
and will not automatically attack other monsters in other factions
Application
#3508 posted by Preach on 2024/12/22 23:01:23
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.
|