mons_spells(), and added the new spells Fire Breath, Cold Breath and Draconian Breath to trigger that functionality. Also added the new spell Acid Splash to replace monstuff's _plant_spit(), and Sticky Flame Splash, which is exactly the same as Sticky Flame except for the messages it gives and when it makes noise (monsters now spit sticky flame instead of breathing it). All things that were handled as monster special abilities are still handled as such, and were just changed to manually invoke mons_cast().
The spell messages in dat/database/monspell.txt can now take advantage of a new substitution, "@target@", which is expanded into the spell's target.
Added the spell flags SPFLAG_INNATE, for monster spells which are innate even when the monster is a priest or wizard, and which can be used by them when silenced, and SPFLAG_NOISY, for spells which produce noise even when used by monsters other than priests or wizards.
Added the monster class flags M_SPELL_NO_SILENT, for monsters which aren't wizards or priests, yet still can't use spells if silenced (currently only used for Geryon blowing his horn to summon beasts), and M_NOISY_SPELLS, for monsters which can cast spells when silenced, yet whose spells make noise when not silenced (currently only used by curse skulls and Murray).
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7828 c06c8d41-db1a-0410-9941-cceddc491573
JWJGOMVBPZRSP2VSHLFFFDIF2CS6UPBA6AHL7DAJWGBCHAV3PJDQC
YCKUKLTWICHK32BNDLUBCJXKQWR6ESYWZIQWSCKTDKMLMOSTFPVQC
4M56FGNV3IDCB7I4H7TMK3EWSQKWEJ5Z2AKIMHED272TOB34DO4QC
FWNNTOEERPUKXPE4OC52UABFZLKIU3O5GRNNLDK4QI4HR2IOU36QC
ZEFGFQHN6J2S6EIPX7EPDG22C5YXTI6DMKQHHRCLWN5MQC44KY3AC
CHO4U5JC3RNTLXVIDXXJYZMOBZJ4VXW2GVJWDOTBRKK3AJ36LDLQC
46MRRHVYJ3BS74R2BEVUWEWJCI4FCRBSLZ3NWMQCE6FUCNE5P73QC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
SUWIERONPDATHPDMZRYO6GYIXSW6XIS5V5MK5IV23DWQH2LL7VIAC
DDU4A3JGN5IUIPP5IASOODKPR2WBHSDSV4FITZ6HNXNSXXQACWAQC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
UAJN2CFA2QHYDHW2UFAVPPHDQFCD54RKM6V2UC4AMEDJUBBLNWIQC
TGJZXTUIAKCFZQJ54ZQEBGFBVZSJCAX6AWDRSH3TP7UJRLGUM5SAC
3DQXSE4YGFBBDUWK4YEOFWW4UPWILWELFSLP37SL6BERGAZJC5YAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
LS3DAZVRDCXVN2BKBC6RGCKO3R43Z7HKG4GXJWLBK4AKBL2G6QDQC
NG53L53MSFQZAIVKHG54IEMXNJ33AYVPF2OZM4MMJFDKEJCGPBSAC
EO4FXWNFJRHPOSDHWH2Y6QNUP7KB5ANLX43GA3TJLXR3QOOJZ7VQC
NVSFIV2ZKP44XHCSCXG6OZVGL67OIFINC34J2EMKTA4KULCERUEAC
KEANRIMF5CGFVZ2XJYNFPOAKLXOSOJUOVA73IWBWOG576265ERHAC
HMC247EGUJ3Q25DQ3VKUCIGLIO4SZORFQQWAPAF6S2WLQY3WU5TQC
4NBPZKMZBKB3QYX4FFUAKDXQS43NJCBDLMHKDJFVXHQLX4MQDINAC
IE3INS4WUXZOBVXB5VWRBYPVPXADD2U2W5H3TBTOYNWJ3EGRSGQQC
MI3QIGA6VDUMXL7NO3Q2NWCJN5FGX5CDMDYLEKOMD6SALHEINW5QC
Y2NYY7HWFZ2LQDK3ACSLGS37F2J2IJ5LRGCIMZYXLEOSVPD3A4DAC
KAOE5HB3THUKVGFZRO5EZESHEB3Q34WUO5DFMLWIKOBF47LZTIYAC
542UIZKI65UDRNEMGFFDBWYD5XC7AYLTZ3JZQRR2GHYJALD3YY6QC
WQIEW3O4MANA2KKYRUWEZP44KHVJ4RRHEZTDXSF4EDELX66LO26QC
5JS3QSE3EIXSBVI4DATH2EIFD7QN3POAFEUM7MK4NRMPH5JOPAAQC
G2IBQUJ2V2OGM4TR6VKC4CLNVLALTDLB5STOBN7637GOUX2SH4ZAC
FEBNNCNH6C44FRFAJKLVC4QF6JLXVEPYUNMCVUJK3KA3ALKSOC3QC
T2AYVN57EFJQLFUFLAZDXKDAFDGTDLQIEQWQZNYFWJZBYSTYH4QQC
ZBPS5ZTPF3DVTR5WET4XEFHYXU26CRHU2OHX3YO6PD4MTM2DUXAQC
SIDH2P7NBIG5KEOE27XHD3ZT2NQ2OJZFN6VZXWNWYFFY5YVXSSVQC
KS4WBDOLZ45T5Q742JZQIRN3H7XZTYDPM4TCIAILIRO3OV7ANR2AC
ABYG7KPBG5FZFQQB5QYJKZCEDJLQBJDBS6KAV2ZFKQU5QY4WDGWQC
3WHI3KM43ZCN4ITJLFQQBQBC4OJPRS7QTBPIQ6QBCUVKRSK476SAC
BGJ7P65JV2OFVXMGAJDHV5Y36TR7JOFDWJUZJBHUBD7SCQMDRBEAC
SPELL_STICKY_FLAME_SPLASH, "Sticky Flame Splash",
SPTYP_CONJURATION | SPTYP_FIRE,
SPFLAG_DIR_OR_TARGET | SPFLAG_MONSTER | SPFLAG_INNATE | SPFLAG_NOISY,
4,
100,
5, 5,
0,
NULL,
true,
false
},
{
SPELL_FIRE_BREATH, "Fire Breath",
SPTYP_CONJURATION | SPTYP_FIRE,
SPFLAG_DIR_OR_TARGET | SPFLAG_MONSTER | SPFLAG_INNATE | SPFLAG_NOISY,
5,
0,
6, 6,
0,
NULL,
true,
false
},
{
SPELL_COLD_BREATH, "Cold Breath",
SPTYP_CONJURATION | SPTYP_ICE,
SPFLAG_DIR_OR_TARGET | SPFLAG_MONSTER | SPFLAG_INNATE | SPFLAG_NOISY,
5,
0,
6, 6,
0,
NULL,
true,
false
},
{
SPELL_DRACONIAN_BREATH, "Draconian Breath",
SPTYP_CONJURATION,
SPFLAG_DIR_OR_TARGET | SPFLAG_MONSTER | SPFLAG_INNATE | SPFLAG_NOISY,
8,
0,
LOS_RADIUS, LOS_RADIUS,
0,
NULL,
true,
false
},
{
SPFLAG_TESTING = 0x08000, // a testing/debugging spell
SPFLAG_DEVEL = 0x10000 // a spell under development
SPFLAG_INNATE = 0x08000, // an innate spell, even if
// use by a priest/wizard
SPFLAG_NOISY = 0x10000, // makes noise, even if innate
SPFLAG_TESTING = 0x20000, // a testing/debugging spell
SPFLAG_DEVEL = 0x40000 // a spell under development
bool force_silent = false;
spell_type real_spell = spell_cast;
if (spell_cast == SPELL_DRACONIAN_BREATH)
{
int type = monster->type;
if (mons_genus(type) == MONS_DRACONIAN)
type = draco_subspecies(monster);
switch(type)
{
case MONS_MOTTLED_DRACONIAN:
real_spell = SPELL_STICKY_FLAME_SPLASH;
break;
case MONS_YELLOW_DRACONIAN:
real_spell = SPELL_ACID_SPLASH;
break;
case MONS_PLAYER_GHOST:
// Draining breath is silent.
force_silent = true;
break;
default:
break;
}
}
else if (monster->type == MONS_SHADOW_DRAGON)
// Draining breath is silent.
force_silent = true;
}
std::string target = "something";
if (pbolt.target == you.pos())
target = "you";
else if (see_grid(pbolt.target))
{
int midx = mgrd(pbolt.target);
if (midx != NON_MONSTER)
{
monsters* mtarg = &menv[midx];
if (you.can_see(mtarg))
target = mtarg->name(mons_friendly(mtarg) ? DESC_NOCAP_YOUR :
DESC_NOCAP_THE);
}
void setup_dragon(struct monsters *monster, bolt &pbolt)
{
const int type = (mons_genus(monster->type) == MONS_DRACONIAN)
? draco_subspecies(monster) : monster->type;
int scaling = 100;
pbolt.name.clear();
switch (type)
{
case MONS_FIREDRAKE:
case MONS_HELL_HOUND:
case MONS_DRAGON:
case MONS_LINDWURM:
case MONS_XTAHUA:
pbolt.name += "blast of flame";
pbolt.aux_source = "blast of fiery breath";
pbolt.flavour = BEAM_FIRE;
pbolt.colour = RED;
pbolt.range = 6;
break;
case MONS_ICE_DRAGON:
pbolt.name += "blast of cold";
pbolt.aux_source = "blast of icy breath";
pbolt.flavour = BEAM_COLD;
pbolt.colour = WHITE;
pbolt.range = 7;
break;
case MONS_RED_DRACONIAN:
pbolt.name += "searing blast";
pbolt.aux_source = "blast of searing breath";
pbolt.flavour = BEAM_FIRE;
pbolt.colour = RED;
pbolt.range = 6;
scaling = 65;
break;
case MONS_WHITE_DRACONIAN:
pbolt.name += "chilling blast";
pbolt.aux_source = "blast of chilling breath";
pbolt.flavour = BEAM_COLD;
pbolt.colour = WHITE;
pbolt.range = 7;
scaling = 65;
break;
case MONS_PLAYER_GHOST: // draconians only
pbolt.name += "blast of negative energy";
pbolt.aux_source = "blast of draining breath";
pbolt.flavour = BEAM_NEG;
pbolt.colour = DARKGREY;
pbolt.range = LOS_RADIUS;
scaling = 65;
break;
default:
DEBUGSTR("Bad monster class in setup_dragon()");
break;
}
#ifdef DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS, "bolt name: '%s'", pbolt.name.c_str() );
#endif
pbolt.damage = dice_def( 3, (monster->hit_dice * 2) );
pbolt.damage.size = scaling * pbolt.damage.size / 100;
pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP);
pbolt.hit = 30;
pbolt.beam_source = monster_index(monster);
pbolt.thrower = KILL_MON;
pbolt.is_beam = true;
// Accuracy is lowered by one quarter if the dragon is attacking
// a target that is wielding a weapon of dragon slaying (which
// makes the dragon/draconian avoid looking at the foe).
// FIXME: This effect is not yet implemented for player draconians
// or characters in dragon form breathing at monsters wielding a
// weapon with this brand.
if (is_dragonkind(monster, monster))
{
if (actor *foe = monster->get_foe())
{
if (const item_def *weapon = foe->weapon())
{
if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING)
{
pbolt.hit *= 3;
pbolt.hit /= 4;
}
}
}
}
}
const int drac_type = (mons_genus(mons->type) == MONS_DRACONIAN)
? draco_subspecies(mons) : mons->type;
int real_spell = spell_cast;
if (spell_cast == SPELL_DRACONIAN_BREATH)
{
switch(drac_type)
{
case MONS_BLACK_DRACONIAN:
real_spell = SPELL_LIGHTNING_BOLT;
break;
case MONS_MOTTLED_DRACONIAN:
real_spell = SPELL_STICKY_FLAME_SPLASH;
break;
case MONS_YELLOW_DRACONIAN:
real_spell = SPELL_ACID_SPLASH;
break;
case MONS_GREEN_DRACONIAN:
real_spell = SPELL_POISONOUS_CLOUD;
break;
switch (spell_cast)
case MONS_PURPLE_DRACONIAN:
real_spell = SPELL_ISKENDERUNS_MYSTIC_BLAST;
break;
case MONS_RED_DRACONIAN:
real_spell = SPELL_FIRE_BREATH;
break;
case MONS_WHITE_DRACONIAN:
real_spell = SPELL_COLD_BREATH;
break;
case MONS_PALE_DRACONIAN:
real_spell = SPELL_STEAM_BALL;
break;
case MONS_PLAYER_GHOST:
// Handled later
break;
default:
DEBUGSTR("Invalid monster using draconian breath spell");
break;
}
}
switch (real_spell)
break;
case SPELL_COLD_BREATH:
beam.name = "blast of cold";
beam.aux_source = "blast of icy breath";
beam.damage = dice_def( 3, (mons->hit_dice * 2) );
beam.colour = WHITE;
beam.type = dchar_glyph(DCHAR_FIRED_ZAP);
beam.thrower = KILL_MON;
beam.hit = 30;
beam.flavour = BEAM_COLD;
beam.is_beam = true;
break;
case SPELL_DRACONIAN_BREATH:
beam.damage = dice_def( 3, (mons->hit_dice * 2) );
beam.type = dchar_glyph(DCHAR_FIRED_ZAP);
beam.thrower = KILL_MON;
beam.hit = 30;
beam.is_beam = true;
if (spell_cast == SPELL_DRACONIAN_BREATH)
{
int scaling = 100;
switch(drac_type)
{
case MONS_RED_DRACONIAN:
beam.name = "searing blast";
beam.aux_source = "blast of searing breath";
scaling = 65;
break;
case MONS_WHITE_DRACONIAN:
beam.name = "chilling blast";
beam.aux_source = "blast of chilling breath";
scaling = 65;
break;
case MONS_PLAYER_GHOST: // draconians only
beam.name = "blast of negative energy";
beam.aux_source = "blast of draining breath";
beam.flavour = BEAM_NEG;
beam.colour = DARKGREY;
scaling = 65;
break;
}
beam.damage.size = scaling * beam.damage.size / 100;
}
// Accuracy is lowered by one quarter if the dragon is attacking
// a target that is wielding a weapon of dragon slaying (which
// makes the dragon/draconian avoid looking at the foe).
// FIXME: This effect is not yet implemented for player draconians
// or characters in dragon form breathing at monsters wielding a
// weapon with this brand.
if (is_dragonkind(mons, mons))
{
if (actor *foe = mons->get_foe())
{
if (const item_def *weapon = foe->weapon())
{
if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING)
{
beam.hit *= 3;
beam.hit /= 4;
}
}
}
}
case MONS_BLACK_DRACONIAN:
draco_breath = SPELL_LIGHTNING_BOLT;
break;
case MONS_PALE_DRACONIAN:
draco_breath = SPELL_STEAM_BALL;
break;
case MONS_GREEN_DRACONIAN:
draco_breath = SPELL_POISONOUS_CLOUD;
break;
case MONS_PURPLE_DRACONIAN:
draco_breath = SPELL_ISKENDERUNS_MYSTIC_BLAST;
break;
case MONS_MOTTLED_DRACONIAN:
draco_breath = SPELL_STICKY_FLAME;
break;
static bool _mons_announce_cast(monsters *monster, bool nearby,
spell_type spell_cast,
spell_type draco_breath)
{
const msg_channel_type spl = (mons_friendly(monster) ? MSGCH_FRIEND_SPELL
: MSGCH_MONSTER_SPELL);
if (nearby)
{
if (spell_cast == draco_breath)
{
if (simple_monster_message(monster, " breathes.", spl))
return (true);
else
{
if (!silenced(monster->pos())
&& !silenced(you.pos()))
{
mpr("You hear a roar.", MSGCH_SOUND);
return (true);
}
}
}
else if (monster->type == -1)
monster->hit_points = -1;
}
return (false);
}
static void _setup_plant_spit(monsters *monster, bolt &pbolt)
{
pbolt.name = "acid";
pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP);
// immobile plants get long-range spit...
pbolt.range = LOS_RADIUS;
pbolt.colour = YELLOW;
pbolt.flavour = BEAM_ACID;
pbolt.beam_source = monster_index(monster);
pbolt.damage = dice_def( 3, 7 );
pbolt.hit = 20 + (3 * monster->hit_dice);
pbolt.thrower = KILL_MON_MISSILE;
pbolt.aux_source.clear();
}
static bool _plant_spit(monsters *monster, bolt &pbolt)
{
bool did_spit = false;
char spit_string[INFO_SIZE];
_setup_plant_spit(monster, pbolt);
// Fire tracer.
fire_tracer(monster, pbolt);
if (mons_should_fire(pbolt))
{
_make_mons_stop_fleeing(monster);
strcpy( spit_string, " spits" );
if (pbolt.target == you.pos())
strcat( spit_string, " at you" );
strcat( spit_string, "." );
simple_monster_message( monster, spit_string );
fire_beam( pbolt );
did_spit = true;
}
return (did_spit);
}
M_NO_SKELETON = (1<<29), // boneless corpses
M_SPELL_NO_SILENT = (1<<28), // cannot cast spells when silenced,
// even though it's not a priest or
// wizard
M_NOISY_SPELLS = (1<<29), // can cast spells when silenced, but
// casting makes noise when not
// silenced
M_NO_SKELETON = (1<<30), // boneless corpses