Contentious Quake Design Nitpicking
#670 posted by Preach on 2012/02/18 21:51:04
I contend that the th_* style ai functions are over-engineered in the original qc code.
There is not, to my recollection, a single instance where quake monsters share a th_* function like th_melee, th_pain etc. Therefore it would be best to just have a single function
.float th_handle(float eventnum)
for each monster. The eventnum would then have EVENT_MELEE, EVENT_RUN and so on, and which handler to run would be chosen from a switch statement. There's no reason to suppose that the handling code would all go inline, most likely each statement in the switch would just call the relevant function.
The two big wins of this change would be:
Firstly to get rid of all that cruft for all the other entities in the quake universe.
Secondly to allow many more 'events' to be handled. Whack in default handling which does nothing and you can add event handling for just selected monsters. Things currently dealt with by hacky classname searching like monster waking sounds or attack decision making could be refactored into events.
Having laid out a grand vision, I would grant mercy to the special case of th_pain. Mercy is extended on the grounds that pain functions are parametrised on attacker and damage, and hacking around that would probably be worse than just letting it stay.
#671 posted by necros on 2012/02/18 22:09:25
wouldn't it be better to expand the .functions available to monsters?
for example, replacing all the sight sound checks with wake functions so each monster could have it's own method of playing sounds (some monsters might want different attenuations, other might play random sounds).
one thing i liked about doom3 monsters was that their animations and thinking were separated into two threads. that'd be interesting to do in quake, have your monster model with frame handling and some invisible thinking entity that actually animated based on events and such.
Hierarchy
#672 posted by Preach on 2012/02/18 23:01:31
Expanding the available functions is exactly the sort of thing that was motivating me. The feeling I have is that there's a tension between adding all the fields a monster could ever need and not burdening all the other entities in the game with stuff they don't use. This leads us to the current compromise where the most vital events get handlers and the rest get hacks.
Adding new fields is possible, but if you wanted to support 50 or 60 events it would get pretty crazy. In a world where lots of monsters shared th_melee or th_stand functions, or even where such things might change dynamically for a given entity (like an injured monster switching to a limping run) the current architecture would have more weight. But as it is, you're born with the think functions of one class, and you die by it, so why not bundle the whole package into one function for the monster to carry about?
#673 posted by necros on 2012/02/18 23:27:32
i know what you mean about the th functions...
i run into it when designing a boss monster that doesn't fit into the standard mold of 1 melee, 1 ranged.
i usually end up overriding the checkattack function for those monsters with a function that directly calls whatever attacks are needed.
still, a function field is stored as, what, an integer? or float? 32 bits is not really a big deal, even with thousands of entities.
seems like it would be more hard to deal with code where there is a single entry point for all monsters and hundreds of checks for each one.
that would be very annoying, i'd think.
Sketching
#674 posted by Preach on 2012/02/18 23:33:18
You could have a list of the events starting with the mandatory ones
//====MANDATORY====
float EVENT_STAND = 1;
float EVENT_RUN = 2;
float EVENT_MELEE = 3;
...
float EVENT_WAKE
float EVENT_DIE = 8;
//=====OPTIONAL====
float EVENT_OPTIONAL_START = EVENT_DIE + 1;
float EVENT_STRUCK_BY_LIGHTNING = EVENT_OPTIONAL_START;
float EVENT_SHOT_MIDAIR = EVENT_STRUCK_BY_LIGHTNING +1;
...
Then the pattern for event handling goes:
void monster_shamber_handler(float eventnum)
{
��if(eventnum < EVENT_OPTIONAL_START)
��{
��//code to handle events all monsters are expected to deal with
��//even if grunt response to EVENT_MELEE is no-op etc.
��return;
��}
��else
��{
��//handle any other selection of events with set of if..else lines
��}
}
In theory you could do some sort of jump table with the mandatory events using the event number as an index. Of course, since qc doesn't really handle integer calculations all that well you'd probably find the most efficient solution was to redefine all the event numbers to already be the desired offset into the table stored as an integer bytewise. Assuming you need 4 qc instructions per handler to call a function and return you'd be looking at
float EVENT_STAND = 0.00000000000000000000000000000000000000000000056051938;
And so on...wasn't there a compiler that let you define integer constants like %4.
HUD Text Shizzles
#675 posted by Kinn on 2012/02/21 19:35:29
I've never ever looked into mucking around with the HUD before - but would like to do one thing if possible...
You know when you press tab to see your monster and secret counts? Is it possible with QC to change the text "secrets" to say something else? As well as on the end level tally screen?
Kinn:
#676 posted by metlslime on 2012/02/21 19:44:34
the score bar (when you press "tab") is hard-coded in the engine.
The intermission screen is an image, so you could edit that image to say anything.
Ah Ok
#677 posted by Kinn on 2012/02/22 00:30:35
hmmm, so i can change one but not the other :/
#678 posted by necros on 2012/02/22 00:40:13
you could always make your own menu/screen.
even with stock quake exe you can make a functional menu that pauses the game.
#679 posted by Kinn on 2012/03/04 00:26:36
you could always make your own menu/screen.
Nah that would be huge overkill for what I'd be doing. Besides, I realised what I originally had in mind is probably just a silly gimmick anyway, I'm gonna shove it to one side for now.
Brushmodel Texture Swapping
#680 posted by Kinn on 2012/03/06 16:36:20
So let's say I have a brushmodel and i want the texture it wears to change according to events. From what I understand, all I can do is this:
- have an (optionally) animated looping texture with frames following the +0blah, +1blah etc. convention, then change to a non-animated image with the +ablah naming convention, by setting frame = 1 on the bmodel.
What I'd like to do is have 4 different non-animated textures, and i can just swap between any one of these images.
I assume it's not possible without doing crap like having 4 seperate bmodels and hiding 3 of them (or I guess just using 2 if i abuse +0blah and +ablah).
Or is it?
Also I don't wanna use a .mdl
#681 posted by necros on 2012/03/06 19:40:49
yeah, you'd need multiple bmodels.
the frame stuff is hard coded, unfortunately and treats +0 ... +9 as a frame group, hence frame = 0 and 1 only.
Ok Cheers
#682 posted by Kinn on 2012/03/06 20:06:44
I'll move to plan B which is to do the effect with sprites. Lighting won't be ideal, but i think i can get away with it.
Lightning Bolts
#683 posted by Kinn on 2012/03/07 17:52:54
so, spawning a lightning bolt:
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
WriteByte (MSG_BROADCAST, TE_LIGHTNING2);
WriteEntity (MSG_BROADCAST, self);
WriteCoord (MSG_BROADCAST, org_x);
WriteCoord (MSG_BROADCAST, org_y);
WriteCoord (MSG_BROADCAST, org_z);
WriteCoord (MSG_BROADCAST, self.enemy_pos_x);
WriteCoord (MSG_BROADCAST, self.enemy_pos_y);
WriteCoord (MSG_BROADCAST, self.enemy_pos_z);
no matter what i set the vector org to, it only spawns the bolt starting from the entity origin - basically the calls for setting the starting point are ignored - is that how quake works?
Only If The Given Entity Is A Player, Otherwise It Works As Expected
#684 posted by czg on 2012/03/07 18:40:50
Ah I See
#685 posted by Kinn on 2012/03/07 18:44:17
sneaky, sneeaky quake :}
#686 posted by necros on 2012/03/07 21:10:16
note also that you can only have one lightning bolt per entity.
these days i build my lightning bolts manually out of edicts because every engine besides fq/qs tries to be cute and put in effects. :(
Stealing Weapons
#687 posted by Mike Woodham on 2012/03/10 15:29:42
What do I need to consider when trying to implement something like this:-
void() steal_weapons_use
{
other = activator;
if (self.spawnflags & SPAWNFLAG_SHOTGUN)
{
other.items = self.items - (self.items & IT_SHOTGUN);
other.ammo_shells = 0;
other.weapon = IT_AXE;
}
};
Or am I running in treacle here?
I can see it taking the weapon and the shells, but it breaks with an error in the weapon_cycle command.
Activator's Other Self
#688 posted by Preach on 2012/03/10 16:03:07
There seem to be three entities in this story, and I'm having to guess who is who:
self: presumed to be an entity added to the map as a trigger perhaps?
activator: if the above is correct then activator is whoever set off the trigger called self
other: known to be the same entity as activator
The first thing seems to be that other is unnecessary, which makes the function confusing. If we replace other with activator we get:
void() steal_weapons_use
{
�if (self.spawnflags & SPAWNFLAG_SHOTGUN)
�{
��activator.items = self.items - (self.items & IT_SHOTGUN);
��activator.ammo_shells = 0;
��activator.weapon = IT_AXE;
�}
};
We can then see more easily what might be dangerous. We only act if self has the spawnflag for removing shotguns. On that condition, we then set the activator's items to be equal to self's items with item shotgun removed. I'm pretty sure that this line should be referring to activator all the way through.
Thanks Preach
#689 posted by Mike Woodham on 2012/03/10 16:20:21
I didn't see the wood for the trees.
#690 posted by necros on 2012/03/10 18:36:38
don't know if you trimmed things out for clarity, but you should probably add a quick check:
if (!activator.flags & FL_CLIENT)
return;
just to avoid any possible problems in the case of monsters some how activating it.
Spawn()
#691 posted by necros on 2012/03/10 19:10:01
is spawn() completely reliable? is there any time where it can fail and not create anything? or it creates an entity but the entity somehow gets lost? maybe if you're spawning many entities in the same think?
Invalidation
#692 posted by Preach on 2012/03/10 19:24:36
In the standard implementation spawn only fails if there are no free edicts, which produces a game-ending error. As long as it doesn't error out, it returns some entity and as far as I can see there's no way this entity could fail to be a clean slot. But that entity might be more familiar than it seems.
Edict slots aren't allowed to be reused with 0.5 seconds of an entity being removed from a slot*. However, if a reference to an removed entity persisted longer than that it could end up creating a reference invalidly relating to a newly spawned entity. Could a stale reference be somehow responsible for your issue?
*This is not true for the first two seconds of the map, in order to not waste lots of slots if entities are loaded from the map but removed by the qc straight away.
#693 posted by necros on 2012/03/10 19:39:50
right, i totally forgot about that! i still had that .doNotRemove trick from before and i found that the entity is indeed being removed by something else. thanks, i'm off to track that down. :P
And
#694 posted by necros on 2012/03/10 19:55:18
it's done. thanks! turns out i had some sloppy linked list clean up. i was clearing out the list but forgot to clean up the head link. :P
|