Skip to content

Commit ce479ad

Browse files
committed
Special attacks to optional
This requires a really gross reader because there's two interrelated bits of data spread across two different variables, and the data definition is complex, so needs some special handling for delete. The loading of the array syntax is gross too. But the bugs being fixed shows it is an improvement :)
1 parent ea4e304 commit ce479ad

File tree

9 files changed

+96
-110
lines changed

9 files changed

+96
-110
lines changed

data/json/monsters/insect_spider.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2370,7 +2370,7 @@
23702370
"weakpoints": [ { "id": "abdomen", "name": "the abdomen", "coverage": 30 } ],
23712371
"vision_night": 8,
23722372
"zombify_into": "mon_meat_cocoon_tiny",
2373-
"delete": { "weakpoints": [ { "id": "stinger" } ], "flags": [ "FLIES" ], "special_attacks": [ "DERMATIK" ] }
2373+
"delete": { "weakpoints": [ { "id": "stinger" } ], "flags": [ "FLIES" ], "special_attacks": [ "dermatik_impale" ] }
23742374
},
23752375
{
23762376
"id": "mon_dermatik_incubator_deer",

data/json/monsters/zed_nomex.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"families": [ "prof_wp_syn_armored", "prof_wp_nat_armored" ],
3333
"flags": [ "ACIDPROOF", "FIREPROOF" ]
3434
},
35-
"delete": { "special_attacks": [ "bite_humanoid" ] }
35+
"delete": { "special_attacks": [ "bite" ] }
3636
},
3737
{
3838
"id": "mon_zombie_fireproof_grappler",

data/mods/Xedra_Evolved/monsters/zombies.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"corpse_type": "NO_CORPSE"
9797
},
9898
"emit_fields": [ { "emit_id": "xedra_monochrome_boomer_frozen_time", "delay": "1 s" } ],
99-
"delete": { "special_attacks": [ "BOOMER" ] },
99+
"delete": { "special_attacks": [ "boomer_bile" ] },
100100
"extend": { "flags": [ "STABILIZED_TIMELINE" ] },
101101
"upgrades": false
102102
}

src/mattack_actors.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ static const skill_id skill_throw( "throw" );
9393
static const trait_id trait_TOXICFLESH( "TOXICFLESH" );
9494
static const trait_id trait_VAMPIRE( "VAMPIRE" );
9595

