OSP Code Combat Lancers: Use the right weapon! Battle edition

Users who are viewing this thread

I have tested this in custom battle mode.
1. 75% of my cavalry will use lances, but there are still sword users.
2. 100% of dehorsed cavalry will avoid lances and use effective weapons.

A big improvement over Native.
 
Well I spent pretty much all this evening trying to find something that would allow us to detect who an NPC is targeting and had zero luck in the matter.  Even had something rather odd happen in that agent_get_look_position and agent_get_position return the exact same co-ordinates when fed the same NPC Agent ID.  Doesn't make sense to me at all.  Just hoping I've made a stupid error in my code somewhere.  Either way it's not a big deal as neither function help with the ultimate goal.  I also looked into the various defined slots, and every command that looked like it may have some bearing on the situation.  Oh well...

CryptoCactus said:
Yeah, hat, I have absolutely no problem with you or anyone else altering this code (like I say, it's 98% yours anyway :p), and besides that I'm *really* a rank amateur. I only did this because it annoyed me personally to have lancers be so goofy, so.. yeah. And then I thought I should share it. :smile:

So alter it, scrap it entirely, whatever works. I'll help with anything I can, but I don't know how much help I'll be.
I have a few (ok it's a _lot_ more than a few, but that doesn't mean I'm any good) years coding experience in various languages, but I'm also a rank amateur when it comes to Warband.  I'm still finding out things.  For example I didn't even know that the slot system existed.  Sad but true.

To me you seem far better at this than you give yourself credit for.

CryptoCactus said:
I had figured that it would be pretty laggy in bigger battles, especially on older machines. I built my computer a year or two back (it has an 8800GT, which was king to everything but the GTX at the time iirc), and it works fine for me on max sized (non-tweaked/non-battlesizer'd) battles, but beyond that I'm sure it'll start hiccuping.

But... I couldn't think of a better way to get it done at the time.
I'm not convinced that there is a huge jump in performance that can be made with this script as it is.  I think that it needs to be broken up into different versions for different situations, linked by using some globals to keep track of what has and hasn't been run.  Maybe.

BTW where is the code that handles the battlefield commands (using the F keys)?  They hard coded or script?  I couldn't find it in the scripts, so I suspect hard coded.  Pity, that would have allowed a few other options that could have allowed the use of once off scripts as an alternative such as when you order cavalry to dismount/mount.  And other things too.

Another possibility that springs to mind is I'd count lance users when you run through the first time and mark the ones that are.  Then in the scripts that handle counting casualties (I'm pretty sure they exist, I think I was looking at one earlier tonight, but maybe that was just multi-player) reduce the count every time one of those marked dies.  Then when it hits zero, don't bother running the script (have a check at the start).  Wouldn't add much if you had bag loads of lancers in a battle I guess, but it's another possible optimisation such as for sieges.  Maybe.

CryptoCactus said:
xenoargh said:
I can see the point of yanking the lances from the troop before initiating a siege, but this situation comes up a lot on the battlefield.
Yeah, that's the problem. The conditions on the battlefield can change so often and so quickly I can't see any way to do something like this without running regular checks (and thus adding potential for lag).
I agree.

CryptoCactus said:
Somebody said:
Also, your code will only find the first appropriate sidearm listed, and then quits the loop. And it doesn't check if it's one of 4 items (agent_has_item_equipped) that any agent can have, so it's fortunate that Native's troops work for their intended purposes.

Yeah, that's because of the limits of my knowledge. >_>

I actually noted agent_has_item_equipped, but I wasn't sure what exactly it meant by "equipped". Which sounds kinda stupid when I type it out. I mean I was thinking of wielded vs equipped.. eh, nevermind.

As for finding the first available sidearm and breaking the loop, yeah, I knew that wouldn't be the ideal situation (for instance if it finds a dagger before a greatsword or what have you), but I don't really know how to run through each item and choose the best one.
One thing I did for my mod is to alter my module_items.py file to break items (especially weapons) up into groups eg. polearms, one handed swords, two handed swords, shields etc.  This means that I can use the range commands to set a bunch of ranges in module_constants.py (currently you get ones like weapons_begin = "itm_wooden_stick" and weapons_end = "itm_wooden_shield") for each class, then you can just use something like (is_between, ":item_id", <group>_begin, <group>_end), to check to see if a given ":item_id" is part of the <group> eg. (is_between,":item_id",polearms_begin,polearms_end).  You already use this method in the alterations you made to my script, but with the items sorted you can make more intelligent assumptions on the usefulness of one weapon over another.  That was my original intention with the battle version of the script (before I got distracted) -- which would have been even better if you could determine who/what the NPC was targeting/fighting.  The downside is sorting out the items file is a heck of a lot of editing.  Mind you I went a bit over the top with mine...  Anyway it comes in handy for other things such as making scripts for items that prevent players using them if they are too small, or too large (gnome and giant gear) while allowing you to still loot and sell them (although you can do that anyway without sorting the file out).

CryptoCactus said:
HEY. I just noticed agent_unequip_item. Could we use that to just strip any unhorsed units of their lances for the duration of the battle? Of course (and I'm kinda just editing this as I go along and talking it out to myself), if we had an agent that *only* had a lance, then they'd be helpless... hrm. Oh, wait, no, because we'd only remove the lance after we've gone through and checked for an alternate weapon, of course. Duh.
Sounds like a good idea.  I honestly think you'll do a better job with taking this script to the next level so am going to leave it in your hands.

I'm feeling a bit discouraged at the moment (it wont last).  Very few of my more recent ideas for my Warband mod have turned out. :smile:
 
Well, I give up. I've been screwing with this for hours and the only thing I've accomplished is that I can no longer even get the code I posted here to work for me. So.. yeah. If anyone else can improve it, please feel free.
 
Well, okay, I don't give up, but this is frustrating.

One super-easy way to make lancers use the right damned weapon when dismounted would be if we could automatically issue orders to an agent when it is unhorsed. A simple F3-F4 ("use any weapon" command) seems to make units using lances switch to sidearms with (what seems to be) 100% efficiency. But.. I don't know that that's possible.

I'm really rather stuck. I'm testing with a small band of Swadian men at arms and knights, and while the horseback bit seems to work (they all switch to lances as soon as the battle starts) they aren't switching to their sidearm when given the order to dismount. No error messages, and when I insert debug display_messages the code seems to be doing everything it should... but the units don't respond. One or two of them will switch to swords, but the rest just keep on using lances (unless I F3-F4).

So. My brain's frazzled and I'm pretty much out of ideas.
 
I'm now using a start trigger script and a death trigger script for horses.  The start trigger is the only time it now runs through every single agent on the battlefield.  It's used to initially set lancers to using their lance, plus record the agent ID of every rider in a slot on a horse.  Now I can imagine that causing people to scratch their heads as there's a function called agent_get_rider, but the problem is that this returns -1 the instant a horse is killed/wounded.  So the only way to get the dead horses riders agent ID (I've tried flogging the horse, but it doesn't help :smile:) is to store it in a slot beforehand.  This may still cause problems though, depending on how the game handles assigning horse ownership in a scene ie. does giving a mount order cause cavalry to try and mount their any horse they can find  or do they only attempt to mount a horse they started with?  I guess I can always order my troops to dismount, kill a couple of horses then ask them to mount up again.  I suspect they only attempt to mount a horse they 'own' (started the battle with), but I might be wrong.

These two scripts work fine, but they don't handle mount/dismount orders.

So the with script I'm now working on (rather slowly because I still don't get the relationship between agent (I get them fine, I also understand faction), troop, party and team.  The latter three are the ones I still don't fully get) I'm hoping to skip running through every agent on the battlefield for mount/dismount (otherwise the horse death script becomes pointless) by running through every team and subclass on the battlefield and checking for a mount/dismount order, then running through every agent in a team that has been given such an order.  I might add a slot for agents in the start script to record who has a lance in their inventory to cut down a little on the checks when I run through agents.

No idea if this will work and the whole thing is a hack, for example it wont handle those individuals that decide to dismount under their own steam and join the footsloggers, I get those in my battles occasionally, but I'm hoping to end up with something pretty much as useful as my old script but with a lot less CPU usage.

Edit:  Interesting.  In my little experiment I asked my cavalry to dismount, then scared a horse away (it exited the battlefield), then asked them to mount up again.  They all headed for the nearest horse first.  So ownership isn't taken into account.  This could mess my horse death script up, because each horse will only have the ID of the original rider recorded, which wont necessarily be the last person to ride it.

I'm starting to think that Somebody's original suggestion of just marking lancers at the start and only messing with agents marked as lancers as the script runs is the only way to handle every situation.  I can use something to keep track of whether there are any lancers at all and not run the script if they don't exist in the current scene I guess.
 
1.  I've tested it as written, with minor changes (upped the timing loop, made it a common_ event so it could be used generically, little crap, basically). 

It's fine.  Performance is fine.  Just run it every 5 seconds or thereabouts.  Brute force is OK when it's very infrequent.

2.  I will finish the non-lazy change I mentioned when I get done testing my script to exclude items from being used with one another in certain combinations (which has been a scary little thing to make it solid).  Should be really straightforward, just one minor loop to cut Agents out of the stack immediately. It'll still involve one boolean, but whatever, a couple of hundred boolean operations is nothing at all.
 
After your above comment I reworked mine along the lines of what I had originally, with several changes.  Still, mine wont do what yours will (haven't gotten around to sorting out what weapons etc. to use yet) so looking forward to your final script.
 
Back with fresh eyes; still not working for me.

I'm just gonna throw it up here and if anyone can tell me what the (terribly obvious I'm sure) reason is that it isn't working, that'd be awesome. Otherwise, meh. Still testing with Swadian cav, still not switching to appropriate weapons when ordered to dismount. Interestingly, companions and heroes do switch weapons correctly. I'm about to grab some Kherg lancers and some Vaegir cav to see how they respond.

Code:
# LANCE USAGE BEGIN
common_lance_usage_horseback = (
   # Force mounted NPCs to switch to their lance.  This is called once at the
   # start of the battle. If you want lancers to ALWAYS use lances on horseback,
   # replace ti_once with 1. Otherwise they may switch to sword if bogged down
   0, 1, ti_once, [],
   [
      # Run through all active NPCs on the battle field.
      (try_for_agents, ":agent"),
        # Isn't a player.
        (agent_is_non_player, ":agent"),
        # Isn't a horse.
        (agent_is_human, ":agent"),
        # Hasn't been defeated.
        (agent_is_alive, ":agent"),
        # They riding a horse?
        (agent_get_horse, ":horse", ":agent"),
        # Is riding a horse.
        (gt, ":horse", 0),
        # Get wielded item.
        (agent_get_wielded_item, ":wielded", ":agent", 0),
        # Is it a lance?
        (neg|is_between, ":wielded", "itm_light_lance","itm_pike"),
        # Force the NPC to wield the lance, but this will only happen if they
        # actually have a lance in their inventory.  Otherwise this does
        # nothing.
      (try_for_range,":item","itm_light_lance","itm_pike"), # adjust as needed
         (agent_set_wielded_item, ":agent", ":item"),
      (try_end),
      (try_end),   
   ])
   
common_lance_usage_foot = (
   # Check to make sure there are no lance users on foot, if so force them to
   # switch to their sword. This should also affect troops that were NEVER mounted,
   # but are still equipped with lances, such as Taiga Bandits.
   5, 0, 0, [],
   [
      # Run through all active NPCs on the battle field.
      (try_for_agents, ":agent"),
        # Isn't a player.
        (agent_is_non_player, ":agent"),
        # Isn't a horse.
        (agent_is_human, ":agent"),
        # Hasn't been defeated.
        (agent_is_alive, ":agent"),
        # They riding a horse?
        (agent_get_horse, ":horse", ":agent"),
        # Isn't riding a horse.
        (le, ":horse", 0),
        # Get wielded item.
        (agent_get_wielded_item, ":wielded", ":agent", 0),
        # Is it a lance?
        (is_between, ":wielded", "itm_light_lance","itm_pike"),
      # Find non-lance item in inventory
      (agent_get_troop_id, ":troop",":agent"),
      (troop_get_inventory_capacity,":cap",":troop"),
        (assign,":has_choice",0),
      (try_for_range, ":i", 0, ":cap"),
         (troop_get_inventory_slot,":item",":troop",":i"),
         (is_between, ":item", "itm_wooden_stick","itm_light_lance"),
         (assign,":has_choice",1),
            (assign,":cap",0),
      (try_end),
        # Equip their backup weapon.
      (try_begin),
         (eq, ":has_choice",1),
         (agent_set_wielded_item, ":agent", ":item"),
      (try_end),
      (try_end),
   ])
# LANCE USAGE END

The only change I made from the code posted in the OP is that I replaced all the neg|is_betweens in the foot code with (is_between,":item","itm_wooden_stick","itm_light_lance"), where wooden_stick is the first weapon and light_lance is the beginning of the lance weapons (which are themselves at the end of the list of common melee weapons), because it just seemed to make more sense. I've tried it the original way as well to no effect, though. Oh, and I also changed the foot code from running every 0 seconds with a 1 second rearm time to running every 5 seconds with a 0 second rearm time, again per (my interpretation of) xeno's suggestions.

I made them common events like xeno suggested because it looked so sloppy having the entire code thrown in all over the place. Now I just insert common_lance_usage_horseback under lead_charge and common_lance_usage_foot under lead_charge as well as in the siege templates.

Example (lead_charge):
Code:
...
         (agent_set_slot, ":agent_no", slot_agent_courage_score, ":initial_courage_score"), 
         ]),

      common_battle_init_banner,
# LANCE USAGE BEGIN
	  common_lance_usage_horseback,
	  common_lance_usage_foot,
# LANCE USAGE END

      (ti_on_agent_killed_or_wounded, 0, 0, [],
...

Has anyone else experienced troops just NOT switching for no apparent reason? Is it just me? I'm beginning to think I've fundamentally screwed something up that's not directly related to the code in question, but I haven't the foggiest what that could be as this is the only thing I've (intentionally) done to the module I'm working with.

I apologize for noobing up the thread. I mean, I know it's my thread, but I think I'm the least-qualified among ye. :razz:

...and yes I realize I keep saying that, but what can I say, I'm self conscious about this stuff. >_>
 
Version with some minor fixes and the agent-slot value change to improve the speed.  You need to add slot_agent_no_lance to module_constants (value I used was 26).

Give it a go, let me know if it's still not working for you :smile:

Code:
common_lancer_fix = (0, 0, 3, [],
   [
      # Run through all active NPCs on the battlefield.
      (try_for_agents, ":agent"),
	  (try_begin),
	  (agent_get_slot, ":not_lancer", ":agent", slot_agent_no_lance), 
		(eq, ":not_lancer", 0),#Stop right here, if this condition isn't met.
		#If this is ambiguous, weed them out.  This could also be done as a one-time event early.
		(try_begin),
			# Isn't a player.
			(agent_is_non_player, ":agent"),
			(agent_set_slot, ":agent", slot_agent_no_lance, 1),
		(try_end),
		(try_begin),
			# Isn't a horse.
			(agent_is_human, ":agent"),
			(agent_set_slot, ":agent", slot_agent_no_lance, 1),		
		(try_end),
		(try_begin),
			# Hasn't been defeated.
			(agent_is_alive, ":agent"),
			(agent_set_slot, ":agent", slot_agent_no_lance, 1),	
		(try_end),
		(try_begin),			
			# They riding a horse?
			(agent_get_horse, ":horse", ":agent"),
			(agent_set_slot, ":agent", slot_agent_no_lance, 1),
		(try_end),
		(try_begin),
			# Isn't riding a horse.
			(le, ":horse", 0),
			(agent_set_slot, ":agent", slot_agent_no_lance, 1),	
		(try_end),	
		
        # Get wielded item.
        (agent_get_wielded_item, ":wielded", ":agent", 0),
        # Is it a lance?
        (is_between, ":wielded", "itm_jousting_lance","itm_jam_poleaxe"),#end of lances +1
      # Find non-lance item in inventory
      (agent_get_troop_id, ":troop",":agent"),
      (troop_get_inventory_capacity,":cap",":troop"),
        (assign,":has_choice",0),
		  (try_for_range, ":i", 0, ":cap"),
			(eq, ":has_choice",0),#Stop if we've found a valid item
			(troop_get_inventory_slot,":item",":troop",":i"),
			(neg|is_between, ":item", "itm_jousting_lance","itm_jam_poleaxe"), # adjust as needed
			(neg|is_between, ":item", "itm_wooden_shield","itm_darts"), # not a shield - adjust as needed
			(neg|is_between, ":item", "itm_hunting_bow","itm_torch"), # not ranged - adjust as needed
			(assign,":has_choice",1),
			(assign,":cap",0),
		  (try_end),
		  (try_for_range, ":slot", ek_item_0, ek_item_3),
		     (eq, ":has_choice", 0),#Stop if we've found a valid item
		     (troop_get_inventory_slot,":item",":troop",":slot"),
			 (neg|is_between, ":item", "itm_jousting_lance","itm_jam_poleaxe"), # adjust as needed
			 (neg|is_between, ":item", "itm_wooden_shield","itm_darts"), # not a shield - adjust as needed
			 (neg|is_between, ":item", "itm_hunting_bow","itm_torch"), # not ranged - adjust as needed
			 (assign,":has_choice",1),		  
		  (try_end),
			
		  (try_end),
        # Equip their backup weapon.
		  (try_begin),
			 (eq, ":has_choice",1),
			 (agent_set_wielded_item, ":agent", ":item"),
		  (try_end),
      (try_end),
	  (try_end),
   ])
 
Your agent_get_slot is missing a comma. :razz:

Aside from that, I'll give it a go and see how it works and report back.


Well, hmm.

It seems to work fine on its own (as far as I can tell - it's something of a trick to get cavalry to use lances in large numbers in the first place when they're not forced to - were you planning on forcing cav to use lances if they have them, at least at the beginning?). If I use it in conjunction with the horseback code I posted above (common_lance_usage_horseback), it doesn't. I'm sure there's just a simple conflict somewhere, but nothing jumps out at me.

[size=8pt](Also I discovered what my problem was earlier, and it was too stupid to speak of publicly. Well, okay, fine: I fooled with module_items and didn't start a new game, because I was being lazy and didn't think it through. /idiot)
 
I'm just going to try and mash both of those together. Nothing is guaranteed. Also, define lances_begin/end.
Code:
common_lancer_usage_horseback = (
  ti_on_agent_spawn, 0, 0, [],
  [
    (store_trigger_param_1, ":agent"),
    (agent_get_troop_id, ":troop_no", ":agent_no"),
    # Isn't a player.
    (agent_is_non_player, ":agent"),
    # Isn't a horse.
    (agent_is_human, ":agent"),
	# Hasn't been defeated. #Just spawned
	#(agent_is_alive, ":agent"),
	# They riding a horse?
    (agent_get_horse, ":horse", ":agent"),
	# Is riding a horse.
    (gt, ":horse", 0),
	# Get wielded item.
    (agent_get_wielded_item, ":wielded", ":agent", 0),
	# Is it a lance?
    (neg|is_between, ":wielded", lances_begin, lances_end),
	# Force the NPC to wield the lance, but this will only happen if they
	# actually have a lance in their inventory.  Otherwise this does
	# nothing.
    (try_for_range, ":item", lances_begin, lances_end), # adjust as needed
	  (agent_set_wielded_item, ":agent", ":item"),
	  (try_begin), # Get wielded item. Again.
		(agent_get_wielded_item, ":wielded", ":agent", 0),
		(eq, ":wielded", ":item"),		    
		(agent_set_slot, ":agent", slot_agent_lance, ":item"),#do whatever with this slot (nonzero)
		(assign, ":item", lances_end),#loop breaker (it's only 4 items, but w/e)			
	  (try_end),
	(try_end),
   ])
   
common_lance_usage_foot = (
   # Check to make sure there are no lance users on foot, if so force them to
   # switch to their sword. This should also affect troops that were NEVER mounted,
   # but are still equipped with lances, such as Taiga Bandits.
   5, 0, 0, [],
   [# Run through all active NPCs on the battle field.
    (try_for_agents, ":agent"),
      (agent_get_slot, ":lance", ":agent", slot_agent_lance),
      (gt, ":lance", 0),#or whatever, agent_slot_ge would also work but then we won't have the actual lance id
      # Isn't a player.
      (agent_is_non_player, ":agent"),
      # Isn't a horse.
      (agent_is_human, ":agent"),
      # Hasn't been defeated.
      (agent_is_alive, ":agent"),
      # They riding a horse?
      (agent_get_horse, ":horse", ":agent"),
      # Isn't riding a horse.
      (le, ":horse", 0),
      # Get wielded item.
      (agent_get_wielded_item, ":wielded", ":agent", 0),
      # Is it a lance?
      (eq, ":wielded", ":lance"),#troops never switch between two items of the same type
		
      # Find non-lance item in inventory
      (agent_get_troop_id, ":troop",":agent"),      
      (assign,":has_choice",0),
      (try_begin),
        (troop_is_hero, ":troop"),
        (try_for_range, ":i", ek_item_0, ek_head),
          (troop_get_inventory_slot,":item",":troop",":i"),
          (is_between, ":item", weapons_begin, lances_begin),#adjust
          #(agent_has_item_equipped, ":agent", ":item"),#always equipped
          (assign,":has_choice",1),
          (assign, ":i", ek_head),#loop breaker
        (try_end),
      (else_try),#regular troops
        (troop_get_inventory_capacity,":cap",":troop"),
        (try_for_range, ":i", 0, ":cap"),#not sure if reg. troops have equipped items (slots < 10), but w/e
           (troop_get_inventory_slot,":item",":troop",":i"),
           (is_between, ":item", weapons_begin, lances_begin),#adjust
           (agent_has_item_equipped, ":agent", ":item"),#this step is essential, 
           (assign,":has_choice",1),#but I'm not sure if this would work or not
           (assign, ":cap", 0),
        (try_end),
      (try_end),
	  
      (try_begin),# Equip their backup weapon.
         (eq, ":has_choice",1),
         (agent_set_wielded_item, ":agent", ":item"),
         (agent_set_slot, ":agent", slot_agent_lance, 0),
      (try_end),
    (try_end),
   ])
I'm not sure if store_trigger_param can be used in the preconditions, but that would also help. Not with the try_for_agent loop though, for the other sort of stuff. Also considering sticking the backup weapon check in a script, sticking the rider's id inside the same slot for its horse, and calling that script when the horse agent is killed in ti_on_agent_killed_or_wounded rather than checking every 5 seconds. Again, we would run into problems when horses are used by those others than their original owner, but w/e.
 
Sorry about the comma, lol.  Fixed.

If I use it in conjunction with the horseback code I posted above (common_lance_usage_horseback), it doesn't.
You don't really need two scripts.  If it's cut down to one quick check for bad Agent IDs, it's more than fast enough, for something that runs infrequently.
 
xenoargh said:
Sorry about the comma, lol.  Fixed.

If I use it in conjunction with the horseback code I posted above (common_lance_usage_horseback), it doesn't.
You don't really need two scripts.  If it's cut down to one quick check for bad Agent IDs, it's more than fast enough, for something that runs infrequently.

Well, granted, but 50% of the purpose (for me, anyway) was getting cav to use lances more regularly on horse. That isn't addressed in the code you posted, is all. Of course, just tacking the previous code on top of it was sloppy on my part. Probably the best way to do it would be on the first run-through, during which I believe every individual agent will be checked (right?), to force anyone on a horse to pull out a lance if possible there.

I'll fiddle with it some.


edit: You know, what I really wish I could do was make it where cav is forced to use lances (if they have them) until they make first contact with the enemy. Thinking about it now, forcing them to switch to lances during the initial charge only really works if you let them charge directly in, not stopping them to put them in formation or anything else (as they tend to pull out swords on their own when stopped). I'm afraid that's beyond the capability of the module system, though. The way I did it in the OP really only made bandit cav tougher, since they're almost the only ones that will just charge in immediately, lances down. Although it does make horse bandits a good deal more frightening when you see 40 of them charging on you with lances lowered and ready.
 
Not sure I need them to be more frightening.  I get scared enough especially when I start in the Khergit area.  Spend most of my time moving around in small increments getting ready to run at the first sight before I've managed to find enough recruits that will allow me to fight them off -- even then it's often a vicious war of attrition. :smile:

I think maybe we will have to post two lots of code.  One for those who can't handle the thought of a script that runs through all marked agents in a scene every so often (using a method that can't handle mount/dismount -- although how many people do that anyway? :smile:), and one for those that can.

My set of scripts are along the lines of Xenoarghs, but with a couple of differences, and will be for the latter sorts of people.  Mine is also not finished yet so I wont be posting it just now.  Chances are by the time I do, you guys will have posted a much better version anyway.

Edit (2nd: this time in the English language, 3rd: because I just realised I repeated myself, 4th: because of typos):  If the results I got in my tests were accurate, we can't use ti_on_agent_spawn for setting things up for tournaments but instead have to resort to a run-once-but-process-all-agents script instead -- due to pokery jiggery done to force troops to use tournament equipment.  However that method for initialisation wont work for normal battles because it wont process reinforcements, in which case ti_on_agent_spawn becomes essential.  So different method for each situation is necessary I think -- if, like I said, my tests were accurate.
 
xenoargh, after I added slot_agent_no_lance to module_constants, I tried to insert your code into module_mission_templates.py after lead_charge and got invalid syntax:

...\module_mission_templates.py", line 2214
    common_lancer_fix = (0, 0, 3, [],
                                        ^
SyntaxError: invalid syntax
Exporting postfx_params...

What could be the problem? Or could you provide more specific instructions on how to use this code (where to put it)?
 
Thank you!

Do I understand it right that a reverse algorithm (check units on horse and force them use lances) doesn’t work for some reason? Shouldn't even bother to try?
 
See final spoiler at bottom for a BETA (and currently flawed) combined code for Lancers, Horse Archers and Spearmen

I've monkeyed with the scripts as provided for some time and tried to make the lance/nonlance decision making a bit more dynamic.

While there does not seem to be a clean way to get/set an agent's target enemy agent (and thus its distance to that target, etc) to force wielding the weapon of appropriate reach, the code approximates this by doing two things: 1) it gets the average distance of the 3 closest enemies to the agent (using a native script) and 2) checks the agent's combat state. "Close combat" is here defined as an average distance to three enemies of less than 5 meters and a combat state of hitting, following through, being hit or blocking. If both are met, the agents switch to a non-lance, non-polearm weapon; if not, they switch to/maintain their lance or spear. Hopefully this means that the switch would only occur AFTER the contact of an initial charge, and then will continue to be dynamic throughout the battle.

I've broken the code into 3 parts: 1) a near-spawn process that tags lancers using an agent_slot and instructs lancers to wield their lances at the battle's start; 2) the main usage bit that checks if the lancer has been dehorsed OR if a lancer on horseback is in close-range combat and then calls; 3) a script that equips a non-lance, non-polearm weapon.

