Skip to content

Commit

Permalink
Summon several kinds of kittycat from Bag of Cats (#40656)
Browse files Browse the repository at this point in the history
* Add spell flag SPAWN_GROUP to summon monster group

With this flag, a spell with "summon" effect spawns not a specific
monster type ID, but rather a monster type from a monster group ID.

* Add GROUP_STRAY_CATS monster group with cat types

* Make "Bag of Cats" summon a paw-pourri of felines

* Document spell flags and summon effect

* Swap position of damage and range in spell info

This makes the typically shorter strings (Range and AOE) share a line in
two colums, while Damage (or Summon, Spawn, etc.) gets its own line.

* Show "N from GROUP" for group summon spells
  • Loading branch information
wapcaplet authored May 19, 2020
1 parent 8b51286 commit 593791b
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 16 deletions.
29 changes: 29 additions & 0 deletions data/json/monstergroups/mammal.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,35 @@
{ "monster": "mon_dog_auscattle_pup", "freq": 5, "cost_multiplier": 0 }
]
},
{
"name": "GROUP_STRAY_CATS",
"type": "monstergroup",
"default": "mon_cat",
"monsters": [
{ "monster": "mon_cat", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_tabby", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_tabby_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_longhair", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_longhair_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_siamese", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_siamese_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_persian", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_persian_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_calico", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_calico_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_maine_coon", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_maine_coon_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_bengal", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_bengal_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_devon_rex", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_devon_rex_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_sphynx", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_sphynx_kitten", "freq": 5, "cost_multiplier": 0 },
{ "monster": "mon_cat_chonker", "freq": 50, "cost_multiplier": 0 },
{ "monster": "mon_cat_chonker_kitten", "freq": 5, "cost_multiplier": 0 }
]
},
{
"name": "GROUP_PETS",
"type": "monstergroup",
Expand Down
4 changes: 2 additions & 2 deletions data/mods/Magiclysm/Spells/druid.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
"name": { "str": "Bag of Cats" },
"description": "Are you the crazy cat lady?",
"valid_targets": [ "ground" ],
"flags": [ "LOUD", "SOMATIC" ],
"flags": [ "LOUD", "SOMATIC", "SPAWN_GROUP" ],
"min_damage": 1,
"max_damage": 12,
"damage_increment": 1.0,
Expand All @@ -161,7 +161,7 @@
"difficulty": 1,
"base_energy_cost": 265,
"effect": "summon",
"effect_str": "mon_cat"
"effect_str": "GROUP_STRAY_CATS"
},
{
"id": "summon_bear",
Expand Down
49 changes: 45 additions & 4 deletions doc/MAGIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ In `data/mods/Magiclysm` there is a template spell, copied here for your perusal
"description": "This is a template to show off all the available values",
"valid_targets": [ "hostile", "ground", "self", "ally" ], // if a valid target is not included, you cannot cast the spell on that target.
"effect": "shallow_pit", // effects are coded in C++. A list will be provided below of possible effects that have been coded.
"effect_str": "template" // special. see below
"effect_str": "template", // special. see below
"extra_effects": [ { "id": "fireball", "hit_self": false, "max_level": 3 } ], // this allows you to cast multiple spells with only one spell
"affected_body_parts": [ "HEAD", "TORSO", "MOUTH", "EYES", "ARM_L", "ARM_R", "HAND_R", "HAND_L", "LEG_L", "FOOT_L", "FOOT_R" ], // body parts affected by effects
"spell_class": "NONE" //
"flags": [ "SILENT", "LOUD", "SOMATIC", "VERBAL", "NO_HANDS", "NO_LEGS", "SPAWN_GROUP" ], // see "Spell Flags" below
"spell_class": "NONE", //
"base_casting_time": 100, // this is the casting time (in moves)
"base_energy_cost": 10, // the amount of energy (of the requisite type) to cast the spell
"energy_source": "MANA", // the type of energy used to cast the spell. types are: MANA, BIONIC, HP, STAMINA, FATIGUE, NONE (none will not use mana)
Expand Down Expand Up @@ -100,6 +101,7 @@ Below is a table of currently implemented effects, along with special rules for
| `cone_attack` | fires a cone toward the target up to your range. The arc of the cone in degrees is aoe. Stops at walls. If "effect_str" is included, it will add that effect (defined elsewhere in json) to the targets if able, to the body parts defined in affected_body_parts.
| `line_attack` | fires a line with width aoe toward the target, being blocked by walls on the way. If "effect_str" is included, it will add that effect (defined elsewhere in json) to the targets if able, to the body parts defined in affected_body_parts.
| `spawn_item` | spawns an item that will disappear at the end of its duration. Default duration is 0.
| `summon` | summons a monster ID or group ID from `effect_str` that will disappear at the end of its duration. Default duration is 0.
| `teleport_random` | teleports the player randomly range spaces with aoe variation
| `recover_energy` | recovers an energy source equal to damage of the spell. The energy source recovered is defined in "effect_str" and may be one of "MANA", "STAMINA", "FATIGUE", "PAIN", "BIONIC"
| `ter_transform` | transform the terrain and furniture in an area centered at the target. The chance of any one of the points in the area of effect changing is one_in( damage ). The effect_str is the id of a ter_furn_transform.
Expand All @@ -113,8 +115,47 @@ Below is a table of currently implemented effects, along with special rules for
| `charm_monster` | charms a monster that has less hp than damage() for approximately duration()
| `mutate` | mutates the target(s). if effect_str is defined, mutates toward that category instead of picking at random. the "MUTATE_TRAIT" flag allows effect_str to be a specific trait instead of a category. damage() / 100 is the percent chance the mutation will be successful (a value of 10000 represents 100.00%)
| `bash` | bashes the terrain at the target. uses damage() as the strength of the bash.
| `WONDER` | Unlike the above, this is not an "effect" but a "flag". This alters the behavior of the parent spell drastically: The spell itself doesn't cast, but its damage and range information is used in order to cast the extra_effects. N of the extra_effects will be chosen at random to be cast, where N is the current damage of the spell (stacks with RANDOM_DAMAGE flag) and the message of the spell cast by this spell will also be displayed. If this spell's message is not wanted to be displayed, make sure the message is an empty string.
| `RANDOM_TARGET` | A special spell flag (like wonder) that forces the spell to choose a random valid target within range instead of the caster choosing the target. This also affects extra_effects.

### Spell Flags

Flags allow you to provide additional customizations for spell effects, behavior, and limitations.
Spells may have any number of flags, for example:

```json
{
"id": "bless",
"//": "Encumbrance on the mouth (verbal) or arms (somatic) affect casting success, but not legs.",
"flags": [ "VERBAL", "SOMATIC", "NO_LEGS" ]
}
```

| Flag | Description
| --- | ---
| `WONDER` | This alters the behavior of the parent spell drastically: The spell itself doesn't cast, but its damage and range information is used in order to cast the extra_effects. N of the extra_effects will be chosen at random to be cast, where N is the current damage of the spell (stacks with RANDOM_DAMAGE flag) and the message of the spell cast by this spell will also be displayed. If this spell's message is not wanted to be displayed, make sure the message is an empty string.
| `RANDOM_TARGET` | Forces the spell to choose a random valid target within range instead of the caster choosing the target. This also affects extra_effects.
| `RANDOM_DURATION` | picks random number between min+increment*level and max instead of normal behavior
| `RANDOM_DAMAGE` | picks random number between min+increment*level and max instead of normal behavior
| `RANDOM_AOE` | picks random number between min+increment*level and max instead of normal behavior
| `PERMANENT` | items or creatures spawned with this spell do not disappear and die as normal
| `IGNORE_WALLS` | spell's aoe goes through walls
| `SWAP_POS` | a projectile spell swaps the positions of the caster and target
| `HOSTILE_SUMMON` | summon spell always spawns a hostile monster
| `HOSTILE_50` | summoned monster spawns friendly 50% of the time
| `SILENT` | spell makes no noise at target
| `LOUD` | spell makes extra noise at target
| `VERBAL` | spell makes noise at caster location, mouth encumbrance affects fail %
| `SOMATIC` | arm encumbrance affects fail % and casting time (slightly)
| `NO_HANDS` | hands do not affect spell energy cost
| `NO_LEGS` | legs do not affect casting time
| `CONCENTRATE` | focus affects spell fail %
| `MUTATE_TRAIT` | overrides the mutate spell_effect to use a specific trait_id instead of a category
| `PAIN_NORESIST` | pain altering spells can't be resisted (like with the deadened trait)
| `WITH_CONTAINER` | items spawned with container
| `UNSAFE_TELEPORT` | teleport spell risks killing the caster or others
| `SPAWN_GROUP` | spawn or summon from an item or monster group, instead of individual item/monster ID


### Damage Types

For Spells that have an attack type, these are the available damage types:

Expand Down
27 changes: 22 additions & 5 deletions src/magic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "magic_enchantment.h"
#include "map.h"
#include "messages.h"
#include "mongroup.h"
#include "monster.h"
#include "mtype.h"
#include "mutation.h"
Expand Down Expand Up @@ -116,6 +117,7 @@ std::string enum_to_string<spell_flag>( spell_flag data )
case spell_flag::MUTATE_TRAIT: return "MUTATE_TRAIT";
case spell_flag::PAIN_NORESIST: return "PAIN_NORESIST";
case spell_flag::WITH_CONTAINER: return "WITH_CONTAINER";
case spell_flag::SPAWN_GROUP: return "SPAWN_GROUP";
case spell_flag::WONDER: return "WONDER";
case spell_flag::LAST: break;
}
Expand Down Expand Up @@ -1728,6 +1730,7 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu

