nosoop / SM-TFOnTakeDamage

Hooks into TF2's slightly extended OnTakeDamage function.
GNU General Public License v3.0
7 stars 6 forks source link

TraceAttack implementation #8

Open kir68k opened 8 months ago

kir68k commented 8 months ago

Hey, I have to wonder if it'd be possible to add this to ontakedamage.sp and .inc. I have limited sourcepawn knowledge currently so don't feel confident trying myself yet, but would really like this feature for a different plugin.

nosoop commented 8 months ago

It's possible, though I'm not at all sure that I'd be motivated to implement and maintain support for it at this time. I'll leave this issue open in case it's revisited in the future.

Did you have a particular use case in mind? It doesn't sound like you're certain that TraceAttack would be appropriate for your needs.

If nothing else, it wouldn't be too difficult to set up the hooks yourself — headshot_damage_modify is a unique string used for CTFPlayer::TraceAttack (this would be called when a player is hit). If you need more information on the hook setup (signatures, DHooks options), feel free to ask here.

kir68k commented 8 months ago

I see. I want to specifically use this on my slightly modified version of a plugin for headshots on any hitscan weapon. This plugins uses TraceAttack to register whether a hit was landed on the head hitbox, if I understand correctly.

Ideally I'd have a convar to switch between normal crit and minicrit, but I primarily wanted to change it from simply using DMG_CRIT for full crits to a minicrit, as to not be too overpowered.

Learning how the game handles crits and how it's DMG_CRIT for both, I found this plugin.

kir68k commented 8 months ago

Also, I'd try setting up the hooks myself, but as said my knowledge is very limited, as in I started very recently. After learning more myself about how this plugin works I could try implementing this, possibly.

kir68k commented 8 months ago

If you need more information on the hook setup (signatures, DHooks options), feel free to ask here.

As far as I understand, in classdefs.h there would have to be another struct for TraceAttack, right? I would try to add one to complete other parts that use that struct, but I don't quite understand where to get the values of the struct like m_iAmmoType from. Is there a list anywhere of all these values? I don't know all their names.

nosoop commented 8 months ago

The trace_t struct is an alias for CGameTrace.

In the context of implementing a TraceAttack hook, you can just define it as an objectptr in DHooks and not worry about creating a structure definition for it.


If you do care about the details, keep in mind that it inherits from CBaseTrace so you'd need to define the members of each property you're interested in manipulating.

For the sake of prototyping for your own case, you'd probably be better off not dealing with classdefs.h and the codegen stuff. Instead, you can calculate the offsets by hand, e.g.:

int offs_CBaseTrace_startpos = 0;
int offs_CBaseTrace_endpos = 12; // a vector is 3 float values and each float is 4 bytes each
int offs_CBaseTrace_plane = 24; // keep in mind that this value is the start position (the size of the previous member is added to it)
int offs_CBaseTrace_fraction = 44; // + size of cplane_t

int offs_CGameTrace_fractionleftsolid; // rest of the owl... (because this inherits from CBaseTrace, this starts at the end of that struct)
kir68k commented 4 months ago

It's been a while, but I currently have time to implement this if I understand everything correctly, so far I understand:

For clarity, a plugin with this could have TF2_TraceAttack instead of TraceAttack, like so:

public Action TF2_TraceAttack(int iVictim, int &iAttacker, int &iInflictor, float &flDamage,
        int &iDamageType, int &iAmmoType, int iHitBox, int iHitgroup,
        CritType &critType) {
    if (iHitgroup == 1) {
        critType = view_as<CritType>(1);
        return Plugin_Changed;
    }
    return Plugin_Continue;
}

Would an implementation like this, where in this case if the value of iHitgroup from the action is 1, set the crit type to a mini-crit, make sense, or am I misunderstanding something? I'm not exactly sure how the crit type could be modified in TraceAttack, but some weapons can do this, so it must be possible.

Sorry for all the questions, just a bit confused on how this could be achieved. If I understand everything right, I could try to implement it and make a PR if it works and doesn't crash. Thanks in advance.

nosoop commented 4 months ago

I'm not exactly sure how the crit type could be modified in TraceAttack, but some weapons can do this, so it must be possible.

CTFPlayer::TraceAttack receives a const CTakeDamageInfo instance, so you should be able to modify that information in the pre-hook. It seems sound, though I don't know how that all plays out in reality (i.e. whether the game correctly handles this).

Note that a pre-hook on that function is before checks on whether or not player can take damage in the first place and hitgroups are updated, and the function passes a copy of CTakeDamageInfo to AddMultiDamage, so a post hook isn't really effective in mutating that.

nosoop commented 4 months ago

Bonus aside: If your goal is to modify damage per-bullet for a shotgun, that won't fly — crits scale the aggregate damage of all bullets that land.

Otherwise, there is a plugin that modifies crits based on last hitgroup, but I believe that's only good if the last bullet to apply damage is on the desired hitgroup. If you want to apply minicrits if any bullet lands, then yes, a TF2_TraceAttack would make sense.

kir68k commented 4 months ago

Okay, I have the proper signature:

_ZN9CTFPlayer11TraceAttackERK15CTakeDamageInfoRK6VectorP10CGameTraceP15CDmgAccumulator

I've tried setting up a DynamicDetour for it, like so:

g_DHookTraceAttack = new DynamicDetour(Address_Null, CallConv_THISCALL,
    ReturnType_Void, ThisPointer_CBaseEntity);
g_DHookTraceAttack.SetFromConf(hGameConf, SDKConf_Signature, "CTFPlayer::TraceAttack()");
g_DHookTraceAttack.AddParam(HookParamType_CBaseEntity);
g_DHookTraceAttack.Enable(Hook_Pre, Internal_TraceAttack);
g_DHookTraceAttack.Enable(Hook_Post, Internal_TraceAttackPost);

The function is a void, per sdkhooks/extension.cpp and the disassembler I got signatures from, so not sure what HookParamType would be.

Then g_FwdTraceAttack with ET_Ignore (as TraceAttack is a void), and parameters according to how I defined it in the include file, so far so good?

The server reports "Exception reported: Invalid Handle 0" for Internal_TraceAttack which looks like Internal_OnTakeDamage, but for g_FwdTraceAttack. Why would hParams be invalid? I can try modifying CallTakeDamageInfoForward and the struct to include m_nHitbox, m_hitGroup, which are needed.

This feels more complicated than I expected, but my approach might be wrong. I'll read more about hooks later this week. Asking to know if I fully misunderstand this, or if it's at least somewhat right.

Thanks for helping so far.

kir68k commented 4 months ago

Wanted to continue tomorrow, but tried now. Created a struct CTraceAttackInfo, CallTraceAttackInfoForward and a post hook forward. I'm a bit confused about the build process now, spcomp can't find the new created CTraceAttackInfo, so it can't be compiled.

I did add it to the configure script, removed the Ninja file/build dir just in case, and the new sp file is in the generated dir... Shouldn't this work, or am I missing something to add?

nosoop commented 4 months ago

If you've defined CTraceAttackInfo in classdefs/classdefs.h then you'd need to add it to the list here.

Depending on how it's set up you may want to look at misc/generate_classes.py, but it's admittedly hacked up for this project since I didn't expect other people to stray from the happy path of only trying to build it as-is.

Have you built a CTraceAttackInfo hook outside of the plugin? I think you'd make better progress prototyping the whole thing without wrestling with the code generation tooling in this plugin and other quirks of class property alignment.