Skip to content

Commit

Permalink
Mongroup: subgroup spawning fixes (CleverRaven#59281)
Browse files Browse the repository at this point in the history
* Mongroup: do not expand subgroup when spawning

* Mongroup: unit test

* Mongroup: use parent pack_size to select # of subentries + unit test
  • Loading branch information
dseguin authored Jul 19, 2022
1 parent c7089cc commit 2f227d4
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 81 deletions.
41 changes: 41 additions & 0 deletions data/mods/TEST_DATA/monstergroups.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,46 @@
{ "group": "test_event_mongroup", "weight": 10 },
{ "group": "test_event_only", "weight": 5 }
]
},
{
"name": "test_l2_nested_mongroup",
"type": "monstergroup",
"monsters": [
{ "monster": "mon_test_speed_desc_base", "weight": 50 },
{ "monster": "mon_test_speed_desc_base_immobile", "weight": 50 }
]
},
{
"name": "test_l1_nested_mongroup",
"type": "monstergroup",
"monsters": [
{ "group": "test_l2_nested_mongroup", "weight": 5 },
{ "monster": "mon_test_shearable", "weight": 50 },
{ "monster": "mon_test_non_shearable", "weight": 50 }
]
},
{
"name": "test_top_level_mongroup",
"type": "monstergroup",
"monsters": [
{ "group": "test_l1_nested_mongroup", "weight": 5 },
{ "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
56 changes: 31 additions & 25 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(
const mongroup_id &group_name, int *quantity, bool *mon_found )
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,19 +131,6 @@ MonsterGroupResult MonsterGroupManager::GetResultFromGroup(
valid_entry = false;
}

// Check for monsters within subgroup
if( valid_entry && it->is_group() ) {
MonsterGroupResult tmp = GetResultFromGroup( it->group, quantity, &monster_found );
if( monster_found ) {
// Valid monster found within subgroup, break early
spawn_details = tmp;
break;
} else if( quantity ) {
// Nothing found in subgroup, reset quantity
( *quantity )++;
}
continue;
}
//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 @@ -201,19 +188,34 @@ 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( it->pack_maximum > 1 ) {
spawn_details = MonsterGroupResult( it->name, rng( it->pack_minimum, it->pack_maximum ), it->data );
if( found_in_subgroup ) {
//If spawned from a subgroup, we've already obtained that data
spawn_details = tmp_grp_list;
} else {
spawn_details = MonsterGroupResult( it->name, 1, 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 );
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 * pack_size );
}
}
monster_found = true;
//Otherwise, subtract the frequency from spawn result for the next loop around
Expand All @@ -224,13 +226,17 @@ MonsterGroupResult MonsterGroupManager::GetResultFromGroup(
}

// Force quantity to decrement regardless of whether we found a monster.
if( quantity && !monster_found ) {
if( quantity && !monster_found && !from_subgroup ) {
( *quantity )--;
}
if( mon_found ) {
( *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 );
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
Loading

0 comments on commit 2f227d4

Please sign in to comment.