Skip to content

Commit

Permalink
Mongroup: use parent pack_size to select # of subentries + unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
dseguin committed Jul 18, 2022
1 parent 734c94b commit 3679297
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 79 deletions.
15 changes: 15 additions & 0 deletions data/mods/TEST_DATA/monstergroups.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,20 @@
{ "monster": "mon_test_CBM", "weight": 5 },
{ "monster": "mon_test_bovine", "weight": 5 }
]
},
{
"name": "test_nested_packsize",
"type": "monstergroup",
"monsters": [ { "monster": "mon_test_CBM", "pack_size": [ 2, 4 ] } ]
},
{
"name": "test_top_level_packsize",
"type": "monstergroup",
"monsters": [ { "group": "test_nested_packsize", "pack_size": [ 4, 6 ] } ]
},
{
"name": "test_top_level_no_packsize",
"type": "monstergroup",
"monsters": [ { "group": "test_nested_packsize" } ]
}
]
13 changes: 8 additions & 5 deletions src/explosion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,6 @@ void resonance_cascade( const tripoint &p )
Character &player_character = get_player_character();
const time_duration maxglow = time_duration::from_turns( 100 - 5 * trig_dist( p,
player_character.pos() ) );
MonsterGroupResult spawn_details;
if( maxglow > 0_turns ) {
const time_duration minglow = std::max( 0_turns, time_duration::from_turns( 60 - 5 * trig_dist( p,
player_character.pos() ) ) );
Expand Down Expand Up @@ -846,10 +845,14 @@ void resonance_cascade( const tripoint &p )
break;
case 13:
case 14:
case 15:
spawn_details = MonsterGroupManager::GetResultFromGroup( GROUP_NETHER );
g->place_critter_at( spawn_details.name, dest );
break;
case 15: {
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( GROUP_NETHER );
for( const MonsterGroupResult &mgr : spawn_details ) {
g->place_critter_at( mgr.name, dest );
}
}
break;
case 16:
case 17:
case 18:
Expand Down
31 changes: 18 additions & 13 deletions src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7518,8 +7518,10 @@ void map::rotten_item_spawn( const item &item, const tripoint &pnt )
}

