Fun with game_event_simulate_battle

Users who are viewing this thread

Spurred on by some "interesting" results playing Native Expansion mod, I started poking around in game_event_simulate_battle to find out what's up.  Utilizing the search feature of this board, there's been a couple discussions already but I've got further questions that don't seem to be addressed.  I'll start with the questions first so people don't get bored of reading my drivel and follow with my current understanding of how the game resolves combat automagically. 

Questions
[list type=decimal]
[*]Once all the strength determination is done and it's time for bodies to hit the floor, it looks like the normal thing to do is to call inflict_casualties_to_party_group which is assumedly not a script and hardcoded somewhere.  Does anyone know what the domain is for the strength inputs to this function? 
[*]Also in inflict_casualties_to_party_group:  it takes the opposing force's strength as an input as well as "this" party's ID.  Does it call script_party_calculate_strength to calculate the strength or is there another script/internal function that handles it?
[*]What's the most reasonable/efficient way to test any hackeries I might make to these funcitons?
[/list]

Discussion
The basic code does something like this:
- calculate the strengths of both sides
- do some normalization
- call inflict_casualties_to_party_group for both sides
- rinse & repeat until someone bails or is dead

The strength calculation goes something like this:
  for each unit stack:
      get the level of the stack and add 12 to it
      square the result
      integer divide by 100
      multiply by the number of fit troops in the stack
      add this to the force's total strength

