by the MiscastEffect class, which has helper methods to make most of the non-helper code agnostic with respect to whether the miscaster is the player or a monster. Mummy death curses now affect monsters, and Zot traps now directly affect friendly and good-neutral monsters. In wizard mode you can force the player or a monster to miscast by targeting it and pressing 'M'.
Todo/issues/notes:
Clouds now have a killer_type in addition to a kill_category.
There aren't any divination monster miscast effects yet.
Many of the harmless message-only miscast effects are missing monster messages.
If a monster actually miscasts a spell (not getting a mummy death curse or setting off a Zot trap) and this kills both the monster and the player then the wrong monster will be listed in hiscore entry. Since monsters can't do true spell miscasts yet, this can wait.
There was old, non-functioning code making Zot traps heal, haste or turn invisible hostile monsters that triggered it. I fixed it and then commented it out.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@6723 c06c8d41-db1a-0410-9941-cceddc491573
3DQXSE4YGFBBDUWK4YEOFWW4UPWILWELFSLP37SL6BERGAZJC5YAC
UO275FSZAZ4O7VY75YK5TFVFMC6I65GCCMD7WR4BWXTDQ2ZMNQ6AC
3A6E2MQQZDGITBNWJ4G2GUTXADMVOYXL6ZHEFTE675YH2N5GKXNQC
C35OWVJDUJ3PNX3OEXQJVCIAQDDN5EZCKZWK5IUYRWQO2DUYPSEAC
ECV4Q6KVRMSJUGKRNQ44NQEZVBQUEPTQK2Z5LQ3J3PFXA7Z3VO3AC
RM6HVV4RZQPC5P7OO7CN6Y2FA2NO2HJ7KES4UPPYQUMZVSMMTIHQC
ZNDS3LUJYJQJ7GUOHCQUDUAAYNQAOJMC2PTMRF7XXTHOPOFLMP3AC
EYCSK2CDMOD3OB3W4XHKERJNM6SFONQ5FP4KO7OHCZFXFPI3XOOQC
BNZD2CICSMU33O4Y2CG7HHVB4P34MCRVZG4LA6TRZE5DGSI672JQC
JBUBVMOE6YJFZKFTADC63WLSZKI3EPCATPQ2KPZPGM7ZIPFZN5TAC
PHAQZTU5EJ4QPZISLB4ASGQZT7FOZZYZSZF4TVMCPTMW62JI2VQAC
TTMNJFUL4SJSX6BY3K3N7YJFTHI7DMI2GMMHUG3W2WAN5Q4ZHQ2AC
MKRJCNUW7KUBBNAX4Y7LHIV7WBXKVTWXTN6A5NUUFSG32XYTZBZQC
GE7XFWXEYNZXWNCKWHBHYIBSYQPZMBAE43PN7RW4B7WOCJKM7DZQC
BFP3PE35HHW7ZMETO7XKYYJ23ZQQYBLILTJAZXEB3M6PITQD3X7AC
BBAFAQFU3JBSUQT7XYPKOVKGUKCOVLBLBU2YGAEMEBL52CE4MDNQC
6LTQT4CP7LJZLAE7JTPQ4OT4JOG3IFYM7RNXL3FINKBFO6452DTAC
6ZFD72GL6F7BV2WPQZHKOLB6EFFCYBYW2FJDEWE7J5YNQHGBTFJQC
UIOGH5PMU2M3THTAJLY3FID4USBFZZDQ7H4OUOOUY53BHAAH7X7QC
WNUKUYBBSIL7AUP4XJAGUFMKBBMZ7QY5DS67OVI7JSPAUOAZJV3AC
34LSODHJVOGATMHLJN67YGGWOV2PYDGAY3OAZGCU6J2ESULN6S3QC
LUH6GAJODIQXNPBEHMSUTKH3KA3DYVRCKZ6GJJ4HHNHOIMMUEP6QC
KFULGQQOHWUTXOM3BXCCYPGGVGGY4Z6265XUFRCBPNLTZAEHJZSQC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
5MGUZD2UACJCSG74TEZHI3Z4YL5KL6ZVUCQ3XVZKDOLKM7EMGWJAC
TPO6FNMPNUSWH4NCKO3VLYNAADEPSAXLUITCCACLZZSY53PKA62QC
NVSFIV2ZKP44XHCSCXG6OZVGL67OIFINC34J2EMKTA4KULCERUEAC
ZLQAAP55CJ77XIJN3DZVPT4GTTVLIBFJLIJJKI6L5UBSHX7VUK6AC
B62ICMDTN5V7R7RBL4JALFVKEMVOOVLRSJASRNYS6CGFWBEEF5JQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
4PGOIN7EO5PXZHDRHBIWFTT4UI6QTINSWVN333R7S6VHFMOYQYIAC
PAYI4UTJCR3XZSFOX5L35EURHRXQ6STO4Z7AQ3525QPNL3QYLNBAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
QKGDOYIYKE6B36ION5O2DRW65DWWPZMYNWJVH7LJJ7FPGGM2MYAQC
UEI5JAVCMN7Y2SACTEZPZSNFJWOJTC55G24Q6LKQCT4XNDH5ZQIAC
J44YLLLSMPKEY3IXV3CPOM257PQZW45A4QWSSBYVFXS5T5XGQQWAC
S34LKQDIQJLIWVIPASOJBBZ6ZCXDHP5KPS7TRBZJSCDRVNCLK6UAC
UL7XFKMUX3WIU4O2LZANK4ECJ654UZPDBFGNXUEYZYOLKBYBCG6AC
W4LFYTIOCEWMSU45KP2YAVH7PCTGHTM7LUCGZNEEHW2WL43FZBKQC
EFS55XWWSC2ARLHL7KI3SZYQB45IUT33RW7JFDGCLUP4A4MBHDYQC
WG6O475IOLZFMUQSLVR2KHM7XTBF5HH276L2KDGF7UOSESDOAILQC
DDU4A3JGN5IUIPP5IASOODKPR2WBHSDSV4FITZ6HNXNSXXQACWAQC
DYFOFUZ7UXOUYTID6UXX6HVU6OAFWYWMTSCG367S6TRLOOK5OXRAC
XBCBXWTIEPOBIZAV3YQWRETC2RY5NAMZAEYSUNAQEC4YCXGYYIGQC
PHBACPMH3F34GODHVDKNCMXWU373RJQGVTDLBFCCDLLWDXVYOLTAC
B7MSPF6X2RLGWN4M6ZZF3WSOPKGYPTTD7LIJVST7DXN27DG6JHNAC
3WAR2UAC4C4D2GOK4AUV4JITGFSBO3NIBAXGQZXSMFUTNTCLTU4AC
JCWJWGMQIKQGSSFJUQRKNIWW3HBOJSHYDTOPPE5BWOJTIJTDYUTAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
TV3ZC6WOZKSQQJQN26JIVKCHK6UK7WMDBYZDUYRWEAZ4JB4YVNAAC
XPDHA3DMNJURSBYQVMW2237PKMVBZEOQAKK3PNOHGGSEWQULLZUAC
AREBCIU2RU2RNHBWD4GARWEBKSL7HDFGDLII22H56OJO2AQUOMLQC
CA3UUPVVUCMU7V7AUUIG6LOPEQEQKNYRPCSKF7FECSCA6VCZYAAAC
L2IP3Q6O2GRUT6XQMHCIHMHZFQRFUDAP7XPPLQ7MZLVWQXCPOY2AC
UZ5623MOLKBTGBSRBJ4OBOEI4IEZSPV3NCV2DRMUZ3CHHJQVHIIAC
CHO4U5JC3RNTLXVIDXXJYZMOBZJ4VXW2GVJWDOTBRKK3AJ36LDLQC
5XNQ3SSNBFXFNWA6DPM74W6FH65NX665P3DMH6YCWVFOPZTJSYCQC
4SWAT5KCKQV527NKELAXFQ5XA4Q5HONQXD4VBXMUZNPVPQKPCPNAC
DMDUCZ3IY5MNAFQJZZOVZSDWV36PWF4YXEJJSDU4LWBUP7AESDSAC
67WUNL6XO77CNOMQEZ6BMV4LL2VTCMUBG57DQMCHNEMPCGYXLO3QC
ABQY3PFFBHPOGF4TRKNIV5KOTKZ2IYLAJR362XIKHJMQSYS7TKHQC
KPSCSVGHKEQ4ENM3QQU2U3GYMV52NDFO5L6ML7YDPUKV3365OA3QC
IPGDTHHTT2NM4UZNEE37JDMBRSER222VVI5XGSDBTHBSD3JNU56QC
64LQALS66EFDRQUEN3NRAWD2PWE7VMNRSEUTMRKQSEQTS55ZRZXAC
GPEJOT73KMACP33IPAKFR5ROGHCOIP22VXZMQNYTGLEA2OSZUM2AC
VKDDAEFRLGRFE6XRPVIKFK43PCMISCTIWXS5RV4KW7C6MSBNYSKQC
IE3INS4WUXZOBVXB5VWRBYPVPXADD2U2W5H3TBTOYNWJ3EGRSGQQC
MKTQW2V35UQ45B6BQETHQEMNNXQHKWDERKNKY2CSUTUS22LHNKNQC
K44IHUR7ISXCKC4GUAAJVVVMSD745H3FZE7XOW27UPLBFC7JZYHAC
OC2BLHBUIA6VC66NLJXAITGF26DENMDNITIPAQXQTXSYDH4NZ2KQC
BLXJID5ZBBH42F2LKNF33DWE7ROPCK6CRXBVO6BLOHXKS4LZETKAC
AZEDPVKNGB4BVHN2XBSBQAC3RCG7AWT3TWPH7ATUG43RQADRPNKAC
CLIEHAE2PP7ZIGLLIMYCWM4FC54KBOAN5AILOLAZJ5S26GTJM4RQC
UEEDQIFBOUZOXHCWGSEBED4Z3SYGN3DVHOVRJYQVWQQ6BIDLWAQAC
PR42BCP5BPRFD2MP5H6CIJP7E57Q6TKL6SOXZWFKMFVR2OZWHT7AC
74LQ7JXVLAFSHLI7LCBKFX47CNTYSKGUQSXNX5FCIUIGCC2JTR3QC
XGHRLF5KASEHD5UTAT7XBQFET3EXXFBYNT5KM6VGWVW4URJNKAYQC
TGJZXTUIAKCFZQJ54ZQEBGFBVZSJCAX6AWDRSH3TP7UJRLGUM5SAC
ASLW3Z5PAVZSWJEMMMVZT226P44EKSAD47QS72JIFJESAI3RPN3AC
7YUGK5Q64KG5O7GJGTUBRRLHAHBCJ5YOE23YUPT6UBKUSB67CYAQC
5BJPWUPLJFS34FUTFJVKA4A52YMIGV6EWDXLNSDCWBJWBGVSQFGQC
U2AV5FJ7UR6QMGMSZBW6MMLBZHWTANK46QOBP42PYHMAXJCBMRBQC
QDBILCQHREAKSVXBJJGE2EPNR4ATTQWUZMV7BLJYZWWUI737TSKQC
EJEHTLHZ5DL4SJQJBMQL5K3IV2ZMCZQBVCRRERER7SPOMNKFJTVQC
RRNEVUYOK5S4M4QH77N34IXSSNUXVQAIZQLXAQKQCRITMPUKGP6AC
RLN5WLM2K5U6QBIBWBLK5XTDD3MPPXXXOVPUT33JKOQF3PHF6WZQC
XUCCWGMXKPIR34BBCCOI67YHI3RST4STDWSDUZTN4B2CJWXQLQ7AC
WZNB427K3EUNV3FMVXLQTM4UIHER4FIKQXWLUUXRNQC3OQ33VQYAC
AXRXY7RSEN3QHQRK6SFEH2OZAAYJRR5RBBBFF4YJF756V2FPFJ3AC
CQD3RU44235F3CYFDZBC3JRT2H3AE7PXJNVBBMCRF65Q5LPA3EHQC
SWOYPTHJAWFEDBMB3ROT33VQZIXGZD5UOXEV456DDUENW2HGA66QC
XII3RPTU6PPAY7U7TE4MTVPSOFZTOPF7ZKHMBGG2GHLAWPUFOKCQC
CQ24AVAI6SW3AHTIDMLPSTRRBEU6FHRF5I5FD6G5QIYE6PO4BQMQC
XMX2Y7QSEXGV2SPDOFDNM2BQJH3S3WTMYLJYUREYV72NWTURHMSQC
V4DWL5WBO2JCODVS5QQNWXDH4DAYZN3D5V3UDCHM2KKOMADOTEDQC
RT4UZQXFGZCMWEP553GYPQKMYIKQHRJO7EZ73N76HJI45AW5O3DQC
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
AVCMVFA3MKCXHO6H44UK5KJNIHTGQV7UA7GYXM26VI6TXXU5ZN6QC
WRPGGZRVGWWYCBWL5W5HMAM7CWLYICKZ6A36ZGQ3UEDW5O25FMOQC
PFEJ4LMDNEKLMGRCMWQ7EIRVU4JMYGICI4G7X4WVWOROVXQCBZ7QC
OK6A6PMHOYIQIPXK32AKOH55QRFWQA43BJWHUOCZA2VIJ6NROHMQC
FIYBXLWALQINNQTHG2KNDUUTAQAZRDDLXW2XOVSKDKBADJ3XCJ4AC
KN5WFFRSNGHIGI5IUYRBOA4R4K7XK3AEAAMN6ELDFTSYKPN7QX7AC
YOH32TMLN6QJG4ZFLYWPJF3YUEGIMZPVPYN57RTB26QBBHMICV3AC
NNG27Y5ZQAZX6UD7F7M4F6KEZBEDFXPEEC3LFUSX4ESKT7K6UJQAC
C5VA63WAQRWPENIMXRPUPZLZJMC77PL2B3A77HYFWDCZU5QDG7VQC
NLQNXH3SVJ52CWXEV35FSSZP32VHC4QFGN3HINF4KO5GZHZMOBKQC
KAOE5HB3THUKVGFZRO5EZESHEB3Q34WUO5DFMLWIKOBF47LZTIYAC
TR4NPGNO5QNNRJNVMNSUEO5QLT37HCXXDOBKXCB5XWXRQNAJ5SHAC
JM7UAK777RAVDAVLQLEOBRTGNW2B47S5G55XITJXO243IUNZHVYQC
X5WLJCJVW55SXZVP7IKP7ADCJIGNKN4PKAXFECVR6TNK7XSMZR7QC
NG53L53MSFQZAIVKHG54IEMXNJ33AYVPF2OZM4MMJFDKEJCGPBSAC
XKFXUJ5YV3HQUSBDHADFIH5JFFNBMORLXOUJKWFXV743F5Y2BSSAC
2TFYJ7D72JY4DYQW3GSPEONA2WYIVHAJXTIQ2QRDIWF65XN2QFGAC
F7QFSXE22UPQTBLYJLY26HJ3QPHFNBJMUOFJRV35R5YCHSGKTBYQC
OWERGKLVPNPGIIS23FZ7ZDOBWUIXCKYAFG3URXU75JAUDX3N5ENAC
NDCVITU5R7TBEUC4FILNFRKWQGUNCJUGW5HA5KW3CQTU6AYRLYWQC
MAVCYXGLWJHJ53DKGEIG7JLXZUFOBMUDF3BIGRU772QTMRJT7CSAC
2GRGJSH5XOCKFQEZ4FWSLRJPAMWETEQCC53T7JUBMY5WNAREGE3AC
UPNIIOG2FHJ4WFGGPZBFYK3GYONL3Z5LO4FEYRJPE7WHNI5R7UZQC
FO6UIRVOTDPHBERV5JLNNJ4QYGZGXOP7ZZF2UBB26CNYIR5XCXAQC
KPAWRUS24XJH5H5Q5YC26JTRHBDV6QOJ53UP7B7BKIEK6JM2EPUAC
WQLOHSNCA3VOMDJF6IINJYKSYVYZEBPJJWBB33QSNE4RP5HEXPMAC
DOZORMA366M4HB5JKSS27BMCR6ET7QNZNND2B7KV3NVEEPR5H7EAC
T6TL6NTIOBYNUIONGK3JFZJ5ONWV6S4CTIRDC5JMKMCBGG5IY3EAC
ED62QWGKBPORWVKDFOQRKJXEIWZVNGR3O4KWQBDSRNPT36AYOQYAC
WBAFNYODKTL3YSG3UOJITBJSTFYGJLIWKRNK6NMGIIP5TPC2BDGQC
B3FDHXVBFR7YQF56JLQT2EYWRTWV6Y4N44GI6EWVWUU2KBNMZDCAC
33ZMPQC6OXTESW7SRW765GRNJUEJRSYONRVZVIEUDAUEJ2PPMB4AC
5B36ULBPPMKZJBURDAAVN4VBMKY3PIZIIDY3LAZYSIVTWGW4CRIAC
47NSOFQMBZCDIBHEAZSENFUGDSQCX3GJHFBUZ65ARDKCYIZ435LAC
RSIUBEQUGNU4LO6KH4PKVROWQS33DAKSY4XFVGN7T3CEKSXABCSAC
RM2JXW3ATVYRYHF3NMG5ALGI64OJ7IP2F3MDUDPUT5TBKSSN4KVQC
AUXHSGS4EFOPZ6TVZYWNVOUDO7NYKUKE3HBKGQQWTALSVFOE3HAAC
JJULXW764V5C2HJKZNWQAEWB6QM5YZADD7ZCE35LYTBFEM6PMYCAC
BWAQ3FHBBM6G3K3KYP75CRTR343RDQZJRYX5ZGYUEXYBAC3APDLAC
627QATWYWAYCCD3YFK2ZTFC2BPQF5DYFDLCTV6ULH53UVJOA265AC
SHSIZVHSB4RPTLGMFJXUDVHF3FTEZZYCRIH4CBZP4MXOBR3HC4QAC
3ZWALZFSTSIVYXY4BAY6ANGINTDACZC6RSSJTEMQSTSUIE66YOBQC
KXHG3CHEUCZX6UZXAQG6QXPS6IRTIJWGGZWYVAMHWYYTCM4KBUSQC
KHHAE5ZK7ITEZVMMUKYROKECLE2RU5ZU5OQ4Z4XSRQXE2R65O67AC
PCNHBE46VURT3FYX4SUCJ4FF7HXWJPQIKC3ICH43Y34P6ITXDNZQC
KBNY5FWKTEAKABFCLPC3QFKFSVZKAGXINPCIFV6WDSWFO4VCKNTAC
QS3ZRS3E6KL3YJHPKYEWCWJYRBJSXD5OOYF6Y25HZVECGPJRDB5QC
4UXFU3FZOCBSLDQ4S7MJKAE2H7VUHCNRDQMIY6NJ3PHYXWNGISDQC
Y4NA3JSN63RLATF4NNBPSR5CWF5Z7UEMWCGVX4B6NOAR47CGM4GQC
HIRKGUMNJPWKSVTR6TVBPD3MWNA63CEHCLCIPWEMGDFHVB3NPLDQC
JXYRJYQUCKNTAAQOCMWEMJ6AKQRDJ2MQCLCN3754LQYPBO2XBNOQC
7XJLSTDZDOSHW6JF3Y4545YGGK5BC2SOXJVQXXVOFR3I7PXYUJZQC
Y5RFQ6KNJCBQUSV2T6WDR7TPZLZYLOAWBVMUTHDXGOZQDZ2U423AC
L254F6ZIU2HWGLFFGPIORTN4C3TDQ3E5JZ7Z7GQA5AEDIKL6PKDAC
3KAINFIXO7WNWGUGZB43EUNFRS2ZPBLQZDTY456QACMRHYIJ7WDAC
ABLV37FMURRJPEZV2VRKOUYAKEMLI7E6RA4PDAII2EJ5L7WBHKZQC
O7S3ILRELHICJXXTDGMF7KPPZWYHPYCNDPV2I77FZXXH4I454B4QC
WLX2RQMMOMP2PYPAGJRM4VFD2WTLJTOAZZPPY3MV76FU2EGEJ54QC
NS3KXJXQSN33UQSOBDK3WXXKA3KY5YOUJL67NBZKGQAJYDYZ2COQC
ZP2KE7A2LE7Z2S7AC45WE4CXDSEVDTWIMV2EM4IBUKXYJIDU6R7QC
WFED7ME7LXUZCZY3TWX7PCPW4EAA55W626CM2OOYVJTLI2BWFTVAC
ATDAT2AONG2BDLZFBJZB4WVNRUFQAU7RDIVUBAZ6STAV62NX5R4AC
Y5GWVQ5SM7DJEAPFOBPMMJH4D3NXBB2MPONSJUMG3KIQMBVGEE6AC
4FQAKUKUO6PCAZ3N4HUR5XL6E4VA5UQUZ3AEDGRBLVY7W2LMWI7QC
CAGCTYIUYWDHQAJOLVLKOEV5HG6K5ZG7IDHONLIG6BDNCWZJAK4AC
AOAJ6D3OKSELEYKAT55XCVU5LYJ7SMCZKC6DIEGLLB3TF2LEENWQC
}
spschool_flag_type school_by_name(std::string name)
{
spschool_flag_type short_match, long_match;
int short_matches, long_matches;
short_match = long_match = SPTYP_NONE;
short_matches = long_matches = 0;
for (int i = 0; i <= SPTYP_RANDOM; i++)
{
spschool_flag_type type = (spschool_flag_type) (1 << i);
const char* short_name = spelltype_short_name(type);
const char* long_name = spelltype_long_name(type);
if (strcasecmp(short_name, name.c_str()) == 0)
return type;
if (strcasecmp(long_name, name.c_str()) == 0)
return type;
if (strcasestr(short_name, name.c_str()))
{
short_match = type;
short_matches++;
}
if (strcasestr(long_name, name.c_str()))
{
long_match = type;
long_matches++;
}
}
if (short_matches != 1 && long_matches != 1)
return SPTYP_NONE;
if (short_matches == 1 && long_matches != 1)
return short_match;
if (short_matches != 1 && long_matches == 1)
return long_match;
if (short_match == long_match)
return short_match;
return SPTYP_NONE;
const char* spelltype_long_name( int which_spelltype )
{
switch (which_spelltype)
{
case SPTYP_CONJURATION:
return ("Conjuration");
case SPTYP_ENCHANTMENT:
return ("Enchantment");
case SPTYP_FIRE:
return ("Fire");
case SPTYP_ICE:
return ("Ice");
case SPTYP_TRANSMIGRATION:
return ("Transmigration");
case SPTYP_NECROMANCY:
return ("Necromancy");
case SPTYP_HOLY:
return ("Holy");
case SPTYP_SUMMONING:
return ("Summoning");
case SPTYP_DIVINATION:
return ("Divination");
case SPTYP_TRANSLOCATION:
return ("Translocation");
case SPTYP_POISON:
return ("Poison");
case SPTYP_EARTH:
return ("Earth");
case SPTYP_AIR:
return ("Air");
case SPTYP_RANDOM:
return ("Random");
default:
return "Bug";
}
}
/*
* File: spl-mis.h
* Summary: Spell miscast class.
* Written by: Matthew Cline
*
* Modified for Crawl Reference by $Author$ on $Date: 2008-06-28 22:
16:39 -0700 (Sat, 28 Jun 2008) $
*
* Change History (most recent first):
*
* <1> -/--/-- LRH Created
*/
#ifndef SPL_MIS_H
#define SPL_MIS_H
// last updated 23jul2008 {mpc}
/* ***********************************************************************
* called from: decks - effects - fight - item_use - it_use2 - it_use3 -
* item_use - monstuff - religion - spells2 - spells4 -
* spl-book - spl-cast - traps - xom
* *********************************************************************** */
#include "enum.h"
#include "beam.h"
#include "mpr.h"
#include "spl-util.h"
#define ZOT_TRAP_MISCAST (NON_MONSTER + 1)
#define WIELD_MISCAST (NON_MONSTER + 2)
#define MELEE_MISCAST (NON_MONSTER + 3)
#define MISC_KNOWN_MISCAST (NON_MONSTER + 4)
#define MISC_UNKNOWN_MISCAST (NON_MONSTER + 5)
enum nothing_happens_when_type
{
NH_DEFAULT,
NH_NEVER,
NH_ALWAYS
};
class actor;
class MiscastEffect
{
public:
MiscastEffect(actor* _target, int _source, spell_type _spell, int _pow,
int _fail, std::string _cause = "",
nothing_happens_when_type _nothing_happens = NH_DEFAULT);
MiscastEffect(actor *_target, int _source, spschool_flag_type _school,
int _level, std::string _cause,
nothing_happens_when_type _nothing_happens = NH_DEFAULT);
MiscastEffect(actor *_target, int _source, spschool_flag_type _school,
int _pow, int _fail, std::string _cause,
nothing_happens_when_type _nothing_happens = NH_DEFAULT);
void do_miscast();
private:
actor* target;
int source;
std::string cause;
spell_type spell;
spschool_flag_type school;
int pow;
int fail;
int level;
private:
kill_category kc;
killer_type kt;
monsters* mon_target;
monsters* mon_source;
nothing_happens_when_type nothing_happens_when;
int kill_source;
actor* act_source;
bool source_known;
bool target_known;
bolt beam;
std::string all_msg;
std::string you_msg;
std::string mon_msg;
std::string mon_msg_seen;
std::string mon_msg_unseen;
msg_channel_type msg_ch;
private:
void init();
std::string get_default_cause();
bool neither_end_silenced();
void do_msg(bool suppress_nothing_happens = false);
void _ouch(int dam, beam_type flavour = BEAM_NONE);
void _explosion();
void _potion_effect(int pot_eff, int pow);
bool _create_monster(monster_type what, int abj_deg, bool alert = false);
void send_abyss();
void _conjuration(int severity);
void _enchantment(int severity);
void _translocation(int severity);
void _summoning(int severity);
void _divination_you(int severity);
void _divination_mon(int severity);
void _necromancy(int severity);
void _transmigration(int severity);
void _fire(int severity);
void _ice(int severity);
void _earth(int severity);
void _air(int severity);
void _poison(int severity);
};
#endif
// last updated 12may2000 {dlb}
/* ***********************************************************************
* called from: acr - decks - fight - it_use2 - it_use3 - item_use - items -
* misc - mstuff2 - religion - spell - spl-book - spells4
* *********************************************************************** */
void miscast_effect(unsigned int sp_type, int mag_pow, int mag_fail,
int force_effect, const char *cause = NULL);
}
MiscastEffect::MiscastEffect(actor* _target, int _source, spell_type _spell,
int _pow, int _fail, std::string _cause,
nothing_happens_when_type _nothing_happens) :
target(_target), source(_source), cause(_cause), spell(_spell),
school(SPTYP_NONE), pow(_pow), fail(_fail), level(-1), kc(KC_NCATEGORIES),
kt(KILL_NONE), mon_target(NULL), mon_source(NULL),
nothing_happens_when(_nothing_happens)
{
ASSERT(is_valid_spell(_spell));
unsigned int schools = get_spell_disciplines(_spell);
ASSERT(schools != SPTYP_NONE);
ASSERT(!(schools & SPTYP_HOLY));
init();
do_miscast();
}
MiscastEffect::MiscastEffect(actor *_target, int _source,
spschool_flag_type _school, int _level,
std::string _cause,
nothing_happens_when_type _nothing_happens) :
target(_target), source(_source), cause(_cause), spell(SPELL_NO_SPELL),
school(_school), pow(-1), fail(-1), level(_level), kc(KC_NCATEGORIES),
kt(KILL_NONE), mon_target(NULL), mon_source(NULL),
nothing_happens_when(_nothing_happens)
{
ASSERT(!_cause.empty());
ASSERT(count_bits(_school) == 1);
ASSERT(_school < SPTYP_HOLY || _school == SPTYP_RANDOM);
ASSERT(level >= 0 && level <= 3);
init();
do_miscast();
}
MiscastEffect::MiscastEffect(actor *_target, int _source,
spschool_flag_type _school, int _pow, int _fail,
std::string _cause,
nothing_happens_when_type _nothing_happens) :
target(_target), source(_source), cause(_cause), spell(SPELL_NO_SPELL),
school(_school), pow(_pow), fail(_fail), level(-1), kc(KC_NCATEGORIES),
kt(KILL_NONE), mon_target(NULL), mon_source(NULL),
nothing_happens_when(_nothing_happens)
{
ASSERT(!_cause.empty());
ASSERT(count_bits(_school) == 1);
ASSERT(_school < SPTYP_HOLY || _school == SPTYP_RANDOM);
init();
do_miscast();
}
void MiscastEffect::init()
{
ASSERT(spell != SPELL_NO_SPELL && school == SPTYP_NONE
|| spell == SPELL_NO_SPELL && school != SPTYP_NONE);
ASSERT(pow != -1 && fail != -1 && level == -1
|| pow == -1 && fail == -1 && level >= 0 && level <= 3);
ASSERT(target != NULL);
// A dead but not-yet-exploded giant spore or ball lightning *might*
// be the target of a miscast effect.
ASSERT(target->alive() || target->id() == MONS_GIANT_SPORE
|| target->id() == MONS_BALL_LIGHTNING);
source_known = target_known = false;
mon_target = mon_source = NULL;
act_source = NULL;
const bool death_curse = (cause.find("death curse") != std::string::npos);
if (target->atype() == ACT_MONSTER)
{
mon_target = dynamic_cast<monsters*>(target);
target_known = you.can_see(mon_target);
}
else
target_known = true;
kill_source = source;
if (source == WIELD_MISCAST || source == MELEE_MISCAST)
{
if (target->atype() == ACT_MONSTER)
{
mon_source = dynamic_cast<monsters*>(target);
kill_source = monster_index(mon_source);
}
else
kill_source = NON_MONSTER;
}
if (kill_source == NON_MONSTER)
{
kc = KC_YOU;
kt = KILL_YOU_MISSILE;
act_source = dynamic_cast<actor*>(&you);
source_known = true;
}
else if (kill_source >= 0 && kill_source < NON_MONSTER)
{
mon_source = &menv[kill_source];
act_source = dynamic_cast<actor*>(mon_source);
ASSERT(mon_source->type != -1);
if (death_curse && target->atype() == ACT_MONSTER
&& mon_target->confused_by_you())
{
kt = KILL_YOU_CONF;
}
else if (!death_curse && mon_source->confused_by_you()
&& !mons_friendly(mon_source))
{
kt = KILL_YOU_CONF;
}
else
kt = KILL_MON_MISSILE;
if (mons_friendly(mon_source))
kc = KC_FRIENDLY;
else
kc = KC_OTHER;
source_known = you.can_see(mon_source);
if (target_known && death_curse)
source_known = true;
}
else
{
ASSERT(source == ZOT_TRAP_MISCAST
|| source == MISC_KNOWN_MISCAST
|| source == MISC_UNKNOWN_MISCAST
|| (source < 0 && -source < NUM_GODS));
kc = KC_OTHER;
kt = KILL_MISC;
if (source == ZOT_TRAP_MISCAST)
{
source_known = target_known;
if (target->atype() == ACT_MONSTER
&& mon_target->confused_by_you())
{
kt = KILL_YOU_CONF;
}
}
else if (source == MISC_KNOWN_MISCAST)
source_known = true;
else if (source == MISC_UNKNOWN_MISCAST)
source_known = false;
else
source_known = true;
}
ASSERT(kc != KC_NCATEGORIES && kt != KILL_NONE);
if (death_curse && !invalid_monster_index(kill_source))
{
if (starts_with(cause, "a "))
cause.replace(cause.begin(), cause.begin() + 1, "an indirect");
if (starts_with(cause, "an "))
cause.replace(cause.begin(), cause.begin() + 2, "an indirect");
else
cause = replace_all(cause, "death curse", "indirect death curse");
}
// source_known = false for MELEE_MISCAST so that melee miscasts
// won't give a "nothing happens" message.
if (source == MELEE_MISCAST)
source_known = false;
beam.is_beam = false;
beam.is_explosion = true;
if (cause.empty())
cause = get_default_cause();
beam.aux_source = cause;
beam.beam_source = kill_source;
beam.thrower = kt;
}
std::string MiscastEffect::get_default_cause()
{
// This is only for true miscasts, which means both a spell and that
// the source of the miscast is the same as the target of the miscast.
ASSERT(source >= 0 && source <= NON_MONSTER);
ASSERT(spell != SPELL_NO_SPELL);
ASSERT(school == SPTYP_NONE);
if (source == NON_MONSTER)
{
ASSERT(target->atype() == ACT_PLAYER);
std::string str = "you miscasting ";
str += spell_title(spell);
return str;
}
ASSERT(mon_source != NULL);
ASSERT(mon_source == mon_target);
if (you.can_see(mon_source))
{
std::string str = mon_source->base_name(DESC_PLAIN, false);
str += "'s spell miscasting";
str = replace_all(str, "s's", "s'");
return str;
}
else
return "something's spell miscasting";
}
bool MiscastEffect::neither_end_silenced()
{
return (!silenced(you.pos()) && !silenced(target->pos()));
static bool _send_abyss(const char *cause)
void MiscastEffect::do_miscast()
{
// Repeated calls to do_miscast() on a single object instance have
// killed a target which was alive when the object was created,
// or the target is a dead but not-yet-exploded giant spore or
// ball lightning.
if (!target->alive())
{
mprf(MSGCH_DIAGNOSTICS, "Miscast target '%s' already dead",
target->name(DESC_PLAIN, true).c_str());
return;
}
spschool_flag_type sp_type;
int severity;
if (spell != SPELL_NO_SPELL)
{
std::vector<int> school_list;
for (int i = 0; i < SPTYP_LAST_EXPONENT; i++)
if (spell_typematch(spell, 1 << i))
school_list.push_back(i);
unsigned int _school = school_list[random2(school_list.size())];
sp_type = static_cast<spschool_flag_type>(1 << _school);
}
else
{
sp_type = school;
if (sp_type == SPTYP_RANDOM)
{
int exp = (random2(SPTYP_LAST_EXPONENT));
sp_type = (spschool_flag_type) (1 << exp);
}
}
if (level != -1)
severity = level;
else
{
severity = (pow * fail * (10 + pow) / 7 * WILD_MAGIC_NASTINESS) / 100;
#if DEBUG_DIAGNOSTICS || DEBUG_MISCAT
mprf(MSGCH_DIAGNOSTICS, "'%s' miscast power: %d",
spell != SPELL_NO_SPELL ? spell_title(spell)
: spelltype_short_name(sp_type),
severity);
#endif
if (random2(40) > severity && random2(40) > severity)
{
if (target->atype() == ACT_PLAYER)
canned_msg(MSG_NOTHING_HAPPENS);
return;
}
severity /= 100;
severity = random2(severity);
if (severity > 3)
severity = 3;
else if (severity < 0)
severity = 0;
}
#if DEBUG_DIAGNOSTICS || DEBUG_MISCAT
mprf(MSGCH_DIAGNOSTICS, "Sptype: %u, severity: %d",
spelltype_short_name(sp_type), severity );
#endif
beam.ex_size = 1;
beam.name = "";
beam.damage = dice_def(0, 0);
beam.flavour = BEAM_NONE;
beam.msg_generated = false;
beam.in_explosion_phase = false;
// Do this here since multiple miscasts (wizmode testing) might move
// the target around.
beam.source_x = target->pos().x;
beam.source_y = target->pos().y;
beam.target_x = target->pos().x;
beam.target_y = target->pos().y;
beam.pos = target->pos();
all_msg = you_msg = mon_msg = mon_msg_seen = mon_msg_unseen = "";
msg_ch = MSGCH_PLAIN;
switch (sp_type)
{
case SPTYP_CONJURATION: _conjuration(severity); break;
case SPTYP_ENCHANTMENT: _enchantment(severity); break;
case SPTYP_TRANSLOCATION: _translocation(severity); break;
case SPTYP_SUMMONING: _summoning(severity); break;
case SPTYP_NECROMANCY: _necromancy(severity); break;
case SPTYP_TRANSMIGRATION: _transmigration(severity); break;
case SPTYP_FIRE: _fire(severity); break;
case SPTYP_ICE: _ice(severity); break;
case SPTYP_EARTH: _earth(severity); break;
case SPTYP_AIR: _air(severity); break;
case SPTYP_POISON: _poison(severity); break;
case SPTYP_DIVINATION:
// Divination miscasts have nothing in common between the player
// and monsters.
if (target->atype() == ACT_PLAYER)
_divination_you(severity);
else
_divination_mon(severity);
break;
default:
ASSERT(false);
}
if (target->atype() == ACT_PLAYER)
xom_is_stimulated(severity * 50);
}
void MiscastEffect::do_msg(bool suppress_nothing_happnes)
{
if (mon_target != NULL && !mons_near(mon_target))
return;
std::string msg;
if (!all_msg.empty())
msg = all_msg;
else if (target->atype() == ACT_PLAYER)
msg = you_msg;
else if (!mon_msg.empty())
{
msg = mon_msg;
// Monster might be unseen with hands that can't be seen.
ASSERT(msg.find("@hand") == std::string::npos);
}
else
{
if (you.can_see(target))
msg = mon_msg_seen;
else
{
msg = mon_msg_unseen;
// Can't see the hands of invisible monsters.
ASSERT(msg.find("@hand") == std::string::npos);
}
}
if (msg.empty())
{
if (!suppress_nothing_happnes
&& (nothing_happens_when == NH_ALWAYS
|| (nothing_happens_when == NH_DEFAULT && source_known
&& target_known)))
{
canned_msg(MSG_NOTHING_HAPPENS);
}
return;
}
msg = replace_all(msg, "@hand@", target->hand_name(false));
msg = replace_all(msg, "@hands@", target->hand_name(true));
if (target->atype() == ACT_MONSTER)
msg = do_mon_str_replacements(msg, mon_target, S_SILENT);
mpr(msg.c_str(), msg_ch);
}
void MiscastEffect::_ouch(int dam, beam_type flavour)
you.banish(cause ? cause : "");
return (true);
bolt beem;
beem.flavour = flavour;
dam = mons_adjust_flavoured(mon_target, beem, dam, true);
hurt_monster(mon_target, dam);
if (mon_target->hit_points < 1)
monster_die(mon_target, kt, kill_source);
{
kill_method_type method;
if (source == NON_MONSTER && spell != SPELL_NO_SPELL)
method = KILLED_BY_WILD_MAGIC;
else if (source == ZOT_TRAP_MISCAST)
method = KILLED_BY_TRAP;
else if (source < 0 && -source < NUM_GODS)
{
god_type god = static_cast<god_type>(-source);
if (god == GOD_XOM && you.penance[GOD_XOM] == 0)
method = KILLED_BY_XOM;
else
{
method = KILLED_BY_DIVINE_WRATH;
if (you.penance[god] == 0)
mpr("God making you miscast, but not under penance.",
MSGCH_DIAGNOSTICS);
}
}
else if (source >= 0 && source < NON_MONSTER)
method = KILLED_BY_MONSTER;
else
method = KILLED_BY_SOMETHING;
dam = check_your_resists(dam, flavour);
bool see_source = false;
if (kill_source != NON_MONSTER)
see_source = you.can_see(&menv[kill_source]);
ouch(dam, kill_source, method, cause.c_str(), see_source);
}
}
void MiscastEffect::_explosion()
{
ASSERT(!beam.name.empty());
ASSERT(beam.damage.num != 0 && beam.damage.size != 0);
ASSERT(beam.flavour != BEAM_NONE);
do_msg(true);
explosion(beam, false, true);
}
void MiscastEffect::_potion_effect(int pot_eff, int pot_pow)
{
if (target->atype() == ACT_PLAYER)
{
potion_effect(static_cast<potion_type>(pot_eff), pot_pow, false);
return;
}
switch(pot_eff)
mpr("The world appears momentarily distorted.");
return (false);
case POT_LEVITATION:
// There's no levitation enchantment for monsters, and anyways
// it's not nearly as inconveniencing for monsters as for the
// player, so backlight them instead.
mon_target->add_ench( mon_enchant(ENCH_BACKLIGHT, pot_pow, kc) );
break;
case POT_BERSERK_RAGE:
if (target->can_go_berserk())
{
target->go_berserk(false);
break;
}
// Intentional fallthrough if that didn't work.
case POT_SLOWING:
target->slow_down(pot_pow);
break;
case POT_PARALYSIS:
target->paralyse(pot_pow);
break;
case POT_CONFUSION:
target->confuse(pot_pow);
break;
default:
ASSERT(false);
}
}
void MiscastEffect::send_abyss()
{
if (you.level_type == LEVEL_ABYSS)
{
you_msg = "The world appears momentarily distorted.";
mon_msg_seen = "@The_monster@ wobbles for a moment.";
mon_msg_unseen = "An empty piece of space momentarily distorts.";
do_msg();
return;
}
target->banish(cause);
}
bool MiscastEffect::_create_monster(monster_type what, int abj_deg,
bool alert)
{
const god_type god =
(crawl_state.is_god_acting()) ? crawl_state.which_god_acting()
: GOD_NO_GOD;
mgen_data data = mgen_data::hostile_at(what, target->pos(),
abj_deg, 0, alert, god);
// hostile_at() assumes the monster is hostile to the player,
// but should be hostile to the target monster unless the miscast
// is a result of either divine wrath or a Zot trap.
if (target->atype() == ACT_MONSTER && you.penance[god] == 0
&& source != ZOT_TRAP_MISCAST)
{
switch(mon_target->temp_attitude())
{
case ATT_FRIENDLY: data.behaviour = BEH_HOSTILE; break;
case ATT_HOSTILE: data.behaviour = BEH_FRIENDLY; break;
case ATT_GOOD_NEUTRAL:
case ATT_NEUTRAL:
data.behaviour = BEH_NEUTRAL;
break;
}
if (alert)
data.foe = monster_index(mon_target);
// No permanent allies from miscasts.
if (data.behaviour == BEH_FRIENDLY && abj_deg == 0)
data.abjuration_duration = 6;
msg::stream << "Smoke pours from your " << your_hand(true)
<< '!' << std::endl;
big_cloud( CLOUD_GREY_SMOKE, (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20,
7 + random2(7) );
you_msg = "Smoke pours from your @hands@!";
mon_msg_seen = "Smoke pours from @the_monster@'s @hands@!";
mon_msg_unseen = "Smoke appears from out of nowhere!";
do_msg();
big_cloud( CLOUD_GREY_SMOKE, kc, kt,
target->pos().x, target->pos().y,
20, 7 + random2(7) );
mpr("You are caught in a violent explosion!");
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 12 );
you_msg = "You are caught in a violent explosion!";
mon_msg_seen = "@The_monster@ is caugh in a violent explosion!";
mon_msg_unseen = "A violent explosion happens from "
"out of thin air!";
beam.damage = dice_def( 3, 12 );
mpr("You are blasted with magical energy!");
ouch(12 + random2avg(29, 2), 0, KILLED_BY_WILD_MAGIC, cause);
you_msg = "You are blasted with magical energy!";
mon_msg_seen = "@The_monster@ is blasted with magical energy!";
// No message for invis monsters?
_ouch(12 + random2avg(29, 2));
mpr("There is a sudden explosion of magical energy!");
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 20 );
all_msg = "There is a sudden explosion of magical energy!";
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 20 );
beam.target_x = you.x_pos;
beam.target_y = you.y_pos;
beam.name = "explosion";
beam.colour = random_colour();
beam.beam_source = NON_MONSTER;
beam.thrower = (cause) ? KILL_MISC : KILL_YOU;
beam.aux_source.clear();
if (cause)
beam.aux_source = cause;
beam.name = "explosion";
beam.colour = random_colour();
static void _miscast_enchantment(int severity, const char* cause)
bool pluralized = true;
target->hand_name(true, &pluralized);
if (pluralized)
{
you_msg += "glow";
mon_msg_seen += "glow";
}
else
{
you_msg += "glows";
mon_msg_seen += "glows";
}
you_msg += " momentarily.";
mon_msg_seen += " momentarily.";
}
void MiscastEffect::_enchantment(int severity)
if (!silenced(you.pos()))
mpr("You hear something strange.", MSGCH_SOUND);
else if (you.attribute[ATTR_TRANSFORMATION] != TRAN_AIR)
mpr("Your skull vibrates slightly.");
else
canned_msg(MSG_NOTHING_HAPPENS);
if (neither_end_silenced())
{
// XXX: Should use noisy().
all_msg = "You hear something strange.";
msg_ch = MSGCH_SOUND;
return;
}
else if (target->atype() == ACT_PLAYER
&& you.attribute[ATTR_TRANSFORMATION] != TRAN_AIR)
{
you_msg = "Your skull vibrates slightly.";
}
break;
case 1:
potion_effect(POT_PARALYSIS, 10);
break;
case 2:
potion_effect(POT_CONFUSION, 10);
break;
case 3:
mpr("You feel saturated with unharnessed energies!");
you.magic_contamination += random2avg(19,3);
mpr("You are caught in a localised field of spatial distortion.");
ouch(4 + random2avg(9, 2), 0, KILLED_BY_WILD_MAGIC, cause);
you_msg = "You are caught in a localised field of spatial "
"distortion.";
mon_msg_seen = "@The_monster@ is caught in a localised field of "
"spatial distortion.";
mon_msg_unseen = "A piece of empty space twists and distorts.";
_ouch(4 + random2avg(9, 2));
mpr("Space bends around you!");
random_blink(false);
ouch(4 + random2avg(7, 2), 0, KILLED_BY_WILD_MAGIC, cause);
you_msg = "Space bends around you!";
mon_msg_seen = "Space bends around @the_monster@!";
mon_msg_unseen = "A piece of empty space twists and distorts.";
_ouch(4 + random2avg(7, 2));
target->blink(false);
if (create_monster(
mgen_data::hostile_at(MONS_SPATIAL_VORTEX,
you.pos(), 3, 0, false, god)) != -1)
{
mpr("Space twists in upon itself!");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
if (_create_monster(MONS_SPATIAL_VORTEX, 3))
all_msg = "Space twists in upon itself!";
do_msg();
mpr("You are caught in a strong localised spatial distortion.");
ouch(9 + random2avg(23, 2), 0, KILLED_BY_WILD_MAGIC, cause);
you_msg = "You are caught in a strong localised spatial "
"distortion.";
mon_msg_seen = "@The_monster@ is caught in a strong localised "
"spatial distortion.";
mon_msg_unseen = "A piece of empty space twists and writhes.";
_ouch(9 + random2avg(23, 2));
if (create_monster(
mgen_data::hostile_at(MONS_SPATIAL_VORTEX,
you.pos(), 3, 0, false, god)) != -1)
{
if (_create_monster(MONS_SPATIAL_VORTEX, 3))
mpr("You are caught in an extremely strong localised spatial distortion!");
ouch(15 + random2avg(29, 2), 0, KILLED_BY_WILD_MAGIC, cause);
you_msg = "You are caught in an extremely strong localised "
"spatial distortion!";
mon_msg_seen = "@The_monster@ is caught in an extremely strong "
"localised spatial distortion!";
mon_msg_unseen = "A rift temporarily opens in the fabric of space!";
_ouch(15 + random2avg(29, 2));
if (!silenced(you.pos()))
mpr("You hear strange voices.", MSGCH_SOUND);
else
mpr("You feel momentarily dizzy.");
if (neither_end_silenced())
{
all_msg = "You hear strange voices.";
msg_ch = MSGCH_SOUND;
}
else if (target->atype() == ACT_PLAYER)
you_msg = "You feel momentarily dizzy.";
mpr("Distant voices call out to you!");
if (neither_end_silenced())
{
you_msg = "Distant voices call out to you!";
mon_msg_seen = "Distant voices call out to @the_monster@!";
msg_ch = MSGCH_SOUND;
}
else if (target->atype() == ACT_PLAYER)
you_msg = "You feel watched.";
mpr("You are caught in a localised spatial distortion.");
ouch(5 + random2avg(9, 2), 0, KILLED_BY_WILD_MAGIC, cause);
you_msg = "You are caught in a localised spatial "
"distortion.";
mon_msg_seen = "@The_monster@ is caught in a localised spatial "
"distortion.";
mon_msg_unseen = "An empty piece of space distorts and twists.";
_ouch(5 + random2avg(9, 2));
if (create_monster(
mgen_data::hostile_at(MONS_SPATIAL_VORTEX,
you.pos(), 3, 0, false, god)) != -1)
{
mpr("Space twists in upon itself!");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
if (_create_monster(MONS_SPATIAL_VORTEX, 3))
all_msg = "Space twists in upon itself!";
do_msg();
if (create_monster(
mgen_data::hostile_at(
summon_any_demon(DEMON_LESSER),
you.pos(), 5, 0, true, god)) != -1)
{
mpr("Something appears in a flash of light!");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
if (_create_monster(summon_any_demon(DEMON_LESSER), 5, true))
all_msg = "Something appears in a flash of light!";
do_msg();
if (create_monster(
mgen_data::hostile_at(MONS_SPATIAL_VORTEX,
you.pos(), 3, 0, false, god)) != -1)
{
if (_create_monster(MONS_SPATIAL_VORTEX, 3))
if (create_monster(
mgen_data::hostile_at(
summon_any_demon(DEMON_COMMON),
you.pos(), 5, 0, true, god)) != -1)
{
mpr("Something forms out of thin air!");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
if (_create_monster(summon_any_demon(DEMON_COMMON), 5, true))
all_msg = "Something forms from out of thin air!";
do_msg();
if (create_monster(
mgen_data::hostile_at(
summon_any_demon(DEMON_LESSER),
you.pos(), 5, 0, true, god)) != -1)
{
if (_create_monster(summon_any_demon(DEMON_LESSER), 5, true))
if (success)
mpr("A chorus of chattering voices calls out to you!");
else
canned_msg(MSG_NOTHING_HAPPENS);
if (success && neither_end_silenced())
{
you_msg = "A chorus of chattering voices calls out to you!";
mon_msg = "A chorus of chattering voices calls out!";
msg_ch = MSGCH_SOUND;
}
do_msg();
if (create_monster(
mgen_data::hostile_at(MONS_ABOMINATION_SMALL,
you.pos(), 0, 0, true, god)) != -1)
{
mpr("Something forms out of thin air.");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
if (_create_monster(MONS_ABOMINATION_SMALL, 0, true))
all_msg = "Something forms from out of thin air.";
do_msg();
if (create_monster(
mgen_data::hostile_at(
summon_any_demon(DEMON_GREATER),
you.pos(), 0, 0, true, god)) != -1)
{
mpr("You sense a hostile presence.");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
if (_create_monster(summon_any_demon(DEMON_GREATER), 0, true))
all_msg = "You sense a hostile presence.";
do_msg();
if (create_monster(
mgen_data::hostile_at(
summon_any_demon(DEMON_COMMON),
you.pos(), 3, 0, true, god)) != -1)
{
if (_create_monster(summon_any_demon(DEMON_COMMON), 3, true))
if (!silenced(you.pos()))
mpr("You hear strange and distant voices.", MSGCH_SOUND);
else
mpr("You feel homesick.");
if (neither_end_silenced())
{
all_msg = "You hear strange and distant voices.";
msg_ch = MSGCH_SOUND;
}
else if (target->atype() == ACT_PLAYER)
you_msg = "You feel homesick.";
mpr("You smell decay."); // identical to a harmless message
you.rotting++;
if (player_can_smell())
// identical to a harmless message
all_msg = "You smell decay.";
if (target->atype() == ACT_PLAYER)
you.rotting++;
else
mon_target->add_ench( mon_enchant(ENCH_ROT, 1, kc) );
drain_exp();
break;
} // otherwise it just flows through...
case 2:
lose_stat(STAT_RANDOM, 1 + random2avg(7, 2), false, cause);
// Monster messages needed.
you_msg = "Something reaches out for you...";
}
do_msg();
mpr("Something reaches out for you...");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
break;
drain_exp();
break;
} // otherwise it just flows through...
if (create_monster(
mgen_data::hostile_at(MONS_REAPER,
you.pos(), 4, 0, true, god)) != -1)
{
mpr("Death has come for you...");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
lose_stat(STAT_RANDOM, 1 + random2avg(7, 2), false, cause);
mpr("You feel very strange.");
delete_mutation(RANDOM_MUTATION);
ouch(5 + random2avg(23, 2), 0, KILLED_BY_WILD_MAGIC, cause);
if (target->atype() == ACT_PLAYER)
{
mpr("You feel very strange.");
delete_mutation(RANDOM_MUTATION);
}
_ouch(5 + random2avg(23, 2));
if (!silenced(you.pos()))
mpr("You hear a sizzling sound.", MSGCH_SOUND);
else
mpr("You feel like you have heartburn.");
if (neither_end_silenced())
{
all_msg = "You hear a sizzling sound.";
msg_ch = MSGCH_SOUND;
}
else if (target->atype() == ACT_PLAYER)
you_msg = "You feel like you have heartburn.";
msg::stream << "Smoke pours from your "
<< your_hand(true) << "!" << std::endl;
big_cloud( random_smoke_type(), (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20, 7 + random2(7) );
you_msg = "Smoke pours from your @hands@!";
mon_msg_seen = "Smoke pours from @the_monster@'s @hands@!";
mon_msg_unseen = "Smoke appears out of nowhere!";
do_msg();
big_cloud( random_smoke_type(), kc, kt,
target->pos().x, target->pos().y, 20, 7 + random2(7) );
ouch( check_your_resists( 5 + random2avg(29, 2), BEAM_FIRE ), 0,
KILLED_BY_WILD_MAGIC, cause );
expose_player_to_element(BEAM_FIRE, 5);
_ouch(5 + random2avg(29, 2), BEAM_FIRE );
target->expose_to_element(BEAM_FIRE, 5);
mpr("You are caught in a fiery explosion!");
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 14 );
you_msg = "You are caught in a fiery explosion!";
mon_msg_seen = "@The_monster@ is caught in a fiery explosion!";
mon_msg_unseen = "Fire explodes from out of thin air!";
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 14 );
beam.target_x = you.x_pos;
beam.target_y = you.y_pos;
beam.name = "explosion";
beam.colour = RED;
beam.beam_source = NON_MONSTER;
beam.thrower = (cause) ? KILL_MISC : KILL_YOU;
beam.aux_source.clear();
if (cause)
beam.aux_source = cause;
beam.ex_size = 1;
beam.is_explosion = true;
explosion(beam);
beam.name = "explosion";
beam.colour = RED;
_explosion();
mpr("There is a sudden and violent explosion of flames!");
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 20 );
all_msg = "There is a sudden and violent explosion of flames!";
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 20 );
beam.target_x = you.x_pos;
beam.target_y = you.y_pos;
beam.name = "fireball";
beam.colour = RED;
beam.beam_source = NON_MONSTER;
beam.thrower = (cause) ? KILL_MISC : KILL_YOU;
beam.aux_source.clear();
if (cause)
beam.aux_source = cause;
beam.name = "fireball";
beam.colour = RED;
mpr("You are covered in liquid flames!");
you.duration[DUR_LIQUID_FLAMES] += random2avg(7, 3) + 1;
{
you_msg = "You are covered in liquid flames!";
mon_msg_seen = "@The_monster@ is covered in liquid flames!";
do_msg();
int dur = random2avg(7, 3) + 1;
if (target->atype() == ACT_PLAYER)
you.duration[DUR_LIQUID_FLAMES] += dur;
else
mon_target->add_ench( mon_enchant(ENCH_STICKY_FLAME, dur, kc) );
if (!silenced(you.pos()))
mpr("You hear a crackling sound.", MSGCH_SOUND);
else
mpr("A snowflake lands on your nose.");
if (neither_end_silenced())
{
all_msg = "You hear a crackling sound.";
msg_ch = MSGCH_SOUND;
}
else if (target->atype() == ACT_PLAYER)
you_msg = "A snowflake lands on your nose.";
mpr("You are covered in a thin layer of ice.");
expose_player_to_element(BEAM_COLD, 2);
you_msg = "You are covered in a thin layer of ice.";
mon_msg_seen = "@The_monster@ is covered in a thin layer of ice.";
mpr("You are caught in an explosion of ice and frost!");
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 11 );
you_msg = "You are caught in an explosion of ice and frost!";
mon_msg_seen = "@The_monster@ is caught in an explosion of "
"ice and frost!";
mon_msg_unseen = "Ice and frost explode from out of thin air!";
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 11 );
beam.target_x = you.x_pos;
beam.target_y = you.y_pos;
beam.name = "explosion";
beam.colour = WHITE;
beam.beam_source = NON_MONSTER;
beam.thrower = (cause) ? KILL_MISC : KILL_YOU;
beam.aux_source.clear();
if (cause)
beam.aux_source = cause;
beam.ex_size = 1;
beam.is_explosion = true;
beam.name = "explosion";
beam.colour = WHITE;
msg::stream << "Freezing gasses pour from your "
<< your_hand(true) << "!" << std::endl;
big_cloud(CLOUD_COLD, (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20, 8 + random2(4));
you_msg = "Freezing gasses pour from your @hands@!";
mon_msg_seen = "Freezing gasses pour from @the_monsters@'s "
" @hands@!";
do_msg();
big_cloud(CLOUD_COLD, kc, kt,
target->pos().x, target->pos().y, 20, 8 + random2(4));
if (!silenced(you.pos()))
mpr("You hear a distant rumble.", MSGCH_SOUND);
else
mpr("You sympathise with the stones.");
if (neither_end_silenced())
{
all_msg = "You hear a distant rumble.";
msg_ch = MSGCH_SOUND;
}
else if (target->atype() == ACT_PLAYER)
you_msg = "You sympathise with the stones.";
mprf("Your %s warm.",
(you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR)
? "lowest portion feels" :
(!transform_changed_physiology() ?
(player_mutation_level(MUT_HOOVES)) ? "hooves feel" :
(player_mutation_level(MUT_TALONS)) ? "talons feel" :
(you.species == SP_NAGA) ? "underbelly feels" :
(you.species == SP_MERFOLK
&& player_is_swimming()) ? "tail feels"
: "feet feel"
: "feet feel"));
{
bool pluralized = true;
std::string feet = you.foot_name(true, &pluralized);
std::ostringstream str;
str << "Your " << feet << (pluralized ? " feel" : " feels")
<< " warm";
you_msg = str.str();
// Monster messages needed.
mpr("You are caught in an explosion of flying shrapnel!");
you_msg = "You are caught in an explosion of flying "
"shrapnel!";
mon_msg_seen = "@The_monster@ is caught in an explosion of "
"flying shrapnel!";
mon_msg_unseen = "Flying shrapnel explodes from the thin air!";
beam.beam_source = NON_MONSTER;
beam.thrower = (cause) ? KILL_MISC : KILL_YOU;
beam.aux_source.clear();
if (cause)
beam.aux_source = cause;
beam.ex_size = 1;
beam.is_explosion = true;
explosion(beam);
_explosion();
msg::stream << "Sparks of electricity dance between your "
<< your_hand(true) << "." << std::endl;
{
bool pluralized = true;
target->hand_name(true, &pluralized);
if (pluralized)
{
you_msg = "Sparks of electricity dance between your "
"@hands@.";
mon_msg_seen = "Sparks of electricity dance between "
"@the_monster@'s @hands@.";
}
else
{
you_msg = "Sparks of electricity dance over your "
"@hand@.";
mon_msg_seen = "Sparks of electricity dance over "
"@the_monster@'s @hand@.";
}
mpr("Electricity courses through your body.");
ouch(check_your_resists(4 + random2avg(9, 2), BEAM_ELECTRICITY), 0,
KILLED_BY_WILD_MAGIC, cause);
you_msg = "Electricity courses through your body.";
// Monster messages needed.
_ouch(4 + random2avg(9, 2), BEAM_ELECTRICITY);
msg::stream << "Noxious gasses pour from your "
<< your_hand(true) << "!" << std::endl;
big_cloud(CLOUD_STINK, (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20, 9 + random2(4));
you_msg = "Noxious gasses pour from your @hands@!";
mon_msg_seen = "Noxious gasses pour from @the_monster@'s "
"@hands@!";
mon_msg_unseen = "Noxious gasses appear from out of thin air!";
do_msg();
big_cloud(CLOUD_STINK, kc, kt,
target->pos().x, target->pos().y, 20, 9 + random2(4));
mpr("You are caught in an explosion of electrical discharges!");
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 8 );
you_msg = "You are caught in an explosion of electrical "
"discharges!";
mon_msg_seen = "@The_monster@ is caught in an explosion of "
"electrical discharges!";
mon_msg_unseen = "Elecectrical discharges explodes from out of "
"thin air!";
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def( 3, 8 );
beam.target_x = you.x_pos;
beam.target_y = you.y_pos;
beam.name = "explosion";
beam.colour = LIGHTBLUE;
beam.beam_source = NON_MONSTER;
beam.thrower = (cause) ? KILL_MISC : KILL_YOU;
beam.aux_source.clear();
if (cause)
beam.aux_source = cause;
beam.name = "explosion";
beam.colour = LIGHTBLUE;
msg::stream << "Venomous gasses pour from your "
<< your_hand(true) << "!" << std::endl;
big_cloud( CLOUD_POISON, (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20, 8 + random2(5) );
you_msg = "Venomous gasses pour from your @hands@!";
mon_msg_seen = "Venomous gasses pour from @the_monster@'s "
"@hands@!";
mon_msg_unseen = "Venomous gasses pour forth from the thin air!";
do_msg();
big_cloud( CLOUD_POISON, kc, kt,
target->pos().x, target->pos().y, 20, 8 + random2(5) );
msg::stream << "Noxious gasses pour from your "
<< your_hand(true) << "!" << std::endl;
place_cloud(CLOUD_STINK, you.pos(),
2 + random2(4), (cause ? KC_OTHER : KC_YOU) );
you_msg = "Noxious gasses pour from your @hands@!";
mon_msg_seen = "Noxious gasses pour from @the_monster@'s "
"@hands@!";
mon_msg_unseen = "Noxious gasses pour forth from the thin air!";
place_cloud(CLOUD_STINK, target->pos(), 2 + random2(4), kc, kt );
mpr("Noxious gasses pour from your hands!");
big_cloud(CLOUD_STINK, (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20, 8 + random2(5));
you_msg = "Noxious gasses pour from your @hands@!";
mon_msg_seen = "Noxious gasses pour from @the_monster@'s "
"@hands@!";
mon_msg_unseen = "Noxious gasses pour forth from the thin air!";
do_msg();
big_cloud(CLOUD_STINK, kc, kt,
target->pos().x, target->pos().y, 20, 8 + random2(5));
msg::stream << "Venomous gasses pour from your "
<< your_hand(true) << "!" << std::endl;
big_cloud(CLOUD_POISON, (cause ? KC_OTHER : KC_YOU),
you.x_pos, you.y_pos, 20, 7 + random2(7));
you_msg = "Venomous gasses pour from your @hands@!";
mon_msg_seen = "Venomous gasses pour from @the_monster@'s @hands@!";
mon_msg_unseen = "Venomous gasses pour forth from the thin air!";
do_msg();
big_cloud(CLOUD_POISON, kc, kt,
target->pos().x, target->pos().y, 20, 7 + random2(7));
}
}
// sp_type: The type of the spell.
// mag_pow: The overall power of the spell or effect (i.e. its level).
// mag_fail: The degree to which you failed.
// force_effect: This forces a certain severity of effect to occur. It
// can be disabled by being set to 100.
//
// If a god is making you miscast, any monsters produced will count as
// god gifts.
void miscast_effect(unsigned int sp_type, int mag_pow, int mag_fail,
int force_effect, const char *cause)
{
if (sp_type == SPTYP_RANDOM)
sp_type = 1 << (random2(SPTYP_LAST_EXPONENT));
int sever = (mag_pow*mag_fail*(10+mag_pow)/7 * WILD_MAGIC_NASTINESS)/100;
if (force_effect == 100 && random2(40) > sever && random2(40) > sever)
{
canned_msg(MSG_NOTHING_HAPPENS);
return;
}
if (cause == NULL || strlen(cause) == 0)
cause = "spell miscasting";
sever /= 100;
#if DEBUG_DIAGNOSTICS
const int old_fail = sever;
#endif
sever = random2(sever);
if (sever > 3)
sever = 3;
else if (sever < 0)
sever = 0;
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Sptype: %u, failure1: %d, failure2: %d",
sp_type, old_fail, sever );
#endif
if (force_effect != 100)
sever = force_effect;
switch (sp_type)
{
case SPTYP_CONJURATION: _miscast_conjuration(sever, cause); break;
case SPTYP_ENCHANTMENT: _miscast_enchantment(sever, cause); break;
case SPTYP_TRANSLOCATION: _miscast_translocation(sever, cause); break;
case SPTYP_SUMMONING: _miscast_summoning(sever, cause); break;
case SPTYP_DIVINATION: _miscast_divination(sever, cause); break;
case SPTYP_NECROMANCY: _miscast_necromancy(sever, cause); break;
case SPTYP_TRANSMIGRATION: _miscast_transmigration(sever, cause); break;
case SPTYP_FIRE: _miscast_fire(sever, cause); break;
case SPTYP_ICE: _miscast_ice(sever, cause); break;
case SPTYP_EARTH: _miscast_earth(sever, cause); break;
case SPTYP_AIR: _miscast_air(sever, cause); break;
case SPTYP_POISON: _miscast_poison(sever, cause); break;
miscast_effect( SPTYP_NECROMANCY, 8, random2avg(88, 3), 100,
"reading the Necronomicon" );
MiscastEffect( &you, MISC_KNOWN_MISCAST, SPTYP_NECROMANCY,
8, random2avg(88, 3),
"reading the Necronomicon" );
miscast_effect( SPTYP_FIRE, 8 + you.experience_level,
random2avg(98, 3), 100, "the fiery rage of Trog" );
MiscastEffect( &you, -god, SPTYP_FIRE, 8 + you.experience_level,
random2avg(98, 3), "the fiery rage of Trog" );
miscast_effect( coinflip() ? SPTYP_CONJURATION : SPTYP_SUMMONING,
8 + you.experience_level, random2avg(98, 3), 100,
"the wrath of Vehumet" );
MiscastEffect( &you, -god,
coinflip() ? SPTYP_CONJURATION : SPTYP_SUMMONING,
8 + you.experience_level, random2avg(98, 3),
"the wrath of Vehumet" );
miscast_effect((coinflip() ? SPTYP_CONJURATION : SPTYP_SUMMONING),
8 + you.experience_level, random2avg(98, 3), 100,
"the wrath of Vehumet");
MiscastEffect(&you, -old_god,
(coinflip() ? SPTYP_CONJURATION : SPTYP_SUMMONING),
8 + you.experience_level, random2avg(98, 3),
"the wrath of Vehumet");
miscast_effect((coinflip() ? SPTYP_CONJURATION : SPTYP_SUMMONING),
8 + you.experience_level, random2avg(98, 3), 100,
"the fury of Makhleb");
MiscastEffect(&you, -old_god,
(coinflip() ? SPTYP_CONJURATION : SPTYP_SUMMONING),
8 + you.experience_level, random2avg(98, 3),
"the fury of Makhleb");
if (death_type == NUM_KILLBY)
switch(god)
{
case GOD_BEOGH:
death_type = KILLED_BY_BEOGH_SMITING;
break;
case GOD_SHINING_ONE:
death_type = KILLED_BY_TSO_SMITING;
break;
default:
death_type = KILLED_BY_DIVINE_WRATH;
break;
}
std::string aux;
if (death_type != KILLED_BY_BEOGH_SMITING
&& death_type != KILLED_BY_TSO_SMITING)
{
aux = "smited by ";
aux += god_name(god);
}
std::string player::foot_name(bool plural, bool *can_plural) const
{
bool _can_plural;
if (can_plural == NULL)
can_plural = &_can_plural;
*can_plural = true;
std::string str;
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR)
{
str = "lowest portion";
*can_plural = false;
}
else if (!transform_changed_physiology())
{
if (player_mutation_level(MUT_HOOVES))
str = "hoof";
else if (player_mutation_level(MUT_TALONS))
str = "talon";
else if (you.species == SP_NAGA)
{
str = "underbelly";
*can_plural = false;
}
else if (you.species == SP_MERFOLK && player_is_swimming())
{
str = "tail";
*can_plural = false;
}
}
if (str.empty())
return (plural ? "feet" : "foot");
if (plural && *can_plural)
str = pluralise(str);
return str;
}
// Determine trap effects upon monster, based upon
// whether it is naughty or nice to the player. {dlb}
// XXX: It seem that back when a beam's colour determined its
// flavour that Zot traps would heal, haste or make invisible
// hostile monsters. The code has been fixed to work but
// commented out.
#if 0
if (!mons_friendly(monster) && !mons_good_neutral(monster))
{
int temp_rand = random2(16);
// NB: beem[].colour values are mislabeled as colours (?) -
// cf. mons_ench_f2() [which are also mislabeled] {dlb}
temp_rand = random2(16);
beem.thrower = KILL_MON; // probably unnecessary
beem.aux_source.clear();
if (mons_friendly(monster))
{
beem.colour = ((temp_rand < 3) ? CYAN : //paralyze - 3 in 16
(temp_rand < 7) ? RED // confuse - 4 in 16
: BLACK); // slow - 9 in 16
beem.thrower = KILL_MON; // probably unnecessary
beem.aux_source.clear();
beem.flavour = ((temp_rand < 3) ? BEAM_HASTE : // 3 in 16 {dlb}
(temp_rand < 7) ? BEAM_INVISIBILITY //4 in 16 {dlb}
: BEAM_HEALING); // 9 in 16 {dlb}
mons_ench_f2(monster, beem);
else
{
beem.colour = ((temp_rand < 3) ? BLUE : //haste - 3 in 16 {dlb}
(temp_rand < 7) ? MAGENTA //invis - 4 in 16 {dlb}
: GREEN); // heal - 9 in 16 {dlb}
}
#endif
static void _mummy_curse(monsters* monster, killer_type killer, int index)
{
int pow;
switch(killer)
{
// Mummy killed by trap or something other than the player or
// another monster, so no curse.
case KILL_MISC:
// Mummy sent to the Abyss wasn't actually killed, so no curse.
case KILL_RESET:
case KILL_DISMISSED:
return;
default:
break;
}
switch(monster->type)
{
case MONS_MUMMY: pow = 1; break;
case MONS_GUARDIAN_MUMMY: pow = 3; break;
case MONS_MUMMY_PRIEST: pow = 8; break;
case MONS_GREATER_MUMMY: pow = 11; break;
default:
mpr("Unkown mummy type.", MSGCH_DIAGNOSTICS);
return;
}
void monster_die(monsters *monster, killer_type killer, int i, bool silent)
// Killed by a Zot trap, a god, etc
if (index != NON_MONSTER && invalid_monster_index(killer))
return;
actor* target;
if (index == NON_MONSTER)
target = dynamic_cast<actor*>(&you);
else
{
// Mummies committing suicide don't cause a death curse.
if (index == (int) monster_index(monster))
return;
target = dynamic_cast<actor*>(&menv[index]);
}
// Mummy was killed by a giant spore or ball lightning?
if (!target->alive())
return;
if (monster->type == MONS_MUMMY && killer == NON_MONSTER)
curse_an_item(true);
else
{
if (index == NON_MONSTER)
mpr("You feel extremely nervous for a moment...",
MSGCH_MONSTER_SPELL);
else if (you.can_see(target))
mprf(MSGCH_MONSTER_SPELL, "A malignant arua surrounds %s.",
target->name(DESC_NOCAP_THE).c_str());
MiscastEffect(target, monster_index(monster), SPTYP_NECROMANCY,
pow, random2avg(88, 3), "a mummy death curse");
}
}
void monster_die(monsters *monster, killer_type killer,
int killer_index, bool silent)
else if (monster->type == MONS_GUARDIAN_MUMMY
|| monster->type == MONS_GREATER_MUMMY
|| monster->type == MONS_MUMMY_PRIEST)
{
if (YOU_KILL(killer) && killer != KILL_YOU_CONF)
{
mpr("You feel extremely nervous for a moment...",
MSGCH_MONSTER_SPELL);
miscast_effect( SPTYP_NECROMANCY,
3 + (monster->type == MONS_GREATER_MUMMY) * 8
+ (monster->type == MONS_MUMMY_PRIEST) * 5,
random2avg(88, 3), 100, "a mummy death curse" );
}
}
std::string monsters::hand_name(bool plural, bool *can_plural) const
{
bool _can_plural;
if (can_plural == NULL)
can_plural = &_can_plural;
*can_plural = true;
std::string str;
char ch = mons_char(type);
switch(get_mon_shape(this))
{
case MON_SHAPE_CENTAUR:
case MON_SHAPE_NAGA:
// Defaults to "hand"
break;
case MON_SHAPE_HUMANOID:
case MON_SHAPE_HUMANOID_WINGED:
case MON_SHAPE_HUMANOID_TAILED:
case MON_SHAPE_HUMANOID_WINGED_TAILED:
if (ch == 'T' || ch == 'd' || ch == 'n' || mons_is_demon(type))
str = "claw";
break;
case MON_SHAPE_QUADRUPED:
case MON_SHAPE_QUADRUPED_TAILLESS:
case MON_SHAPE_QUADRUPED_WINGED:
case MON_SHAPE_ARACHNID:
if (type == MONS_SCORPION)
str = "pincer";
else
{
str = "front ";
return (str + foot_name(plural, can_plural));
}
break;
case MON_SHAPE_BLOB:
case MON_SHAPE_SNAKE:
case MON_SHAPE_FISH:
return foot_name(plural);
case MON_SHAPE_BAT:
str = "wing";
break;
case MON_SHAPE_INSECT:
case MON_SHAPE_INSECT_WINGED:
case MON_SHAPE_CENTIPEDE:
str = "antena";
break;
case MON_SHAPE_SNAIL:
str = "eye-stalk";
break;
case MON_SHAPE_PLANT:
str = "leaf";
break;
case MON_SHAPE_MISC:
if (ch == 'x' || ch == 'X')
{
str = "tentacle";
break;
}
// Deliberate fallthrough.
case MON_SHAPE_FUNGUS:
str = "body";
*can_plural = false;
break;
case MON_SHAPE_ORB:
switch(type)
{
case MONS_GIANT_SPORE:
str = "rhizome";
break;
case MONS_GIANT_EYEBALL:
case MONS_EYE_OF_DRAINING:
case MONS_SHINING_EYE:
case MONS_EYE_OF_DEVASTATION:
*can_plural = false;
// Deliberate fallthrough.
case MONS_GREAT_ORB_OF_EYES:
str = "pupil";
break;
case MONS_GIANT_ORANGE_BRAIN:
default:
str = "body";
can_plural = false;
break;
}
}
if (str.empty())
str = "hand";
if (plural && *can_plural)
str = pluralise(str);
return (str);
}
std::string monsters::foot_name(bool plural, bool *can_plural) const
{
bool _can_plural;
if (can_plural == NULL)
can_plural = &_can_plural;
*can_plural = true;
std::string str;
char ch = mons_char(type);
switch(get_mon_shape(this))
{
case MON_SHAPE_INSECT:
case MON_SHAPE_INSECT_WINGED:
case MON_SHAPE_ARACHNID:
case MON_SHAPE_CENTIPEDE:
str = "leg";
break;
case MON_SHAPE_HUMANOID:
case MON_SHAPE_HUMANOID_WINGED:
case MON_SHAPE_HUMANOID_TAILED:
case MON_SHAPE_HUMANOID_WINGED_TAILED:
if (type == MONS_MINOTAUR)
str = "hoof";
else if (swimming() && (type == MONS_MERFOLK || type == MONS_MERMAID))
{
str = "tail";
*can_plural = false;
}
break;
case MON_SHAPE_CENTAUR:
str = "hoof";
break;
case MON_SHAPE_QUADRUPED:
case MON_SHAPE_QUADRUPED_TAILLESS:
case MON_SHAPE_QUADRUPED_WINGED:
if (ch == 'h')
str = "paw";
else if (ch == 'l' || ch == 'D')
str = "talon";
else if (type == MONS_YAK || type == MONS_DEATH_YAK)
str = "hoof";
else if (ch == 'H')
{
if (type == MONS_MANTICORE || type == MONS_SPHINX)
str = "paw";
else
str = "talon";
}
break;
case MON_SHAPE_BAT:
str = "claw";
break;
case MON_SHAPE_SNAKE:
case MON_SHAPE_FISH:
str = "tail";
*can_plural = false;
break;
case MON_SHAPE_PLANT:
str = "root";
break;
case MON_SHAPE_FUNGUS:
str = "stem";
*can_plural = false;
break;
case MON_SHAPE_BLOB:
str = "pseudopod";
break;
case MON_SHAPE_MISC:
if (ch == 'x' || ch == 'X')
{
str = "tentacle";
break;
}
// Deliberate fallthrough.
case MON_SHAPE_SNAIL:
case MON_SHAPE_NAGA:
case MON_SHAPE_ORB:
str = "underside";
*can_plural = false;
break;
}
if (str.empty())
return (plural ? "feet" : "foot");
if (plural && *can_plural)
str = pluralise(str);
return (str);
}
miscast_effect( SPTYP_DIVINATION, random2(9),
random2(70), 100, "the Staff of Wucad Mu" );
// NH_NEVER prevents "nothing happens" messages.
MiscastEffect( &you, NON_MONSTER, SPTYP_DIVINATION,
random2(9), random2(70),
"the Staff of Wucad Mu", NH_NEVER );
miscast_effect( SPTYP_DIVINATION, random2(9), random2(70), 100,
"the Staff of Wucad Mu" );
MiscastEffect(attacker, MELEE_MISCAST, SPTYP_DIVINATION,
random2(9), random2(70), "the Staff of Wucad Mu");
virtual std::string name(description_level_type type) const = 0;
virtual std::string pronoun(pronoun_type which_pronoun) const = 0;
virtual std::string name(description_level_type type,
bool force_visible = false) const = 0;
virtual std::string pronoun(pronoun_type which_pronoun,
bool force_visible = false) const = 0;
std::string name(description_level_type type) const;
std::string pronoun(pronoun_type pro) const;
std::string name(description_level_type type,
bool force_visible = false) const;
std::string pronoun(pronoun_type pro, bool force_visible = false) const;
std::string name(description_level_type type) const;
std::string name(description_level_type type, bool force_visible) const;
std::string name(description_level_type type,
bool force_visible = false) const;
miscast_effect( SPTYP_RANDOM, random2(power/15) + 5,
random2(power), 0, "a card of wild magic" );
MiscastEffect( &you, god == GOD_NO_GOD ? NON_MONSTER : -god,
SPTYP_RANDOM, random2(power/15) + 5, random2(power),
"a card of wild magic" );
}
void debug_miscast( int target_index )
{
crawl_state.cancel_cmd_repeat();
actor* target;
if (target_index == NON_MONSTER)
target = dynamic_cast<actor*>(&you);
else
target = dynamic_cast<actor*>(&menv[target_index]);
if (!target->alive())
{
mpr("Can't make already dead target miscast.");
return;
}
char specs[100];
mpr( "Miscast which school or spell, by name? ", MSGCH_PROMPT );
if (cancelable_get_line(specs, sizeof specs) || !*specs)
{
canned_msg(MSG_OK);
return;
}
spell_type spell = spell_by_name(specs, true);
spschool_flag_type school = school_by_name(specs);
// Prefer exact matches for school name over partial matches for
// spell name.
if (school != SPTYP_NONE
&& (strcasecmp(specs, spelltype_short_name(school)) == 0
|| strcasecmp(specs, spelltype_long_name(school)) == 0))
{
spell = SPELL_NO_SPELL;
}
if (spell == SPELL_NO_SPELL && school == SPTYP_NONE)
{
mpr("No matching spell or spell school.");
return;
}
else if (spell != SPELL_NO_SPELL && school != SPTYP_NONE)
{
mprf("Ambiguous: can be spell '%s' or school '%s'.",
spell_title(spell), spelltype_short_name(school));
return;
}
int disciplines = 0;
if (spell != SPELL_NO_SPELL)
{
disciplines = get_spell_disciplines(spell);
if (disciplines == 0)
{
mprf("Spell '%s' has no disciplines.", spell_title(spell));
return;
}
}
if (school == SPTYP_HOLY || (disciplines & SPTYP_HOLY))
{
mpr("Can't miscast holy spells.");
return;
}
if (spell != SPELL_NO_SPELL)
mprf("Miscasting spell %s.", spell_title(spell));
else
mprf("Miscasting school %s.", spelltype_long_name(school));
if (spell != SPELL_NO_SPELL)
mpr( "Enter spell_power,spell_failure: ",
MSGCH_PROMPT );
else
mpr( "Enter miscast_level or spell_power,spell_failure: ",
MSGCH_PROMPT );
if (cancelable_get_line(specs, sizeof specs) || !*specs)
{
canned_msg(MSG_OK);
return;
}
int level = -1, pow = -1, fail = -1;
if (strchr(specs, ','))
{
std::vector<std::string> nums = split_string(",", specs);
pow = atoi(nums[0].c_str());
fail = atoi(nums[1].c_str());
if (pow <= 0 || fail <= 0)
{
canned_msg(MSG_OK);
return;
}
}
else
{
if (spell != SPELL_NO_SPELL)
{
mpr("Can only enter fixed miscast level for schools, not spells.");
return;
}
level = atoi(specs);
if (level < 0)
{
canned_msg(MSG_OK);
return;
}
else if (level > 3)
{
mpr("Miscast level can be at most 3.");
return;
}
}
// Handle repeats ourselves since miscasts are likely to interrupt
// command repetions, especially if the player is the target.
int repeats = _debug_prompt_for_int("Number of repitions? ", true);
if (repeats < 1)
{
canned_msg(MSG_OK);
return;
}
// Supress "nothing happens" message for monster miscasts which are
// only harmless messages, since a large number of those are missing
// monster messages.
nothing_happens_when_type nothing = NH_DEFAULT;
if (target_index != NON_MONSTER && level == 0)
nothing = NH_NEVER;
MiscastEffect *miscast;
if (spell != SPELL_NO_SPELL)
miscast = new MiscastEffect(target, target_index, spell, pow, fail,
"", nothing);
else
{
if (level != -1)
miscast = new MiscastEffect(target, target_index, school,
level, "wizard testing miscast",
nothing);
else
miscast = new MiscastEffect(target, target_index, school,
pow, fail, "wizard testing miscast",
nothing);
}
// Merely creating the miscast object causes one miscast effect to
// happen.
repeats--;
if (level != 0)
_miscast_screen_update();
while (target->alive() && repeats-- > 0)
{
miscast->do_miscast();
if (level != 0)
_miscast_screen_update();
}
delete miscast;
}
#endif
}
}
static bool _killer_whose_match(kill_category whose, killer_type killer)
{
switch(whose)
{
case KC_YOU:
return (killer == KILL_YOU_MISSILE || killer == KILL_YOU_CONF);
case KC_FRIENDLY:
return (killer == KILL_MON_MISSILE || killer == KILL_YOU_CONF);
case KC_OTHER:
return (killer == KILL_MON_MISSILE || killer == KILL_MISC);
case KC_NCATEGORIES:
ASSERT(false);
{
check_place_cloud(cl_type, p, lifetime, whose,
cloud_struct::whose_to_killer(whose), spread_rate);
}
// Places a cloud with the given stats assuming one doesn't already
// exist at that point.
void check_place_cloud( cloud_type cl_type, const coord_def& p, int lifetime,
killer_type killer, int spread_rate )
{
check_place_cloud(cl_type, p, lifetime,
cloud_struct::killer_to_whose(killer), killer,
spread_rate);
}
// Places a cloud with the given stats assuming one doesn't already
// exist at that point.
void check_place_cloud( cloud_type cl_type, const coord_def& p, int lifetime,
kill_category whose, killer_type killer,
int spread_rate )
place_cloud(cl_type, ctarget, cl_range, whose,
cloud_struct::whose_to_killer(whose), _spread_rate);
}
// Places a cloud with the given stats. May delete old clouds to
// make way if there are too many on level. Will overwrite an old
// cloud under some circumstances.
void place_cloud(cloud_type cl_type, const coord_def& ctarget, int cl_range,
killer_type killer, int _spread_rate)
{
place_cloud(cl_type, ctarget, cl_range,
cloud_struct::killer_to_whose(killer), killer, _spread_rate);
}
// Places a cloud with the given stats. May delete old clouds to
// make way if there are too many on level. Will overwrite an old
// cloud under some circumstances.
void place_cloud(cloud_type cl_type, const coord_def& ctarget, int cl_range,
kill_category whose, killer_type killer, int _spread_rate)
{
killer_type cloud_struct::beam_thrower() const
case KILL_MON:
case KILL_MON_MISSILE:
case KILL_MISC:
return (KC_OTHER);
default:
ASSERT(false);
}
return (KC_OTHER);
}
killer_type cloud_struct::whose_to_killer(kill_category whose)
{
switch(whose)
{
case KC_YOU: return(KILL_YOU_MISSILE);
case KC_FRIENDLY: return(KILL_MON_MISSILE);
case KC_OTHER: return(KILL_MON_MISSILE);
case KC_NCATEGORIES: ASSERT(false);
}
return (KILL_NONE);
}
void cloud_struct::set_whose(kill_category _whose)
{
whose = _whose;
killer = whose_to_killer(whose);
}
void cloud_struct::set_killer(killer_type _killer)
return (whose == KC_YOU ? KILL_YOU_MISSILE : KILL_MON_MISSILE);
killer = _killer;
whose = killer_to_whose(killer);
switch(killer)
{
case KILL_YOU:
killer = KILL_YOU_MISSILE;
break;
case KILL_MON:
killer = KILL_MON_MISSILE;
break;
default:
break;
}