|
Posted by ijed on 2008/08/24 20:31:46 |
Thought I'd start a thread on this, it could have gone in Gameplay Potential, but this is more specific.
What are the favoured special abilities for monsters to have?
Shield
Homing shots
poison
wall / ceiling climbing
leaping
explosive death
ressurection
cannabalism
spawning / cloning
Teleportation
Dodging
That's off the top of my head, anyone have any other cool ideas or concepts?
And what new abilties could the old monsters have - like Scrags that change to swimming if they enter water (thanks text_fish) or an Ogre that pisses on the player after killing them (thanks Romero). |
|
|
On Reflection
#408 posted by Preach on 2010/01/17 21:15:02
Reflecting attacks is one of those things that's difficult to do in a "nicely coded" way. You can't deal with projectiles in the same way as you do traceline attacks. That breaks it into two cases, which is managable. However, my instinct would be to reduce damage from player attacks so that a reflected player grenade was no more damaging than a regular ogre grenade. This pretty quickly leads you to adding a little bit of code to each of the player attacks. Although that's not too much work, the problem is every time you need to change something, you have to make 6 or 7 identical changes all over the code. Just imagine how hard it would be to get monster attacks reflected too!
I'll try and describe all the things I'd do to get regular nails reflected correctly, which would probably be enough to get any other projectiles working. If you've understood all of the nails code, you can probably go on to do shotguns and lightning too.
So the most important bit of maths we need to do is to calculate the angle that things reflect at. We could just have the ogre send all attacks directly at the player, but not only does that make it highly dangerous, but a little bit dull to watch. I want to see a storm of nails ricochet everywhere when I spray the ogre. So we imagine that the ogre presents a sphere that the nail has collided against, centred on the origin of the ogre, and that we use the standard law of reflection:
R = 2(I.N)N - I
where R is the velocity the nail leaves with, I is the velocity it arrives with, and N is the surface normal. We find the surface normal by normalising the difference between the point of impact and the origin of the ogre. In qc this will look like:
//REFLECT CODE BEGINS
local vector I,N;
I = self.velocity;
N = normalize(self.origin - other.origin);
self.velocity = 2*(N*I)*N - I;
The next question is where to inject this code, and when to run it. I think the easiest way is to isolate the points where you are about to call t_damage, and reflect instead. So we need to modify spike_touch, within the block
if (other.takedamage)
�{
�}
In there, we detect if we have hit an ogre with a shield with something like
if(other.classname == "monster_ogre_melee")
�{
�//all of our new reflection code goes here
�}
else
�{
�//all of the original code goes here
�}
The new reflection code starts with the bit of vector maths described above(//REFLECT CODE BEGINS). We do need to think about a few other things though. We need to add self.owner = other; so that the nail will collide with the player, and not the ogre. We need to set self.touch = spike_touch_reflected, where spike_touch_reflected is a new touch function which does reduced damage - say 4 per hit. We might want to add a sound showing that the shot got reflected. We need to return; at the end, so that we don't remove the entity.
Join us for post two where I post less practical things, and instead just some possible designs...
Clever
#409 posted by ijed on 2010/01/17 21:45:07
looking forward to post 2 :)
Blocking Filler
#410 posted by Preach on 2010/01/17 22:18:45
Ok, so the first post was how to make projectile reflect, but I've got one bigger idea, which I'll talk about after a few one line thoughts:
� When reflecting a lightning bolt, make sure that the entire length of the bolt does not exceed 600 units. Where the original attack sends out a trace of length 600, the reflection should be 600 * (1 - trace_fraction) in length.
� If you don't add any other visual reflection effect, you ought to add impact effects for shotgun attacks, as there's no visible trace.
� There's a risk that the shield ogre will end up infighting a great deal when reflecting projectiles, so it might be worth inserting an exception for them initiating an fight. Especially useful if you don't implement reflecting monster attacks, as infighting might expose that.
� When it comes to the axe you could have the shield only parry, rather than reverse the damage.
So far, we have created a monster which is almost impervious to damage and can turn our own attacks against us to boot(splash damage from rockets and grenades will probably still kill them unless you were really thorough). So maybe it's time to put back some vulnerability, while also giving it some reaction animation which could build into interesting play dynamics.
What I have in mind is twofold: firstly adding a short "blocking" animation which triggers when a projectile gets reflected - or even better multiple interchangeable animations to provide variety. Each one would only have to be 3 or 4 frames long, starting with the shield held centrally in a defensive stance, and then a quick transition back to the default combat pose.
While taking rocket or shotgun fire, the ogre would play the animation in full, showing it deflect a single shot and then have it take a step forward before the next attack. Continuous attacks would leave the ogre unable to advance. Here, having multiple block animations would be valuable here so that it does continue to animate while under nail fire, moving from the first frame in one block animation to the next as if reacting to block each nail.
So that puts some kind of trade off on having the shield - you can't move while using it. If you were extremely clever in how you designed your animations (coupled with similarly careful code to back them up), you could make an ogre that slowly advanced even while blocking nail fire. Cool as it sounds, I'm not sure the effort expended to make it work would pay off, as the player would rarely see it happen - you've just reduced the effectiveness of the already weak tactic of expending nails to pin the ogre down.
The second part of the twofold plan is thus: once you have a blocking animation - don't block all of the time! Make it so that the blocking animation is not allowed to interrupt certain other animations. This way the player has a means to defeat the ogre - lure it into letting the guard down, and then hitting it.
So what animations do we select. Preventing them from interrupting pain animations seems wise - they're meant to be involuntary. Making melee attacks uninterruptable sounds sensible too, from the point of view of creating a smooth animation as well as a risk/reward trade off - the ogre only becomes vulnerable to damage once you've let them get close enough to attack you back!
I'd also make idle animations uninterruptable - the reasoning being that the ogre won't block attacks that they aren't expecting. This also gives you a kind of justification for not blocking other monsters' attacks - that kind of backstabbing doesn't cross the ogre's mind. it certainly gives you a large combat advantage if you can sneak up on one - between the idle and the pain animation you can probably get two attacks in before blocking comes back up!
I can think of one other way the ogre could give up blocking - if it chooses to! You might decide to write some sophisticated ai whereby the ogre decides "I've been pinned down by nail fire for long enough - I'm just going to make a charge for a second or so before I block again". The trick would be to make that decision process smart enough to be distinguishable from the ogre just forgetting to block due to really bad ai if you keep shooting them long enough - that quote about how hard it is to make enemies appear smart in the 3 seconds before they die springs to mind...
I've written more than I expected to, next post...
Keep Going
#411 posted by ijed on 2010/01/17 22:29:51
Block Party
#412 posted by Preach on 2010/01/17 22:39:22
As I was about to say, technical details on how I'd handle this:
I presented the two ideas together because they both interact, and because they can both be handled by the same system. We go back to the line
if(other.classname == "monster_ogre_melee")
and change it to
if(BlockOnRequest(other))
This is calling a new function, which does two things. It checks if the other is able to block (so if it's a monster_ogre_melee and has the opportunity to shield) and returns false if not. Otherwise, it triggers a random block animation on the monster, and returns true. So it would look something like
float (entity bearer) BlockOnRequest =
{
local entity oself
if(bearer.classname == "monster_ogre_melee")
�return FALSE;
if(bearer.guarded = FALSE)
�return FALSE;
oself = self;
self = bearer;
//select a random block animation and play it here
self = oself;
return TRUE;
}
The part crucial to the second idea (some animations don't block) is the new field we have added: .guarded. This field is set to TRUE if the ogre is ready to block a shot, and FALSE if not. In the monster_ogre_melee spawn function, we need to set it to FALSE (to prevent mappers turning it on and allowing idle ogres to block!). Then at the start of the run animation sequence, we need to set it to TRUE. At the start of a pain or melee attack animation, we set it to FALSE. We can set it back to TRUE towards the end of the attack/pain, or simply wait until we jump to the first running frame, which will activate it then. It should suffice to change it at the start of each sequence, rather than every frame, unless you have some animations which jump to run4 or the like.
And that should wrap things up. Once you get to that stage the monster should be playable, so the fun part of creating interesting behaviour starts. One way of creating that ai behaviour where the ogre eventually tires of blocking shots and charges for a bit would be to have blocking as a scalar quantity rather than a boolean value. Hitting the start of the run animation increments the .guarded value, while blocking an attack reduces .guarded to min(10, .guarded - 1). If .guarded drops below zero because of that, set it to (say) -3. Then make it so the BlockOnRequest tests bearer.guarded <= 0 . Pain and attack animations should reset .guarded to a reasonable positive value at the end.
Even without that, you should have an unusual melee monster once the player gets to grips with them. I'd love it if they had some kind of charging ability, dangerous enough to give the player real incentive to shoot them and force them to block. That might be making them a bit too complex though, it's easy to overwork a monster when you post 17 paragraphs of text...
One Or Two More Things
#413 posted by Preach on 2010/01/17 22:50:14
I was going to mention something about co-op behaviour in the paragraph where unaware monsters don't block attacks. If you have fixed monsters constantly switching enemies when attacked by two co-op players(that old exploit), you could make it so that the ogre only blocks attacks from self.enemy. As long as you pass the attacker as a parameter to BlockOnRequest, it's a easy check to make there. This would make them much easier to kill in coop though.
The other thing I just though of leaves an exercise for anyone who spent half an hour reading all that guff: What would happen if you set .guarded to FALSE for the first two frames during a block animation? Is that likely to improve gameplay given the current method of reflection?
Typo
#414 posted by Preach on 2010/01/17 22:51:34
if(bearer.classname == "monster_ogre_melee")
return FALSE;
should read
if(bearer.classname != "monster_ogre_melee")
return FALSE;
But you all saw that, right?
I Look
#415 posted by ijed on 2010/01/17 23:37:34
At this from more of a design POV, and the most intersting bit of the posts for me is the idea of the Ogre getting angry and bored blocking all the time.
I love putting in lots of logic to monster functions, of which the player only registers a tiny percentage, where the above would fall neatly.
It makes me think of the Ogre getting pissed off enough to do a running charge with the sheild (still blocking) kind of like a L4D2 Charger.
I need to stop playing that game - its affecting my thinking too much.
AI That's Worth Writing
#416 posted by Preach on 2010/01/18 02:03:41
(I was kind of thinking charger too...)
For a while now I've had some idea about what is and is not, in my opinion, "good" AI, and this post is me trying to put that into words. I think there are three desirable properties for AI design, which I try to apply when designing and coding. Maybe "not worth writing" is too strong a term for things that don't meet them, but if you've got limited resources then they can guide you on what to focus on. They are:
� Useful
� Visible
� Effective
���Useful
This means that pretty much any player will see the behaviour after enough encounters. Either that means it has to arise in combat naturally, or it is something that a mapper can highlight by setting up combat in such a way that the behaviour is seen. Writing AI which 99% of people will never see is time that could have been spent better.
���Visible
The player has to be able to recognise what monster is doin. The example of AI that isn't visible which I always pick on is the ability of some half-life monsters to track you by scent. It's almost impossible for the player to see the difference between this and the ability of quake monsters to always know where you are. The latter is miles simpler to program, of course.
���Effective
The behaviour should change how combat between the player and monster unfolds. Effective doesn't have to mean that it makes the monster harder to kill. Making it so that the shield ogre won't interrupt melee attacks to block projectiles makes it vulnerable to attacks other than splash damage. This makes it much easier to fight, and also opens up a whole second kind of combat with them.
I know that they aren't entirely orthogonal concepts as written, but that's the closest I've come to describing them. They were things I was thinking about in the part of the post about the ogre choosing not to shield any more, and how the charge idea related to that.
Firstly, in order for unshielding to be useful, there needs to be some reason for the player to be spamming nails at the ogre in the first place. As a player, once I've worked out that it reflects everything I fire at it, I'm not going to waste nails doing that without some compelling reason. The ability to hold it at bay by firing isn't very helpful if I need it to be in melee range before I can hurt it. The charge attack was an idea for a threat it might pose which could make the player apply suppressive fire, and so make the behaviour useful.
The charge ability was also helpful because it could make the decision to unshield visible, if the ogre always launched into a charge as they drop the shield. You'd have to take care that reusing the charge idea didn't backfire - if the ogre will charge anyway, is suppressive fire going to remain a useful tactic? At least it's clear that this AI behaviour is effective, because once the decision is made, the ogre changes from invulnerable and static to vulnerable and moving, and the player will have to react to that.
By this point, it feels a lot like theorycraft, and even if you think you've satisfied all three in theory, you can find an ability doesn't work at all in practice*. But my feeling is that unshielding would probably fail as a useful behaviour. Once players figure out the deal with the shield, they won't ever find the need to fire at them for that amount of time, the benefit if any exists is too marginal to spend 10 nails on. So I would have to weight the effort of writing that code in order to experiment with it against the chance of it ever being seen outside my own testing.
* This happened 5-6 times with the Sentinel in Quoth, that thing was a nightmare...
Hey That's Terrific!
#417 posted by madfox on 2010/01/18 14:17:04
That's more assambly on my plate then I can hatch, but I asked for it.
Not sure if I can understand it at once, but there's a clue how to estimate the given question.
I'm not good with maths, and the only reason I'm still reading is that I feel there could be a logical scripting to make the monster do what it need.
Hadn't expect that reaktion, but when I remade the Zdoom monsters I already had this feeling quake monsters could be made better.
The animation frames for shielding are already there.
Now screwing that .scr to unknown puntuation.
grmrpf...
Thanks Preach
#418 posted by madfox on 2010/03/11 00:14:20
I tried your code of 408, much further I can't follow due to errors. If I place it just after void()spike_touch in WEAPONS.QC the compiler keeps asking for a definition of the spike_touch_reflected.
.float hit_z;
void() spike_touch =
{
local float rand;
if (other == self.owner)
return;
if (other.solid == SOLID_TRIGGER)
return; // trigger field, do nothing
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
// hit something that bleeds
if (other.takedamage)
{
spawn_touchblood (9);
T_Damage (other, self, self.owner, 9);
}
if(other.classname == "monster_ogre_melee")
{
// all of our new reflection code goes here
local vector I,N;
I = self.velocity;
N = normalize(self.origin - other.origin);
self.velocity = 2*(N*I)*N - I;
self.owner = other;
self.touch = spike_touch_reflected;
return;
// all of our new reflection code end here
}
else
{
So I tried something like
void(vector org, vector dir) spike_touch_reflected =
{
if (other.takedamage)
{
spawn_touchblood (9);
T_Damage (other, self, self.owner, 5);
}
newmis = spawn ();
newmis.owner = other;
newmis.movetype = MOVETYPE_FLYMISSILE;
newmis.solid = SOLID_BBOX;
newmis.angles = vectoangles(dir);
newmis.touch = spike_touch_reflected;
newmis.classname = "spike";
newmis.think = SUB_Remove;
newmis.nextthink = time + 6;
setmodel (newmis, "progs/spike.mdl");
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
setorigin (newmis, org);
newmis.velocity = dir * 1000;
};
And then the compiler gives no error, only me.
^v^
Spike_touch
#419 posted by madfox on 2010/03/24 02:41:46
It was worth reading.
Really don't have a clue where to start now.
I keep trying to find a right scripture, but if I wonder where my reflected spike has gone.
Preach's
#420 posted by ijed on 2010/03/24 03:02:10
Posts are like classes in Qc.
Rinie, try adding �print's to everything - so when an attack deflects the console says "attack deflected" and then for every other stage "attack hit somethin" and so on.
Its the only way to know for sure what's going on.
#12
|
|
You must be logged in to post in this thread.
|
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.
|
|