BL Coding Help request : c# modding

Users who are viewing this thread

Hexnibbler

Recruit
I have setup vs with the "hello world" project.

I'm a bit lost at "what override what" and can't figure out how to do add/modify existing behavior.

I would like to get "OnAgentHit" method from SandBox.BattleAgentLogic to add a simple debug text output such as "was the victim targetting the agent that hit it or no".

Any pointer ?
 

Bloc

Archduke
WB
I think you opened this thread in the wrong sub-section. It should be in -> https://forums.taleworlds.com/index.php?forums/modding-q-a.538/
@Marko͘ can move it to the correct section I guess

I'm a bit lost at "what override what" and can't figure out how to do add/modify existing behavior.

I would like to get "OnAgentHit" method from SandBox.BattleAgentLogic to add a simple debug text output such as "was the victim targetting the agent that hit it or no".
I wouldn't say what you are asking is suitable for "hello world" project especially since you are confused with "what overrides what". It's better for you to check and make sure you understand that logic first. Otherwise, things can be complicated. Bannerlord has a strange architecture and you can get confused easily if you are not able to estimate what they were thinking.

There are multiple ways to achieve what you want but a more straightforward way is:
- Have one CampaignBehavior that is based from CampaignBehaviorBase
- Have one MissionBehavior that is based from MissionLogic
- In your CampaignBehavior, register to OnMissionStartedEvent and in there, based on Mission type, add your MissionBehavior with using AddMissionBehaviour
- In your MissionBehavior, override OnAgentHit and do what you want to do.

