Add exclusive unit traits handling similar to require_amla (#7109)
This commit is contained in:
parent
a4e17ac867
commit
492c20baa6
8 changed files with 321 additions and 13 deletions
|
@ -217,6 +217,8 @@
|
|||
{SIMPLE_KEY male_name t_string}
|
||||
{SIMPLE_KEY female_name t_string}
|
||||
{SIMPLE_KEY description t_string}
|
||||
{SIMPLE_KEY exclude_traits string_list}
|
||||
{SIMPLE_KEY require_traits string_list}
|
||||
{SIMPLE_KEY help_text t_string}
|
||||
[/tag]
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@
|
|||
{test/scenarios/wml_tests/UnitsWML/AbilitiesWML}
|
||||
{test/scenarios/wml_tests/WesnothFormulaLanguage}
|
||||
|
||||
# Load test unit wml
|
||||
{test/units.cfg}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef DONT_RELOAD_CORE
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
# wmllint: no translatables
|
||||
|
||||
#####
|
||||
# API(s) being tested: [trait]exclude_traits=
|
||||
##
|
||||
# Actions:
|
||||
# Bob is told to generate special random traits.
|
||||
##
|
||||
# Expected end state:
|
||||
# One of the two special traits excludes another trait to be already had by Bob.
|
||||
# Bob should either not have the trait or have only the excluded one.
|
||||
#####
|
||||
|
||||
{GENERIC_UNIT_TEST "trait_exclusion_test" (
|
||||
[event]
|
||||
name=start
|
||||
{UNIT 1 (Test Unit Exclude) 1 1 (id=excluder1)}
|
||||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 1
|
||||
[store_unit]
|
||||
[filter]
|
||||
side=1
|
||||
id=excluder1
|
||||
[/filter]
|
||||
variable=excluded
|
||||
kill=no
|
||||
[/store_unit]
|
||||
[if]
|
||||
# Check if the first randomly generated trait is the excluded one, then the
|
||||
# next trait slot should be empty
|
||||
{VARIABLE_CONDITIONAL excluded.modifications.trait[0].id equals "test_excluded"}
|
||||
[then]
|
||||
{ASSERT ({VARIABLE_CONDITIONAL excluded.modifications.trait[1].id equals $nil})}
|
||||
[/then]
|
||||
[else]
|
||||
# Otherwise if it is "test_exclude" then the next slot should be empty as well
|
||||
{ASSERT ({VARIABLE_CONDITIONAL excluded.modifications.trait[0].id equals "test_exclude"})}
|
||||
{ASSERT ({VARIABLE_CONDITIONAL excluded.modifications.trait[1].id equals $nil})}
|
||||
[/else]
|
||||
[/if]
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
#####
|
||||
# API(s) being tested: [trait]require_traits=
|
||||
##
|
||||
# Actions:
|
||||
# Bob is told to generate special random traits.
|
||||
##
|
||||
# Expected end state:
|
||||
# One of the two special traits needs another trait to be already had by Bob.
|
||||
# Bob only have the trait if it also has the one it requires one.
|
||||
#####
|
||||
|
||||
|
||||
{GENERIC_UNIT_TEST "trait_requirement_test" (
|
||||
[event]
|
||||
name=start
|
||||
{UNIT 1 (Test Unit Require) 1 1 (id=requirer1)}
|
||||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 1
|
||||
[store_unit]
|
||||
[filter]
|
||||
side=1
|
||||
id=requirer1
|
||||
[/filter]
|
||||
variable=required
|
||||
kill=no
|
||||
[/store_unit]
|
||||
# Check if the second randomly generated trait is the one requiring the other one
|
||||
{ASSERT ({VARIABLE_CONDITIONAL required.modifications.trait[1].id equals "test_require"})}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
36
data/test/units.cfg
Normal file
36
data/test/units.cfg
Normal file
|
@ -0,0 +1,36 @@
|
|||
#textdomain wesnoth-test
|
||||
|
||||
[units]
|
||||
{test/units/}
|
||||
|
||||
[race]
|
||||
id=test
|
||||
name= _ "test"
|
||||
female_name= _ "test"
|
||||
plural_name= _ "tests"
|
||||
description= _ "test race"
|
||||
num_traits=2
|
||||
undead_variation=wolf
|
||||
{ORCISH_NAMES}
|
||||
[/race]
|
||||
|
||||
[movetype]
|
||||
name=fly
|
||||
flying=yes
|
||||
[movement_costs]
|
||||
{FLY_MOVE}
|
||||
cave=3
|
||||
fungus=3
|
||||
[/movement_costs]
|
||||
[defense]
|
||||
{FLY_DEFENSE 50}
|
||||
cave=80
|
||||
fungus=-70
|
||||
[/defense]
|
||||
{FLY_RESISTANCE}
|
||||
[special_note]
|
||||
note={INTERNAL:SPECIAL_NOTES_DEFENSE_CAP}
|
||||
[/special_note]
|
||||
[/movetype]
|
||||
|
||||
[/units]
|
56
data/test/units/test_unit_exclude.cfg
Normal file
56
data/test/units/test_unit_exclude.cfg
Normal file
|
@ -0,0 +1,56 @@
|
|||
#textdomain wesnoth-test
|
||||
|
||||
#define TRAIT_TEST_EXCLUDE
|
||||
# Test for exclude trait
|
||||
[trait]
|
||||
id=test_exclude
|
||||
male_name= _ "test_exclude"
|
||||
female_name= _ "female^test_exclude"
|
||||
help_text= _ "Test exclude"
|
||||
exclude_traits=test_excluded,strong,quick,intelligent,resilient
|
||||
[effect]
|
||||
apply_to=hitpoints
|
||||
increase_total=1
|
||||
[/effect]
|
||||
[/trait]
|
||||
#enddef
|
||||
|
||||
#define TRAIT_TEST_EXCLUDED
|
||||
# Test for require trait
|
||||
[trait]
|
||||
id=test_excluded
|
||||
male_name= _ "test_excluded"
|
||||
female_name= _ "female^test_excluded"
|
||||
help_text= _ "Test excluded"
|
||||
[effect]
|
||||
apply_to=hitpoints
|
||||
increase_total=1
|
||||
[/effect]
|
||||
[/trait]
|
||||
#enddef
|
||||
|
||||
# This unit is used for testing the exclude traits functionality.
|
||||
|
||||
[unit_type]
|
||||
id=Test Unit Exclude
|
||||
name=_ "dummy_unit^Test Unit Exclude"
|
||||
race=test
|
||||
image="misc/blank-hex.png"
|
||||
ignore_race_traits=yes
|
||||
{TRAIT_TEST_EXCLUDE}
|
||||
{TRAIT_TEST_EXCLUDED}
|
||||
hitpoints=1
|
||||
movement_type=fly
|
||||
movement=1
|
||||
experience=1
|
||||
level=1
|
||||
alignment=neutral
|
||||
advances_to=null
|
||||
cost=1
|
||||
usage=scout
|
||||
hide_help=yes
|
||||
do_not_list=yes
|
||||
[/unit_type]
|
||||
|
||||
#undef TRAIT_TEST_EXCLUDE
|
||||
#undef TRAIT_TEST_EXCLUDED
|
56
data/test/units/test_unit_require.cfg
Normal file
56
data/test/units/test_unit_require.cfg
Normal file
|
@ -0,0 +1,56 @@
|
|||
#textdomain wesnoth-test
|
||||
|
||||
#define TRAIT_TEST_REQUIRE
|
||||
# Test for require trait
|
||||
[trait]
|
||||
id=test_require
|
||||
male_name= _ "test_require"
|
||||
female_name= _ "female^test_require"
|
||||
help_text= _ "Test require"
|
||||
require_traits=test_required
|
||||
[effect]
|
||||
apply_to=hitpoints
|
||||
increase_total=1
|
||||
[/effect]
|
||||
[/trait]
|
||||
#enddef
|
||||
|
||||
#define TRAIT_TEST_REQUIRED
|
||||
# Test for require trait
|
||||
[trait]
|
||||
id=test_required
|
||||
male_name= _ "test_required"
|
||||
female_name= _ "female^test_required"
|
||||
help_text= _ "Test required"
|
||||
[effect]
|
||||
apply_to=hitpoints
|
||||
increase_total=1
|
||||
[/effect]
|
||||
[/trait]
|
||||
#enddef
|
||||
|
||||
# This unit is used for testing the require traits functionality.
|
||||
|
||||
[unit_type]
|
||||
id=Test Unit Require
|
||||
name=_ "dummy_unit^Test Unit Require"
|
||||
race=test
|
||||
image="misc/blank-hex.png"
|
||||
ignore_race_traits=yes
|
||||
{TRAIT_TEST_REQUIRE}
|
||||
{TRAIT_TEST_REQUIRED}
|
||||
hitpoints=1
|
||||
movement_type=fly
|
||||
movement=1
|
||||
experience=1
|
||||
level=1
|
||||
alignment=neutral
|
||||
advances_to=null
|
||||
cost=1
|
||||
usage=scout
|
||||
hide_help=yes
|
||||
do_not_list=yes
|
||||
[/unit_type]
|
||||
|
||||
#undef TRAIT_TEST_REQUIRE
|
||||
#undef TRAIT_TEST_REQUIRED
|
|
@ -806,10 +806,47 @@ void unit::generate_traits(bool must_have_only)
|
|||
LOG_UT << "Generating a trait for unit type " << type().log_id() << " with must_have_only " << must_have_only;
|
||||
const unit_type& u_type = type();
|
||||
|
||||
// Calculate the unit's traits
|
||||
config::const_child_itors current_traits = modifications_.child_range("trait");
|
||||
std::vector<const config*> candidate_traits;
|
||||
|
||||
// Handle must-have only at the beginning
|
||||
for(const config& t : u_type.possible_traits()) {
|
||||
// Skip the trait if the unit already has it.
|
||||
const std::string& tid = t["id"];
|
||||
bool already = false;
|
||||
for(const config& mod : current_traits) {
|
||||
if(mod["id"] == tid) {
|
||||
already = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(already) {
|
||||
continue;
|
||||
}
|
||||
// Add the trait if it is mandatory.
|
||||
const std::string& avl = t["availability"];
|
||||
if(avl == "musthave") {
|
||||
modifications_.add_child("trait", t);
|
||||
current_traits = modifications_.child_range("trait");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(must_have_only) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<const config*> candidate_traits;
|
||||
std::vector<std::string> temp_require_traits;
|
||||
std::vector<std::string> temp_exclude_traits;
|
||||
|
||||
// Now randomly fill out to the number of traits required or until
|
||||
// there aren't any more traits.
|
||||
int nb_traits = current_traits.size();
|
||||
int max_traits = u_type.num_traits();
|
||||
for(; nb_traits < max_traits; ++nb_traits)
|
||||
{
|
||||
current_traits = modifications_.child_range("trait");
|
||||
candidate_traits.clear();
|
||||
for(const config& t : u_type.possible_traits()) {
|
||||
// Skip the trait if the unit already has it.
|
||||
const std::string& tid = t["id"];
|
||||
|
@ -824,35 +861,74 @@ void unit::generate_traits(bool must_have_only)
|
|||
if(already) {
|
||||
continue;
|
||||
}
|
||||
// Skip trait if trait requirements are not met
|
||||
// or trait exclusions are present
|
||||
temp_require_traits = utils::split(t["require_traits"]);
|
||||
temp_exclude_traits = utils::split(t["exclude_traits"]);
|
||||
|
||||
// Add the trait if it is mandatory.
|
||||
const std::string& avl = t["availability"];
|
||||
if(avl == "musthave") {
|
||||
modifications_.add_child("trait", t);
|
||||
current_traits = modifications_.child_range("trait");
|
||||
// See if the unit already has a trait that excludes the current one
|
||||
for(const config& mod : current_traits) {
|
||||
if (mod["exclude_traits"] != "") {
|
||||
for (const auto& c: utils::split(mod["exclude_traits"])) {
|
||||
temp_exclude_traits.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First check for requirements
|
||||
bool trait_req_met = true;
|
||||
for(const std::string& s : temp_require_traits) {
|
||||
bool has_trait = false;
|
||||
for(const config& mod : current_traits) {
|
||||
if (mod["id"] == s)
|
||||
has_trait = true;
|
||||
}
|
||||
if(!has_trait) {
|
||||
trait_req_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!trait_req_met) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now check for exclusionary traits
|
||||
bool trait_exc_met = true;
|
||||
|
||||
for(const std::string& s : temp_exclude_traits) {
|
||||
bool has_exclusionary_trait = false;
|
||||
for(const config& mod : current_traits) {
|
||||
if (mod["id"] == s)
|
||||
has_exclusionary_trait = true;
|
||||
}
|
||||
if (tid == s) {
|
||||
has_exclusionary_trait = true;
|
||||
}
|
||||
if(has_exclusionary_trait) {
|
||||
trait_exc_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!trait_exc_met) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& avl = t["availability"];
|
||||
// The trait is still available, mark it as a candidate for randomizing.
|
||||
// For leaders, only traits with availability "any" are considered.
|
||||
if(!must_have_only && (!can_recruit() || avl == "any")) {
|
||||
candidate_traits.push_back(&t);
|
||||
}
|
||||
}
|
||||
// No traits available anymore? Break
|
||||
if(candidate_traits.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(must_have_only) return;
|
||||
|
||||
// Now randomly fill out to the number of traits required or until
|
||||
// there aren't any more traits.
|
||||
int nb_traits = current_traits.size();
|
||||
int max_traits = u_type.num_traits();
|
||||
for(; nb_traits < max_traits && !candidate_traits.empty(); ++nb_traits)
|
||||
{
|
||||
int num = randomness::generator->get_random_int(0,candidate_traits.size()-1);
|
||||
modifications_.add_child("trait", *candidate_traits[num]);
|
||||
candidate_traits.erase(candidate_traits.begin() + num);
|
||||
}
|
||||
|
||||
// Once random traits are added, don't do it again.
|
||||
// Such as when restoring a saved character.
|
||||
random_traits_ = false;
|
||||
|
|
|
@ -309,6 +309,8 @@
|
|||
0 unslowable_status_test
|
||||
0 unpetrifiable_status_test
|
||||
0 test_force_chance_to_hit_macro
|
||||
0 trait_exclusion_test
|
||||
0 trait_requirement_test
|
||||
0 swarms_filter_student_by_type
|
||||
0 swarms_effects_not_checkable
|
||||
0 filter_special_id_active
|
||||
|
|
Loading…
Add table
Reference in a new issue