News | Forum | People | FAQ | Links | Search | Register | Log in
Quake Gameplay Potential...
Very interesting discussion in the GA thread, worthy of it's own discussion thread I think, for archive and research purposes.

There seem to be several viewpoints floating around, which I'll badly paraphrase...

Quake gameplay is the same as it always was (kill monsters find exit) and thus is boring and not really worth bothering with.

Quake gameplay is the same as it always was but that's it's appeal and it's still great fun.

Quake gameplay is the same as it always was and thus it needs to rely on mods and extra monsters and features to remain fresh and interesting.

Quake gameplay has evolved and improved enough (with or without those enhancements) to still remain worthwhile.

etc etc.

I don't think any of these perspectives can be shown to be right or wrong - mostly they seem to be the depth with which you look at gameplay and gaming in general. I.e. Quake gameplay might seem exactly the same as always when looked at on broad kill monster exit map terms, but looked at on narrower terms the refinement in monster placing, gameflow, surprises, balance etc etc that modern mappers have achieved could be seem as quite progressive.

I haven't argued much so far but as a big Quake fan I am interested in Quake gameplay, how it has progressed, and how far it can progress (with or without enhancements). Thus I think the ideas would be worth more exploration. More thoughts in a mo...
First | Previous | Next | Last
 
I forgot to mention that I was mainly thinking about bosses when I wrote my previous post. I know I said 'monster' a lot, but I was thinking of bosses. 
Long Post On Quake Shotguns. 
i wanted to mention because there was some talk a while ago regarding quake's shotguns and how they suck.

there's a pretty serious bug with all shotgun type attacks involving the method id uses to combine all the individual pellet damage calls into one big call.

in case you're wondering, this is what id did: (heh)

when a shotgun is fired, a loop runs where it traces each shotgun pellet.
however, instead of dealing damage each time in the loop, the progs starts up a counter (which is always reset when a new shotgun shot is fired).
each time it compares the target it hit last time with the new one. if the target is the same, then it just adds the damage to the counter and moves on. if it hit a different target (your shotgun blast hit two targets), then it dumps the current damage into the old target and starts a new count with the new target.
this keeps going until all pellets are accounted for.
note that, because shotgun spread is random, it's actually possible for pellets to hit two targets in such a way that each pellet is forced to dump damage individually anyway.

anyway, the bug is that there is no check to see if the current target is going to die from the next pellet.
what this means in game is that, if there are two grunts, one in the back at full HP and one in front of that one with 1 HP, even if you blast the nearly dead grunt with the SSG, all the pellets will hit that grunt and none at all will hit the grunt behind it.

this has the effect of giving the SSG the ability to gib enemies, but at the same time, seriously nerfs the damage that it can cause.

