After messing with it quite a bit, I've found a couple things that bug me, and would like some non-hacky workarounds:
A_SkullAttack (Int Speed, Float MaxTurn, State ImpactState, State InterruptState, Int Flags)
Speed: It's what we've got already. (Default: 20)
MaxTurn: Degrees of angle/pitch that is the farthest it will turn toward the target before charging. (Default: 0, no limit)
ImpactState: What state to go to when it crashes into anything. (Default: "See")
InterruptState: What state to go to when it is attacked while charging. (Default: "See")
Flags:
SAF_NOATTACKSOUND, prevents it from playing AttackSound when called.
SAF_IGNOREATTACKS: Prevents cancellation of the charge when attacked.
SAF_STOPONATTACK: Immediately halts the calling actor when it is attacked - Rather than waiting for its velocity to zero out before it can act.
SAF_FEELPAIN: Allows the actor to run its usual pain chance. (If pain is triggered, the charge immediately ceases, and it enters Pain rather than InterruptState.)
While we're at it, I'd like to throw out that AFAIK the attack still stops against pickups as if they were infinitely tall, I wouldn't mind seeing a fix + compat flag for this.
ImpactState and InterruptState seem difficult, for a reason I've recently brought up in another feature thread: This code pointer does not complete the state-jump during its lifetime. You call A_SkullAttack, and then the skull hits something at a later point, which means that the function will require the actor to add two dedicated properties to remember these states and use them on impact/interrupt.
I wonder if the flags that specify later behaviour (ignoreattacks, stoponattack, feelpain) don't suffer from similar issues. Adding flags isn't hopelessly messy and costly, though. I think the statejumps are more than what you can expect to get. However, I have yet to study A_SkullAttack closely. There are/were some other features requested for it as well. When/if I have time and energy to spare, I might make a patch of the most feasible parts of these requests. It seems unlikely that the complete package will get in.
Would the following be worth anything?
Convert speed from int to float (to allow for decimal values)
Add the flag SAF_THINGSPEED to multiply the speed parameter by the currently stored speed of the actor.
((Add the flag SAF_CLASSPEED to multiply the speed parameter by the defined speed of the actor's class :: EDIT :: Seems like this feature woul be a little hackish in the engine))
Benefit: Allows you to use this function in an actor, and make the skull speed a function of its configured speed, and to let inheriting actors change the speed without redefining the state/frame.
Question: Will anybody ever want to do any of that?
Aha, that's the one. Now... What can be done with a minimum of fuzz, what options do we have for fuzzier challenges, and what is it all worth; what will we want in the end?
Starting with neural features:
float (angle?) MaxTurn: must be feasible
state impactstate: fuzzy (no pleasing implementation found)
state interruptstate: hairy (no pleasing implementation found)
FLAG SAF_NOATTACKSOUND: must be feasible
FLAG SAF_IGNOREATTACKS: unfeasible as a function parameter, because it needs to be persistent; could be added as an actor flag
FLAG SAF_STOPONATTACK: as above
FLAG SAF_FEELPAIN: as above
Then Xtyfe's little request, A check for species:
Check on outset is feasible (don't charge if same species)
Check on impact is non-feasible (don't damage if same species); the reason and immediate solution is the same as for the flag SAF_IGNOREATTACKS.
And the referenced thread:
int damage: Like the flags, it has its effect after the end of the function, and needs its value to persist until this happens
str damagetype: Like damage
str state: (See NeuralStunner, same entry)
FLAG "Allow pain" (Neural SAF_FEELPAIN)
FLAG "No random damage": Probably not needed
-- If we use the damage property of the actor, there is a method to specify a precise formula with as much or little randomness as you require.
-- If we use a decorate parameter, we are always writing a precise formula.
-- If we want a particular randomisation, we achieve it by enclosing it in a random()-function, or multiplying it by one. Or something else entirely.
FLAG "Don't lose momentum when hit": resembles Neural's SAF_IGNOREATTACKS, is that it?
FLAG "Don't adjust z momentum": FEASIBLE! At least at a glance. It is completely processed during A_SkullAttack, which is what we need.
FLAG "Charge-where-facing, not at target": FEASIBLE! AGAIN!
Gez also made mention of a flag from Eternity or something: INVULNCHARGE. It may be covered by something else, I don't know its specifics.
There'd be a lot of flags added to the actor class that must support this, and optionally an int (if we allow decorate to save an amount of damage for the impact to use instead of normal amounts of damage).
Or maybe we can cut it down to A_SkullAttack(int/float speed, int feasible_flags, class PuffType)
We'd still need to store a bit of extra information to support the feature, but most would be handled by reference. It causes a loss of flexibility, though.
So I see the following possibilities right away:
1* Implement only a few of the suggested features
2* Increase the size and number of definitions for the AActor class (affecting every actor in the game, however insignificantly; perhaps quite tolerably)
3* Add a skullattack_pufftype field to the AActor class (this actually seems like a gruesomly inferior solution in and of itself)
4* Reference an external structure from the actor class, that can store function data. ... I don't really think it beats 1 and 2.
5* Create a native actor class for advanced skull behaviours
1 is quite alright, but disappointing no doubt.
2 is the simplest satisfying solution, and it's messy.
The rest is messier and probably offers no relevant advantage in terms of flexibility or performance.
5 appears to be a step in the wrong direction
With regard to no random damage, i believe other attack functions have a no random flag because simply providing that formula wont stop the engine from using it's default which in this case would be random(1,8)*3 for the lost soul. I could be wrong though.
and for the don't charge if same species idea, i don't see this working very well. What if an actor is hit of the same species after the lost soul has charged at an enemy? would it stop itself when this new target came into it's line of sight?
That's the thing: It can only deal with the flags you use with A_SkullAttack when A_SkullAttack is called. All that information is lost before game continues (and the charge commences). Therefore, all those features require us to save the parameters somewhere else until they are needed.
Resolution 2 is basically to add a whole heap of flags to the actor class, and either updating them with every call to A_SkullAttack, or managing them through A_ChangeFlag.
FDARI wrote:FLAG SAF_IGNOREATTACKS: unfeasible as a function parameter, because it needs to be persistent; could be added as an actor flag
I really like the sounds of this as a separate actor flag. If I'm reading this right, would a monster with this flag set not go into their pain state while attacking? If so, that could be very useful for boss monsters.
That's not exactly it. I think it means "not be interrupted in a skull-charge" and nothing more. For your bosses, you will need to set and unset NOPAIN at key points. (set at the beginning of attack, unset at end of attack, unset on resurrection in case they died during the attack)I'm not very familiar with the LostSoul / A_SkullAttack operation to begin with, though. In order to make more than half-educated guesses I'll need a little time with the code. I have quite a lot to do before then. Maybe the suggestions will have been refined into something workable by that time.
Do the developers have a point of view regarding the addition of flags to the actor for this somewhat specialised purpose? What considerations do we make in that regard?
Empowerment of A_SkullAttack seems to be a rather popular idea, and flags don't "cost" that much.
Spoiler: I should be losing you here
Maybe... we could just expand the base actor class with "int skullAttackFlags" (not made available to A_ChangeFlag) containing flags from the most recent call to A_SkullAttack. That would give us some chance of covering all the flags that need to have effect after the end of A_SkullAttack.
For skull-attack damage, expand the base class with "int skullAttackDamage", and create a decorate parameter (int damage, default -1). Save the parameter value. On impact, perform normal damage calculation if skullAttackDamage is less than 0 (default), and use "skullAttackDamage" as a non-random damage amount otherwise. Gives you full control. Just make sure that your random formula is unable to return a number lower than 0 when you don't want to use...
Look... I'll just code it. Code explains itself, and I struggle to put C++ in english.
At some point I might make two patches:
Patch a does only that which can be handled instantly
Patch b extends the actor class with a couple of ints to save damage and a bundle of skullattack-specific flags that you can only access through A_SkullAttack.
Of course, the effects of a skull attack may be found a bit round and about in the code; patch b might not be an easy job. Patch a should be.