So if we look through some random examples:
- a level 1 farmer has strength 1
- a level 10 footman has strength 4
- a level 20 knight has strength 10
- a level 30 super-elite troop has strength 17 (note that Native doesn't seem to have troops over L2:cool:

The normalization step divides each team's strength by 20 and clamps it to [1...50].  With a usable force strength range of roughly 20 to 1000.

The Plot Thicks!
Heading backward from the normalization step and factoring in some test numbers, it starts to become clear why the auto-resolver gives us some wacky results. 

If you have a group of 10 farmers vs. one Swadian Knight (L25, strength 13) you end up resolving one group of 10 guys with strength 1 and one group of one guy with strength 1.  Naturally, the peasants typically win this even though our in-game experience tells us otherwise. 

Worse yet, if you have a big army of really good troops and fight a bigger army of really poor troops, you can sometimes get strange results.  A group of 100 Huscarls (L28, strength 16) has a raw strength score of 1600 which gets divided by 20 and then clamped to 50.  A group of 500 Vaegir Recruits (L4, strength 2) with raw strength score 1000 has the same strength of 50.  Result:  bloodbath for the Huscarls even though our in-game experience again tells us otherwise.

Closing Thoughts
What I'll probably do is change the strength calculation and then change the normalization.  It wouldn't be terribly difficult to find (or assign) an armor/weapon/whatever value to a troop and factor that into their individual strengths.  This would better capture the multiplicative nature of troop level (higher level troops->more hitpoints->more damage output->better weapons->better armor means more staying power->yadda yadda).  Changing the normalization is similarly easy.  I can almost trivially keep the [1...50] range by scaling the strength of forces relative to each other if their unnormalized strengths are out of bounds. 

This would also open up some fun opportunities like:
- If I have the Talisman of AssKicking, my kingdom does better in sieges.
- I can have werewolf patrols that are very Bruce Banner during the day and super Hulk at night (though, for some reason, auto-battles don't happen at night).
- Actually using the auto-resolver for battles I'm leading for those pesky 3 guys that always seem to be at the end of every big fight rather than running them down and putting arrows into them.

Assuming nothing blows up given my penchant for hackery, I suspect I'll have some results to spam in the future.
 
ok first off, I applaud your interest in this. Bringing out a game like this with such a poor battle resolver when the AI uses it continually, not to mention the player in the unfortunate occurance of a knock-out, is nothing short of criminal.

To continue at the end, I think including some kind of assesment of strength based on items would be a good start, if perhaps a bit involved. Perhaps you could borrow from the autoloot script there since it checks items to determine the 'best' one. Either way you could at least do something with armor and main weapon dmg to produce a more reliable unit strength than just unit lvl.. since an unarmed lvl 30 unit would still get his asses handed to him by five farmers with pitchforks.

Now the most reasonable way to test this would be to spawn two parties close to eachother and set the ai_bhvr of one of them to attack the other one. So make two new templates, add the units you want to that in the numbers you want there to be. You can make sure of the amount of units by making the upper and lower bounds the same. Alternatively, just spawn two empty parties and then add troops to both with a script/trigger:

Code:
(1, [
(lt, "$g_spawning_done", 1),
(spawn_around_party, "p_main_party", "pt_temp_combatant"),
(assign, ":combatant_1", reg0),
(spawn_around_party, "p_main_party", "pt_temp_combatant"),
(assign, ":combatant_2", reg0),
(party_add_members,":combatant_1","trp_whatever",10),
(party_add_members,":combatant_2","trp_whatever",10),
(party_set_ai_behavior, ":combatant_1", ai_bhvr_attack_party),
(party_set_ai_object, ":combatant_1", ":combatant_2"),
(assign, "$g_spawningdone", 1),
]),

Just add the template for temp_combatant to the party_templates thing. I'm not sure, but the fact that they are both of the same faction might be a problem here, so you may have to assign them both a hostile faction, say bandits and kingdom one. You can do that with
(party_set_faction, ":combatant_1", "fac_whatever_faction"),

Another thing that pisses me off about the resolver is how long it takes for some battles. I've had battles with 60 of my patrol troops against 5 bandits and it took them over 24 hours to resolve the fight, not very realistic and that is something that's been on my 'to tweak/fix' list. So if I can help you with this quest, I'm more than happy to do so :smile:

The inflict_casualties thing seems to be coded into the module system so there's not much room to move with that one. If you wanted, you could maybe find the actual c++ code that's used for that function and see how it works exactly, but it might be easier just to pass it by completely and let the event_simulate_battle calculate the casualties and then inflict them manually since that way you have total control over what happens.

Not sure what you mean by the domain of the input to the operation, from looking at the script it just uses the strength as determined by script_party_calculate_strength.

So first step would be to improve on that function. The val_clamp seems to be the most logical place to start, after all, why should there be a maximum? If you want to balance unit nrs vs individual unit strength, you can do so by making 10 units of str 10 stronger than 1 unit of str 100 (say by adding an additional base amount per unit, this can be a percentage of individual unit str.
 
Ok, I've spent the morning trying to figure out how this stuff works and I've got some preliminary results.

I suppose I should have been able to puzzle it out from the name, but inflict_casualties_to_party_group does just that:  figures out how many dead guys you get on a per-call basis.  The input to the function (strength) is basically a damage amount upon which it does some maths with a random element.  My worries about the domain of the function seem to be unfounded; you can give it some wildly huge numbers and it doesn't seem to care.  In fact, when the player stays back and orders troops forward, it doesn't do any normalization at all (as seen in order_attack_2 in menus). 

The maths in this case seems to be around the input strength divided by ten plus or minus 20% of the input value.  So if you throw it a 500, you typically end up with around 50 casualties give or take 10 or so with (seemingly) a minimum variance of 3.  I don't know if it's a true bell curve as I don't have the patience to do a proper statistical analysis but this seems to hold true for very large values as well as very small values. 

It does seem take the troop type into account although not as much as I'd expected.  I was testing Sea Raiders vs. Huscarls and got roughly even losses on both sides for the same strength amount.  Switching to Looters vs. Huscarls, losses started mounting at a rate of roughly 2:1.  As Huscarls have something like 1.5x the armor of Sea Raiders (banded vs. byrnies), I'd have expected to see this though it might just be hiding in the noise of the random variation.  I'll keep my eyes open to see if I can figure out what the difference is, but I don't think it's the results of party_calculate_strength.

So...

Where the rubber meets the road is how the strength calculation is done and how the results are fed into inflict_casualties_to_party_group.  In particular, I'll be looking into doing a hacky attack strength vs. defense calculation and modifying both party's strengths based on that before calling inflict_casualties_to_party_group in the various places it's used.  I'll probably also add in a factor for having completely lopsided fights to limit losses on the winning side and some accounting for tactics skills because that seems fitting.  Finally, if I'm really feeling ambitious, I'll add a parameter to the strength calculation function to manage a bonus for mounted troops so you can turn off that part for sieges (Swadian Knights are overpowered as it is without getting bonuses for their horses when they're on foot).  There's an off chance that I'll get fed up with the randomness of inflict_casualties_to_party and roll my own method in script.

MartinF:  In the case of battles between AI bands, I would absolutely remove the top bound of the normalization step so that instead of values from 1 to 50 it gets values from 1 to whatever so that casualties are higher on a per-round basis.  The next thing I would do is take out the night checks which keep battles from resolving in the evening (parties will stay in battle but won't do any damage to each other until morning which might explain your 60 guys vs 5 bandits).  I haven't a clue as to why those checks are there.  I haven't been able to find the place where it decides to keep fighting but it seems to be a trigger of some sort so you could also modify that length though it's probably not necessary if the previous mods have been made.

With any luck, I'll be able to follow this with a code snippet later today.  Thanks a bunch for the input! 
 
Well if I decide to tackle this myself at some point (rather than just leach off your code :grin:) I'll just call it from a trigger or simple trigger so I can decide on the interval of each 'round' of fighting.

Maybe you're right and it was just the night thing that was messing up the fights, in the Star Wars mod it's a bit hard to see since we don't use day and night on the world map for obvious reasons. Still.. I know I've seen fights during the day that lasted way too long as well.

Personally, I'd just go with a completely new setup from scratch rather than using the inflict_casualties thing, unless you can predict exactly what it will do. That will give you much more control over what happens.

Anyway it sounds like you've got your work cut out for you, I'm looking forward to the results!
 
MartinF said:
Maybe you're right and it was just the night thing that was messing up the fights, in the Star Wars mod it's a bit hard to see since we don't use day and night on the world map for obvious reasons. Still.. I know I've seen fights during the day that lasted way too long as well.
Since you can only really kill 5ish guys per round, fights between non-trivially sized AI parties can last for a pretty large number of rounds :smile:  It's the div 20 and clamp 50 that draws these things out; especially between very large groups (e.g., a group of 500 Huscarls can only really expect to kill 5 guys a round which is notably the same as a group of 50000 Huscarls as it's currently implemented).

MartinF said:
Anyway it sounds like you've got your work cut out for you, I'm looking forward to the results!
It'll be an adventure! :grin:
 
kt0 said:
Since you can only really kill 5ish guys per round, fights between non-trivially sized AI parties can last for a pretty large number of rounds :smile:  It's the div 20 and clamp 50 that draws these things out; especially between very large groups (e.g., a group of 500 Huscarls can only really expect to kill 5 guys a round which is notably the same as a group of 50000 Huscarls as it's currently implemented).

aaah ok, of course.. that makes sense. I didn't grasp that concept in your earlier posts. I was just thinking in terms of relative dmg, i.e. no matter their size, they do a max amount of dmg, but of course that is per round so you're right.

Anyway I have no problem with a battle between two 200 sized armies taking a day or two, if anything, that's realistic (although of course they wouldn't be fighting continuously). But 50 vs 8 should be over in an hour or so..

Interesting stuff.
 
Hoookay.  I have a result.  I'm not entirely happy with it as it doesn't seem to be possible to:
- get the combat values off of items (you can get shop value, but that's not what I'm after)
- get values off of individual soldiers (seems to only grab the first troop)

Instead, I hacked up the existing code and changed the battle formulas.  For those following along at home, here's what I did:
- changed the strength calculation to have a wider dynamic range (better troops have much larger numbers)
- added an advantage bonus if your guys are just way better than theirs or if you've got the massive numerical advantage

Changes come in two pieces.  I added this script function to module_scripts.py to change the strength calculation.  If you want to replace yours, feel free, but understand that the strength code is used in *A BUNCH* of different calculations and the two ranges are not compatible.  I added a 50% bonus for heroes and a 25% bonus for cavalry.  I've left some debug prints commented out in there in case anyone wants to tweak values.
# INPUT: 
#  arg1:  party_id
#  arg2:  exclude leader
#  arg3:  ignore horses
# OUTPUT:
#  reg0:  attack strength
( "kt_party_calculate_strength",
  [
  # remember our params
  (store_script_param_1, ":party"), # party id
  (store_script_param_2, ":exclude_leader"), # also a party id apparently
  (store_script_param, ":ignore_horses", 3), # so we don't count horses for sieges

  # clear out our returns
  (assign, reg0, 0),

  # figure out which stack to start with and how many we have
  (party_get_num_companion_stacks, ":num_stacks", ":party"),
  (assign, ":first_stack", 0),
  (try_begin),
      (neq, ":exclude_leader", 0),
      (assign, ":first_stack", 1),
  (try_end),

  (try_for_range, ":i_stack", ":first_stack", ":num_stacks"),
      (party_stack_get_troop_id, ":stack_troop", ":party", ":i_stack"),
      (store_character_level, ":stack_strength", ":stack_troop"),
      (val_add, ":stack_strength", 5),
      (val_mul, ":stack_strength", ":stack_strength"),
      (val_div, ":stack_strength", 10),
      (party_stack_get_size, ":stack_size",":party",":i_stack"),
      (party_stack_get_num_wounded, ":num_wounded",":party",":i_stack"),
      (val_sub, ":stack_size", ":num_wounded"),
      (try_begin),
        (gt, ":stack_size", 0),
      (try_begin),
        # if this is not a hero, multiply by stack strength
        (neg|troop_is_hero, ":stack_troop"),
        (val_mul, ":stack_strength", ":stack_size"),       
      (else_try),
        # if this is a hero, give them 50% more
        (neg|troop_is_wounded, ":stack_troop"),
        (val_mul, ":stack_strength", 3),
        (val_div, ":stack_strength", 2),
        (assign, reg10, ":i_stack"), # hax
        #(display_message, "@HERO found at stack {reg10}!"), # hax
      (try_end),
      # if this is a mounted troop, give them 25% more
      # (mounted heroes get a total bonus of 87.5%)
      (try_begin),
        (neq, ":ignore_horses", 1),
        (troop_is_mounted, ":stack_troop"),
        (val_mul, ":stack_strength", 5),
        (val_div, ":stack_strength", 4),
        (assign, reg10, ":i_stack"), # hax
        #(display_message, "@CAVALRY found at stack {reg10}!"), # hax
      (try_end),
        (assign, reg10, ":i_stack"), # hax
        (assign, reg11, ":stack_strength"), #hax
        #(display_message, "@STACK STRENGTH for stack {reg10} is {reg11}"), # hax
        (val_add, reg0, ":stack_strength"),
      (try_end),
  (try_end),
  # storing on the slot will have unintended consequences!  caveat emptor!
  #(party_set_slot, ":party", slot_party_cached_strength, reg0),
]),

The second bit of code needs to exist anywhere you call this function and want to use it for auto-resolving battles.  There are *A BUNCH* of places that resolve battle automagically which really bothers me.  I don't know if they're all used.  The ones I know about are:
- total_victory in module_game_menus.py (untested)
- join_order_attack in module_game_menus.py (untested)
- castle_attack_walls_simulate in module_game_menus.py (untested)
- castle_attack_walls_with_allies_simulate in modules_game_menus.py (untested)
- siege_join_defense in modules_game_menus.py (untested)
- game_event_simulate_battle in module_scripts.py (tested)
- order_attack_begin in modules_game_menus.py (tested; where I did most of my testing in fact)

Many of these are cut & paste jobs right down to the crappy formatting.  My code-nazi tendencies aside, here's what I did.
Sorry, but this isn't a drop-in replacement and as much as I'd like to give y'all one, it's better that I don't so that nothing indvertently breaks.  You basically want to find a sequence that looks like this, annotated for your approval:

  # calculate the strengths of both parties and divide by five
  (call_script, "script_party_calculate_strength", "p_main_party", 1), #skip player
  (assign, ":player_party_strength", reg0),
  (val_div, ":player_party_strength", 5),
  (call_script, "script_party_calculate_strength", "p_collective_enemy", 0),
  (assign, ":enemy_party_strength", reg0),                                   
  (val_div, ":enemy_party_strength", 5),

  # hurt the player party and display some stuff.
  (inflict_casualties_to_party_group, "p_main_party", ":enemy_party_strength", "p_temp_casualties"),
  (call_script, "script_print_casualties_to_s0", "p_temp_casualties", 0),
  (str_store_string_reg, s8, s0),

  # hurt the enemy and display some stuff.
  (inflict_casualties_to_party_group, "$g_encountered_party", ":player_party_strength", "p_temp_casualties"),
  (call_script, "script_print_casualties_to_s0", "p_temp_casualties", 0),
  (str_store_string_reg, s9, s0),

The only changes I made were to call my strength function instead of the original and intersperse a bit of code between the strength calculation and the damage resolution.  Mine now looks like this in order_attack_begin:

  (call_script, "script_kt_party_calculate_strength", "p_main_party", 1, 0),
  #(display_message, "@TEST P:  {reg1} troops considered, total strength {reg0}"),
  (assign, ":pl_str", reg0),
  (call_script, "script_kt_party_calculate_strength", "p_collective_enemy", 0, 0),
  #(display_message, "@TEST E:  {reg1} troops considered, total strength {reg0}"),
  (assign, ":en_str", reg0),
  (try_begin),
      (gt, ":pl_str", ":en_str"),
      (assign, ":advantage", ":pl_str"),
      (val_mul, ":advantage", 10), # working in tenths
      (val_div, ":advantage", ":en_str"),
      #(assign, reg0, ":advantage"),
      #(display_message, "@PLAYER ADV:  {reg0}"),
      (try_begin),
        # for battles pitched 50% in the player's favor divide the
        # enemy's strength by the ratio of the advantage amount and
        # 150%.  so an advantage of 2:1 would be :advantage == 20
        # and a divisor of 4/3 (enemy strength is at 75%).
        (gt, ":advantage", 15),
        (val_mul, ":en_str", 15), # bring enemy strength up to the advantage amount
        (val_div, ":en_str", ":advantage"),
        #(assign, reg0, ":en_str"),
        #(display_message, "@NEW ENEMY STR:  {reg0}"),
      (try_end),
  (else_try),
      (assign, ":advantage", ":en_str"),
      (val_mul, ":advantage", 10), # working in tenths
      (val_div, ":advantage", ":pl_str"),
      #(assign, reg0, ":advantage"),
      #(display_message, "@ENEMY ADV:  {reg0}"),
      (try_begin),
        (gt, ":advantage", 15),
        (val_mul, ":pl_str", 15),
        (val_div, ":pl_str", ":advantage"),
        #(assign, reg0, ":pl_str"),
        #(display_message, "@NEW PLAYER STR:  {reg0}"),
      (try_end),
  (try_end),

  # slow this down so that the player can make choices
  (val_div, ":pl_str", 100),
  (val_max, ":pl_str", 1),
  (val_div, ":en_str", 100),
  (val_max, ":en_str", 1),

  #(assign, reg10, ":pl_str"),
  #(display_message, "@pl_str:  {reg10}"),
  (inflict_casualties_to_party_group, "p_main_party", ":en_str", "p_temp_casualties"),
  (call_script, "script_print_casualties_to_s0", "p_temp_casualties", 0),
  (str_store_string_reg, s8, s0),

  #(assign, reg10, ":en_str"),
  #(display_message, "@en_str:  {reg10}"),
  (inflict_casualties_to_party_group, "$g_encountered_party", ":pl_str", "p_temp_casualties"),
  (call_script, "script_print_casualties_to_s0", "p_temp_casualties", 0),
  (str_store_string_reg, s9, s0),

The results were acceptable but I'd really liked to have done it based on armor, weapons, and skills.  As follow up tasks, I'll add tactics skill in there and go through the places that use the strength script and see if they're easily modified to be compatible.  If I get really motivated, I'll collapse the auto-resolver code from its far flung locations to a single script with parameters since that would be far saner.

Code was formatted for posting, so there are very likely bugs herein.
 
I can't program worth a damn, but I'm pretty decent at discrete modelling and statistical models if you want a hand developing the mathematical model. I've always wanted to do some kind of statistical battle simulation like this.

When you say get values off the first troop, is that per army or per stack? I'm guessing army since stack wouldn't be much of a problem.
 
It's per stack which is still a problem.  So (for instance) Sword Sisters sometimes come with helmets and sometimes not so you wouldn't want to modify your Sword Sister stack strength based on the first one who doesn't have a lid.  It also doesn't seem like you can access the gear values aside from denar cost, nor can you get at the troop template tuple to figure out what they're supposed to come with--at least, as far as I can tell.  So when I had trouble getting the troop skills (like Ironflesh and Power Draw and the like) I kind of gave up with that approach.  Without other hints from somewhere (e.g., building a constant list with item values at compile time and/or adding tuples to the troop declaration likewise requiring compiling changes), I don't see a way to figure in any equipment bonuses reliably. 

I think the best result is to fix some of the questionable stuff in the original code and give a few parameters to tweak and maybe some smarter maths.  If you've got smarter maths, I'd be more than happy to give it a go  :grin:
 
I've already got a very basic model using level and attributes that uses a binomial distribution and some pretty steep assumptions (it reduces both armies to a blob and each hit reduces it in size and modifies its attributes), but I have an idea of working distinctions between infantry, ranged, and cavalry into the mix. From there I hope to work individual skills in, especially concerning leaders and heroes. One ***** of a confounding variable I've had with this in the past is morale. Battles rarely ever end with everyone dying, so I figure when that comes into play I'd throw them into the wounded pool.

Also, it's stochastic. My model generates a maximum possible dead amount and multiplies it by a random number to account for variables that never really set themselves above the noise (terrain, weather, how hung over everyone is, etc.) From there it rather arbitrarily assigns the dead by multiplying it by a random number and subtracting that amount from each stack using an ordered list.

I'm also writing this from scratch because the power scores and ranking system in there now is pretty arbitrary.

*edit*
Right now I'm just playing with ideas. I'm also digging up stuff I read a while ago about Lancaster's Law and going through mathematical models on arxiv looking for nifty things to take. Fractal attrition looks badass.

*edit*
I've found a few authors on arxiv I'm going to leave here for future reference:
M.K. Lauren, N.J. MacKay, Boris D. Lubachevsky, Michael Bowman
 
nice idea, but I came to the same conculsion.  You will have to set up constants for the troops to use in the calaculations for gear.  You can set a lower value to gear that is not always present, such as with sword sisters.  If you set the bonus value to the helmet of 10, reduce it to 7 for the sword sisters to show that not all of them have one.  As for the stats, that's a hard one.

This kind of thing might go easier in my mod.  I won't have the dozens upon dozens of type of units present in native.  I may have 15-20, so creating the constants won't be as hard.
 
jik said:
nice idea, but I came to the same conculsion.  You will have to set up constants for the troops to use in the calaculations for gear.  You can set a lower value to gear that is not always present, such as with sword sisters.  If you set the bonus value to the helmet of 10, reduce it to 7 for the sword sisters to show that not all of them have one.  As for the stats, that's a hard one.

This kind of thing might go easier in my mod.  I won't have the dozens upon dozens of type of units present in native.  I may have 15-20, so creating the constants won't be as hard.

hmm yeah. that's a tough one. I guess apart from guarantee_mounted, you can look for the other flags from the game engine. Still, the stuff they have in their inventory will give you a fair idea of what they can field. Even if it's not 100% accurate, it's still a lot better than not taking it into account at all.

Interesting stuff btw.. I wish I knew enough about the models and things involved to say something intelligent
 
If there are different troops with different gear, is that gear randomly assigned or is it distributed by some function? Either way you could just use either observations of the functions to find an expected value and variance of what that gear level would be and use that distribution in the calculations. That's the good thing about mathematical models, of complex systems in particular: you don't need to know everything, just enough to approximate. There's so many intricate little things going on that you could spend eternity modelling detailed variables that will never affect the model significantly enough to change the distribution above the noise.
 
I'm no programmer(know the basic and can read, but not write) but:
As for the stats, that's a hard one.
Just an idea:

Count all the points that are divided on Skills and divide/remove the ones (or don't count) that do not mean anything for fighter (like trade and leadership)...

If unit has "power draw" but lacks points in archery, then calcs for archery skill/powerdraw could be made. or not?

You can also add in variable from weapon and armor value the unit carries... but that means more complex calculation...

Just ideas, I have no idea what the code looks like or should look like and what is possible....
 
Northen Wolf said:
I'm no programmer(know the basic and can read, but not write) but:
As for the stats, that's a hard one.
Just an idea:

Count all the points that are divided on Skills and divide/remove the ones (or don't count) that do not mean anything for fighter (like trade and leadership)...

If unit has "power draw" but lacks points in archery, then calcs for archery skill/powerdraw could be made. or not?

You can also add in variable from weapon and armor value the unit carries... but that means more complex calculation...

Just ideas, I have no idea what the code looks like or should look like and what is possible....
That's pretty much the idea. My present model factors that all in, as well as the number of soldiers each man has to kill to break even, to the probability that one randomly chosen soldier in one army will kill another. Then I'm using that probability in a simple binomial distribution to obtain the maximum number of men that army can kill. It's a pretty simple model and doesn't describe it accurately enough, though. But it works from the tests (which I do by hand and is a pain in the ass) I've run it through.

For my next iteration, I'm going to divide troops into ranged, cavalry, and infantry and then divide it into things like the probability that an infantryman will kill a cavalryman, and then calculate the number of dead infantry, ranged, and cavalry in the opposing army. It's still a really basic model that works the same way, except with three blobs instead of one, but the same principle. I can get more advanced once I have those things working.
 
To be clear, it doesn't seem like you can inspect the gear for a given solder on a given stack.  You can see the gear and get denar values for the kit of the guy at the top of the stack but that's not quite the same.  It also doesn't seem like you can get the troop type data so any kind of statistical analysis based on gear seems to be a dead end without some very error-prone hackery. 

You can get proficiencies, skill levels, hitpoints, level, and attributes.  You can also tell if a unit is mounted or otherwise.  Beyond that I don't know what other data you can get for a troop/stack. 

Is there a way to get at tf_guarantee_horse and other troop guarantee flags?
 
This is why I talked about constants.  From what you are saying you can tell the troop type.  Compare it to something in constants.  I would suggest this only for the gear.  As for the skills and such, if you created the troops properly, you should really only need to go by thier level.  Level should give a general idea of how good the troop is.  Going further makes quite a bit more work.  You might also want to give archers a bonus for the first round as they can hit at range while other units close in.
 
While what jik says is true, I think using just the levels is the whole wrong way of thinking that native uses. If all troops were the same, we wouldn't need different skills. Yes, they should be balanced to a point, but an army of lvl 30 archers is going to get murdered by an army of lvl 30 heavy infantry. And quite apart from that, different units have differents str and weaknesses which is what makes for interesting strategy, otherwise you could just grab any stack of high tier units and the fights would always be more or less the same. Similarly, skills like powerstrike will have a huge impact on the dmg output of a unit and should be factored in. Yes, that will make it complicated but that's the whole point of an auto battle resolver, it should be realistic. If you want to use use the unit level as a general rule you might as well use the current system.

Perhaps the constants idea is the way to go.. but it would be a bit complicated and messy since it would be up to the author of the mod to correctly define the units there. Maybe rig something with the item value? You could scan a whole bunch of items using this and then look at item value vs in-game strength. Take out the extremes and then work that out into a formula of sorts. I think armor especially should be considered since that really makes a big difference to unit durability. As for global unit type (infantry/ranged/cavalry), there's troop_is_guarantee_horse and troop_is_guarantee_ranged. While those aren't always used for ranged units, they should be.

I just had a look at the autoloot script since they deal with items and they use this:

Code:
          (item_get_type, ":type", ":item"),
		  (try_begin),
			(this_or_next|eq, ":type", itp_type_one_handed_wpn),
			(this_or_next|eq, ":type", itp_type_two_handed_wpn),
			(this_or_next|eq, ":type", itp_type_polearm),
			(this_or_next|eq, ":type", itp_type_head_armor),
			(this_or_next|eq, ":type", itp_type_body_armor),
			(this_or_next|eq, ":type", itp_type_foot_armor),
			(eq, ":type", itp_type_hand_armor),
			(val_add, ":difficulty", 5),
		  (try_end),

So it's at least possible to get the item type and item modifier. Actually their script is worth looking at, they have a whole system worked out for the score values based on different imod types, etc.
 
Can you tell if a stack is a ranged unit or not, and can you tell if the stack is a pike or spear or something? That's basically all I need from that. It's not a perfect approximation, but a first guy in the stack approximation can still be really effective.
 
troop_is_guarantee_horse and troop_is_guarantee_ranged are the only two you can see.. so the first one means they're cavalry and the second one that they should be archers.

You can see the itp of the stuff in a troops inventory, so assuming that swordsmen don't carry spears in their inventory, yes, you should be able to see that. But I think you'll just have to write the script and then run it with a message for a bunch of test troops, then you can see exactly what it detects and how to process that.
 
Back
Top Bottom