One slot is needed in module_constants; set it equal to the first available agent slot (in Native, 26):
slot_agent_lance  =  26

Code:
common_lance_use_spawn = (
   # Just after spawn, mark lancers using a slot.
   # Force lancers to equip lances.
  1, 0, ti_once, [], [(call_script, "script_lance_use_classify_agent")])
   
   
common_lance_use = (
   # Check to make sure there are no lance users on foot, if so force them to
   # switch to their sword. This should also affect troops that were NEVER mounted,
   # but are still equipped with lances, such as Taiga Bandits.
   # For mounted lancers affect their Decision on weapon use, based on if the
   # closest 3 enemies are within 5 meters and if currently attacking/defending.
   2, 0, 0, [],
   [# Run through all active NPCs on the battle field.
   (try_for_agents, ":agent"),
     # Hasn't been defeated.
        (agent_is_alive, ":agent"),
        (agent_get_slot, ":lance", ":agent", slot_agent_lance),
        (gt, ":lance", 0),  # Lancer?
     # Get wielded item.
        (agent_get_wielded_item, ":wielded", ":agent", 0),
      # They riding a horse?
        (agent_get_horse, ":horse", ":agent"),
        (try_begin),
            (le, ":horse", 0), # Isn't riding a horse.
            (agent_set_slot, ":agent", slot_agent_lance, 0), # No longer a lancer
            (eq, ":wielded", ":lance"), # Still using lance?
            (call_script, "script_lance_use_backup_weapon", ":agent"), # Then equip a close weapon
        (else_try),
     # Still mounted
            (agent_get_position, pos1, ":agent"),    
            (agent_get_team, ":team_no", ":agent"),  # Find distance of nearest 3 enemies
            (call_script, "script_get_closest3_distance_of_enemies_at_pos1", ":team_no", pos1),
            (assign, ":avg_dist", reg0),
            (try_begin),
                (lt, ":avg_dist", 500), # Are the enemies within 5 meters?
                (agent_get_combat_state, ":combat", ":agent"),
                (gt, ":combat", 3), # Agent currently in combat? ...avoids switching before contact
                (eq, ":wielded", ":lance"), # Still using lance?
                (call_script, "script_lance_use_backup_weapon", ":agent"), # Then equip a close weapon
            (else_try),
                (neq, ":wielded", ":lance"), # Enemies farther than 5 meters and/or not fighting, and not using lance?
                (agent_set_wielded_item, ":agent", ":lance"), # Then equip it!
            (try_end),
        (try_end),
   (try_end),
])

