Defensive retaliation

The place to post your WML questions and answers.

Moderator: Forum Moderators

Forum rules
  • Please use [code] BBCode tags in your posts for embedding WML snippets.
  • To keep your code readable so that others can easily help you, make sure to indent it following our conventions.
Post Reply
User avatar
Dugi
Posts: 4960
Joined: July 22nd, 2010, 10:29 am
Location: Carpathian Mountains
Contact:

Defensive retaliation

Post by Dugi »

After that mattsc stated that units played have 0.5 aggression when choosing counterattack during the enemy's turn, it became clear that there is practically no way to make units like Elvish Shyde choose to retaliate with ensnare instead of thorns if they have high hp. This was bugging me when I was playing IftU, my army consisted purely of Elvish Shydes and Elvish Sylphs, and the Shaxthal drones were causing severe damages to Shydes when I left them uncovered. They dumbly chose to counter with an attack without slow, and had problems with health (as they were those who were supposed to heal others). I left it be then, but recently nuorc mentioned a similar issue with SXRPG, where slowing attacks were very important for protection, and I figured out a way to help him. He tested my code, so the code operates correctly (well, I have made a small finishing touch, but that should work..).

This code does:
- For each unit you control, you may activate defensive retaliation mode in the right-click menu
- All units in defensive retaliation mode will counterattack with a slowing attack of that range, unless they have another attack with that range that can slay the attacker with a single hit

Placement:
Place it into the [era], [scenario], [multiplayer], [unit_type] tags. In the case of era, its effect is quite obvious. In case of scenario, you have to place it into every scenario where you want it to apply. Same with multiplayer (careful, all other people you play with have to have it installed, or you'll see Out of Sync errors). In the case of unit_type, it must be inside a unit that you start with, e.g. your leader (take care about OoS again).
To see if you've placed it correctly, right-click on a unit. If you'll see there an option named 'Activate defensive retaliation', it was placed correctly.

Code (it is relatively simple, so there is no need to explain how exactly it is done):

Code: Select all

[event]
    name=start
	id=defensive_retaliation_control
	[set_menu_item]
		id=defensive_retaliation_activation
		description="Activate defensive retaliation"
	      [show_if]
		[have_unit]
		  x,y=$x1,$y1
		  side=$side_number
		  [not]
			[filter_wml]
				[variables]
					defensive_retaliation=1
				[/variables]
			[/filter_wml]
		  [/not]
		[/have_unit]
	      [/show_if]
		[command]
			{VARIABLE unit.variables.defensive_retaliation 1}
			     [unstore_unit]
				variable=unit
				find_vacant=no
			    [/unstore_unit]
		[/command]
	[/set_menu_item]
	[set_menu_item]
		id=defensive_retaliation_deactivations
		description="Deactivate defensive retaliation"
	      [show_if]
		[have_unit]
		  x,y=$x1,$y1
		  side=$side_number
		[filter_wml]
			[variables]
				defensive_retaliation=1
			[/variables]
		[/filter_wml]
		[/have_unit]
	      [/show_if]
		[command]
			{VARIABLE unit.variables.defensive_retaliation 0}
			     [unstore_unit]
				variable=unit
				find_vacant=no
			    [/unstore_unit]
		[/command]
	[/set_menu_item]
   [/event]
    [event]
        name=attack
        first_time_only=no
	id=defensive_retaliation_event
        [filter_second]
	  [filter_wml]
		[variables]
			defensive_retaliation=1
		[/variables]
	  [/filter_wml]
        [/filter_second]
	{VARIABLE dont_slow 0}
	{FOREACH second_unit.attack i}
		[if]
			[variable]
				name=second_unit.attack[$i].range
				greater_than_equal_to=$weapon.range
			[/variable]
			[then]
				[set_variable]
					name=resistance
					to_variable=unit.resistance.$second_unit.attack[$i].type
				[/set_variable]
				{VARIABLE this_damage "$($resistance*$second_unit.attack[$i].damage)"}
				[if]
					[variable]
						name=this_damage
						greater_than_equal_to="$($unit.hitpoints*100)"
					[/variable]
					[then]
						{VARIABLE dont_slow 1}
					[/then]
				[/if]
			[/then]
		[/if]
	{NEXT i}
	[if]
		[variable]
			name=dont_slow
			not_equals=1
		[/variable]
		[then]
			{FOREACH second_unit.attack i}
				[if]
					[variable]
						name=second_unit.attack[$i].range
						greater_than_equal_to=$weapon.range
					[/variable]
					[then]
					    [set_variables]
						name=second_unit.variables.former_attacks[$i]
						to_variable=second_unit.attack[$i]
						mode=replace
					    [/set_variables]
					    [if]
						[variable]
						    name=second_unit.attack[$i].specials.slow.length
						    greater_than=0
						[/variable]
						[then]
						    [set_variables]
							name=slow_attack
							to_variable=second_unit.attack[$i]
							mode=replace
						    [/set_variables]
						[/then]
					    [/if]
					[/then]
				[/if]
			{NEXT i}
			{FOREACH second_unit.attack i}
			    [set_variables]
				name=second_unit.attack[$i]
				to_variable=slow_attack
			    [/set_variables]
			{NEXT i}
			[unstore_unit]
				variable=second_unit
				find_vacant=no
			[/unstore_unit]
			{CLEAR_VARIABLE slow_attack,dont_slow,this_damage,resistance}
			[event]
			    name=attack end
			    delayed_variable_substitution=yes
				{FOREACH second_unit.attack i}
				    [set_variables]
					name=second_unit.attack[$i]
					to_variable=second_unit.variables.former_attacks[$i]
				    [/set_variables]
				{NEXT i}
			    {CLEAR_VARIABLE unit.variables.former_attacks}

			    [unstore_unit]
				variable=second_unit
				find_vacant=no
			    [/unstore_unit]
			[/event]
		[/then]
	[/if]

    [/event]
Mabuse
Posts: 2130
Joined: November 6th, 2007, 1:38 pm

Re: Defensive retaliation

Post by Mabuse »

Hey dugi, nice code.

would like to try it out - :)