if( rng( 0, 100 ) < comest->rot_spawn_chance ) {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( mgroup );
add_spawn( spawn_details, pnt );
std::vector<MonsterGroupResult> spawn_details = MonsterGroupManager::GetResultFromGroup( mgroup );
for( const MonsterGroupResult &mgr : spawn_details ) {
add_spawn( mgr, pnt );
}
if( get_player_view().sees( pnt ) ) {
if( item.is_seed() ) {
add_msg( m_warning, _( "Something has crawled out of the %s plants!" ), item.get_plant_name() );
Expand Down Expand Up @@ -8019,18 +8021,21 @@ void map::spawn_monsters_submap_group( const tripoint &gp, mongroup &group,
if( pop ) {
// Populate the group from its population variable.
for( int m = 0; m < pop; m++ ) {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( group.type, &pop );
if( !spawn_details.name ) {
continue;
}
monster tmp( spawn_details.name );
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( group.type, &pop );
for( const MonsterGroupResult &mgr : spawn_details ) {
if( !mgr.name ) {
continue;
}
monster tmp( mgr.name );

// If a monster came from a horde population, configure them to always be willing to rejoin a horde.
if( group.horde ) {
tmp.set_horde_attraction( MHA_ALWAYS );
}
for( int i = 0; i < spawn_details.pack_size; i++ ) {
group.monsters.push_back( tmp );
// If a monster came from a horde population, configure them to always be willing to rejoin a horde.
if( group.horde ) {
tmp.set_horde_attraction( MHA_ALWAYS );
}
for( int i = 0; i < mgr.pack_size; i++ ) {
group.monsters.push_back( tmp );
}
}
}
}
Expand Down
24 changes: 13 additions & 11 deletions src/map_field.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -742,17 +742,19 @@ static void field_processor_monster_spawn( const tripoint &p, field_entry &cur,
int monster_spawn_count = int_level.monster_spawn_count;
if( monster_spawn_count > 0 && monster_spawn_chance > 0 && one_in( monster_spawn_chance ) ) {
for( ; monster_spawn_count > 0; monster_spawn_count-- ) {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup(
int_level.monster_spawn_group, &monster_spawn_count );
if( !spawn_details.name ) {
continue;
}
if( const cata::optional<tripoint> spawn_point = random_point(
points_in_radius( p, int_level.monster_spawn_radius ),
[&pd]( const tripoint & n ) {
return pd.here.passable( n );
} ) ) {
pd.here.add_spawn( spawn_details, *spawn_point );
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( int_level.monster_spawn_group, &monster_spawn_count );
for( const MonsterGroupResult &mgr : spawn_details ) {
if( !mgr.name ) {
continue;
}
if( const cata::optional<tripoint> spawn_point =
random_point( points_in_radius( p, int_level.monster_spawn_radius ),
[&pd]( const tripoint & n ) {
return pd.here.passable( n );
} ) ) {
pd.here.add_spawn( mgr, *spawn_point );
}
}
}
}
Expand Down
40 changes: 24 additions & 16 deletions src/mapgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,15 +267,18 @@ void map::generate( const tripoint &p, const time_point &when )
if( spawns.group && x_in_y( odds_after_density, 100 ) ) {
int pop = spawn_count * rng( spawns.population.min, spawns.population.max );
for( ; pop > 0; pop-- ) {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( spawns.group, &pop );
if( !spawn_details.name ) {
continue;
}
if( const cata::optional<tripoint> pt =
random_point( *this, [this]( const tripoint & n ) {
return passable( n );
} ) ) {
add_spawn( spawn_details, *pt );
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( spawns.group, &pop );
for( const MonsterGroupResult &mgr : spawn_details ) {
if( !mgr.name ) {
continue;
}
if( const cata::optional<tripoint> pt =
random_point( *this, [this]( const tripoint & n ) {
return passable( n );
} ) ) {
add_spawn( mgr, *pt );
}
}
}
}
Expand Down Expand Up @@ -2355,11 +2358,13 @@ class jmapgen_monster : public jmapgen_piece

mongroup_id chosen_group = m_id.get( dat );
if( !chosen_group.is_null() ) {
MonsterGroupResult spawn_details =
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( chosen_group );
dat.m.add_spawn( spawn_details.name, spawn_count * pack_size.get(),
{ x.get(), y.get(), dat.m.get_abs_sub().z() },
friendly, -1, mission_id, name, data );
for( const MonsterGroupResult &mgr : spawn_details ) {
dat.m.add_spawn( mgr.name, spawn_count * pack_size.get(),
{ x.get(), y.get(), dat.m.get_abs_sub().z() },
friendly, -1, mission_id, name, data );
}
} else {
mtype_id chosen_type = ids.pick()->get( dat );
if( !chosen_type.is_null() ) {
Expand Down Expand Up @@ -6433,9 +6438,12 @@ void map::place_spawns( const mongroup_id &group, const int chance,
} while( impassable( p ) && tries > 0 );

// Pick a monster type
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( group, &num );
add_spawn( spawn_details.name, spawn_details.pack_size, { p, abs_sub.z() },
friendly, -1, mission_id, name, spawn_details.data );
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( group, &num );
for( const MonsterGroupResult &mgr : spawn_details ) {
add_spawn( mgr.name, mgr.pack_size, { p, abs_sub.z() },
friendly, -1, mission_id, name, mgr.data );
}
}
}

Expand Down
44 changes: 23 additions & 21 deletions src/mongroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ const MonsterGroup &MonsterGroupManager::GetUpgradedMonsterGroup( const mongroup
}

//Quantity is adjusted directly as a side effect of this function
MonsterGroupResult MonsterGroupManager::GetResultFromGroup(
std::vector<MonsterGroupResult> MonsterGroupManager::GetResultFromGroup(
const mongroup_id &group_name, int *quantity, bool *mon_found, bool from_subgroup )
{
const MonsterGroup &group = GetUpgradedMonsterGroup( group_name );
int spawn_chance = rng( 1, group.event_adjusted_freq_total() );
//Our spawn details specify, by default, a single instance of the default monster
MonsterGroupResult spawn_details = MonsterGroupResult( group.defaultMonster, 1, spawn_data() );
std::vector<MonsterGroupResult> spawn_details;

bool monster_found = false;
// Loop invariant values
Expand All @@ -131,15 +131,6 @@ MonsterGroupResult MonsterGroupManager::GetResultFromGroup(
valid_entry = false;
}

// Check for monsters within subgroup
bool found_in_subgroup = false;
int tmp_qty = !!quantity ? *quantity : 1;
MonsterGroupResult tmp_grp( group.defaultMonster, 1, spawn_data() );
if( valid_entry && it->is_group() ) {
tmp_grp = GetResultFromGroup( it->group, !!quantity ? &tmp_qty : nullptr,
&found_in_subgroup, true );
}

//Insure that the time is not before the spawn first appears or after it stops appearing
valid_entry = valid_entry && ( calendar::start_of_cataclysm + it->starts < calendar::turn );
valid_entry = valid_entry && ( it->lasts_forever() ||
Expand Down Expand Up @@ -197,26 +188,33 @@ MonsterGroupResult MonsterGroupManager::GetResultFromGroup(
valid_entry = false;
}

const int pack_size = it->pack_maximum > 1 ? rng( it->pack_minimum, it->pack_maximum ) : 1;

// Check for monsters within subgroup
bool found_in_subgroup = false;
int tmp_qty = !!quantity ? *quantity : 1;
std::vector<MonsterGroupResult> tmp_grp_list;
if( valid_entry && it->is_group() ) {
for( int i = 0; i < pack_size; i++ ) {
std::vector<MonsterGroupResult> tmp_grp =
GetResultFromGroup( it->group, !!quantity ? &tmp_qty : nullptr, &found_in_subgroup, true );
tmp_grp_list.insert( tmp_grp_list.end(), tmp_grp.begin(), tmp_grp.end() );
}
}

//If the entry was valid, check to see if we actually spawn it
if( valid_entry ) {
//If the monsters frequency is greater than the spawn_chance, select this spawn rule
if( it->frequency >= spawn_chance ) {
if( found_in_subgroup ) {
//If spawned from a subgroup, we've already obtained that data
spawn_details = std::move( tmp_grp );
if( !!quantity ) {
( *quantity ) = tmp_qty;
}
spawn_details = tmp_grp_list;
} else {
if( it->pack_maximum > 1 ) {
spawn_details = MonsterGroupResult( it->name, rng( it->pack_minimum, it->pack_maximum ), it->data );
} else {
spawn_details = MonsterGroupResult( it->name, 1, it->data );
}
spawn_details.emplace_back( MonsterGroupResult( it->name, pack_size, it->data ) );
//And if a quantity pointer with remaining value was passed, will modify the external value as a side effect
//We will reduce it by the spawn rule's cost multiplier
if( quantity ) {
*quantity -= std::max( 1, it->cost_multiplier * spawn_details.pack_size );
*quantity -= std::max( 1, it->cost_multiplier * pack_size );
}
}
monster_found = true;
Expand All @@ -235,6 +233,10 @@ MonsterGroupResult MonsterGroupManager::GetResultFromGroup(
( *mon_found ) = monster_found;
}

if( !from_subgroup && spawn_details.empty() ) {
spawn_details.emplace_back( MonsterGroupResult( group.defaultMonster, 1, spawn_data() ) );
}

return spawn_details;
}

Expand Down
4 changes: 2 additions & 2 deletions src/mongroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ class MonsterGroupManager
static void LoadMonsterBlacklist( const JsonObject &jo );
static void LoadMonsterWhitelist( const JsonObject &jo );
static void FinalizeMonsterGroups();
static MonsterGroupResult GetResultFromGroup( const mongroup_id &group, int *quantity = nullptr,
bool *mon_found = nullptr, bool from_subgroup = false );
static std::vector<MonsterGroupResult> GetResultFromGroup( const mongroup_id &group,
int *quantity = nullptr, bool *mon_found = nullptr, bool from_subgroup = false );
static bool IsMonsterInGroup( const mongroup_id &group, const mtype_id &monster );
static bool isValidMonsterGroup( const mongroup_id &group );
static const mongroup_id &Monster2Group( const mtype_id &monster );
Expand Down
16 changes: 10 additions & 6 deletions src/player_hardcoded_effects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,11 @@ static void eff_fun_teleglow( Character &u, effect &it )
if( here.impassable( dest ) ) {
here.make_rubble( dest, f_rubble_rock, true );
}
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup(
GROUP_NETHER );
g->place_critter_at( spawn_details.name, dest );
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( GROUP_NETHER );
for( const MonsterGroupResult &mgr : spawn_details ) {
g->place_critter_at( mgr.name, dest );
}
if( uistate.distraction_hostile_spotted && player_character.sees( dest ) ) {
g->cancel_activity_or_ignore_query( distraction_type::hostile_spotted_far,
_( "A monster appears nearby!" ) );
Expand Down Expand Up @@ -1300,9 +1302,11 @@ void Character::hardcoded_effects( effect &it )
if( here.impassable( dest ) ) {
here.make_rubble( dest, f_rubble_rock, true );
}
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup(
GROUP_NETHER );
g->place_critter_at( spawn_details.name, dest );
std::vector<MonsterGroupResult> spawn_details =
MonsterGroupManager::GetResultFromGroup( GROUP_NETHER );
for( const MonsterGroupResult &mgr : spawn_details ) {
g->place_critter_at( mgr.name, dest );
}
if( uistate.distraction_hostile_spotted && player_character.sees( dest ) ) {
g->cancel_activity_or_ignore_query( distraction_type::hostile_spotted_far,
_( "A monster appears nearby!" ) );
Expand Down
50 changes: 45 additions & 5 deletions tests/mongroup_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ static void spawn_x_monsters( int x, const mongroup_id &grp, const std::vector<m
std::set<mtype_id> rand_results;
calendar::turn = time_point( 1 );
for( int i = 0; i < x; i++ ) {
const mtype_id &tmp_get = MonsterGroupManager::GetRandomMonsterFromGroup( grp );
mtype_id tmp_get = MonsterGroupManager::GetRandomMonsterFromGroup( grp );
if( !tmp_get.is_null() ) {
rand_gets.emplace( tmp_get );
}

const mtype_id &tmp_res = MonsterGroupManager::GetResultFromGroup( grp ).name;
mtype_id tmp_res = MonsterGroupManager::GetResultFromGroup( grp ).front().name;
if( !tmp_res.is_null() ) {
rand_results.emplace( tmp_res );
}
Expand Down Expand Up @@ -185,9 +185,7 @@ TEST_CASE( "Nested monster groups spawn chance", "[mongroup]" )
calendar::turn += 1_turns;

for( int i = 0; i < iters; i++ ) {
int qty = 1;
bool monfound = false;
MonsterGroupResult res = MonsterGroupManager::GetResultFromGroup( mg, &qty, &monfound );
MonsterGroupResult res = MonsterGroupManager::GetResultFromGroup( mg ).front();
auto iter = results.find( res.name );
CAPTURE( res.name.c_str() );
REQUIRE( iter != results.end() );
Expand All @@ -209,3 +207,45 @@ TEST_CASE( "Nested monster groups spawn chance", "[mongroup]" )
Approx( static_cast<float>( std::get<2>( res.second ) ) / iters ).epsilon( 0.5 ) );
}
}

TEST_CASE( "Nested monster group pack size", "[mongroup]" )
{
const int iters = 100;
calendar::turn += 1_turns;

SECTION( "Nested group pack size used as-is" ) {
mongroup_id mg( "test_top_level_no_packsize" );
for( int i = 0; i < iters; i++ ) {
bool found = false;
std::vector<MonsterGroupResult> res =
MonsterGroupManager::GetResultFromGroup( mg, nullptr, &found );
REQUIRE( found );

// pack_size == [2, 4] * 1
CHECK( res.size() == 1 );
int total = 0;
for( const MonsterGroupResult &mgr : res ) {
total += mgr.pack_size;
}
CHECK( total == Approx( 3 ).margin( 1 ) );
}
}

SECTION( "Nested group pack size multiplied by top level pack size" ) {
mongroup_id mg( "test_top_level_packsize" );
for( int i = 0; i < iters; i++ ) {
bool found = false;
std::vector<MonsterGroupResult> res =
MonsterGroupManager::GetResultFromGroup( mg, nullptr, &found );
REQUIRE( found );

// pack_size == [2, 4] * [4, 6]
CHECK( res.size() == Approx( 5 ).margin( 1 ) );
int total = 0;
for( const MonsterGroupResult &mgr : res ) {
total += mgr.pack_size;
}
CHECK( total == Approx( 16 ).margin( 8 ) );
}
}
}

0 comments on commit 3679297

Please sign in to comment.