const int damage = sp.damage();
std::string damage_string;
std::string range_string;
std::string aoe_string;
// if it's any type of attack spell, the stats are normal.
if( fx == "target_attack" || fx == "projectile_attack" || fx == "cone_attack" ||
Expand Down Expand Up @@ -1764,18 +1767,32 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu
damage_string = string_format( "%s %d %s", _( "Spawn" ), sp.damage(), item::nname( sp.effect_data(),
sp.damage() ) );
} else if( fx == "summon" ) {
damage_string = string_format( "%s %d %s", _( "Summon" ), sp.damage(),
_( monster( mtype_id( sp.effect_data() ) ).get_name( ) ) );
std::string monster_name = "FIXME";
if( sp.has_flag( spell_flag::SPAWN_GROUP ) ) {
// TODO: Get a more user-friendly group name
if( MonsterGroupManager::isValidMonsterGroup( mongroup_id( sp.effect_data() ) ) ) {
monster_name = "from " + sp.effect_data();
} else {
debugmsg( "Unknown monster group: %s", sp.effect_data() );
}
} else {
monster_name = monster( mtype_id( sp.effect_data() ) ).get_name( );
}
damage_string = string_format( "%s %d %s", _( "Summon" ), sp.damage(), _( monster_name ) );
aoe_string = string_format( "%s: %d", _( "Spell Radius" ), sp.aoe() );
} else if( fx == "ter_transform" ) {
aoe_string = string_format( "%s: %s", _( "Spell Radius" ), sp.aoe_string() );
}