EDIT:
-------

as i think more about it .. would it not be better to skim through the units weapons and give all weapons with slow a high defense-weight?

in this case even the damage of the weapon is taken into account. in case of SXRPG this seems a better choice.

so not only any random weapon with slow is chosen, instead, the slow weapon with the most damage is chosen.
The best bet is your own, good Taste.
User avatar
Dugi
Posts: 4960
Joined: July 22nd, 2010, 10:29 am
Location: Carpathian Mountains
Contact:

Re: Defensive retaliation

Post by Dugi »

The problem with defense_weight is that it seems to work only of it is set to 0. Maybe I am wrong, but I remember giving a unit two melee attacks, one had like 10-4 damage, the other one had like 8-3 damage, berserk, and defense_weight set to 0.01, and it still chose berserk against normal attack when it was defending, getting killed very easily.

You can create a unit with one attack with slow and a high defense_weight and one attack with low, but non-zero defense_weight to see how it really is.

Btw, I have written this thing for SXRPG upon a request from a guy.
Image
Mabuse
Posts: 2130
Joined: November 6th, 2007, 1:38 pm

Re: Defensive retaliation

Post by Mabuse »

Dugi wrote:The problem with defense_weight is that it seems to work only of it is set to 0. Maybe I am wrong, but I remember giving a unit two melee attacks, one had like 10-4 damage, the other one had like 8-3 damage, berserk, and defense_weight set to 0.01, and it still chose berserk against normal attack when it was defending, getting killed very easily.

You can create a unit with one attack with slow and a high defense_weight and one attack with low, but non-zero defense_weight to see how it really is.

Btw, I have written this thing for SXRPG upon a request from a guy.
yes, you are damn right.

def_weight only works if set to 0. still it can be used for "our" purposes.
so the conclusion to make a unit use only slow-weapons on defense is to set ALL non-slow attacks to def_weight 0.

