How to create a hook

Creating a hook (with code snippets)

Created by HJHughJanus on github, copied here for easier maintainability

What is a hook?

wrapMethod

Also called wrapper function, this piece of code hooks into existing game functions.

The only exception from that rule are functions and classes marked with the native keyword.

You define those wrappers outside of your own class at the root level.

To trigger the original callback, you need to call wrappedMethod(evt) either before or after you did your own thing:

@wrapMethod(YourWrappingTarget)
protected cb func OnSomethingHappens(originalEventParam: ref<SomeRef>) -> Bool {
  wrappedMethod(originalEventParam);
  // do stuff here
}

replaceMethod

Opposed to a wrapped method, a replaced method will stop the original game function from doing anything.

@replaceMethod(YourWrappingTarget)
protected cb func OnSomethingHappens(originalEventParam: ref<SomeRef>) -> Bool {
  return false;
}

Things to hook

Example

Goal: Ragdoll any NPC hit by a bullet. System: I need to find out how Cyberpunk 2077 handles bullets and damage → a look through the decompiled game scripts shows there is a class called „DamageSystem“. This class looked up on Cyberdoc shows there is a function wihtin that class called „ProcessLocalizedDamage“. Back in the decompiled game scripts the code of this function seems to handle a „hit event“ and checking if the bullet hit the head or any weak spots. That is very close to my goal, so I will use this function for the hook. Functions: I want to ragdoll an NPC, so I will need a function to do that. I search on Cyberdoc for „ragdoll“ and find something called „ApplyRagdollImpulseEvent“. I search this function in the decompiled game scripts to find out how it is used.

First, I need to specify which system I want to use for injection, then specify the function.

@wrapMethod(DamageSystem)
private func ProcessLocalizedDamage(hitEvent: ref<gameHitEvent>) {
}

In this function I must now call the vanilla function first, so I don‘t overwrite any of the vanilla action – you can do this via the function wrappedMethod(params) (this one automatically calls the vanilla function).

@wrapMethod(DamageSystem)
private func ProcessLocalizedDamage(hitEvent: ref<gameHitEvent>) {
wrappedMethod(hitEvent);
}

Then I copy some code from the vanilla function to handle some errors and set up my variables.

@wrapMethod(DamageSystem)
private func ProcessLocalizedDamage(hitEvent: ref<gameHitEvent>) {
    wrappedMethod(hitEvent);
    
    //variables
    let hitUserData: ref<HitShapeUserDataBase>;
    let hitShapes: array<HitShapeData> = hitEvent.hitRepresentationResult.hitShapes;
    
    //error handling and setting up hitUserData (copy & paste from original)
    if !hitEvent.attackData.GetInstigator().IsPlayer() {
      return;
    };
    
    if AttackData.IsAreaOfEffect(hitEvent.attackData.GetAttackType()) {
      return;
    };
    
    if ArraySize(hitShapes) > 0 {
      hitUserData = DamageSystemHelper.GetHitShapeUserDataBase(hitShapes[0]);
    };
    
    if !IsDefined(hitUserData) {
      return;
    };
}

Now my actual code can take effect. Since I found the „ApplyRagdollImpulseEvent“ and looked up how it is used, I know this will not be done in one line. So, to keep the code in the wrapper function simple, I will write a separate function below which sets up the ragdolling for me.

@wrapMethod(DamageSystem)
private func RagdollNPC(target: ref<NPCPuppet>, pushForce: Float) -> Void {
  if IsDefined(target) {
    target.QueueEvent(CreateForceRagdollEvent(n"Debug Command"));
    if pushForce > 0.0 {
      GameInstance.GetDelaySystem(target.GetGame()).DelayEvent(target, CreateRagdollApplyImpulseEvent(target.GetWorldPosition(), Vector4.Normalize(target.GetWorldPosition()) * pushForce, 5.00), 0.10, false);
    };
  };
}

Now my actual code can take effect. Since I found the „ApplyRagdollImpulseEvent“ and looked up how it is used, I know this will not be done in one line. So, to keep the code in the wrapper function simple, I will write a separate function below which sets up the ragdolling for me.

@wrapMethod(DamageSystem)
private func ProcessLocalizedDamage(hitEvent: ref<gameHitEvent>) {
    //start vanilla function, so everything at least runs normally (even if the mod doesnt work)
    wrappedMethod(hitEvent);
    
    //variables
    let hitUserData: ref<HitShapeUserDataBase>;
    let hitShapes: array<HitShapeData> = hitEvent.hitRepresentationResult.hitShapes;
    
    //error handling and setting up hitUserData (copy & paste from original)
    if !hitEvent.attackData.GetInstigator().IsPlayer() {
      return;
    };
    
    if AttackData.IsAreaOfEffect(hitEvent.attackData.GetAttackType()) {
      return;
    };
    
    if ArraySize(hitShapes) > 0 {
      hitUserData = DamageSystemHelper.GetHitShapeUserDataBase(hitShapes[0]);
    };
    
    if !IsDefined(hitUserData) {
      return;
    };

    //actual mod code
    let npc: ref<NPCPuppet> = hitEvent.target as NPCPuppet;
    let npcID: StatsObjectID = Cast(npc.GetEntityID());
    if IsDefined(npc) {
      RagdollNPC(npc, 10.0);
    };
}

The whole file now looks like this:

This file I save and then put into D:\PATH\TO\CYBERPUNK2077\r6\scripts so the game will load it.

When I start the game, every NPC hit by a bullet should be ragdolled with a push.

Last updated