Add exclusive unit traits handling similar to require_amla (#7109)

This commit is contained in:
Kingofd 2023-01-19 19:49:39 +01:00 committed by GitHub
parent a4e17ac867
commit 492c20baa6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 321 additions and 13 deletions

View file

@ -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]

View file

@ -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

View file

@ -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
View 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]

View 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

View 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

View file

@ -806,10 +806,9 @@ 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"];
@ -820,11 +819,9 @@ void unit::generate_traits(bool must_have_only)
break;
}
}
if(already) {
continue;
}
// Add the trait if it is mandatory.
const std::string& avl = t["availability"];
if(avl == "musthave") {
@ -832,27 +829,106 @@ void unit::generate_traits(bool must_have_only)
current_traits = modifications_.child_range("trait");
continue;
}
// 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);
}
}
if(must_have_only) return;
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 && !candidate_traits.empty(); ++nb_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"];
bool already = false;
for(const config& mod : current_traits) {
if(mod["id"] == tid) {
already = true;
break;
}
}
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"]);
// 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;
}
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;

View file

@ -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