in doom, you can line up 3 or even 4 zombies and take them ALL out with a single SSG shot. in quake, you can even take out 2. :(

so yeah, while the quake SSG *is* indeed weak, this bug further gimps the weapon by not allowing pellets to pass through dead targets.

this is also why mods that have included more powerful shotguns still feel weak. even if you made a 20barrel shotgun, a single monster with 1 HP would stop all pellets.
note that the riot controller from zerstorer sort of got around this problem by delivering two separate shots.

the fix is actually really easy, and it bothers me that id didn't notice it. it does require creating a new helper get function.

here's the original code:

void(entity hit, float damage) AddMultiDamage =
{
if (!hit)
return;

if (hit != multi_ent)
{
ApplyMultiDamage ();
multi_damage = damage;
multi_ent = hit;
}
else
multi_damage = multi_damage + damage;
};


note the last else statement. this is what we need to change.
first we need to make a new function to find the effective health of a target:

float(entity e) getEffectiveHealth =
{
return e.health + (e.armortype * e.armorvalue);
};


this function takes into account any armor the target has and translates that into health.

now, we change the last else statement from this:
else
multi_damage = multi_damage + damage;


to this:

else
{
multi_damage = multi_damage + damage;

if (multi_damage > getEffectiveHealth(multi_ent)) //this hit would kill the current multi_ent. apply the damage now and start a new count.
{
ApplyMultiDamage ();
multi_damage = 0; //reset accumulation count.
multi_ent = world;
}
}


now, instead of just continuing to add damage to the multi_damage counter, first is checks if the current damage pool + the incoming damage would kill the target, if true, then it goes ahead and does the damage and then resets the counter and the entity container so the next time addMultiDamage is called, it will start a new tally. note that this does mean it will cause a dummy ApplyMultiDamage with 0 damage on the world, but there's a check in T_Damage that will stop that call right away before it causes any trouble.

the only unfortunate side effect is that you don't sometimes gib low health targets. but the plus is that the SSG is now a little more effective.
note: quadded SSG shots still do gib zombies. 
Necros 
This could be real improvement to add this code part ! Let's think about put this in Quoth 3 ;) 
Nicely Thought Out 
Our SSG is more Q2 in style (slower firing, heavier damage, wider spread), but that bug got past us as well.

Railgun style shotguns were considered, but abandoned.

Seems like we missed an important piece of the puzzle. 
 
> in doom, you can line up 3 or even 4 zombies and take them ALL out with a single SSG shot.

That is just overpowered, IMO. The result is that you'll rarely use another weapon anymore.

Regarding the 'bug', hmm, needs some testing I guess. 
Sure 
But when this is done well it makes the weapon very satisfying to use.

The shotgun in L4D1 for example was great, the power being offset by its slow reloading.

Shame they consoled all the weapons for L4D2. 
Missing Something 
This was brought to my attention today; however, the more I look at the code, the more I'm sure that it doesn't do what you want it to do.

The mess that is the shotgun code originally does this: For each pellet in turn, if the pellet didn't hit the same thing as the previous pellet, the previous thing hit gets all of its damage applied to it now.

This ensures that all of the damage goes to the right victims, but often in small chunks instead of a larger chunk. Which means that enemies that should (in theory) gib sometimes won't.

This can be demonstrated by lining up a quad SSG shot at 2 zombies standing right next to each other, and aiming directly between them. 38% of the time, neither will gib. (It needs 4 pellets in a row landing on the same target, to do the 60 damage needed to gib).

Assuming no changes elsewhere to the code, all your code will do is apply the damage needed to kill before processing the other pellets. Which will prevent that monster from accumulating enough damage to be gibbed, as the check to gib only happens when the monster takes lethal damage (and after that, Killed(), which calls .th_die sets .takedamage to DAMAGE_NO). Meaning that they won't gib unless the damage from that pellet is enough to take them below gib health.

What it won't do, is prevent all future pellets in this attack from hitting it. They still will. At least, not without changes elsewhere...

If you want future pellets to hit whatever is behind, you need to have the monsters become SOLID_NOT in their .th_die functions.

If you want Doom-style damage passthrough of the remain damage within a pellet (Doom does this for all bullet attacks, and the shotguns fire multiple bullets), then it's a little more complicated, but having the pellet traceline after every point of damage almost gets you there.

If you want future pellets to not be aimed at that monster, it's much more complicated. You're on your own on this one, because of the potential for runaway loops.

If you weren't trying to do any of that, then what were you trying to do?

Also, your implementation of getEffectiveHealth() is incorrect, as it doesn't account for health running out before armor does. Not an issue for most monsters, of course... 
Hmmm 
It could be that getEffectiveHealth could account for zombies and make it so that their effective health was considered to the level which would gib them rather than kill them - a special case. The function would need to consider armor correctly as you say, so returns false if either armor+health> damage or health*(1-armortype)>damage (this may have an off by one bug if the rounding is not done correctly, not bothered to work that through). Also in Quoth there's the case of the 50% damage reducing shields - you can see why the decision gets delegated to a function.

None of this fixes the existing zombie bug. The way to do that would be to have each entity accumulate damage on fields, and add a new field like .chain to create a linked list of everything that took damage from the shotgun shells processed so far. Then until something passes the effective health test, you just accumulate things in the linked list. Finally, you work through the whole list applying damage which wasn't taken. Now your only problem is that monsters often aren't non-solid in the first frame of their death sequence... 
Lardarse 
this is not quite correct w/regards to zombies. zombies don't actually have a gib check. their pain function is what resets their health, not their death function.

since the method i used will apply damage if they are about to die, they will get gibbed because th_pain() is called later in the t_damage function than killed() is.

otoh:
Assuming no changes elsewhere to the code, all your code will do is apply the damage needed to kill before processing the other pellets. Which will prevent that monster from accumulating enough damage to be gibbed, as the check to gib only happens when the monster takes lethal damage (and after that, Killed(), which calls .th_die sets .takedamage to DAMAGE_NO). Meaning that they won't gib unless the damage from that pellet is enough to take them below gib health.

is true. i've implemented this code change into a large mod with extensive changes, one of which being a self.solid = SOLID_NOT right in the killed() function. this is what actually makes it work. the multidamage change only makes it possible to work. 
Sorry, A Little Tired And I Missed This. 
thinking a little more. i can see what you are saying about zombies. if two zombies were hit in such a way they the damage ping ponged back and forth between the two always at sub 60 damage levels, then you're quite right in that they would never gib.

it might be worth trying to implement preach's much smarted linked list method to only consider damage after every random pellet has been traced.
a cheaper lamer way to do it would be to created a few more temp var pairs of entity and float pairs a shotgun could hit a max of like 5 or 6 targets before bugging out. 
Ok 
rewrote the thing to use linked entity lists.

hopefully a better programmer than i will be able to tell if i have royally screwed up (it seems to work ok in quake though...)

float(entity e) getEffectiveHealth =
{
return e.health + e.shieldValue + (e.armortype * e.armorvalue);
};

entity multi_ent;
.entity multiDamage_nextNode;

void() ClearMultiDamage =
{
multi_ent = spawn();
};


void() ApplyMultiDamage =
{
local entity node;

node = multi_ent;
while(node)
{
if (node.dmg > 0) //hasn't already been used up
T_Damage(node.enemy, self, self, node.dmg); //apply all accumulated damage
node.think = SUB_Remove;
node.nextthink = time; //next frame
node = node.multiDamage_nextNode;
}

multi_ent = world; //break this link just to be safe.
};


entity(entity e) multiDamage_getNode =
{
local entity node, newNode;

node = multi_ent;
while(node)
{
if (node.enemy == e) //this node already exists
return node;

if (node.multiDamage_nextNode) //there is a next one
node = node.multiDamage_nextNode;
else //this is the last in the chain
{
newNode = spawn();
newNode.enemy = e;
node.multiDamage_nextNode = newNode;

return newNode;
}
}

return world;
};


void(entity hit, float damage) AddMultiDamage =
{
local entity node;

node = multiDamage_getNode(hit);
node.dmg = node.dmg + damage;

if (node.dmg > getEffectiveHealth(node.enemy))
{
T_Damage(node.enemy, self, self, node.dmg); //do the damage
node.dmg = 0; //this node's damage is consumed
}
};
 
Self.solid = SOLID_NOT Right In The Killed() Function 
I was wondering about this, as it tangentially relates to the shotgun problem. How long does it normally take for a dead monster to become non-solid? Because, I'm pretty sure I've seen the second shot of a riot controller uselessly hit a corpse as it's in the process of falling down. (grey particles instead of red give it away) 
 
quad posting, sorry.

the previous post is still reliant on the SOLID_NOT in killed() to work correctly. 
Thank God 
grahf broke up my posting string. of course, now i'm starting a new one as i cross-posted with him. :P

in answer: setting non-solid is done in a very weird and inconsistent manner. usually it is set on the second or third frame in the monster animation. for fish, it's terrible. stock fish have their nonsolid setting like near the end of the animation. it's why end.bsp you can actually start to choke when swimming through that little underwater tunnel because the fish take so long to become nonsolid, so you can't move past them. :P

i was wondering though... does changing solid setting that could potentially be in a touch function (like when a missile hits a monster, instead of shotgun tracelines) be bad?
fitzquake and other engines have fixes to most of these types of problems but is it possibly a crash waiting to happen? 
Re: #422 
Zombies actually gibbing at 0 health, and having their health reset by .th_pain is the only reason why they gib, and the reason why they are the only monster that gibs... 
And Just One More Post For My Other Replies... 
thinking a little more. i can see what you are saying about zombies. if two zombies were hit in such a way they the damage ping ponged back and forth between the two always at sub 60 damage levels, then you're quite right in that they would never gib.

Yes. With the example I stated, it's 14 coinflips without getting 4 in a row the same (which is how I was able to calculate it).

I was wondering about this, as it tangentially relates to the shotgun problem. How long does it normally take for a dead monster to become non-solid?

Most monsters become SOLID_NOT in the 3rd frame of their death sequence. The Riot Controller fires its second volley 0.3 seconds after the first. Fish don't go solid for nearly 2 seconds in unmodified progs.dat, as necros mentioned.

Killed() isn't the correct place to set SOLID_NOT, as non-monsters use that function as well. In the monsters' .th_die function is the best place, if that is desired.

i was wondering though... does changing solid setting that could potentially be in a touch function (like when a missile hits a monster, instead of shotgun tracelines) be bad?

Killed() sets .touch to SUB_Null so it can't touch anything else. This mostly stops dead fiends and dogs from doing damage when leaping, once their dead. I don't think there's a potential crash issue, as touches have all been calculated before they are processed.

As for your code snippet: What it looks like you're trying to do, is use the linked list of nodes to track which entities are hit, and how much damage each entity needs to take, and then once all of thed pellets have been processed, do the damage in turn. A solution that works as long as you don't have a shotgun being fired because of a .th_die function. (This is why exploboxes were broken when setting each other off, as T_RadiusDamage() uses findradius(), which creates a linked list of entities that are close enough, and exploboxes explode in their death function. The well known fix is to make the explosion happen next frame.)

However, I'm not entirely sure what your code actually does... 
Addendum 
If your Killed() function checks for FL_MONSTER before making them SOLID_NOT, then you're probably safe. 
Gb 
> in doom, you can line up 3 or even 4 zombies and take them ALL out with a single SSG shot.

That is just overpowered, IMO. The result is that you'll rarely use another weapon anymore.


i don't see why that's overpowered... zombies are the weakest monster (two, sometimes three pistol bullets to kill) and doom's SSG spread is huge. if you're further than ~192 units away, half your pellets will miss a standard sized monster (ie: not fat or huge ones).

i think if you were fighting a closely clumped up horde of zombies, the ssg could probably take out a dozen or so.

at the same time, it's very slow firing and requires nearly melee range. using the RL or plasma rifle is nearly always more desirable.

plasma rifle has insane throughput and RL's is nearly as high (but with the added bonus of splash damage). 
What Is The Essence Of 'Quake' 
I noticed this reading comments on the Remake Quake demo and other releases. People would say that it is or isn't proper Quake. I myself are building a level that certainly doesn't play like a typical iD style Quake level, and I started pondering exactly what it is that makes a level classic Quake style. Is it about exploration, being more non-linear? Key hunts? Something about architecture?

So, I thought I'd ask you chaps what your thoughts are, and perhaps more importantly, post example custom levels that you think really got to the heart of 'Quakeiness', and what you look for usually in custom levels. 
I'm A Conservative Prick 
post example custom levels that you think really got to the heart of 'Quakeiness'

czg03, most of Terra and Warp Spasm (specifically warpc) all accomplish a feeling of a journey through a monolithic, oppressive, hostile location, built for a purpose which yet remains unknown for the end explorer.

After some thought I thought of many more I could list, like lunsp1, or starkmon, or Trinca's latest, and figured that an interconnected layout definitely helps click things forever in a Quakey way... it may be simply because that's what the original maps did, though.

You may find this thread interesting: http://celephais.net/board/view_thread.php?id=27785 
 
ta for the thread link, I didn't notice that one :E

So its not necessarily about being somewhat non-linear, more that classic Quake levels always feel like one big place that you fully explore, and that this exploration leads you back to original areas but at different positions. Compared to the more modern approach of a long corridor of different locations.

Something like Painkiller's Docks are a big environment that you go through in a zig-zag pattern, but you never return to an area you've previously been to. So I suppose that's it mainly :E 
That's 
A reasonable summing up, but for me the ultimate Quake level(s) are those inside Insomnia. In terms of level path you go through loops, continually returning to previous areas, but coming from different directions, until you complete it.

The best map for it was Push Underground (Underworld reference?) since the central canyon is visited minimum four or five times, but never gets boring or confusing in terms of layout.

It's sad to see modern games relying on the lineal path, just because it doesn't take any thought. 
Played Rome1 Fun... Kinda 
Not even THAT hard except spawns that deal 100 dmg. I should rant on that later. I have a demo but its paused near the last ~third of map (

Also why does it look broken in DP? http://i.imgur.com/690G4.jpg 
Lol I Was Falling Asleep 
The post was obviously meant to be in Roman Wilderness Of Pain thread http://celephais.net/board/view_thread.php?id=60400 
 
Quake levels actually are largely linear, often with a dedicated start area that you don't see ever again (lost entrance to dismal oubliette anybody?) and the single overarching goal of finding the exit.

There are typically loops or hub areas (e2m6 is a good example) that connect different parts or branches of a level, and as was said the player does the loops through that part, often for keys or buttons that open the next loop.

Then there also often is a dedicated linear end area.

e1m1 is a perfect example for a very linear Quake map, with a couple optional loops only explored for secrets, and a few dead ends (nailgun room). No one would argue it isn't Quake though.

e1m2 actually also is largely linear, in a circle shaped layout, again with a dedicated start and end section, and a couple optional loops again typically for secrets. The main diversion is that you can enter the main circle from both ends. Clever.

e1m3 is very linear apart from the large loop for the gold key (which in itself is also pretty linear) and a few (small) optional loops and dead ends.

In all 3 maps there is a very obvious and pretty linear main route, augmented by loops and dead ends which are actually often optional to explore.

Yet, many people like episode 1 and some say it is very Quakey. 
First | Previous | Next | Last
You must be logged in to post in this thread.
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.