diff --git a/changelog_entries/attacks_used.md b/changelog_entries/attacks_used.md new file mode 100644 index 00000000000..f95c9519001 --- /dev/null +++ b/changelog_entries/attacks_used.md @@ -0,0 +1,2 @@ + ### WML API + * New attacks_used key in [attack] causes the attack to deduct more than 1 from attacks left \ No newline at end of file diff --git a/data/schema/filters/weapon.cfg b/data/schema/filters/weapon.cfg index d850cbe243c..c8c12acc684 100644 --- a/data/schema/filters/weapon.cfg +++ b/data/schema/filters/weapon.cfg @@ -17,6 +17,7 @@ {SIMPLE_KEY parry s_range_list} {SIMPLE_KEY accuracy s_range_list} {SIMPLE_KEY movement_used s_range_list} + {SIMPLE_KEY attacks_used s_range_list} {FILTER_BOOLEAN_OPS weapon} [/tag] diff --git a/data/schema/units/modifications.cfg b/data/schema/units/modifications.cfg index 4b611e49542..c3ad71a32b2 100644 --- a/data/schema/units/modifications.cfg +++ b/data/schema/units/modifications.cfg @@ -33,12 +33,14 @@ {SIMPLE_KEY set_parry s_int} {SIMPLE_KEY set_accuracy s_int} {SIMPLE_KEY set_movement_used s_int} + {SIMPLE_KEY set_attacks_used s_int} {SIMPLE_KEY increase_damage s_int_percent} {SIMPLE_KEY increase_attacks s_int_percent} {SIMPLE_KEY increase_parry s_int_percent} {SIMPLE_KEY increase_accuracy s_int_percent} {SIMPLE_KEY increase_movement_used s_int_percent} + {SIMPLE_KEY increase_attacks_used s_int_percent} {SIMPLE_KEY attack_weight s_real} {SIMPLE_KEY defense_weight s_real} diff --git a/data/schema/units/types.cfg b/data/schema/units/types.cfg index af97c6c2c3c..05754a5ddce 100644 --- a/data/schema/units/types.cfg +++ b/data/schema/units/types.cfg @@ -56,6 +56,7 @@ {SIMPLE_KEY defense_weight real} {SIMPLE_KEY attack_weight real} {SIMPLE_KEY movement_used int} + {SIMPLE_KEY attacks_used int} {SIMPLE_KEY accuracy int} {SIMPLE_KEY parry int} [tag] diff --git a/data/test/_main.cfg b/data/test/_main.cfg index f928b0b135c..61de71263e9 100644 --- a/data/test/_main.cfg +++ b/data/test/_main.cfg @@ -71,6 +71,7 @@ {test/scenarios/wml_tests/TerrainWML} {test/scenarios/wml_tests/UnitsWML} {test/scenarios/wml_tests/UnitsWML/AbilitiesWML} +{test/scenarios/wml_tests/UnitsWML/Attacks} {test/scenarios/wml_tests/WesnothFormulaLanguage} # Load test unit wml diff --git a/data/test/scenarios/wml_tests/UnitsWML/Attacks/test_attacks_used.cfg b/data/test/scenarios/wml_tests/UnitsWML/Attacks/test_attacks_used.cfg new file mode 100644 index 00000000000..ea726c3bdbe --- /dev/null +++ b/data/test/scenarios/wml_tests/UnitsWML/Attacks/test_attacks_used.cfg @@ -0,0 +1,71 @@ +#textdomain wesnoth-test + +##### +# API(s) being tested: [attack]attacks_used +## +# Expected end state: +# The Elvish Archer (alice) has 3/5 attacks left after attacking the Orcish Grunt (bob) +##### +{COMMON_KEEP_A_B_UNIT_TEST test_attacks_used ( + [event] + name=prestart + [modify_unit] + [filter] + id=alice + [/filter] + [effect] + apply_to=attack + range=melee + set_attacks_used=2 + [/effect] + attacks_left=5 + [/modify_unit] + [/event] + [event] + name=start + [store_unit] + [filter] + id=alice + [/filter] + variable=alice + [/store_unit] + {ASSERT {VARIABLE_CONDITIONAL alice.attacks_left numerical_equals 5}} + [do_command] + [attack] + weapon=0 + [source] + x,y=4,3 + [/source] + [destination] + x,y=5,3 + [/destination] + [/attack] + [/do_command] + [store_unit] + [filter] + id=alice + [/filter] + variable=alice + [/store_unit] + {ASSERT {VARIABLE_CONDITIONAL alice.attacks_left numerical_equals 3}} + [do_command] + [attack] + weapon=1 + [source] + x,y=4,3 + [/source] + [destination] + x,y=5,3 + [/destination] + [/attack] + [/do_command] + [store_unit] + [filter] + id=alice + [/filter] + variable=alice + [/store_unit] + {ASSERT {VARIABLE_CONDITIONAL alice.attacks_left numerical_equals 2}} + {SUCCEED} + [/event] +)} diff --git a/data/test/scenarios/wml_tests/UnitsWML/Attacks/test_movement_used.cfg b/data/test/scenarios/wml_tests/UnitsWML/Attacks/test_movement_used.cfg new file mode 100644 index 00000000000..b9984246f62 --- /dev/null +++ b/data/test/scenarios/wml_tests/UnitsWML/Attacks/test_movement_used.cfg @@ -0,0 +1,52 @@ +#textdomain wesnoth-test + +##### +# API(s) being tested: [attack]movement_used +## +# Expected end state: +# The Elvish Archer (alice) has 5/6 move points left after attacking the Orcish Grunt (bob) +##### +{COMMON_KEEP_A_B_UNIT_TEST test_movement_used ( + [event] + name=prestart + [modify_unit] + [filter] + id=alice + [/filter] + [effect] + apply_to=attack + range=melee + set_movement_used=1 + [/effect] + [/modify_unit] + [/event] + [event] + name=start + [store_unit] + [filter] + id=alice + [/filter] + variable=alice + [/store_unit] + {ASSERT {VARIABLE_CONDITIONAL alice.moves numerical_equals 6}} + [do_command] + [attack] + weapon=0 + [source] + x,y=4,3 + [/source] + [destination] + x,y=5,3 + [/destination] + [/attack] + [/do_command] + [store_unit] + [filter] + id=alice + [/filter] + variable=alice + [/store_unit] + {ASSERT {VARIABLE_CONDITIONAL alice.moves numerical_equals 5}} + {SUCCEED} + [/event] +)} diff --git a/src/actions/attack.cpp b/src/actions/attack.cpp index 1da54c7c1bf..d0fed0296ce 100644 --- a/src/actions/attack.cpp +++ b/src/actions/attack.cpp @@ -1369,11 +1369,10 @@ void attack::perform() return; } - a_.get_unit().set_attacks(a_.get_unit().attacks_left() - 1); - VALIDATE(a_.weapon_ < static_cast(a_.get_unit().attacks().size()), _("An invalid attacker weapon got selected.")); + a_.get_unit().set_attacks(a_.get_unit().attacks_left() - a_.get_unit().attacks()[a_.weapon_].attacks_used()); a_.get_unit().set_movement(a_.get_unit().movement_left() - a_.get_unit().attacks()[a_.weapon_].movement_used(), true); a_.get_unit().set_state(unit::STATE_NOT_MOVED, false); a_.get_unit().set_resting(false); diff --git a/src/formula/callable_objects.cpp b/src/formula/callable_objects.cpp index 69b09a73e9a..4ecba65e5cd 100644 --- a/src/formula/callable_objects.cpp +++ b/src/formula/callable_objects.cpp @@ -107,6 +107,8 @@ variant attack_type_callable::get_value(const std::string& key) const return variant(att_->parry()); } else if(key == "movement_used") { return variant(att_->movement_used()); + } else if(key == "attacks_used") { + return variant(att_->attacks_used()); } else if(key == "specials" || key == "special") { std::vector res; @@ -133,6 +135,7 @@ void attack_type_callable::get_inputs(formula_input_vector& inputs) const add_input(inputs, "accuracy"); add_input(inputs, "parry"); add_input(inputs, "movement_used"); + add_input(inputs, "attacks_used"); add_input(inputs, "attack_weight"); add_input(inputs, "defense_weight"); add_input(inputs, "specials"); diff --git a/src/scripting/lua_unit_attacks.cpp b/src/scripting/lua_unit_attacks.cpp index f6d789fc764..a20ebaf831b 100644 --- a/src/scripting/lua_unit_attacks.cpp +++ b/src/scripting/lua_unit_attacks.cpp @@ -264,6 +264,7 @@ static int impl_unit_attack_get(lua_State *L) return_float_attrib("defense_weight", attack.defense_weight()); return_int_attrib("accuracy", attack.accuracy()); return_int_attrib("movement_used", attack.movement_used()); + return_int_attrib("attacks_used", attack.attacks_used()); return_int_attrib("parry", attack.parry()); return_cfgref_attrib("specials", attack.specials()); return_cfgref_attrib("__cfg", attack.to_config()); @@ -296,6 +297,7 @@ static int impl_unit_attack_set(lua_State *L) modify_int_attrib("defense_weight", attack.set_defense_weight(value)); modify_int_attrib("accuracy", attack.set_accuracy(value)); modify_int_attrib("movement_used", attack.set_movement_used(value)); + modify_int_attrib("attacks_used", attack.set_attacks_used(value)); modify_int_attrib("parry", attack.set_parry(value)); if(strcmp(m, "specials") == 0) { diff --git a/src/units/attack_type.cpp b/src/units/attack_type.cpp index 37190045e86..7724e20aa1e 100644 --- a/src/units/attack_type.cpp +++ b/src/units/attack_type.cpp @@ -65,6 +65,7 @@ attack_type::attack_type(const config& cfg) : defense_weight_(cfg["defense_weight"].to_double(1.0)), accuracy_(cfg["accuracy"]), movement_used_(cfg["movement_used"].to_int(100000)), + attacks_used_(cfg["attacks_used"].to_int(1)), parry_(cfg["parry"]), specials_(cfg.child_or_empty("specials")), changed_(true) @@ -108,6 +109,7 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil const std::string& filter_accuracy = filter["accuracy"]; const std::string& filter_parry = filter["parry"]; const std::string& filter_movement = filter["movement_used"]; + const std::string& filter_attacks_used = filter["attacks_used"]; const std::vector filter_name = utils::split(filter["name"]); const std::vector filter_type = utils::split(filter["type"]); const std::vector filter_special = utils::split(filter["special"]); @@ -136,6 +138,9 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges(filter_movement))) return false; + if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges(filter_attacks_used))) + return false; + if ( !filter_name.empty() && std::find(filter_name.begin(), filter_name.end(), attack.id()) == filter_name.end() ) return false; @@ -295,6 +300,8 @@ bool attack_type::apply_modification(const config& cfg) const std::string& set_parry = cfg["set_parry"]; const std::string& increase_movement = cfg["increase_movement_used"]; const std::string& set_movement = cfg["set_movement_used"]; + const std::string& increase_attacks_used = cfg["increase_attacks_used"]; + const std::string& set_attacks_used = cfg["set_attacks_used"]; // NB: If you add something here that requires a description, // it needs to be added to describe_modification as well. @@ -396,6 +403,14 @@ bool attack_type::apply_modification(const config& cfg) movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1); } + if(set_attacks_used.empty() == false) { + attacks_used_ = std::stoi(set_attacks_used); + } + + if(increase_attacks_used.empty() == false) { + attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1); + } + if(set_attack_weight.empty() == false) { attack_weight_ = lexical_cast_default(set_attack_weight,1.0); } @@ -435,6 +450,8 @@ bool attack_type::describe_modification(const config& cfg,std::string* descripti const std::string& set_parry = cfg["set_parry"]; const std::string& increase_movement = cfg["increase_movement_used"]; const std::string& set_movement = cfg["set_movement_used"]; + const std::string& increase_attacks_used = cfg["increase_attacks_used"]; + const std::string& set_attacks_used = cfg["set_attacks_used"]; std::vector desc; @@ -520,6 +537,24 @@ bool attack_type::describe_modification(const config& cfg,std::string* descripti {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "#f00" : "#0f0"}})); } + if(!set_attacks_used.empty()) { + desc.emplace_back(VNGETTEXT( + // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML + "$number attack used", + "$number attacks used", + std::stoi(set_attacks_used), + {{"number", set_attacks_used}})); + } + + if(!increase_attacks_used.empty()) { + desc.emplace_back(VNGETTEXT( + // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML + "$number_or_percent attack used", + "$number_or_percent attacks used", + std::stoi(increase_attacks_used), + {{"number_or_percent", utils::print_modifier(increase_attacks_used)}, {"color", increase_attacks_used[0] == '-' ? "#f00" : "#0f0"}})); + } + *description = utils::format_conjunct_list("", desc); } @@ -541,6 +576,7 @@ void attack_type::write(config& cfg) const cfg["defense_weight"] = defense_weight_; cfg["accuracy"] = accuracy_; cfg["movement_used"] = movement_used_; + cfg["attacks_used"] = attacks_used_; cfg["parry"] = parry_; cfg.add_child("specials", specials_); } diff --git a/src/units/attack_type.hpp b/src/units/attack_type.hpp index 7bf00985ba6..7b9b36be46c 100644 --- a/src/units/attack_type.hpp +++ b/src/units/attack_type.hpp @@ -123,6 +123,8 @@ public: int movement_used() const { return movement_used_; } void set_movement_used(int value) { movement_used_ = value; } + int attacks_used() const { return attacks_used_; } + void set_attacks_used(int value) { attacks_used_ = value; } void write(config& cfg) const; inline config to_config() const { config c; write(c); return c; } @@ -324,6 +326,7 @@ private: int accuracy_; int movement_used_; + int attacks_used_; int parry_; config specials_; bool changed_; diff --git a/src/whiteboard/attack.cpp b/src/whiteboard/attack.cpp index afc98da2cc2..520aad39f78 100644 --- a/src/whiteboard/attack.cpp +++ b/src/whiteboard/attack.cpp @@ -60,7 +60,9 @@ attack::attack(std::size_t team_index, bool hidden, unit& u, const map_location& target_hex_(target_hex), weapon_choice_(weapon_choice), attack_movement_cost_(u.attacks()[weapon_choice_].movement_used()), - temp_movement_subtracted_(0) + temp_movement_subtracted_(0), + attack_count_(u.attacks()[weapon_choice_].attacks_used()), + temp_attacks_subtracted_(0) { this->init(); } @@ -71,6 +73,8 @@ attack::attack(const config& cfg, bool hidden) , weapon_choice_(cfg["weapon_choice_"].to_int(-1)) //default value: -1 , attack_movement_cost_() , temp_movement_subtracted_(0) + , attack_count_() + , temp_attacks_subtracted_(0) { // Validate target_hex if(!tiles_adjacent(target_hex_,get_dest_hex())) @@ -83,6 +87,7 @@ attack::attack(const config& cfg, bool hidden) // Construct attack_movement_cost_ assert(get_unit()); attack_movement_cost_ = get_unit()->attacks()[weapon_choice_].movement_used(); + attack_count_ = get_unit()->attacks()[weapon_choice_].attacks_used(); this->init(); } @@ -155,16 +160,17 @@ void attack::apply_temp_modifier(unit_map& unit_map) assert(get_unit()); unit& unit = *get_unit(); DBG_WB << unit.name() << " [" << unit.id() - << "] has " << unit.attacks_left() << " attacks, decreasing by one"; - assert(unit.attacks_left() > 0); - unit.set_attacks(unit.attacks_left() - 1); + << "] has " << unit.attacks_left() << " attacks, decreasing by " << attack_count_; + assert(unit.attacks_left() > attack_count_); //Calculate movement to subtract temp_movement_subtracted_ = unit.movement_left() >= attack_movement_cost_ ? attack_movement_cost_ : 0 ; + temp_attacks_subtracted_ = unit.attacks_left() >= attack_count_ ? attack_count_ : 0 ; DBG_WB << "Attack: Changing movement points for unit " << unit.name() << " [" << unit.id() << "] from " << unit.movement_left() << " to " << unit.movement_left() - temp_movement_subtracted_ << "."; unit.set_movement(unit.movement_left() - temp_movement_subtracted_, true); + unit.set_attacks(unit.attacks_left() - temp_attacks_subtracted_); //Update status of fake unit (not undone by remove_temp_modifiers) //@todo this contradicts the name "temp_modifiers" diff --git a/src/whiteboard/attack.hpp b/src/whiteboard/attack.hpp index 255c24f139d..6b391525ffb 100644 --- a/src/whiteboard/attack.hpp +++ b/src/whiteboard/attack.hpp @@ -82,6 +82,8 @@ private: int weapon_choice_; int attack_movement_cost_; int temp_movement_subtracted_; + int attack_count_; + int temp_attacks_subtracted_; }; /** Dumps an attack on a stream, for debug purposes. */ diff --git a/utils/emmylua/wesnoth/units.lua b/utils/emmylua/wesnoth/units.lua index 7ef30a84d37..bed5e67f132 100644 --- a/utils/emmylua/wesnoth/units.lua +++ b/utils/emmylua/wesnoth/units.lua @@ -10,6 +10,7 @@ ---@field range integer ---@field number integer ---@field movement_used integer +---@field attacks_used integer ---@field attack_weight number ---@field defense_weight number ---@field accuracy integer diff --git a/wml_test_schedule b/wml_test_schedule index fb9cb9fc6f1..944217ad18f 100644 --- a/wml_test_schedule +++ b/wml_test_schedule @@ -203,6 +203,8 @@ 0 special_note_from_movetype 0 special_note_individual_unit 0 has_achievement +0 test_movement_used +0 test_attacks_used # Terrain mask tests 0 test_terrain_mask_simple_nop 0 test_terrain_mask_simple_set