ofc such a thing fits perfectly to SXRPG.
especially in the start of mayn maps where you probably want to lure enemy units, such a "defensive options" is more than useful.
(maybe even OP .. hehe)

and in any case it gives the "shield"/"net"-weapon a real, great use.

i would have used your code, but one thing disturbed me, i think the weapon that is finally chosen is the first slow weapon that is available in the attack list.

so i thought to myself: why not simply let the combat-calculator decide which one of the several, possible slow attacks is used.
and this could be only done by staying inside the "system". your solution is a bit outside the box :)
(and maybe a bit complicated ... it saves all attacks, then exchanges all attcks with a slow attack so that it is the only one to be used, then restores old weapons again)

so i used def_weight, and in the manner that all weapons that dont have a slow-special get def_weight set to 0.
also i dont ned further events, just two menu options to be added.

anyway, your idea has really inspired me, thx a ton, dugi.

even more following the "defensive_weight"-path i stumbeld across agreat idea from "Matts" or however his nick is.
def_weight can also help ro reduce combat calculation time. but that another story.



however, the code, concerning the "use only slow weapons of defense" thing.
will chose the most useful slowing weapon for defense. can be activated or deactivated via right click.
in this version it works only for leaders :

Code: Select all

[event]
    name=start
   id=defensive_retaliation_control
   [set_menu_item]
      id=ZZZA
      description="Activate SLOW-Weapon Defense (Inventory more Info)"
      image=misc/defends.png
         [show_if]
      [have_unit]
        x,y=$x1,$y1
        side=$side_number
        canrecruit=yes
        [not]
         [filter_wml]
            [variables]
               defensive_retaliation=1
            [/variables]
         [/filter_wml]
        [/not]
      [/have_unit]
         [/show_if]
      [command]
         {VARIABLE unit.variables.defensive_retaliation 1}
         
         {VARIABLE has_ranged 0}
         {VARIABLE has_melee 0}
         
         {FOREACH unit.attack i}
         [if]
         {SXCON unit.attack[$i].range equals "ranged"}
         [then]
         [if]
         {SXCON unit.attack[$i].specials.slow.length greater_than 0}
         [then]
         {VARIABLE has_ranged 1}
         [/then]
         [/if]
         [/then]
         [/if]
         {NEXT i}
         
         {FOREACH unit.attack i}
         [if]
         {SXCON unit.attack[$i].range equals "melee"}
         [then]
         [if]
         {SXCON unit.attack[$i].specials.slow.length greater_than 0}
         [then]
         {VARIABLE has_melee 1}
         [/then]
         [/if]
         [/then]
         [/if]
         {NEXT i}
         
         {FOREACH unit.attack i}
         [if]
         {SXCON unit.attack[$i].specials.slow.length less_than_equal_to 0}
         {SXCON unit.attack[$i].range equals "ranged"}
         {SXCON has_ranged equals 1}
         [then]
         {VARIABLE unit.attack[$i].defense_weight 0}
         [/then]
         [/if]
         {NEXT i}
         
         {FOREACH unit.attack i}
         [if]
         {SXCON unit.attack[$i].specials.slow.length less_than_equal_to 0}
         {SXCON unit.attack[$i].range equals "melee"}
         {SXCON has_melee equals 1}
         [then]
         {VARIABLE unit.attack[$i].defense_weight 0}
         [/then]
         [/if]
         {NEXT i}
         
              [unstore_unit]
            variable=unit
            find_vacant=no
             [/unstore_unit]
      [/command]
   [/set_menu_item]
   [set_menu_item]
      id=ZZZD
      description="Activate NORMAL Defense (Inventory more Info)"
      image=misc/defendsdac.png
         [show_if]
      [have_unit]
        x,y=$x1,$y1
        side=$side_number
        canrecruit=yes
      [filter_wml]
         [variables]
            defensive_retaliation=1
         [/variables]
      [/filter_wml]
      [/have_unit]
         [/show_if]
      [command]
         {VARIABLE unit.variables.defensive_retaliation 0}
         
         {FOREACH unit.attack i}
         [if]
         {SXCON unit.attack[$i].specials.slow.length less_than_equal_to 0}
         [then]
         {VARIABLE unit.attack[$i].defense_weight 1.0}
         [/then]
         [/if]
         {NEXT i}
         
              [unstore_unit]
            variable=unit
            find_vacant=no
             [/unstore_unit]
      [/command]
   [/set_menu_item]
   [/event]
