User Tools

Site Tools


user:loggy:combatsim

Loggy's combat sim reverse engineering notes

This is me going through a lot of stuff in no particular order. Most if it probaly won't be interesting to anyone or have info that makes someone else think "what is the significance of this", but all my past attempts at looking at chunks of the combatsim code and trying to figure out what the heck is going on have died sadly.

For some reason I find myself starting at unit deployment on the field. I actually started at the beginning of the "dobattle" function which sets up and then calls the main battle sim until it's done. This was the first bit which is nonobvious enough to feel worth making notes on…

  • All units not in squads get one created for them and they are all assigned together, which doesn't have a commander. I assume this is the Big Blob of Unscripted Stuff when you get attacked and have units not on commanders. This has morale play implications, as it seemingly will happily mix "normal" morale units with mindless ones in the same squad and so it might be possible to deliberately make a resulting Big Blob squad have an extraordinarily high average morale
  • Spawn blood slave units (and take the "gems" off the commanders). Each commander's slaves live in their own squad, and slaves are given an action type of 8 and unit target of the commander that spawned them, and a squad target of 0
  • Deploy commanders on the field, then units, then retinues. Cast start battle spells.
  • "preorder": pick some targets!
  • for each commander… and then each squad on that commander…
  • if ordertype is 1 attack
    • gettarget
      • if formation type 1, 2, or 4 (line, skirmish, dbl line) write action type 0xf to all squad members
      • otherwise, write action type 0xc
  • if ordertype is 3 unknown
    • gettarget
    • write action type 0xc to squad members
  • if ordertype is 2 fire
    • gettarget (with ranged weapons)
    • write action 2 to all squad members
  • if ordertype is 4 guard commander
    • DON'T GETTARGET
    • write action 8 to all squad members, target unit = their commander, target squad = -1
  • if ordertype is 6 hold and attack
    • gettarget
    • write action 0xd to all squad members
  • if ordertype is 0xb hold and fire
    • gettarget (with ranged weapons)
    • write action 0x13 to all squad members
  • if order type is 0xc unknown 12 ("walls?")
    • gettarget
    • write action 0x14 to squad members
  • if order type is 7 retreat
    • write action 3 to all squad members, target unit = their commander
  • if order type is 9 fire keep distance
    • gettarget
    • write action 0xb to all squad members
  • autocommunions - skipping for now, lots of checking player spellbooks which I don't have a grasp of and doesn't seem as important right now
  • Set the delay for all squads to random 0-999, or keep whatever they had if it was higher. All squad members get the same value
  • Commanders also get a random 0-999, rolled separately per commander
  • Autobless everyone that gets it (Ind's effect, presence of pretender in friendly dominion)
  • TickBattle until it's over.
    • Projectiles land and exert their effects
      • If someone has time stop active, remove 90 ticks from their delay
      • If it's not post battle cleanup
        • Count: total of all units on the field, number of defender's units, build an array of defender's units. This is scanned in some kind of coordinate order. FIXME how does this scan the field?
        • If delay is zero or negative, do executeorder, otherwise decrease delay by 10 (well, the simulation tick increment, but that's always 10)
        • Repeat the above two steps, but for all units on the battlefield owned by anyone who isn't the defender
        • If the battle round counter (ticks/7500) is 150+, try to clear the field. Inflict 999 true damage (body location) on all units still alive four times, then 9999 true damage (random hit location) twice if that failed to kill them.
      • If the current tick counter % 150 == 0:
        • I don't know what this does. I think it's going to be either sound or graphics related though, so not really of interest to me.
      • If current tick counter/10 % 750 == 0 or 375 (I guess this is every 3750 ticks then, but why not just mod 375…)
        • Update the battle "number of units per nation" values
      • If current tick counter / 10 % 750 == 1, fire a holy avenger strike for the attacker's nation (targeting defenders or any other nations in battle) - this just picks a random enemy unit and hits them with the generic 20AN MRN smite
      • If current tick counter / 10 % 750 == 2, fire a holy avenger strike for the defender nation
      • Some units do unitautoeffects. Specifically, start at unit id (tick counter / 10 % 750), then add 750 to the unit ID until there are no longer units at that id
        • I've written the finer points of these things elsewhere, but…
          • Berserkers gain (value+1)/2 rounded down fatigue.
          • All units lose 1 point of fatigue
          • Reinvigoration removes its fatigue here
          • Land encumbrance adds its fatigue
          • Blood surge removes (magnitude/2) rounded down fatigue
          • If your fatigue is 100+ at this point, you lose 4 fatigue AND stop berserking, even if this 4 fatigue pushed you below 100
          • Temporary morale modifiers (fear, the unused morale bonus effect) have their value halved here
          • If the unit being checked is a pretender in friendly dominion it blesses all sacreds on the field
          • Hell power has a (20 + marks*2) chance to spawn a lesser horror in a 5x5 square around you
          • Entanglement escape check: if you have blink movement you succeed automatically, otherwise roll strength + open 2d6, if this is greater than 19 + terrain modifier you break free. Terrain modifier = +1 in forest, -1 in waste (or if in a province with both flags)
          • Web escape check: blink movement succeeds automatically, everyone else rolls str + open 2d6, if this is 20+ you escape
          • Net escape check: identical to web's except 23+ to escape
          • Bonds of fire escape check: blink movement doesn't help. Roll morale + open 2d6, if the result is 21+ you break free. When you break free (but NOT when you fail) you take 3 AN fire damage, in an unspecified (random) hitzone
          • False fetters: MR + open 2d6 of 21+ to escape. No modifiers (penetration, victim's air levels) matter
          • Slime: MR + open 2d6 of 22+ to end. No modifiers matter
          • Unknown affliction 274877906944 is removed unconditionally
          • Encase in ice does 5 AN cold fatigue damage. Roll (strength + open 2d6) - (12 + open 2d6). Subtract this many points of encase in ice. If this results was 0 or negative, subtract one point anyway.
          • If you are fleeing and have a mind collar (LA Phlegra iron guard), take damage equal to your mind collar attribute, and gain the marker to say it's happened (this is a slot effect). The damage is magical AN internal, no effect on mindless, and always targets the head.
          • If you are swallowed, the thing swallowing you applies Digest/Aciddigest/Incorporate and hurts you here. All of these are AN magic internal damage, aciddigest has the acid flag as well. They all do direct damage and have no DRNs involved. Incorporate heals the swallower for the damage taken or the swallowed victim's current hitpoints, whichever is left. It cannot increase the swallower's current hitpoints beyond 10000 though, and seems to not respect their maximum.
          • If you were swallowed and the thing that had swallowed you died, you get released.
          • Reduce paralysis and cursed luck duration, if applicable
          • If you have poison damage accumulated, take min(remaining accumulated damage, 1 + pool/10 rounded down) damage and reduce the poison pool by the amount of damage done. This is True damage with the poison flag set, and has no rolls on it.
          • Blood surge has a 20% chance to be removed
          • Decay does its stuff, see here
          • Affliction 274877906944 does 1 AN internal damage per round with a 20% chance of ending, if you are undead it does 2 damage (random hit location) and has no chance of being removed here
          • If you have landdamage of 100+ and are on land you take 1 point of true damage with no rolls (random hit location)
          • If you have uwdamage of 100+ and are underwater, the same
          • Burning does its damage of a closed d(size) of true damage without rolls, and you might get extinguished
          • Bleeding does its damage/fatigue and you might stop. If you're ethereal by any means you stop immediately and don't suffer any ill effects
          • Freezing does its fatigue and you might thaw, especially if you have cold vulnerability due to an oopsy
          • Desiccation does its thing, but only if you have nonzero encumbrance. You take open 2d8 fatigue damage with the true flag set, and the effect ends if you roll MR + 2d6 of 23+. No modifier affect this MR roll
          • Plague effects: unless the carrier is undead/demon or a stone being. Adds 1d4 fatigue directly and applies 0 or 1 points of true damage (unspecified hit location)
          • "Summon X monsters per round of combat abilities" trigger, starting with 5 and working backwards to 1 (which probably impacts placement)
          • Regeneration triggers. Unless you're homesick.
          • Bug soul vessels have a 20% chance to transform back to the unit type that died and spawned them via swarmbody. This also removes feeblemindedness
          • Shrink/growhp are checked and may cause changes of shape
      • FIXME Something happens that probably involves battlefield positions. I don't really understand this at the moment
      • Some units get to do something. Specifically, start at unit id (tick counter / 10 % 255), then add 256 to the unit ID until there are no longer units at that id
        • This does a couple of things I don't understand
        • But, if you are asleep, you have a 5% chance to wake up
      • Same, but with % 188. This is where innate casters do their thing, and cast-spell-every-round stuff on items etc happens
      • Innate caster + time stop is handled.
      • Some units get to do something. Specifically, start at unit id (tick counter / 10 % 31), then add 32 to the unit ID until there are no longer units at that id
        • This one is auras.
        • Harassment penalty is multiplied by 95/100 then rounded down.
        • Stun duration is reduced, and might end
        • Petrify duration is reduced, and might end (deals 999 AN damage to the body, MRN, earth effect, always pen +2)
      • If the current tick/10 & 7 == 0, remove enchantments that meet some criteria.
      • If the current tick/10 & 31 == 0, updates the battle lighting/fog? I think. Definitely graphical stuff.
      • Battle enchants are processed. This goes in much the same way as the stuff on a per-unit basis. Start at current tick/10 % 31, do that one, add 32 to the current one to check, continue until there aren't any more…
        • This function has its own 16 cycle timer (5120 ticks) which is used for a few enchantments, notably ravenous swarm (only when bfench timer is 0), growing fury and foul air (splits unit ids hit into 16, hits 1/16th of the rate I thought it did)
      • If the current tick/10 & 127 == 0, do lingering effects (clouds). This is also done by starting at index (current tick/10) & 127 and adding 128 until there are no more
      • Deal with bounceblasts (chain lightning), these too are done by starting at (current tick/10) & 3 and adding 4 until there are no more
      • If ((tickcount/10) + 25) & 127 == 0, for the attacking nation, removeobsoleteorders then ensuregoodorders
      • If ((tickcount/10) + 60) & 127 == 0, for the defending nation, do the same
      • If (tickcount/10) % 750 == 35, attackers make a morale check
      • If (tickcount/10) % 750 == 55, defenders make a morale check
  • A battle is over and the game proceses the post-battle rounds once all units on the field belong to the same nation at any point. This is not a reversible state, if another nation's unit somehow appears during post-battle cleanup, the battle will not resume (though the only way I know for this to occur is to have a swallowed unit with phoenix pyre die to digestion or similar and reappear). Technically, being swallowed removes a unit from the battlefield. If it's a winged monkey battle, this excludes units with >100 fatigue.

gettarget

This is used in a few different places. Time to take a closer look…

  • If we were asked to get a ranged weapon, get the unit's "main ranged weapon…"
    • Get the first ranged weapon of the unit
      • If:
        • That weapon was out of ammo
        • OR the battle is UW and that weapon can't be fired UW
        • OR that weapon's range is 9- AND the next weapon's range is 16+
          • Get the unit's second ranged weapon.
          • If that has no ammo, give up and say there isn't a main ranged weapon.
        • Otherwise, return the first ranged weapon, if one existed
    • If your main ranged weapon has a precision stat of 20+, use ranged weapons = 2, otherwise it should stay at 1… I think
  • If use ranged weapons < 2, and either the ranged weapon's range was 17- or you didn't find one:
    • If (ranged weapon's range + attacker's left-right coordinate < attacker's left-right coordinate - ranged weapon's range), bail and return no target [I have no idea why this comparison is here…? It seems like it will never be true]
    • Make sure something is in range of your weapon. If it's not, bail and return no target.
    • If the highest unit ID in the game < 39999, set val1 to (highest unit ID in the game)/2 rounded up
    • Otherwise, val1 = 20000
    • If target type = 1 (commanders)
      • FIXME
    • If target type = 2 (archers)
      • Make up to (val1/2) attempts, rounded down. Pick a random unit ID up to the highest. Continue until all the following are true:
        • It's in the current battle
        • It's alive
        • It's not swallowed by another creature
        • It's owned by a nation other than the attacker
        • Its unit type innately has any weapon with range > 0 [Giving bows to commanders that don't inherently have a ranged attack will not be specifically picked out by this]
        • And at least one of the following:
          • The target is mindless, and the weapon we are using has no effect on mindless
          • The target has more of its friends than enemies in a 3x3 box around it
      • If this fails to find something, use fallback
    • If target type = 3 (cavalry)
      • This is identical to archers except it checks for the mounted flag instead of having an innate ranged weapon
    • If target type = 4 (fliers)
      • Same again, except the target needs to be able to fly RIGHT NOW. In cases like perpetual storm, this will therefore go only for storm immune/power creatures
    • If target type = 5 (closest)
      • Check all units in the game, starting at 0 and ascending.
      • In order to be valid, units have to meet all of:
        • Be in the battle province
        • Be on the field
        • Either:
          • The target has more of its friends than enemies in a 3x3 box around it
          • userangedweapons != 1
        • Any one of:
          • there is no ranged weapon being used
          • the ranged weapon being used does not only hit mindless
          • the target is not mindless
        • Calculate a "closeness score":
          • closed d3 - 1 + Abs(difference in front-back coordinate) + Abs(difference in left-right coordinate)/2
          • Add 50 if the target is routing
        • The final target is the lowest "closeness score". In the event of a tie, the lowest unit ID wins
    • If target type = 6 (horrors)
      • Make up to (val1/2) attempts, rounded down. Pick a random unit ID up to the highest. Accept it if it's in the battle, it's alive, it's not owned by the unit doing the targeting, and it has a nonzero number of horror marks.
      • If the above fails, go through all unit IDs in ascending order, and accept the LAST one found that matches the criteria above.
      • If that fails, use fallback
    • If target type = 7 (magic users)
      • NOTE: here for completeness. To the best of my current knowledge I do not believe a player can set this command legitimately. I am not aware of anything in the game that uses this in normal gameplay.
      • Make 3000 attempts…
        • Pick a random commander ID: RandInt(HighestCommanderID + 1)
        • If it is all of the following
          • In the battle province
          • Has current hitpoints > 0
          • is not owned by the nation doing the targeting
          • Has non-holy magic paths
          • Has its Front-back coordinate greater than 0
          • Either not using a ranged weapon, the ranged weapon does not hit mindless only, or the target is not mindless
          • The target has more of its friends than enemies in a 3x3 box around it
            • Accept the first target that matches if one is found
      • If those attempts failed:
        • Make 1500 more attempts, this time include holy levels as well
      • Otherwise, proceed through all commanders in ascending ID order, and accept the one with the highest ID
    • If target type = 8 (large monsters)
      • Make up to (val1/2) attempts, rounded down. Pick a random unit ID up to the highest. Accept it if all of the following:
        • It's in the battle province
        • it's alive
        • it's not owned by the unit doing the targeting
        • Any of:
          • we are not using a ranged weapon
          • our ranged weapon does not affect only mindless
          • the target is not mindless
        • The target has more of its friends than enemies in a 3x3 box around it
        • Either:
          • The target is size 5 or 6
          • The target is size 4-6 inclusive AND (val1/2) < (number of attempts * 2)
      • Otherwise, use fallback
    • If target type = 9 (rear)
      • Proceed in ascending unit ID order, starting at 0.
      • If the unit is in the battle province, owned by someone other than the one doing the targeting, has hp > 0, and is in the battle (coordinate check)
        • Get the to-be-targeted unit's front-back coordinate.
        • If the owner of the targeted unit is on the attacking (left hand side) of the battlefield, consider its front/back coordinate to be negative instead of what it really was.
        • If the owner of the targeted unit is neither the attacking nor defending nation (eg Call Horror etc), its front/back coordinate is considered to be -99999 instead.
        • If the target is retreating, subtract 50 from its front/back coordinate.
        • Its front/back coordinate at this point is now a "rearmost-ness score".
        • Accept the unit as the "current best rearmost unit if" all of the following…
          • Either:
            • It is the lowest unit ID to make it here
            • closed d100 - 1 ⇐ (400 / (number of alive units in the province that do not belong to the player doing the targeting/3 + 10) + 15
          • Any of:
            • We are not using a ranged weapon
            • The ranged weapon we are using does not affect only mindless
            • The target is not mindless
          • The target has more of its friends than enemies in a 3x3 box around it
          • The "rearmost-ness score" of this unit is greater than or equal to the best one found so far [ties will therefore favour higher unit IDs]
    • Fallback target type (also used as a "none of the above" option)
      • Make up to val1/2 attempts…
        • Roll RandInt(HighestUnitID + 1)
        • Check that…
          • Unit is in battle province
          • Unit is alive
          • Unit isn't owned by nation doing the targeting
          • Unit is not any of the various kinds of temporary (persists after batle, eg is not PD or battlesummoned or a product of secondtmpshape)
          • Unit's front-back coordinate > 0
          • Either:
            • The ranged weapon we are using doesn't hit only mindless - FIXME this is the only one that doesn't check that the weapon's ID is positive before looking up its spec value - if it's invalid, is this going to result in trying to read weapon -1's "spec" value?
            • The target is not mindless
          • Continuing…
            • If function param5 > 1 and the total number of size points in the target unit's squad is less than param5 and this condition has been met less than three times:
              • multiply param5 by 9, divide by 10 then round down
              • start again as if you failed one of the above checks
            • If userangedweapons > 2:
              • If (your ranged weapon's range squared) < (difference in coord1 squared) + (difference in coord2 squared), start again if you failed one of the above checks
            • If the target has more of its friends than enemies in a 3x3 box around it, start again as if you failed one of the above checks
          • Otherwise, accept this unit
      • If this failed and userangedeweapons < 5:
        • Check all units in ascending unit ID until one is found.
          • Needs to be in the battle province
          • Needs to be alive
          • Needs to be owned by someone other than nation doing the targeting
          • Needs a front-back coordinate on the field
          • Ranged weapon needs to hit non-mindless or the target must be non-mindless
          • If use ranged weapons < 3, accept this unit
          • If (your ranged weapon's range squared) < (difference in coord1 squared) + (difference in coord2 squared), start again if you failed one of the above checks
          • The target must has more of its friends than enemies in a 3x3 box around it
          • If you got here, accept this as the target
user/loggy/combatsim.txt · Last modified: 2023/01/16 03:26 by loggy