print_colored_text( w_menu, point( h_col1, line ), gray, gray, damage_string );
range_string = string_format( "%s: %s", _( "Range" ),
sp.range() <= 0 ? _( "self" ) : to_string( sp.range() ) );

// Range / AOE in two columns
print_colored_text( w_menu, point( h_col1, line ), gray, gray, range_string );
print_colored_text( w_menu, point( h_col2, line++ ), gray, gray, aoe_string );

print_colored_text( w_menu, point( h_col1, line++ ), gray, gray,
string_format( "%s: %s", _( "Range" ), sp.range() <= 0 ? _( "self" ) : to_string( sp.range() ) ) );
// One line for damage / healing / spawn / summon effect
print_colored_text( w_menu, point( h_col1, line++ ), gray, gray, damage_string );

// todo: damage over time here, when it gets implemeted

Expand Down
1 change: 1 addition & 0 deletions src/magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum spell_flag {
WONDER, // instead of casting each of the extra_spells, it picks N of them and casts them (where N is std::min( damage(), number_of_spells ))
PAIN_NORESIST, // pain altering spells can't be resisted (like with the deadened trait)
WITH_CONTAINER, // items spawned with container
SPAWN_GROUP, // spawn or summon from an item or monster group, instead of individual item/monster ID
LAST
};

Expand Down
18 changes: 13 additions & 5 deletions src/magic_spell_effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,10 +783,19 @@ static bool is_summon_friendly( const spell &sp )
return friendly;
}

static bool add_summoned_mon( const mtype_id &id, const tripoint &pos, const time_duration &time,
const spell &sp )
static bool add_summoned_mon( const tripoint &pos, const time_duration &time, const spell &sp )
{
monster *const mon_ptr = g->place_critter_at( id, pos );
std::string monster_id = sp.effect_data();

// Spawn a monster from a group, or a specific monster ID
if( sp.has_flag( spell_flag::SPAWN_GROUP ) ) {
const mongroup_id group_id( sp.effect_data() );
monster_id = MonsterGroupManager::GetRandomMonsterFromGroup( group_id ).str();
}

const mtype_id mon_id( monster_id );
monster *const mon_ptr = g->place_critter_at( mon_id, pos );

if( !mon_ptr ) {
return false;
}
Expand All @@ -807,7 +816,6 @@ static bool add_summoned_mon( const mtype_id &id, const tripoint &pos, const tim
void spell_effect::spawn_summoned_monster( const spell &sp, Creature &caster,
const tripoint &target )
{
const mtype_id mon_id( sp.effect_data() );
std::set<tripoint> area = spell_effect_area( sp, target, spell_effect_blast, caster );
// this should never be negative, but this'll keep problems from happening
size_t num_mons = std::abs( sp.damage() );
Expand All @@ -816,7 +824,7 @@ void spell_effect::spawn_summoned_monster( const spell &sp, Creature &caster,
const size_t mon_spot = rng( 0, area.size() - 1 );
auto iter = area.begin();
std::advance( iter, mon_spot );
if( add_summoned_mon( mon_id, *iter, summon_time, sp ) ) {
if( add_summoned_mon( *iter, summon_time, sp ) ) {
num_mons--;
sp.make_sound( *iter );
} else {
Expand Down

0 comments on commit 593791b

Please sign in to comment.