The best bet is your own, good Taste.
User avatar
Lord-Knightmare
Forum Regular
Posts: 1180
Joined: May 24th, 2010, 5:26 pm
Location: Somewhere in the depths of Irdya, gathering my army to eventually destroy the known world.
Contact:

Re: Defensive retaliation

Post by Lord-Knightmare »

Hey Dugi, will you be releasing this code as an MP Modification? Cause it is very good. :D
User avatar
Dugi
Posts: 4960
Joined: July 22nd, 2010, 10:29 am
Location: Carpathian Mountains
Contact:

Re: Defensive retaliation

Post by Dugi »

@Mabuse I haven't expected that a unit would have more than one slow attack. In that case setting defense_weight to 0 for all attacks without slow is the way it should be done. I'll edit the starting post accordingly when I'll have the time to test it.

@Knightmare Never thought of it, probably because there was no such thing as MP modification when I was writing it. But it is a good idea to make a MP modification from it, I'll do it, but not so soon as I'll have an exam next week.
User avatar
Lord-Knightmare
Forum Regular
Posts: 1180
Joined: May 24th, 2010, 5:26 pm
Location: Somewhere in the depths of Irdya, gathering my army to eventually destroy the known world.
Contact:

Re: Defensive retaliation

Post by Lord-Knightmare »

Okay, I tried this as a modification but this is broken. :evil:
An Orcish Crossbowman used crossbow attack against a Elvish Druid. But, Elvish Druid did not retaliate at all! It just took 20 damage.
AI
Developer
Posts: 2394
Joined: January 31st, 2008, 8:38 pm

Re: Defensive retaliation

Post by AI »

I took a short look at the attack code to figure out why defense_weight behaves the way it does. After filtering out weapons with the wrong range or zero defense weight, this is the next filtering step. I'm not entirely sure what it's supposed to do.

Code: Select all

    // Multiple options:
    // First pass : get the best weight and the minimum simple rating for this weight.
    // simple rating = number of blows * damage per blows (resistance taken in account) * cth * weight
    // Elligible attacks for defense should have a simple rating greater or equal to this weight.

    double max_weight = 0.0;
    int min_rating = 0;

    for (i = 0; i < choices.size(); ++i) {
        const attack_type &def = defender.attacks()[choices[i]];
        if (def.defense_weight() >= max_weight) {
            max_weight = def.defense_weight();
            const battle_context_unit_stats def_stats(defender, defender_loc,
                    choices[i], false, attacker, attacker_loc, &att, units);
            int rating = static_cast<int>(def_stats.num_blows * def_stats.damage *
                    def_stats.chance_to_hit * def.defense_weight());
            if (def.defense_weight() > max_weight || rating < min_rating ) {
                min_rating = rating;
            }
        }
    }
This is the actual decision in the final loop:

Code: Select all

        int simple_rating = static_cast<int>(def_stats->num_blows *
                def_stats->damage * def_stats->chance_to_hit * def.defense_weight());

        if (simple_rating >= min_rating &&
                ( !attacker_combatant_ || better_combat(*def_comb, *att_comb, *defender_combatant_, *attacker_combatant_, 1.0) )
As you can see, defense_weight is used, but the only way an attack will be considered 'better' is if there is no valid attack yet or better_combat() returns true.

This might be considered a bug.
Mabuse
Posts: 2130
Joined: November 6th, 2007, 1:38 pm

Re: Defensive retaliation

Post by Mabuse »

ofc,

this must be noted, right now we can simply switch a "slow-weapon"-defense on/off.

often, this may not be an optimal solution, i can imagine numerous situations where having activated something like this would be even a disadvantage.

so having def-weight works as it should work would be still best solution.
The best bet is your own, good Taste.
Post Reply