lance_use_triggers = [
    common_lance_use_spawn,
    common_lance_use,
    ]

Copy and paste all of the above toward the top of the file (I put them after the multiplayer_* and before the common_* around line 630) and then just include the "lance_use_triggers" title at the end of the templates you want this code used in as demonstrated below:

Code:
//...a number of lines further down...//

   (
    "lead_charge",mtf_battle_mode,charge,
    "You lead your men to battle.",
    [

//...more lines down...//

          (call_script, "script_battle_tactic_apply"),
          ], []), #applying battle tactic

      common_battle_order_panel,
      common_battle_order_panel_tick,

    ] + lance_use_triggers,
  ),

  (
    "village_attack_bandits",mtf_battle_mode,charge,
    "You lead your men to battle.",

//...the next mission...//

Code:
  
  # script_lance_use_backup_weapon
  # Input: arg1: agent
  # Output: none
  ("lance_use_backup_weapon",   
    [    
     # Find non-lance/spear/bow item in inventory
    (store_script_param_1, ":agent"),
    (agent_get_troop_id, ":troop",":agent"),     
    (assign,":has_choice",0),
    (try_begin),
        (troop_is_hero, ":troop"),
        (assign, ":end", ek_head),
        (try_for_range, ":i", ek_item_0, ":end"),
	        (troop_get_inventory_slot,":item",":troop",":i"),
            (gt, ":item", 0),
            (item_get_type, ":weapontype", ":item"),
            (is_between, ":weapontype", itp_type_one_handed_wpn, itp_type_polearm),#one or two handed
            (assign,":has_choice",1),
            (assign, ":end", ek_item_0),#loop breaker
        (try_end),
    (else_try),#regular troops
        (troop_get_inventory_capacity,":cap",":troop"),
        (try_for_range, ":i", 0, ":cap"),#not sure if reg. troops have equipped items (slots < 10), but w/e
            (troop_get_inventory_slot,":item",":troop",":i"),
            (gt, ":item", 0),
            (item_get_type, ":weapontype", ":item"),
            (is_between, ":weapontype", itp_type_one_handed_wpn, itp_type_polearm),#one or two handed
            (agent_has_item_equipped, ":agent", ":item"),#this step is essential,
            (assign,":has_choice",1),#but I'm not sure if this would work or not
            (assign, ":cap", 0),
        (try_end),
    (try_end),
    (try_begin),# Equip their backup weapon.
        (eq, ":has_choice",1),
        (agent_set_wielded_item, ":agent", ":item"),
    (try_end),
    ]),    
      
  # script_lance_use_classify_agent
  # Input: None
  # Output: None
("lance_use_classify_agent", [     
   (try_for_agents, ":agent"),
        (agent_is_alive, ":agent"),
    # Isn't a player.
        (agent_is_non_player, ":agent"),
   # Isn't a horse.
        (agent_is_human, ":agent"),
   # They riding a horse?
        (agent_get_horse, ":horse", ":agent"),
        (gt, ":horse", 0),  # Is riding a horse.
    # Not a horsearcher
		(agent_get_troop_id, ":troop",":agent"),
		(neg|troop_is_guarantee_ranged, ":troop"), 
	# Get wielded item.
        (agent_get_wielded_item, ":wielded", ":agent", 0),
        (try_begin),
            (this_or_next|is_between, ":wielded", "itm_jousting_lance","itm_glaive"), # Is it a lance?
            (is_between, ":wielded", "itm_light_lance","itm_pike"), # Is it a lance?
            (agent_set_slot, ":agent", slot_agent_lance, ":wielded"),
        (else_try),    
   # Force the NPC to wield a lance, but this will only happen if they
   # actually have a lance equipped.  Otherwise this does nothing.
            (assign, ":end", "itm_glaive"),  # adjust as needed
            (try_for_range, ":item", "itm_jousting_lance",":end"),
                (agent_has_item_equipped, ":agent", ":item"),
                (agent_set_wielded_item, ":agent", ":item"),
                (agent_set_slot, ":agent", slot_agent_lance, ":item"), #Mark lancers for later use
                (assign, ":end", "itm_jousting_lance"),#loop breaker      
            (try_end),
            (assign, ":end", "itm_pike"), # adjust as needed
            (try_for_range, ":item", "itm_light_lance",":end"),
	            (agent_has_item_equipped, ":agent", ":item"),
                (agent_set_wielded_item, ":agent", ":item"),
                (agent_set_slot, ":agent", slot_agent_lance, ":item"), #Mark lancers for later use
                (assign, ":end", "itm_light_lance"),#loop breaker      
            (try_end),
        (try_end),
   (try_end), #Agent Loop
    ]),
The scripts portion of the code can go any where in module_scripts so long as it doesn't break up current scripts and is within the first scripts = [  and comes before the final ]. Easiest to drop it at the very end of the file, before the final ].

EDIT: Changed the spawn script, as ti_on_agent_spawn cannot find horses--they haven't spawned yet--that trigger now fires 1 second into the mission, and only fires once to mark agents accordingly.

EDIT 2: Updated and error free, functioning as desired. Distance-to-enemy switch to short range weapon may need tweaking and fine-tuning, however.

EDIT 3: Below. Added horse archers and forced bow usage at spawn. Updated with extra checks to ensure agents have items equipped before trying to wield them.

EDIT 4: Stripped the above code to just lancers with the proximity test for weapon switching. Spears/Pikes and Horse archers now only in the BETA spoiler below.

Using similar logic, I've expanded it to include spearmen/pikemen (in the current version, anyone wielding a polearm at spawn) on foot and edited their decision making to be proximity-based like the Lancers. This block also forces horse archers to use their bows at battle start and until they run out of ammo (ignoring any order to the contrary). The code has many limitations and is a work in progress.

Code:
slot_agent_lance    = 26
slot_agent_spear    = 27
slot_agent_horsebow = 28
Set values to first 3 unused agent slots (26, 27, 28 in Native)

Code:
common_weapon_use_spawn = (
   # Just after spawn, mark lancers, spears, horse archers using a slot.
   # Force lancers to equip lances, horse archers to equip bows
  1, 0, ti_once, [], [(call_script, "script_weapon_use_classify_agent")])
   
   
common_weapon_use = (
   # Check to make sure there are no lance users on foot, if so force them to
   # switch to their sword. This should also affect troops that were NEVER mounted,
   # but are still equipped with lances, such as Taiga Bandits.
   # Check horse archers ammo, and if none left, switch to sword.
   # For mounted lancers and foot spears, affect their Decision on weapon use,
   # based on if closest 3 enemies are within 5 meters and if currently attacking/defending.
   2, 0, 0, [],
   [# Run through all active NPCs on the battle field.
   (try_for_agents, ":agent"),
     # Hasn't been defeated.
        (agent_is_alive, ":agent"),
        (try_begin),
            (agent_get_slot, ":lance", ":agent", slot_agent_lance),
            (gt, ":lance", 0),  # Lancer?
     # Get wielded item.
            (agent_get_wielded_item, ":wielded", ":agent", 0),
      # They riding a horse?
            (agent_get_horse, ":horse", ":agent"),
            (try_begin),
                (le, ":horse", 0), # Isn't riding a horse.
                (agent_set_slot, ":agent", slot_agent_lance, 0), # No longer a lancer
                (eq, ":wielded", ":lance"), # Still using lance?
                (call_script, "script_weapon_use_backup_weapon", ":agent"), # Then equip a close weapon
            (else_try),
     # Still mounted
                (agent_get_position, pos1, ":agent"),    
                (agent_get_team, ":team_no", ":agent"),  # Find distance of nearest 3 enemies
                (call_script, "script_get_closest3_distance_of_enemies_at_pos1", ":team_no", pos1),
                (assign, ":avg_dist", reg0),
                (try_begin),
                    (lt, ":avg_dist", 500), # Are the enemies within 5 meters?
                    (agent_get_combat_state, ":combat", ":agent"),
                    (gt, ":combat", 3), # Agent currently in combat? ...avoids switching before contact
                    (eq, ":wielded", ":lance"), # Still using lance?
                    (call_script, "script_weapon_use_backup_weapon", ":agent"), # Then equip a close weapon
                (else_try),
                    (neq, ":wielded", ":lance"), # Enemies farther than 5 meters and/or not fighting, and not using lance?
                    (agent_set_wielded_item, ":agent", ":lance"), # Then equip it!
                (try_end),
            (try_end),
        (else_try),
		    (agent_get_slot, ":bow", ":agent", slot_agent_horsebow),
            (gt, ":bow", 0),  # Horse archer?
     # Get wielded item.
            (agent_get_wielded_item, ":wielded", ":agent", 0),
      # They have ammo left?
            (agent_get_ammo, ":ammo", ":agent"),
            (try_begin),
			    (le, ":ammo", 0), # No ammo left
				(agent_set_slot, ":agent", slot_agent_horsebow, 0), # No longer a horse archer
                (eq, ":wielded", ":bow"), # Still using bow?
                (call_script, "script_weapon_use_backup_weapon", ":agent"), # Then equip a close weapon
			(else_try),
                (neq, ":wielded", ":bow"), # Still have ammo, and not using bow?
                (agent_set_wielded_item, ":agent", ":bow"), # Then equip it!
			(try_end),
        (else_try),
            (agent_get_slot, ":spear", ":agent", slot_agent_spear),   
            (gt, ":spear", 0), # Spear-Unit?   
            (agent_get_wielded_item, ":wielded", ":agent", 0), # Get wielded
            (agent_get_position, pos1, ":agent"),    
            (agent_get_team, ":team_no", ":agent"),  # Find distance of nearest 3 enemies
            (call_script, "script_get_closest3_distance_of_enemies_at_pos1", ":team_no", pos1),
            (assign, ":avg_dist", reg0),
            (try_begin),
                (lt, ":avg_dist", 500), # Are the enemies within 5 meters?
                (agent_get_combat_state, ":combat", ":agent"),
                (gt, ":combat", 3), # Agent currently in combat? ...avoids switching before contact
                (eq, ":wielded", ":spear"), # Still using spear?
                (call_script, "script_weapon_use_backup_weapon", ":agent"), # Then equip a close weapon
            (else_try),
                (neq, ":wielded", ":spear"), # Enemies farther than 5 meters and/or not fighting, and not using spear?
                (agent_set_wielded_item, ":agent", ":spear"), # Then equip it!      
            (try_end),
        (try_end),
   (try_end),
])

weapon_use_triggers = [
    common_weapon_use_spawn,
    common_weapon_use,
    ]
Install as instructed above, though with "  ] + weapon_use_triggers,  "  in place of lance_spear_triggers

Code:
# script_weapon_use_backup_weapon
  # Input: arg1: agent
  # Output: none
  ("weapon_use_backup_weapon",   
    [    
     # Find non-lance/spear/bow item in inventory
    (store_script_param_1, ":agent"),
    (agent_get_troop_id, ":troop",":agent"),     
    (assign,":has_choice",0),
    (try_begin),
        (troop_is_hero, ":troop"),
        (assign, ":end", ek_head),
        (try_for_range, ":i", ek_item_0, ":end"),
	        (troop_get_inventory_slot,":item",":troop",":i"),
            (gt, ":item", 0),
            (item_get_type, ":weapontype", ":item"),
            (is_between, ":weapontype", itp_type_one_handed_wpn, itp_type_polearm),#one or two handed
            (assign,":has_choice",1),
            (assign, ":end", ek_item_0),#loop breaker
        (try_end),
    (else_try),#regular troops
        (troop_get_inventory_capacity,":cap",":troop"),
        (try_for_range, ":i", 0, ":cap"),#not sure if reg. troops have equipped items (slots < 10), but w/e
            (troop_get_inventory_slot,":item",":troop",":i"),
            (gt, ":item", 0),
            (item_get_type, ":weapontype", ":item"),
            (is_between, ":weapontype", itp_type_one_handed_wpn, itp_type_polearm),#one or two handed
            (agent_has_item_equipped, ":agent", ":item"),#this step is essential,
            (assign,":has_choice",1),#but I'm not sure if this would work or not
            (assign, ":cap", 0),
        (try_end),
    (try_end),
    (try_begin),# Equip their backup weapon.
        (eq, ":has_choice",1),
        (agent_set_wielded_item, ":agent", ":item"),
    (try_end),
    ]),    
      
  # script_weapon_use_classify_agent
  # Input: None
  # Output: None
("weapon_use_classify_agent", [     
   (try_for_agents, ":agent"),
        (agent_is_alive, ":agent"),
    # Isn't a player.
        (agent_is_non_player, ":agent"),
   # Isn't a horse.
        (agent_is_human, ":agent"),
		(agent_get_troop_id, ":troop",":agent"),
   # Get wielded item.
        (agent_get_wielded_item, ":wielded", ":agent", 0),
   # They riding a horse?
        (agent_get_horse, ":horse", ":agent"),
        (try_begin),
            (gt, ":horse", 0),  # Is riding a horse.
			(neg|troop_is_guarantee_ranged, ":troop"), # Not a horsearcher
            (try_begin),
                (this_or_next|is_between, ":wielded", "itm_jousting_lance","itm_glaive"), # Is it a lance?
                (is_between, ":wielded", "itm_light_lance","itm_pike"), # Is it a lance?
                (agent_set_slot, ":agent", slot_agent_lance, ":wielded"),
            (else_try),    
   # Force the NPC to wield a lance, but this will only happen if they
   # actually have a lance equipped.  Otherwise this does nothing.
                (assign, ":end", "itm_glaive"),  # adjust as needed
                (try_for_range, ":item", "itm_jousting_lance",":end"),
                    (agent_has_item_equipped, ":agent", ":item"),
                    (agent_set_wielded_item, ":agent", ":item"),
                    (agent_set_slot, ":agent", slot_agent_lance, ":item"), #Mark lancers for later use
                    (assign, ":end", "itm_jousting_lance"),#loop breaker      
                (try_end),
                (assign, ":end", "itm_pike"), # adjust as needed
                (try_for_range, ":item", "itm_light_lance",":end"),
				    (agent_has_item_equipped, ":agent", ":item"),
                    (agent_set_wielded_item, ":agent", ":item"),
                    (agent_set_slot, ":agent", slot_agent_lance, ":item"), #Mark lancers for later use
                    (assign, ":end", "itm_light_lance"),#loop breaker      
                (try_end),
            (try_end),
		(else_try),
		    (gt, ":horse", 0),  # Is riding a horse.
			(troop_is_guarantee_ranged, ":troop"), # Is a horsearcher...redundant, but making sure
		    (try_begin),
                (is_between, ":wielded", "itm_hunting_bow", "itm_crossbow"), # Is it a bow or a horse-useful crossbow?
                (agent_set_slot, ":agent", slot_agent_horsebow, ":wielded"),
            (else_try), 
   # Force the NPC to wield their bow, but this will only happen if they
   # actually have a bow equipped.  Otherwise this does nothing.
                (assign, ":end", "itm_crossbow"),  # adjust as needed
                (try_for_range, ":item", "itm_hunting_bow",":end"),
                    (agent_has_item_equipped, ":agent", ":item"),
                    (agent_set_wielded_item, ":agent", ":item"),
                    (agent_set_slot, ":agent", slot_agent_horsebow, ":item"), #Mark horse archers for later use
                    (assign, ":end", "itm_hunting_bow"),#loop breaker      
                (try_end),
			(try_end),
	    (else_try), #Agent not mounted
            (le, ":horse", 0),
            (try_begin),
                (gt, ":wielded", 0), #Make sure it's not fists
                (item_get_type, ":weapontype", ":wielded"),
                (eq, ":weapontype", itp_type_polearm),  # Is it a spear?
                (agent_set_slot, ":agent", slot_agent_spear, ":item"), #Mark spearmen
            (else_try), #Check if there's a spear equipped, if not wielded
                (troop_get_inventory_capacity,":cap",":troop"),
                (try_for_range, ":i", 0, ":cap"),#not sure if reg. troops have equipped items (slots < 10), but w/e
                    (troop_get_inventory_slot,":item",":troop",":i"),
                    (gt, ":item", 0),
                    (item_get_type, ":weapontype", ":item"),
                    (eq, ":weapontype", itp_type_polearm),  # Is it a spear?
                    (agent_has_item_equipped, ":agent", ":item"),#this step is essential,
                    (agent_set_slot, ":agent", slot_agent_spear, ":item"), #Mark spearmen
                    (assign, ":cap", 0), #loop Break
                (try_end),
            (try_end),
        (try_end), #Mounted or not
   (try_end), #Agent Loop
    ]),
 
Back
Top Bottom