So basically, what will happen is the following:
Your Module will add CampaignBehavior when the Campaign starts. Your action/method will be called when Mission starts ( each scene is actually a Mission in-game, so Mission doesn't necessarily mean Battle/Combat ) and your action will add MissionBehavior. And in MissionBehavior, your OnAgentHit will be called whenever something happens so that you can catch that event.

I recently uploaded a source-code for a simple banner mod. You can check this and understand better what calls what. You don't have to check CarrierMissionController.cs instead check SoldierResponseController.cs for a simpler version of a MissionController. And this line in CampaignBehavior to see how MissionBehavior is added.
 
Upvote 0

Hexnibbler

Recruit
I think you opened this thread in the wrong sub-section. It should be in -> https://forums.taleworlds.com/index.php?forums/modding-q-a.538/
@Marko͘ can move it to the correct section I guess


I wouldn't say what you are asking is suitable for "hello world" project especially since you are confused with "what overrides what". It's better for you to check and make sure you understand that logic first. Otherwise, things can be complicated. Bannerlord has a strange architecture and you can get confused easily if you are not able to estimate what they were thinking.

There are multiple ways to achieve what you want but a more straightforward way is:
- Have one CampaignBehavior that is based from CampaignBehaviorBase
- Have one MissionBehavior that is based from MissionLogic
- In your CampaignBehavior, register to OnMissionStartedEvent and in there, based on Mission type, add your MissionBehavior with using AddMissionBehaviour
- In your MissionBehavior, override OnAgentHit and do what you want to do.

So basically, what will happen is the following:
Your Module will add CampaignBehavior when the Campaign starts. Your action/method will be called when Mission starts ( each scene is actually a Mission in-game, so Mission doesn't necessarily mean Battle/Combat ) and your action will add MissionBehavior. And in MissionBehavior, your OnAgentHit will be called whenever something happens so that you can catch that event.

I recently uploaded a source-code for a simple banner mod. You can check this and understand better what calls what. You don't have to check CarrierMissionController.cs instead check SoldierResponseController.cs for a simpler version of a MissionController. And this line in CampaignBehavior to see how MissionBehavior is added.

I miscommunicated : I did a tutorial that create a button that print "hello world" in the main menu. What I'm looking for is what you provided : "how/where to hook my crap".

Thanks to your example I could do my goal : I had to use OnBeforeMissionBehaviourInitialize because I test in custom battle, and used CustomBattleAgentLogic as a base for mission behavior, no need for campaign behavior (but now I get a better idea of the differents parts)

So I ran my test, 200v200 imperial infantry no player intervention. "On target" being "affected.GetTargetAgent() == affector" and not on target being the opposite. So that "onTarget" count how many time a soldier is hit by someone who he, himself, is targetting. The question I was asking is : "Does the troops get killed so fast in melee because their AI doesn't take into account attack from nearby units ?"
(note : it includes blocks and does not include friendly_block)

Results :
on / tot = 0.1909814
not / tot = 0.8090186

still not sure, but certainly seems that the answer is yes.
 
Upvote 0

Bloc

Archduke
WB
I miscommunicated : I did a tutorial that create a button that print "hello world" in the main menu. What I'm looking for is what you provided : "how/where to hook my crap".

Thanks to your example I could do my goal : I had to use OnBeforeMissionBehaviourInitialize because I test in custom battle, and used CustomBattleAgentLogic as a base for mission behavior, no need for campaign behavior (but now I get a better idea of the differents parts)
I see, glad to help - at least we have a solution like thing now.
But for the future questions, it's better if you explain what you want to do - since CustomBattle's are different than Campaign Battles as you already figured it out and suggestions people can give will change based on that.

For further checking, since you are doing this only for getting statistics out of it, you can simply use Harmony and hook-up to game methods and do your counting/analysis without bothering with your custom logic. With this way you can take more information since some parts of the game are still and very much internal or private.

So I ran my test, 200v200 imperial infantry no player intervention. "On target" being "affected.GetTargetAgent() == affector" and not on target being the opposite. So that "onTarget" count how many time a soldier is hit by someone who he, himself, is targetting. The question I was asking is : "Does the troops get killed so fast in melee because their AI doesn't take into account attack from nearby units ?"
I understand what you are trying to measure but I don't think this approach is correct.
Because think about this scenario,
Agent A vs Agent B and Agent C. A targets B, meanwhile B and C target A. We will listen Agent A.
A hits B
C hits A ( registered as non target hit )
A switch targets to C since it got attacked by it
B hits A ( registered as non target hit )
in numbers, it will say A is hit by non target two times and died. But in reality, A did what human players do and actually engaged to enemy which damaged them.

So you can make it a more fair analysis for bots by keeping a temporary target list with timestamps and measure if the registered non-target was a previous target or not - so that you can see if AI initially considered this as a target but had to switch because of something else. If you see that it's the case, then you can search for reasons why AI is switching to wrong target.
Or you can measure this for each agent,
When agent picks a target, you can get enemy agents within a small radius and check how many of them are targeted this agent.
Keep them in list and when any of non-targets change stance from defense ( holding shield or blocking ) to attack, you can see if our main agent is able to pick this cue and change it's target for proper defence.

Because issue is ( not doing analysis like you do so it's a hunch based on gameplay experience ):
AI focusing on the same target until they get hit by some another target. This means they are ignoring the fact that agent C is lifting his weapon and about to hit because it's busy with targeting agent B which is defending himself. What agent A have to do in this scenario is turning to C and blocking that attack if possible.
And I guess that's also what you want to find out - with analysis and numbers.
 
Upvote 0

Hexnibbler

Recruit
I see, glad to help - at least we have a solution like thing now.
But for the future questions, it's better if you explain what you want to do - since CustomBattle's are different than Campaign Battles as you already figured it out and suggestions people can give will change based on that.

For further checking, since you are doing this only for getting statistics out of it, you can simply use Harmony and hook-up to game methods and do your counting/analysis without bothering with your custom logic. With this way you can take more information since some parts of the game are still and very much internal or private.


I understand what you are trying to measure but I don't think this approach is correct.
Because think about this scenario,
Agent A vs Agent B and Agent C. A targets B, meanwhile B and C target A. We will listen Agent A.
A hits B
C hits A ( registered as non target hit )
A switch targets to C since it got attacked by it
B hits A ( registered as non target hit )
in numbers, it will say A is hit by non target two times and died. But in reality, A did what human players do and actually engaged to enemy which damaged them.

So you can make it a more fair analysis for bots by keeping a temporary target list with timestamps and measure if the registered non-target was a previous target or not - so that you can see if AI initially considered this as a target but had to switch because of something else. If you see that it's the case, then you can search for reasons why AI is switching to wrong target.
Or you can measure this for each agent,
When agent picks a target, you can get enemy agents within a small radius and check how many of them are targeted this agent.
Keep them in list and when any of non-targets change stance from defense ( holding shield or blocking ) to attack, you can see if our main agent is able to pick this cue and change it's target for proper defence.

Because issue is ( not doing analysis like you do so it's a hunch based on gameplay experience ):
AI focusing on the same target until they get hit by some another target. This means they are ignoring the fact that agent C is lifting his weapon and about to hit because it's busy with targeting agent B which is defending himself. What agent A have to do in this scenario is turning to C and blocking that attack if possible.
And I guess that's also what you want to find out - with analysis and numbers.
yes it's more complex that I first thought. And I have no guarantee about "what" I am really doing stats (haven't digged that much into the meaning of different parameters). But assuming it is meaningful :

45% of the hits in melee are "collateral" (hitting someone that wasn't targeted by the attacker).
but in those collateral I separated those where the defender doesn't target the "hitter" (40%, half the 80% shown above) and only 10% are case where the "hitter" strike the defender "by accident" while the defender is targetting the hitter.
So it does go in the direction of targetting whoever is attacking multiply the survivability by 4.

Also the collateral decrease overtime while the targetting increases (which makes sense : less fighters)
both "upper" graph shows stats when the "defender" is being struck by someone he targetted
both "lower" graph shows stats where he didn't
both "left" graph shows that whoever struck the defender did targetted him
and on the "right", he wasn't.

I.E.the funny upper right : the defender was targetting the attacker, and the attacker wasn't but hit him "by accident".


i0uPrfh.png



might be doing more stats about the effect of formation, accuracy and stuff, will look into what harmony is. But I usually tend to prefer what I already know a bit. Kind of my way of learning how stuff works
 
Upvote 0

Bloc

Archduke
WB
45% of the hits in melee are "collateral" (hitting someone that wasn't targeted by the attacker).
but in those collateral I separated those where the defender doesn't target the "hitter" (40%, half the 80% shown above) and only 10% are case where the "hitter" strike the defender "by accident" while the defender is targetting the hitter.
So it does go in the direction of targetting whoever is attacking multiply the survivability by 4.
Okay in here you switched the strategy I think. Because in previous post you said you were checking affected agent - not the affector agent. Or perhaps I misunderstood. But agreed, looking at affector agent stats is better than simply checking affected agent. Since affector knows its own "motive" better.

will look into what harmony is. But I usually tend to prefer what I already know a bit. Kind of my way of learning how stuff works
That's fine, but patching things with your own code grants you more flexibility on your analysis. For example,
ComputeBlowMagnitudeMelee method in Mission is private and doesn't have any "event" that you can listen. So you can simply add something to it's Prefix or Postfix and get the exact same parameters that normal ComputeBlowMagnitudeMelee is getting. ( You need to check method signatures and stuff first, don't rely on what I posted, it's just an example )
C#:
public class MySuperPatchingClass{
        [HarmonyPatch(typeof(Mission), "ComputeBlowMagnitudeMelee")]
        public class PatchMissionComputeBlowMagnitudeMelee
        {
            public static void Postfix(BasicCharacterObject attackerCharacter,
                                                      BasicCharacterObject attackerCaptainCharacter,
                                                      AttackCollisionData acd,
                                                      bool isAttackerAgentNull,
                                                      Vec3 attackerCurrentWeaponOffset,
                                                      bool isVictimAgentNull,
                                                      bool doesAttackerHaveMount,
                                                      bool doesVictimHaveMount,
                                                      bool isVictimMount,
                                                      float momentumRemaining,
                                                      bool cancelDamage,
                                                      bool hitWithAnotherBone,
                                                      float baseMagnitude,
                                                      float specialMagnitude,
                                                       float movementSpeedDamageModifier,
                                                       int speedBonusInt,
                                                      StrikeType strikeType,
                                                      Agent.UsageDirection attackDir,
                                                      MissionWeapon weapon,
                                                      bool attackerIsDoingPassiveAttack,
                                                      Vec2 attackerVelocity,
                                                      Vec2 victimVelocity)
            {
               // Now you are hooked to ComputeBlowMagnitudeMelee in Mission class
                // Do your analysis with params in here if you need anything from ComputeBlowMagnitudeMelee method
            }
        }
}
and you need to make sure patch is called in submodule so something like this would do the trick
C#:
        protected override void OnSubModuleLoad()
        {
            try
            {
                new Harmony("com.my.analysis.on.weird.ai").PatchAll();
            }
            catch (System.Exception e)
            {

            }
        }


Nevertheless, it's very strange to see that 45% of the hits are done to agents that weren't targeted.
It clearly shows half of the entire combat is simply put, a cluster****. And it's funny to see that 10% of it was actually unintentional self defence lol I think all these collateral damages might be happening because of "unit blob" that is happening because of poor AI target choice or bad collider radius. If I'm not mistaken, @Terco_Viejo had several tests on unit blobs/formation collision clustering - but I think this requires another thread to be discussed since I don't think it's related to coding part of this question. Still he is welcome to correct me if I'm mistaken.
 
Upvote 0

Hexnibbler

Recruit
yes I did switched the idea by playig around what seems available. I don't have the courage or ability to do careful precise analysis, just trying to get a meaningful idea of what is going on. I've 2 new sets of plots that seem to validate that there is an issue with the individual targetting behavior in melee. But I'll post that in general chat to discuss and see what comes around. ( https://forums.taleworlds.com/index.php?threads/ai-agent.447459/ )

as for harmony, it definitively gets me interrested. I felt called out by the "It does this at runtime by monkey patching methods unlike other solutions that change the content of dll files." :grin:
 
Upvote 0

Bloc

Archduke
WB
For the discussion part, I guess you were supposed to open this new thread under https://forums.taleworlds.com/index.php?forums/the-citadel-general-discussion.434/ since it's more like a general discussion thing. But I'm guessing moderators can move it to the correct place. Although I'm seeing that you wrote:
I would like to request a callback from the engine to the c#script side to allow overwriting the target the engine has selected.
which makes it a suggestion. But your "intro" is too long for anyone to read or understand.
I would suggest you separate your discussion/knowledge sharing thread from your request thread.

About your request, they already have this in Agent
C#:
 public void SetTargetPosition(Vec2 value)
    {
      MBAPI.IMBAgent.SetTargetPosition(this.GetPtr(), ref value);
    }
which you can set by getting the target Vec2 position. So you can simply keep track of Agent objects in and when you want to update their target, you can call this with target agent's position.
 
Upvote 0
Top Bottom