96+
bool invalid_mattack_actor::call( monster &m ) const
97+
{
98+
debugmsg( "%s has invalid mattack actor definition!", m.type->id.str() );
99+
return false;
100+
}
101+
102+
std::unique_ptr<mattack_actor> invalid_mattack_actor::clone() const
103+
{
104+
return std::make_unique<invalid_mattack_actor>( *this );
105+
}
106+
96107
void leap_actor::load_internal( const JsonObject &obj, const std::string & )
97108
{
98109
// Mandatory:

src/mattack_actors.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ class Creature;
2222
class JsonObject;
2323
class monster;
2424

25+
class invalid_mattack_actor : public mattack_actor
26+
{
27+
bool call( monster & ) const override;
28+
std::unique_ptr<mattack_actor> clone() const override;
29+
void load_internal( const JsonObject &, const std::string & ) override {}
30+
};
31+
2532
class leap_actor : public mattack_actor
2633
{
2734
public:

src/mattack_common.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class mattack_actor
4444
struct mtype_special_attack {
4545
protected:
4646
// TODO: Remove friend
47-
friend struct mtype;
47+
friend struct special_attacks_reader;
4848
cata::clone_ptr<mattack_actor> actor;
4949

5050
public:

src/monstergenerator.cpp

Lines changed: 73 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,68 @@ void revive_type::deserialize( const JsonObject &jo )
762762
}
763763
}
764764

765+
// this is a really gross reader - special_attacks and special_attacks_names really need to be one struct
766+
// Then this gets a lot less gross and a lot more safe!
767+
struct special_attacks_reader : generic_typed_reader<special_attacks_reader> {
768+
friend class MonsterGenerator;
769+
// For ordering purposes, we want to also grab the names when we load a special attack
770+
std::vector<std::string> &names;
771+
772+
// special attacks load with src
773+
std::string_view src;
774+
mtype_id id;
775+
776+
special_attacks_reader( std::vector<std::string> &name_vec, std::string_view _src,
777+
mtype_id mtype ) : names( name_vec ), src( _src ), id( mtype ) {}
778+
779+
void report_double_def( const std::string &name, const JsonValue &jv ) const {
780+
if( std::find( names.begin(), names.end(), name ) != names.end() ) {
781+
jv.throw_error(
782+
string_format( "%s specifies more than one attack of (sub)type %s, ignoring all but the last. Add different `id`s to each attack of this type to prevent this.",
783+
id.c_str(), name ) );
784+
}
785+
}
786+
787+
std::pair<std::string, mtype_special_attack> get_next( const JsonValue &jv ) const {
788+
MonsterGenerator &gen = MonsterGenerator::generator();
789+
// only for delete
790+
if( jv.test_string() ) {
791+
std::string name = jv.get_string();
792+
auto it = std::find( names.begin(), names.end(), name );
793+
if( it == names.end() ) {
794+
jv.throw_error(
795+
string_format( "Invalid special attack format or does not exist to delete for %s on %s",
796+
name, id.str() ) );
797+
}
798+
names.erase( it );
799+
return std::make_pair( name, mtype_special_attack( std::make_unique<invalid_mattack_actor>() ) );
800+
}
801+
if( jv.test_object() ) {
802+
mtype_special_attack new_attack = gen.create_actor( jv.get_object(), std::string( src ) );
803+
report_double_def( new_attack->id, jv );
804+
names.push_back( new_attack->id );
805+
return std::make_pair( new_attack->id, new_attack );
806+
}
807+
JsonArray inner = jv.get_array();
808+
std::string name = inner.get_string( 0 );
809+
const auto iter = gen.attack_map.find( name );
810+
if( iter == gen.attack_map.end() ) {
811+
inner.throw_error( "Invalid special_attacks" );
812+
}
813+
report_double_def( name, jv );
814+
815+
mtype_special_attack new_attack = mtype_special_attack( iter->second );
816+
if( inner.has_array( 1 ) ) {
817+
new_attack.actor->cooldown.min = get_dbl_or_var_part( inner.get_array( 1 )[0] );
818+
new_attack.actor->cooldown.max = get_dbl_or_var_part( inner.get_array( 1 )[1] );
819+
} else {
820+
new_attack.actor->cooldown.min = get_dbl_or_var_part( inner[1] );
821+
}
822+
names.push_back( name );
823+
return std::make_pair( name, new_attack );
824+
}
825+
};
826+
765827
void mtype::load( const JsonObject &jo, const std::string &src )
766828
{
767829
MonsterGenerator &gen = MonsterGenerator::generator();
@@ -958,24 +1020,18 @@ void mtype::load( const JsonObject &jo, const std::string &src )
9581020
def_chance = 0;
9591021
}
9601022

961-
if( !was_loaded || jo.has_member( "special_attacks" ) ) {
962-
special_attacks.clear();
1023+
// this is really gross, and special_attacks + special_attacks_names should be combined into one struct
1024+
if( jo.has_member( "special_attacks" ) ) {
9631025
special_attacks_names.clear();
964-
add_special_attacks( jo, "special_attacks", src );
965-
} else {
966-
// Note: special_attacks left as is, new attacks are added to it!
967-
// Note: member name prefixes are compatible with those used by generic_typed_reader
968-
if( jo.has_object( "extend" ) ) {
969-
JsonObject tmp = jo.get_object( "extend" );
970-
tmp.allow_omitted_members();
971-
add_special_attacks( tmp, "special_attacks", src );
972-
}
973-
if( jo.has_object( "delete" ) ) {
974-
JsonObject tmp = jo.get_object( "delete" );
975-
tmp.allow_omitted_members();
976-
remove_special_attacks( tmp, "special_attacks", src );
1026+
}
1027+
optional( jo, was_loaded, "special_attacks", special_attacks, special_attacks_reader{special_attacks_names, src, id} );
1028+
// to be extra safe
1029+
for( const std::pair<const std::string, mtype_special_attack> &atk : special_attacks ) {
1030+
if( dynamic_cast<const invalid_mattack_actor *>( atk.second.get() ) != nullptr ) {
1031+
jo.throw_error( string_format( "Invalid special attack format for %s", atk.first ) );
9771032
}
9781033
}
1034+
9791035
optional( jo, was_loaded, "melee_training_cap", melee_training_cap, std::min( melee_skill + 2,
9801036
MAX_SKILL ) );
9811037
optional( jo, was_loaded, "chat_topics", chat_topics );
@@ -1326,6 +1382,8 @@ mtype_special_attack MonsterGenerator::create_actor( const JsonObject &obj,
13261382
new_attack = std::make_unique<gun_actor>();
13271383
} else if( attack_type == "spell" ) {
13281384
new_attack = std::make_unique<mon_spellcasting_actor>();
1385+
} else if( attack_type == "invalid" ) {
1386+
new_attack = std::make_unique<invalid_mattack_actor>();
13291387
} else {
13301388
obj.throw_error_at( "attack_type", "unknown monster attack" );
13311389
}
@@ -1356,87 +1414,6 @@ void MonsterGenerator::load_monster_attack( const JsonObject &jo, const std::str
13561414
add_attack( create_actor( jo, src ) );
13571415
}
13581416

1359-
void mtype::add_special_attack( const JsonObject &obj, const std::string &src )
1360-
{
1361-
mtype_special_attack new_attack = MonsterGenerator::generator().create_actor( obj, src );
1362-
1363-
if( special_attacks.count( new_attack->id ) > 0 ) {
1364-
special_attacks.erase( new_attack->id );
1365-
const auto iter = std::find( special_attacks_names.begin(), special_attacks_names.end(),
1366-
new_attack->id );
1367-
if( iter != special_attacks_names.end() ) {
1368-
special_attacks_names.erase( iter );
1369-
}
1370-
debugmsg( "%s specifies more than one attack of (sub)type %s, ignoring all but the last. Add different `id`s to each attack of this type to prevent this.",
1371-
id.c_str(), new_attack->id.c_str() );
1372-
}
1373-
1374-
special_attacks.emplace( new_attack->id, new_attack );
1375-
special_attacks_names.push_back( new_attack->id );
1376-
}
1377-
1378-
void mtype::add_special_attack( const JsonArray &inner, std::string_view )
1379-
{
1380-
MonsterGenerator &gen = MonsterGenerator::generator();
1381-
const std::string name = inner.get_string( 0 );
1382-
const auto iter = gen.attack_map.find( name );
1383-
if( iter == gen.attack_map.end() ) {
1384-
inner.throw_error( "Invalid special_attacks" );
1385-
}
1386-
1387-
if( special_attacks.count( name ) > 0 ) {
1388-
special_attacks.erase( name );
1389-
const auto iter = std::find( special_attacks_names.begin(), special_attacks_names.end(), name );
1390-
if( iter != special_attacks_names.end() ) {
1391-
special_attacks_names.erase( iter );
1392-
}
1393-
if( test_mode ) {
1394-
debugmsg( "%s specifies more than one attack of (sub)type %s, ignoring all but the last",
1395-
id.c_str(), name );
1396-
}
1397-
}
1398-
mtype_special_attack new_attack = mtype_special_attack( iter->second );
1399-
if( inner.has_array( 1 ) ) {
1400-
new_attack.actor->cooldown.min = get_dbl_or_var_part( inner.get_array( 1 )[0] );
1401-
new_attack.actor->cooldown.max = get_dbl_or_var_part( inner.get_array( 1 )[1] );
1402-
} else {
1403-
new_attack.actor->cooldown.min = get_dbl_or_var_part( inner[1] );
1404-
}
1405-
special_attacks.emplace( name, new_attack );
1406-
special_attacks_names.push_back( name );
1407-
}
1408-
1409-
void mtype::add_special_attacks( const JsonObject &jo, std::string_view member,
1410-
const std::string &src )
1411-
{
1412-
1413-
if( !jo.has_array( member ) ) {
1414-
return;
1415-
}
1416-
1417-
for( const JsonValue entry : jo.get_array( member ) ) {
1418-
if( entry.test_array() ) {
1419-
add_special_attack( entry.get_array(), src );
1420-
} else if( entry.test_object() ) {
1421-
add_special_attack( entry.get_object(), src );
1422-
} else {
1423-
entry.throw_error( "array element is neither array nor object." );
1424-
}
1425-
}
1426-
}
1427-
1428-
void mtype::remove_special_attacks( const JsonObject &jo, std::string_view member_name,
1429-
std::string_view )
1430-
{
1431-
for( const std::string &name : jo.get_tags( member_name ) ) {
1432-
special_attacks.erase( name );
1433-
const auto iter = std::find( special_attacks_names.begin(), special_attacks_names.end(), name );
1434-
if( iter != special_attacks_names.end() ) {
1435-
special_attacks_names.erase( iter );
1436-
}
1437-
}
1438-
}
1439-
14401417
void MonsterGenerator::check_monster_definitions() const
14411418
{
14421419
for( const mtype &mon : mon_templates->get_all() ) {

src/monstergenerator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class MonsterGenerator
8282
const std::vector<mtype> &get_all_mtypes() const;
8383
mtype_id get_valid_hallucination() const;
8484
friend struct mtype;
85+
friend struct special_attacks_reader;
8586
friend struct species_type;
8687
friend class mattack_actor;
8788

src/mtype.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -606,16 +606,6 @@ struct mtype {
606606

607607
// Historically located in monstergenerator.cpp
608608
void load( const JsonObject &jo, const std::string &src );
609-
610-
private:
611-
612-
void add_special_attacks( const JsonObject &jo, std::string_view member_name,
613-
const std::string &src );
614-
void remove_special_attacks( const JsonObject &jo, std::string_view member_name,
615-
std::string_view src );
616-
617-
void add_special_attack( const JsonArray &inner, std::string_view src );
618-
void add_special_attack( const JsonObject &obj, const std::string &src );
619609
};
620610

621611
#endif // CATA_SRC_MTYPE_H

0 commit comments

Comments
 (0)