However, there might be quite a few bugs lurking in this rewrite. Sorry.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7975 c06c8d41-db1a-0410-9941-cceddc491573
UADYVV3UD5ERJTZZJGY4EUQ4NJ2JSBG7YYUJ75ZRBIXRQXQKOJPAC
2YQEGNS3RIIBY5DO7ANRI2PB2GBHWYNYV3DSTG64L2CLYH2Z2XGQC
EOANLWX2F5FMHCLI5UTYPMSHV263EJXLGD3I7X4T44CS777EUPOQC
FC7M3JHVRPOLZP7YWOJN4O6YJV7KRJUMPLTI2Q4ANCO7GD23YKWQC
BP22J4CVS5UFOQNSKFLZMGK7S5AZ6JQPS6HUFR2UEWG4O276J4CAC
3YXZZKALHWPBJ6ZRUP3YNELIQJ5DH2UZPRUTL66OAHOCFXFS5QDQC
QIQSVJFUVSQ6E2NZSKWI244A5C34ZWGGQTYRAJGU4WMQMRYHVFGQC
4Z22LQD5JPBQV7TTYG6MOVJ4LFGH52WVWIHRH4CSCL4YMXRT7PRQC
XC2U46CC57VN7ZWKWSVTN2MNZ43RLMMDZKZT7MQJFXX247WQ3NIAC
PXPAQOXZCDJAY5GJ3RIJ3JS4NVDOG5TSY2EDLKSYHVT2HQ7YZJNQC
UMF43LSOAOVZ2PST2Q5PJBDBFHEJY5FPXSVLJ757XSQ5XHFJPL6QC
Z25GSDPPRTV5H4QDZY7QPDHKAUPVHDI7GF3YHQSAFPKZELECI73AC
YMYRTBUUGYHVIJOEHNRWT6UJED3YPTROISXBDAXYHH4D6XDERXAQC
HXD64VKRQK5JAZVOGMH4J32FZRMDVAMHIDRXHLVNXYUSUHU3XZRAC
OQN4KLXXQJHNCO2SUMCLQDXYBQOJWXOOPSHLFFA373QJ4HGONEVAC
EMOXOULEFTCWLQKIKQ64LXBS6KZLV4FCZ7HAPQLGHWOINK4WDFEQC
HJK5DHQWCCCVTRFF4TCHVK4CGFGJYX7BSLCVUEWW7WGKBRTFUJZQC
3DVNJFRXRU7PQVR6WMIFFUZ3SZNP7TFZXFE4AMHP6ORQKNVM4ITAC
F3STYUG6GEHXQX54YE6ZXXDJE3HWJ77SKHPL3F4GNKOQ43MEIRJQC
TXC3S7LAZKA77EIEYQEST57IZ6JYCDD4QQQMDK2GEX3IQXLPCGEAC
6FFCIPARVSQWQGQ7VVG46KIIHJWBMJAC7JZWTYNKNL7IB2OIK2OAC
YPQVOPLEBFZ6N3KJ2YEGK3XRNW4NYI7EHP2KZACH4S6N2UTTMWRAC
5V44JYVQ756CBA3CVAMEAT7PFEMZIH7RDFU64ZH7LF76K4XVNSOAC
JHC5PJCJ6WIEPJVU2VUZPROZ4WHQOYOF2LY6LCCHLLUHGHQSG32QC
4C4D3H4E5MDQ3KT5CWXN5K6H4FVT567PN66GAAHHIWVROHZWG5CQC
BKRA3JNP3QH7WRCYTYSJQ2KMFBX7TAOHBFUBJ44YAQVR47XUVJMAC
4VWPN7FFIBJIPHLOZC63BTJ65VQBU6GKYZ5XUMGFZW6WWRQBDZZQC
YAT3UPFOI5WPONPBRSXPTKPF73T5I6SJZLIAU3FBJSGS36ILMAJQC
5HBWEMQNKDWQWH4KU3MLLLUTY44UMC2SKEFKNFABTR6UHDMQVWGAC
XK3UF67LUGQZ2RGUSY5MW6W6VVVNERDYXR7CZMJS45RIVLW3A3DAC
UNUTE5NQZG3UCIAKPXV4JGI64H4NJUVUAM5Z7UMS3CB3SQXDDL3QC
Q52ILIIFMJ6HBSIUF7V7XQ6DQJGGYMHUZJ6CWVQKDX2JKBHEP2HAC
M53SMQWSTQBB24VSHBX7KJAAMMWGVKKPKV4TLMFHONWYYCNEDRGAC
2OY3EXIBFR22QCCKPZOA37YUI7CX7BEKRRYSDSBDKQN6VTDBD7LAC
6DNNPEMZGBQDMA7YG4LCTQUVZ7LYPC3R4A2XBYT5SDQ65GYOLJVAC
76LY6EUBLHLVPZAV27LOM4MEHIV7A6MSLR524UCLLGMJ2A52U6FAC
5MNRCFTWZ5CYLK2HCRMZUUMIXEFTI2KQAICVCFLZJ7F4TDN3IF5QC
ZIYM2CQGM7OOE6A3UGXAKDEQRHJHI2NGKWGAPB26TKHRBRVKOJTAC
KBIIMBVTWPMMUZLPLK2KJOXKSVMGUREIYJ57VCYVXDX5F23QXDEQC
KFULGQQOHWUTXOM3BXCCYPGGVGGY4Z6265XUFRCBPNLTZAEHJZSQC
5MGUZD2UACJCSG74TEZHI3Z4YL5KL6ZVUCQ3XVZKDOLKM7EMGWJAC
PFEJ4LMDNEKLMGRCMWQ7EIRVU4JMYGICI4G7X4WVWOROVXQCBZ7QC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
SIDH2P7NBIG5KEOE27XHD3ZT2NQ2OJZFN6VZXWNWYFFY5YVXSSVQC
3DQXSE4YGFBBDUWK4YEOFWW4UPWILWELFSLP37SL6BERGAZJC5YAC
2NVJIPJ5NMHUI2J4WOR6KE4XZOCJIVWHQK4M2M6KG7PL24PGBDGAC
4QRLZDW4KBFG34B3MCG4375NHFR3WKWSLWQKRMQ3OE5R26WCZBBQC
GSJA56E3ORVIBCBA6T6WU2HE4DCLJ6NZPW76O7L54N4CYPKLJOWQC
K33CV7EYR37TTSEXQWQ6QPHSEUFO545AIPUZOA2C47QTUCUWFPAAC
S5CIJLSFFXV445G6CH2AQ3YDU3P7RLSIHWV7GBJ4MZ4C26BWCY6AC
F6FFAD7G7HRTISYA2HABD5R5LRJAZYR5TE4GWMJPKI7EPM6SOPMAC
4CIYU7SAAGQJ6BH2WWS4EJWUS6GX25IV24GLYSPGDNWNASVJSIYAC
UAJN2CFA2QHYDHW2UFAVPPHDQFCD54RKM6V2UC4AMEDJUBBLNWIQC
H7HOAPMVNVME2LOADK6US3IEE22NVLVJJ4VKGNM3ZGMFI5HJKQ2AC
OIT7AR4IL2ZH4ISWXG3Y7Y24ICNQ5WSUFJLSUIYD7P5VGYU5BJLQC
BEPZL7D7JJHJUUDCVLGJRKKWM7NUAC7X2SU5XJMEASE6YTB53P3AC
ZEFGFQHN6J2S6EIPX7EPDG22C5YXTI6DMKQHHRCLWN5MQC44KY3AC
CDFS7Z74W5HKPQIHQICOG442KQFXUSBGGLDDQLE3MG74T44YU6AQC
W74555HMPXUQ72AGKBXC5P3STMMX5DZAW6ZESUDLNVJBCAG43PLAC
TQLWCGVXVZ75H7MDBJD3DJDUFNW62WOAEDJUVKCHQTAXKBP47CSAC
32PXX2XJVV7YSLLYNAVS7RYKYRAOQ565TZMTITSEPSSXOYPB5M2AC
BYURAML2EPTULHH22Q5RPXYGDEYAJYESR6C72UMNPPYHTCJZUPFAC
SFWCESFCUEVKJ6ZQQX3Y5YTIQD5BC6MCVSLVZFRGRTU46BFLKKWAC
3RNRFLMD2X4RUFTDVITLXAP377YB6F6YMQLL3DAXSUZDZBTWSLRQC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
KN5WFFRSNGHIGI5IUYRBOA4R4K7XK3AEAAMN6ELDFTSYKPN7QX7AC
WFED7ME7LXUZCZY3TWX7PCPW4EAA55W626CM2OOYVJTLI2BWFTVAC
JI4NDSOXGGZ7QHXXFB3ZTHAKHABXYBZXPDGLUFV5SKYEOL5FT7JQC
7AMQN7MITMXBNVDAK5VOXTQ4TZIAOD6ZLOFJG7GQMBTY23Y2BKSAC
Q6WLX2VTRJ3LGDPWBH5WKQM7CIIJ5H6AEIKMXPF2PJA5JX5ZLUZQC
3EUPIYJNWOMOQBP2Z5SGSMWK453BXJD6KL2WFTR3NM565MEBYASAC
GT7BSR54BVJKHUCLEBTELGBMNBFFDQW52EVC4XKVEMUVG2UGZMDAC
HJV7BZBM752K5I47ILBQJJXSPODBBLGKDX5DWJTRDXPJ3V7AEVWAC
TX3LTXGB7TJ2QHLW2TEW6XTCTOW5A3KTZGBPBRTDTUYOZFFT3NLAC
4FQAKUKUO6PCAZ3N4HUR5XL6E4VA5UQUZ3AEDGRBLVY7W2LMWI7QC
M43FB6FTERIXPSK26UW7MXRW4CORMAQDIL7NEHI67ODMSTITXUSQC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
74LQ7JXVLAFSHLI7LCBKFX47CNTYSKGUQSXNX5FCIUIGCC2JTR3QC
JYEEOUYQ7ZPKOGWUV7VCORBVSOLF2UCBFBH3TR75RGOSS6PNKYUAC
PI5BATR2SER3RFE76IUGHM2AGXVFOUM3PLU7WC2K2Q2BA5K2E73QC
C5VA63WAQRWPENIMXRPUPZLZJMC77PL2B3A77HYFWDCZU5QDG7VQC
RBAGQ2PB7V5YAM5KSHSZR2E3MLKDSRVM5XYGI2TIXP5QMVBOQHDQC
U3SFBWHMMMO2PY6PAX7FMSBSND4RCWGJR42HB47LNQXLOX7ZOB6QC
JRMVNXBOAVFIEKN3AP3EN63PO5ATZPM2MWQYGWZJFP2LCDOLU36AC
RQFQSU37Y3ZBFR634JWYHE77LIOOINGBRNL4KUHPPRA3ZR6W5QIAC
EMTKVLONOEPVWYJEMEXRPCVC4QKBLYMPETPU537M7GODCXRL77TQC
HIRKGUMNJPWKSVTR6TVBPD3MWNA63CEHCLCIPWEMGDFHVB3NPLDQC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
WBAFNYODKTL3YSG3UOJITBJSTFYGJLIWKRNK6NMGIIP5TPC2BDGQC
5BJPWUPLJFS34FUTFJVKA4A52YMIGV6EWDXLNSDCWBJWBGVSQFGQC
5FHWTG7M6FW4B3I33YI7QSM3OZIB6ZGC6TI6JISSLY5Y43HI56VAC
AJHVP42Y67SB4NKFMZB24524PGX2XA5WLFNEFV52MIGTS47ALMVQC
FUEEIUKGHHFPIRZCN3N753GONWAZTWQ2ZWR53IBJAAZ6FZUNGOMAC
PONOTAV3NEDNVGEJNPMLC5AY2BPV5E3ZHWDLNQCKCEBVKPBIY7QQC
2TZT4GURBCNHSVOXO5ZMN7XEIO3K4GWJCDPVSYUCMVZ6UFDL4DIQC
DKRSOHZXL6EPSLKOKHF7GJXSZEJVY7CXGACSHWLM5B5FTRETWWCAC
FLAGBNUNSIQNFDN53CDWABJRTTFWDL4PG34AI474ZKPXDEPYHOAQC
NDCVITU5R7TBEUC4FILNFRKWQGUNCJUGW5HA5KW3CQTU6AYRLYWQC
KAOE5HB3THUKVGFZRO5EZESHEB3Q34WUO5DFMLWIKOBF47LZTIYAC
TGJZXTUIAKCFZQJ54ZQEBGFBVZSJCAX6AWDRSH3TP7UJRLGUM5SAC
ZZCDMQ2W5QDHGTW3SA2PCMXIBBDMMTUBO4NHGYHXGXRBU4BO3CKAC
5K2ANIEXD3CPJM4XNKNPZINP2G4NT7SJBKRN62WNBUKJXFERTILQC
QNKMXCJSGRBEPE6ZNPEXU5EQIOME6EI4DECVC56GLOBCMGWWE6MQC
7YUGK5Q64KG5O7GJGTUBRRLHAHBCJ5YOE23YUPT6UBKUSB67CYAQC
AOAJ6D3OKSELEYKAT55XCVU5LYJ7SMCZKC6DIEGLLB3TF2LEENWQC
X5WLJCJVW55SXZVP7IKP7ADCJIGNKN4PKAXFECVR6TNK7XSMZR7QC
7HYUCUM7VRDLLOPHIEYMKGNXYFBCGCBQN4BOAFVD7U646KLEPHQQC
OFH2B2UZBK43QLFQBZ54FOCFLFNE54BZVDNCEUGDTBCUGNVZULIQC
WFMQVPMMOPG5SBJD5LUBOIYWRMXVWK3FXENK7SAEGZ5T6XWFKERQC
OPNCHI4UGN7WBIYPAXVV2C4N22ZSWROA435FJCY5UZVXKWRYQ42QC
AS2IQQJNNCEQNXXKTGYHLB7RO3ZKCF4F7GK6FJH66BOOKDDRGNIQC
S34LKQDIQJLIWVIPASOJBBZ6ZCXDHP5KPS7TRBZJSCDRVNCLK6UAC
FWR2IOUBSV4KSFV6YGTKZBTOD2DIUMFYBWHCIYLQJ5PCNAIETDPAC
L7G767VEPTZOILF3VTTKFAAZTJ4NPOLXCFUPF2FKJSGAGSUBW5NAC
JFDTUUUT6AKIGPBKGYFC7HZKGQUZOMG7EUWU4QYKSDVXXPQBBFHAC
AYU5OVG2HZO46KDAPKUWAVHS5HTYFKUWIMIRMTHAXVVFEDJE7YPAC
ZFRNNC6KLYRMKAQSRUIFAJR5LAOTKAULQB2D4ABIZKGZHGOA2XGQC
GVCGKTH5IJ4VSQEIN4CRC7ZFVZW26JPIYNCPTO7GY66CSZZEW3ZQC
JQ2PG3ZI7ISKGQ2BZYNIM2PEG4Z2EJHA4ZNKXEBVFBB6KDSHF7NAC
3USQPDXQMQUTNA3EZJUQ4NJMNPNF3IA7GVMOVG56P2XSVBWDHLXAC
25CH7HH4LKXFIZ75YNMXS3TSXO6O27DYSOPLOD45K4OCNFWLS4LQC
LFBNFE3PZBXTR2ROPKYPARUWLJAYWAKGTS7VBWADZWVVSJ5CLX6AC
VD4KDTGHVKCN35AWREYB4TEOUMCTW7SAUPAMTMF5ABC7VBHVKP4AC
VYGSRD6AGPW3JDTKAMFIUEERMWNCV35SPWXH75XCX2SCMMR72RQQC
ZGUJWUFJ4NFFJ6PGXLFGQWCWBCZHPWGWI44NJHJEVPRG5L36PADQC
VPZUNOEZYK7HPIB7XIWRBE4UNDIIN3S6STSWNLELVIZPF2GAFEUQC
JJULXW764V5C2HJKZNWQAEWB6QM5YZADD7ZCE35LYTBFEM6PMYCAC
WA2OSEGWVCESURYWNERNYWQKHE6SU4XQ4Q4HJY5B6ZPGNFUKQEEQC
OHJE4HQ2B3HVV4BOPBRFKO6HBRTLZEI74P5PLJF5GLSRD2MFWXHQC
ZUKTDWZ4R5VZLWO22E3RWAGTUF4HZ6DUXY65VLWQZVAFYJZGZP4QC
GVW4OBPGXY2Q75HB7QHADZIOHKL22FI2BSJ2TM4K5SBJENBFTQKAC
FCPBRFLI4FHMB3Q4AFIYPG22ALFOLIYFBODHNCW5O6PVMMD65YDQC
YHC3WZ5ATCJCQQ4CMWJA2IDPSY6FQZP7HJL6JI5WXETTY54BJU2AC
YZXHBEKWQPY4BTKG5FFGLP3AIRBQSTKQVLJJHWKQEA3HTN4UHDYQC
EOSO62ARVOII6OTUCOQB2VYCKYAOHPMVGVXKMUVY4YQSGU3PIW5AC
IHOKNI4O7EEW4UBMKHP6XOZNEIQSW4AYOT4O3JXUIXNNR553IQAQC
UZ5623MOLKBTGBSRBJ4OBOEI4IEZSPV3NCV2DRMUZ3CHHJQVHIIAC
LT7MICXP753TFF6I4LTWHZWCYEXO3LBRQ3H2TOXZFBXVHZTIYGNQC
72YZIOXMMSYZ5L6H7O3XGOUP4KTAPEUN2LMXMCXI73QL5B5ZDWCQC
2SNCC2NXKFVBYTWH7THU2QELHONTOBRNCWH6FWYORKKCWZAXTTVAC
Z3YHSEJ2ELRDG5DIDLP4MQSHNDRCTD5BSMVRBWOLF2S7TLRBGSBAC
VT3WFNEYL2JRNSKHN2OEOGCEL4LK4PIY3RXJS5TDY6MDZFF23ELAC
OMTU7OMVWDVAGJCQGQJDZ3YU252T6IM2LPS2ZMPWB7MIXCJK62AQC
MINMR3UAVXGVNIGZKS3SRYFCYDMCVEJZN7AGDWJNHQLR36FDXU7QC
C5U3HSOOQ7BKXKXIDS7MLVXUKDTHAWJ5NXNX6YDXTM3GWY5UWX4QC
LEBCZRNPIEYOSGSVK6Y5MUNX4TCL43RK22MG2STL2SPKYKOSPTEQC
NG53L53MSFQZAIVKHG54IEMXNJ33AYVPF2OZM4MMJFDKEJCGPBSAC
JW5AXME4PI4FNLNKE4NA5BK4FUWH2ZQQAWPVOGLQQBQI5QZNQUHAC
IDTLZ6PEPJP67PO7K5TODLXTBFAEVIE4C3HZCXIQK6OIAQKRVDUQC
QDBILCQHREAKSVXBJJGE2EPNR4ATTQWUZMV7BLJYZWWUI737TSKQC
RSIUBEQUGNU4LO6KH4PKVROWQS33DAKSY4XFVGN7T3CEKSXABCSAC
RCPQNIRPR3NQXEMVLBAQW6657UJFIP43N54UJVEZHJH22OH4UQIQC
OWERGKLVPNPGIIS23FZ7ZDOBWUIXCKYAFG3URXU75JAUDX3N5ENAC
4ZFFJ2D4IE2FXSGONGJWPOTHX4CFIGLVQOGZYV2KFAGYDUBJYCCQC
HMC247EGUJ3Q25DQ3VKUCIGLIO4SZORFQQWAPAF6S2WLQY3WU5TQC
B62LRY6EDGLIYGZCQC6UG76KGGCHQZUP6ZDFBPSWYF352ULQJQOQC
OMSSJON5IE4LBXJ7CZE52IZRZS7ZICS23SKP4AGLHH3QNCRMEFVAC
D2T32ZGFCJFZVJW7H2AEV3QD7Z4ZYKWEPOZTAFTAPDUTBGXNJXNAC
BSAKBTGIAD3JFZPNLMEI6SOJ2XPT4EJNETZYBW2555ASJTZ2GEGQC
2UO6ZOW7UCP5XJ2TJ26PJNQABILK2BZ4J3BAKLOKMZPJDQHW24MAC
5FMXUX2ZFIF6NQZCS54W7ZOCVSH7XR6UIMQ5FW2UZLEN4EWP5PSAC
QKGDOYIYKE6B36ION5O2DRW65DWWPZMYNWJVH7LJJ7FPGGM2MYAQC
QMPOKKUAUJ57DVDXG7CFWKICBKAE6NX27F7VS2RPXR7QMKDUQRSQC
NPTVMSNYWIALN2GSKESU6NKX7XNG7QDH2TNIWUB6R6NHCLK32NFAC
RWY7WOKVHCQFJYJZBNI74IB7W67465BZCA2YU3DE4MAUGHK4JBGQC
NQXYTPHC5ZBLJDIC5OVAUIDYYOXLPSGWUVD464ODH4KBHYGRDPPAC
DDU4A3JGN5IUIPP5IASOODKPR2WBHSDSV4FITZ6HNXNSXXQACWAQC
IZ5YT2GANAFYBACERZYTFQE2H66NM4Y3K5ADYNYF2IUADY23HJ4QC
4UXFU3FZOCBSLDQ4S7MJKAE2H7VUHCNRDQMIY6NJ3PHYXWNGISDQC
BRGAZR5AXWC2IALBVXO5SB354IRQEIRHSK55RZPGFI4AGIOD4LUQC
VGSJKJT74DZCMWQ6ETNAA33EDICJQRUOGSO6Y3QNZYIHYML7QMQQC
FFDIVQMLFB3FCJJO5PUEDNAF527HG6P4EGAV6LHUD6NNACZHGBUQC
QWZR7TCGSJAKK755AVEKFIMF5CX5GQLQC46TLI6URKZO2NJ3O26AC
5FZYO6GQ5DWJFMRHCFSLAI4NVGK2HJ6RE7BLHBVTNQJHN75K5LUQC
I5YD4LAFB2KHZEADJZKBJB346ZO36BSWKWZWGHBMZX7CC7UM2RIQC
CAGCTYIUYWDHQAJOLVLKOEV5HG6K5ZG7IDHONLIG6BDNCWZJAK4AC
SWE3F3BLKHYMJEJLXNFH3MR2E6VDEYXJ6DL7QLPP47BJEKEUCAMAC
AUXVWXWIFSTWFA6VZXN2FMG7FQEKRZVV6MD32VQQ7J2RKCXHAVGAC
ZNMT5CZHP2FC4HTLNA7KYEDGFBXSCUE5QHJOALVPE6RDPHSEDXRQC
J7GPW2YXLT6FGSKJ24FGQ24GTRZ6A2BDYM6CIXV4R6YBEHP6BGPAC
MJWFTUS66PTCNEYXEJA3CUJFXNWXIKDD6H3V24PW7HK64NSVOFSAC
B62ICMDTN5V7R7RBL4JALFVKEMVOOVLRSJASRNYS6CGFWBEEF5JQC
UH3CJQMQ3NPICXD34NTCTUZJWOCEF4P5XEGXFLLNDDFLZK7QPUBQC
HTR7KACUG3OB7ANWNQDJ2DDSAR2FQY6VLNLRAJ7OWW4P2AX5LOGAC
XMX2Y7QSEXGV2SPDOFDNM2BQJH3S3WTMYLJYUREYV72NWTURHMSQC
VK7T7J3IKTOVP7YHXPOWNYC2GC6MFEMU7O3ESQSB7PO7HQ7GCK6AC
45MYNXZEG5E2GIOGVN2JXQFLHC56WTCXPD2SW6S6LZJ643G24JHQC
ODCWOILHAMEQQVMGVUU6PWRSMHAINWP3MYFMPVMQBQZ5RYKGO45AC
AFJRUBE2RNVHRGRXDRBUIRTDCOGPHQIPAEHONGACXLCHC7JJZWPAC
WNUKUYBBSIL7AUP4XJAGUFMKBBMZ7QY5DS67OVI7JSPAUOAZJV3AC
OYANFNGKCXG2NLQMYW4TNFU3LFK3533XQ3HFGI3LNMY4JGMDMK5QC
5DMO2DEV7IK3MJG274EUNZBGQ7EBZWC2WTSKAEGPDMILX7BXYG3AC
RWCCZ64BG3HSOTM54ANIGENC3F3AIR42LJFRYSAKMCPCIUSOZY5QC
HH3HFWVXABJ4IRMN22PPJCREMULZSN6DA7VYKOGECGMNUQTZ5QNQC
TV3ZC6WOZKSQQJQN26JIVKCHK6UK7WMDBYZDUYRWEAZ4JB4YVNAAC
RIMJO42HB75SAZH44KZ2UH2QULOPIJLMEN4CR2CYNR2EKEKLFHSQC
ZM4MC3B3VK22S3VI76JIXIGFMLB4FPMQUEBNUROYF2GG5KAFLW5QC
YMLVBQ6M27MECUVMU3BQP3WSGR7GW4XJMQIHLGHHWMVXHMMIXOYAC
FNY62WM76UGY6IZQYRTRLSBLLUKUHL5N4CG5HQRSWTU2MYIXNJ5QC
5BPJZOGZ6VTUNTJXBTP22KIPGT5NNNSXZVW5J2BGQYBP4MTCZ3CQC
I2B33Z7NZGC33AMDSSK446AZZYWKPHWLAGULVHKKZU4MVB4BNJOAC
TPSCOEB3X6LNZ7F5UBMHDZ4IUE4H3RLX6IGNW65VSJBDMNNZ3FMAC
BXYKBTK36VL22UA6PMF4Z3SCGZEUDBFDJPPLWC7CQTQC55NPTDEAC
3ZWALZFSTSIVYXY4BAY6ANGINTDACZC6RSSJTEMQSTSUIE66YOBQC
XCPNY2Q3F3ELK3L2FRCXHVLANQ7JLB47VBIFIUME5I2PCXNRA3ZAC
6SSW2WIX3LUPDP2QI6Z75YUE53G5H3CZBAPCQA7OEQEPOCIXT73AC
YX2LDGNQNB6AQRKAVXNYQ473X6EVPQEBT5AJKBIIWFIMS3U2BNQQC
CVONAX7DEKDLWPFR6SSOZRTKRDKVXGKURYU6SQAFGG4EGCUTDM5AC
NW74NALMBEWIKEOBIXA65RQULHS6M4GZ4S5IWDMEGWUAAAY7CQNQC
EJYK3CKIEMP4NMZUCMUYLHIBE7A3LPZOU7DUON5V7CPHY7QEKLEAC
OAQEGSRMAOE4UHILW4LSDKERKTPOJPGPJQ3R2FB5QFWFB764UGHQC
5E2WUTWF247HPQ4BT5APN2LKXAG3QCKQRDADSJ4BCYYZ5HSWBHAQC
FCZSQBKDNMJZRJS2LWQQWLUFGOXSKXDJZQIHC7L5S7HXCXQPOMMAC
6GT5JAWOIIL4SQ5MWIID6ZVO3KKQFWDQDZNVFHZ6DNK5QCBXJ4UAC
ACKNLTFL2RI3PMRWLNRVLRWGQAMLRFKNGNS5LED6NFE5GVGFIHFAC
DLVXY6DBP65B3RGWW4PNBEBPBNHNM3LSRYD36F3XDARFY7SPSJYAC
ENOQQ6DEA6ECRNTBGYYNK7G3DFEILMKQBNKP4SUQIZW2L6HWVR7QC
A5YOVQMFZRMT6NK7YUQI632TWC3CFATXDNCSB7M33EEYB62SA3XAC
GBUB77EAYHOFY6GQ5IY3ZSBC7FSQFZZKYNBD5QMCQFIKFLYLWHOQC
KBBSDMAXAGEDFMIVL6BSPMMSOBZKWPJTFCLBGCHRLE5DGTVAACSAC
FFHV7U5Z42CKIQQDFZB2LBFDTBEQKS5TL3376CJKGAIY4RNMJVNAC
IMC2HA2YWCU7SLGGVQQVUEUIST4ILSOEAUYXNZAW2PJZ475GXLDAC
DUBEHQIQABGLXLFIVZ6WXBMFGLFKFRBOCK5LZ6ECUSRM3V2V35NAC
SRVIJUOE4SDVJJCIOZ3H6X4ZW6GZG6ICDRG5TBICVQHUVSR675NQC
E6NTYF4PVIESKDOXXSDV6DFBAGOLBHE66HAJIGG76ZWPNHURKIIAC
PHBACPMH3F34GODHVDKNCMXWU373RJQGVTDLBFCCDLLWDXVYOLTAC
PWY4VZVHDLYL7UVNCCOW7BM7LYK2BOGTL23P75HXUJ33MHJPEJPQC
M4DJQJHLPTFQNOI6S5TDLCTGNQK5SW7H5PTG5M7YWHRGWZGJ72QQC
TCZNXMPFNAWJYUIBTUEOTT73KLB4CU5S6HBKTHRT6KCD4ZVGBGUQC
PQ5IPCENBLGCTUKM7AHXQKUCALLLAX5KAM3OQLAWFY2RDX57NYYQC
5ZVVZ3W5F5QC4TCRFXJ2OKY7YKCJ4HQXP2HI63DPXOULBS5ENOKQC
A5UFFILFAQTJRKKD6YPXVFRLMDRNQ5N3LW57Q3VWRPGD232CCG4AC
DT6OBXKHMLODEFK6UOIYCR4EJSVJKGYWCM35BVM5SN55K2736QHQC
MG6LLF3XYCOEBQRX7TJ4MUTKM3IROYWUMZGCMYVW4TGDG36CJMJQC
B3MFLKPJ34I2USQX7UM3SPDIFVGACB5HXYLEKT3FDULL57QOE2LAC
2CEV66TXUER5NK3NRZAQGWL7X5ES6LPMOSSO4RU25LPJIUC3XGSQC
6MAMXLPWLW5IXKQXGUNSKNZNHZM7U24GQNN4IWZL2FKKGAKL23YAC
IGN3Q5YCLHXKWFZQRSRABCHW2JFW5TFVU7TM5EXE2EW7PDBBSQWAC
AIVXE6QBRVCZAASKQRZO6LBDGTYEYSSD2DZCWRX4VLSKE3GCNIDAC
P5ID2SF6YHSZ75EEU4CQ25HCZ74CWU2JQ2FH536KZEKD4DO3M3FQC
FBJB2WMCYNDOICZJZT356VEMMD5L2FFRTKUARKSPIWIM2LHGBLTQC
MFCHFDPW4YX4QZWUOJIT3UG6OHIJPWZRXLYZQCSAUQFCEPYOYHEAC
5JCN5YNO7MROLVA6X2TBQROHP6QFOKVP3CDFNPW6IQ7HZT2DXU3QC
CGYTZT5QWIEGYKUOLOK7MFXSLJKLYRZONER5ZCDZO5XYWSLG475QC
ZO2PZE57FZOUNGR2MEA3ZRRC7DEC7R3NMXLHZMGWROSIYKH2FAWAC
BX6P2BQYNZH2FM4ADLPULHK5FCWEAVXFEUYNHYLCUTSQCOWGNWAQC
B6X2JQ6LF5EEDKFOXKX4RQYH6GW3LXB5E4WTIUVHSXTO4TIZA24QC
GND5VCGRS2LOJKOJ2QZRENNE3CA3R7OXGGNS7KYPRZHCSX4LZKVQC
PIGVTTBAQGNHOCX6IXJLGVVURBAT77SD6FQUVTTI5SFTV563GMZQC
O2A7I25VYURCVDZEGRMFCP6X6DEYT4EB3TZESZSDCDDJE2U7QYVAC
TWYB52EBZRZEABTKQ7D6OWKMDKIGACWDUGNH5LP47QX4SKC3SFYAC
MZXKH5NHXRMX5Y5FG4XXNHZQ7HTQZFX3ARQU55DO4RM5NR3GIAIQC
FZKMVCODMWQEVVBBQHTTXFBRO4LZEYLB646ZPVUMLHH6EER7FAOAC
6PAG7GHXHIYXJPPTEK4KZQZT4CL2SJDAGTVIUDB4KK66PVSTWUMAC
E5DMZFW6WCFAKTKKOQPYTQXZ2CGLWMVH64LRXDUI2UIG4VYUHIVQC
FA2V3G4NYTWJWUT7EWH75L3YOUX6YVOZ5LNRSFLO2XF273JKSUTAC
X76YXE6RFL7QY5WL6MSSS44WHVA5FQMDQY3XTYP2UL6UZB4E6XMQC
4QNFSNLH7PRJPQII4LN63KBSCAITKYAFJ6EV7AKNS3MFO2IMWYGQC
S26C7QFN3OBMIHF5XRB5PKRCQVBY22OLUIJKTD4CEMHKWCWE457AC
QYQKV4R47PTERXVFQNNWWQVICGSOMBHW6WM5TAZAKLIYOLLPUAJAC
46I36AWFYIFDOUPKFEMJSJTPQ27CQP7FNKOLT3ZOBUI6SF4EL7RQC
5ZTUKFQSIU3NVZGNX7MPQQ3IZVXRCWB5EZSQSYEXXA4G5UFCFPTQC
SUIDQIK6BZDJSYSFG6AK5A6K76EKGPKBKGGRCIU2AN6XOTCUHOBAC
X6W4FXCQ72PYSDPGMQCZ44QQANZFGTXPUHDC5GW4YFSJ2KPBVAMAC
GO6DARZD47R5UPXZIBMPX7PZUGJH24JWYDFMTWWESS4IZR7PAWRQC
BNRY5YIXLFE2TDNU2JQHWWXJQVWNSEWQ52DU7XUWIT5DZWKGBDDAC
EQDLV5OMIFO5ZPYNE27VQLLZEIRMSALGNEFWVSCFXJN22A43GCWQC
2U47I7M3TKXUM3KHMO3UH7KGMRKOGHJQFVYPC4CTAWBV22T2HTEQC
YCL3W2PFE6ILTGBFODCSXNPDIA46KVSZP2TI7HDMYAOEJT65RIEAC
BTAITFZ3R4D5YR62MU7ECSBQQHRVHEJMXGSHV5HXWPOJDQD4SD7QC
35TYXBLIHC45BQJX6VNVICEVZ35KR2VRA7EWW2JWVHX4XZPTHGPQC
BJPPSWEN35BG4KP3XTXPDMAJ2GAUMHXKHCNALAZ4B4OS6B3KDSUQC
NULVQ2A7ES5N3BBQAH6YKFST6UPVKD4KYYFFHYTUKSYLHXR4OYSAC
LUH6GAJODIQXNPBEHMSUTKH3KA3DYVRCKZ6GJJ4HHNHOIMMUEP6QC
BTO5WE4OEK64DZQVNYLOESM74KKQNE7KSNMQVF5UDB26OBKP7ORQC
SUWIERONPDATHPDMZRYO6GYIXSW6XIS5V5MK5IV23DWQH2LL7VIAC
64VBM7SGUX7CVO5TMVOFU4A26BDOFQXKS6G5K7BXCSWKCCXEETOAC
HAM54HXIO2245W6REO4RZDY2QMIH476AWWJSMYAMSYYNEBBJSHWAC
7TK2D5RI45FKCIQBHGU4FA75NSFZA3ZYFF2UYQJEGHCOV7I4XUGQC
QCIPZ52TOIKLA6O22P7USLEAYI4VAZRLGVXMAASSGEP37KN5PEVAC
7RE7J5DEMEO52RKOE77JMEXKZ7ORJJMCRGKLC6NHYDJW66UKYE4AC
XI5VME7HFX6PID3Y4PRICAALEXTJET7NEDI4ANYGWLFDZTBVA2LQC
22RFWMSJGG26Z2MQEEXGKVTFSTLREHQIG46WYOTMDRKI5YVMRNVAC
G3BHIB4AOVZ7P4DF7RMRI2C2O6HPSJYRMYQLF2LYBFLKHN6UUIZAC
3KRKUK3HCBBZWS5MXFAMM4R5PGWWTSURBSFXEEZPRRCFAZ5EZTCAC
DENP72AB5VYMFLNU3VQULFIAWZAXOK4K554O5GBMZPHMVBUTO53AC
ZI7643NG3LOKVSDXHFNV2YU3ZIQUVEY25AOXZCPBXJB5SQAUCE6QC
HV677YP4RLSPSLAUEIFGCFLTBAPMN2WUWM74C4TR7MUC5ABCD4EAC
int wpn = you.equip[EQ_WEAPON];
if (wpn != -1
&& you.inv[wpn].base_type == OBJ_WEAPONS
&& get_weapon_brand( you.inv[wpn] ) == SPWPN_ORC_SLAYING
if (you.weapon()
&& you.weapon()->base_type == OBJ_WEAPONS
&& get_weapon_brand( *you.weapon() ) == SPWPN_ORC_SLAYING
// Returns random2(x) if random_factor is true, otherwise the mean.
static int maybe_random2( int x, bool random_factor )
{
if (random_factor)
return random2(x);
else
return x / 2;
}
bool is_enchantment() const; // no block/dodge, but mag resist
void set_target(const dist &);
bool is_enchantment() const; // no block/dodge, use magic resist
void set_target(const dist &targ);
// Assume that all saving throws are failed, actually apply
// the enchantment.
mon_resist_type apply_enchantment_to_monster(monsters* mon);
// Return whether any affected cell was seen.
bool explode(bool show_more = true, bool hole_in_the_middle = false);
private:
void do_fire();
coord_def pos() const;
// Lots of properties of the beam.
bool is_blockable() const;
bool is_superhot() const;
bool is_fiery() const;
bool affects_wall(dungeon_feature_type wall) const;
bool is_bouncy(dungeon_feature_type feat) const;
bool can_affect_wall_monster(const monsters* mon) const;
bool stop_at_target() const;
bool invisible() const;
bool has_saving_throw() const;
bool is_harmless(const monsters *mon) const;
bool harmless_to_player() const;
bool is_reflectable(const item_def *item) const;
bool nasty_to(const monsters* mon) const;
bool nice_to(const monsters* mon) const;
bool found_player() const;
int beam_source_as_target() const;
int range_used_on_hit() const;
std::string zapper() const;
std::set<std::string> message_cache;
void emit_message(msg_channel_type chan, const char* msg);
void step();
void hit_wall();
// Functions which handle actually affecting things. They all
// operate on the beam's current position (i.e., whatever pos()
// returns.)
public:
void affect_cell();
void affect_wall();
void affect_monster( monsters* m );
void affect_player();
void affect_ground();
void affect_place_clouds();
void affect_place_explosion_clouds();
void affect_endpoint();
// Stuff when a monster or player is hit.
void affect_player_enchantment();
void tracer_affect_player();
void tracer_affect_monster(monsters* mon);
bool handle_statue_disintegration(monsters* mon);
void apply_bolt_paralysis(monsters *monster);
void apply_bolt_petrify(monsters *monster);
void enchantment_affect_monster(monsters* mon);
mon_resist_type try_enchant_monster(monsters *mon);
void tracer_enchantment_affect_monster(monsters* mon);
void tracer_nonenchantment_affect_monster(monsters* mon);
void update_hurt_or_helped(monsters *mon);
bool attempt_block(monsters* mon);
void handle_stop_attack_prompt(monsters* mon);
bool determine_damage(monsters* mon, int& preac, int& postac, int& final);
void monster_post_hit(monsters* mon, int dmg);
bool misses_player();
void initialize_fire();
void apply_beam_conducts();
void choose_ray();
void draw(const coord_def& p);
void bounce();
void reflect();
void fake_flavour();
void digging_wall_effect();
void fire_wall_effect();
void nuke_wall_effect();
void drop_object();
void finish_beam();
bool fuzz_invis_tracer();
void internal_ouch(int dam);
// Various explosion-related stuff.
void refine_for_explosion();
void explosion_draw_cell(const coord_def& p);
void explosion_affect_cell(const coord_def& p);
void determine_affected_cells(explosion_map& m, const coord_def& delta,
int count, int r,
bool stop_at_statues, bool stop_at_walls);
static bool _affects_wall(const bolt &beam, int wall_feature);
static bool _isBouncy(bolt &beam, unsigned char gridtype);
static int _beam_source(const bolt &beam);
static std::string _beam_zapper(const bolt &beam);
static bool _beam_term_on_target(bolt &beam, const coord_def& p);
static void _beam_explodes(bolt &beam, const coord_def& p);
static int _affect_wall(bolt &beam, const coord_def& p);
static int _affect_place_clouds(bolt &beam, const coord_def& p);
static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p);
static int _affect_player(bolt &beam, item_def *item = NULL,
bool affect_items = true);
static int _affect_monster(bolt &beam, monsters *mon, item_def *item = NULL);
static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon);
static void _beam_paralyses_monster( bolt &pbolt, monsters *monster );
static void _beam_petrifies_monster( bolt &pbolt, monsters *monster );
static int _range_used_on_hit(bolt &beam);
static void _explosion1(bolt &pbolt);
static void _explosion_map(bolt &beam, const coord_def& p,
int count, int r);
static void _explosion_cell(bolt &beam, const coord_def& p, bool drawOnly,
bool affect_items = true);
va_list args;
va_start(args, s);
char buf[500];
vsnprintf(buf, sizeof buf, s, args);
va_end(args);
std::string message = buf;
if (beam_message_cache.find(message) == beam_message_cache.end())
mpr(message.c_str(), channel);
const std::string message = m;
if (message_cache.find(message) == message_cache.end())
mpr(m, chan);
if (pbolt.is_enchantment()
|| (!pbolt.is_explosion && !pbolt.is_big_cloud
&& (grd(where) == DNGN_METAL_WALL
|| pbolt.flavour != BEAM_ELECTRICITY)))
{
if (!mons_wall_shielded(&menv[mid]))
return (true);
}
if (is_enchantment() || (!is_explosion && !is_big_cloud))
return (true);
/*
* Beam pseudo code:
*
* 1. Calculate stepx and stepy - algorithms depend on finding a step axis
* which results in a line of rise 1 or less (ie 45 degrees or less)
* 2. Calculate range. Tracers always have max range, otherwise the beam
* will have somewhere between range and rangeMax
* 3. Loop tracing out the line:
* 3a. Check for walls and wall affecting beams
* 3b. If no valid move is found, try a fuzzy move
* 3c. If no valid move is yet found, try bouncing
* 3d. If no valid move or bounce is found, break
* 4. Check for beam termination on target
* 5. Affect the cell which the beam just moved into -> affect()
* 6. If the beam was reflected during affect() then return, since
* a recursive call to fire_beam() took care of the rest.
* 7. Decrease remaining range appropriately
* 8. Check for early out due to aimed_at_feet
* 9. Draw the beam
*10. Drop an object where the beam 'landed'
*11. Beams explode where the beam 'landed'
*12. If no message generated yet, send "nothing happens" (enchantments only)
*
*/
bool bolt::invisible() const
{
return (type == 0 || is_enchantment());
}
ASSERT(pbolt.flavour > BEAM_NONE && pbolt.flavour < BEAM_FIRST_PSEUDO);
ASSERT(!pbolt.drop_item || pbolt.item);
ASSERT(!pbolt.dropped_item);
ASSERT(flavour > BEAM_NONE && flavour < BEAM_FIRST_PSEUDO);
ASSERT(!drop_item || item);
ASSERT(range >= 0);
const int reflections = pbolt.reflections;
if (reflections == 0)
// seen might be set by caller to supress this.
if (!seen && see_grid(source) && range > 0 && !invisible() )
// pbolt.seen might be set by caller to supress this.
if (!pbolt.seen && see_grid(pbolt.source) && pbolt.range > 0
&& !beam_invisible)
if (flavour != BEAM_VISUAL
&& !is_tracer
&& !YOU_KILL(thrower)
&& !crawl_state.is_god_acting()
&& (midx == NON_MONSTER || !you.can_see(&menv[midx])))
pbolt.seen = true;
int midx = mgrd(pbolt.source);
if (pbolt.flavour != BEAM_VISUAL && !pbolt.is_tracer
&& !YOU_KILL(pbolt.thrower) && !crawl_state.is_god_acting()
&& (midx == NON_MONSTER || !you.can_see(&menv[midx])))
{
mprf("%s appears from out of thin air!",
article_a(pbolt.name, false).c_str());
}
mprf("%s appears from out of thin air!",
article_a(name, false).c_str());
if (pbolt.range == pbolt.range_used && pbolt.range > 0)
{
#ifdef DEBUG
mprf(MSGCH_DIAGNOSTICS, "fire_beam() called on already done beam "
"'%s' (item = '%s')", pbolt.name.c_str(),
pbolt.item ? pbolt.item->name(DESC_PLAIN).c_str() : "none");
#if DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS, "%s%s%s [%s] (%d,%d) to (%d,%d): "
"ty=%d col=%d flav=%d hit=%d dam=%dd%d range=%d",
(is_beam) ? "beam" : "missile",
(is_explosion) ? "*" :
(is_big_cloud) ? "+" : "",
(is_tracer) ? " tracer" : "",
name.c_str(),
source.x, source.y,
target.x, target.y,
type, colour, flavour,
hit, damage.num, damage.size,
range);
bool beamTerminate; // Has beam been 'stopped' by something?
coord_def &testpos(pbolt.pos);
bool did_bounce = false;
cursor_control coff(false);
void bolt::choose_ray()
{
if (!chose_ray || reflections > 0)
{
ray.fullray_idx = -1; // to quiet valgrind
find_ray( source, target, true, ray, 0, true );
}
}
// [ds] Forcing the beam out of explosion phase here - currently
// no caller relies on the beam already being in_explosion_phase.
// This fixes beams being in explosion after use as a tracer.
pbolt.in_explosion_phase = false;
// Draw the bolt at p if needed.
void bolt::draw(const coord_def& p)
{
if (is_tracer || is_enchantment() || !see_grid(p))
return;
// We don't clean up the old position.
// First, most people like to see the full path,
// and second, it is hard to do it right with
// respect to killed monsters, cloud trails, etc.
const coord_def drawpos = grid2view(p);
#if DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS, "%s%s%s [%s] (%d,%d) to (%d,%d): "
"ty=%d col=%d flav=%d hit=%d dam=%dd%d range=%d",
(pbolt.is_beam) ? "beam" : "missile",
(pbolt.is_explosion) ? "*" :
(pbolt.is_big_cloud) ? "+" : "",
(pbolt.is_tracer) ? " tracer" : "",
pbolt.name.c_str(),
pbolt.source.x, pbolt.source.y,
pbolt.target.x, pbolt.target.y,
pbolt.type, pbolt.colour, pbolt.flavour,
pbolt.hit, pbolt.damage.num, pbolt.damage.size,
pbolt.range);
{
// bounds check
if (in_los_bounds(drawpos))
{
#ifndef USE_TILE
cgotoxy(drawpos.x, drawpos.y);
put_colour_ch(
colour == BLACK ? random_colour() : element_colour(colour),
type);
// init
pbolt.aimed_at_feet = (pbolt.target == pbolt.source);
pbolt.msg_generated = false;
ray_def ray;
// Get curses to update the screen so we can see the beam.
update_screen();
delay(draw_delay);
}
}
}
ray.fullray_idx = -1; // to quiet valgrind
find_ray( pbolt.source, pbolt.target, true, ray, 0, true );
}
do {
ray.regress();
} while (grid_is_solid(grd(ray.pos())));
bounce_pos = ray.pos();
ray.advance_and_bounce();
range_used += 2;
} while (range_used < range && grid_is_solid(grd(ray.pos())));
// Give chance for beam to affect one cell even if aimed_at_feet.
beamTerminate = false;
void bolt::fake_flavour()
{
if (real_flavour == BEAM_RANDOM)
flavour = static_cast<beam_type>(random_range(BEAM_FIRE, BEAM_ACID));
else if (real_flavour == BEAM_CHAOS)
flavour = _chaos_beam_flavour();
}
// Before we start drawing the beam, turn buffering off.
#ifdef WIN32CONSOLE
bool oldValue = true;
if (!pbolt.is_tracer)
oldValue = set_buffering(false);
#endif
while (!beamTerminate)
void bolt::digging_wall_effect()
{
const dungeon_feature_type feat = grd(pos());
if (feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL)
testpos = ray.pos();
// Random beams: randomize before affect().
if (pbolt.real_flavour == BEAM_RANDOM)
pbolt.flavour = static_cast<beam_type>(
random_range(BEAM_FIRE, BEAM_ACID));
else if (pbolt.real_flavour == BEAM_CHAOS)
pbolt.flavour = _chaos_beam_flavour();
grd(pos()) = DNGN_FLOOR;
// Mark terrain as changed so travel excludes can be updated
// as necessary.
// XXX: This doesn't work for some reason: after digging
// the wrong grids are marked excluded.
set_terrain_changed(pos());
// Shooting through clouds affects accuracy.
if (env.cgrid(testpos) != EMPTY_CLOUD)
pbolt.hit = std::max(pbolt.hit - 2, 0);
// Blood does not transfer onto floor.
if (is_bloodcovered(pos()))
env.map(pos()).property &= ~(FPROP_BLOODY);
// Should we ever get a tracer with a wall-affecting
// beam (possible I suppose), we'll quit tracing now.
if (!pbolt.is_tracer)
{
(void) affect(pbolt, testpos);
if (pbolt.reflections > reflections)
return;
}
// If it's still a wall, quit.
if (grid_is_solid(grd(testpos)))
break; // breaks from line tracing
mpr("You hear a grinding noise.", MSGCH_SOUND);
obvious_effect = true;
else
{
// BEGIN bounce case. Bouncing protects any monster
// in the wall.
if (!_isBouncy(pbolt, grd(testpos)))
{
// Affect any monster that might be in the wall.
(void) affect(pbolt, testpos);
if (pbolt.reflections > reflections)
return;
msg_generated = true;
}
}
else if (grid_is_wall(feat))
{
finish_beam();
}
}
do
{
ray.regress();
pbolt.bounce_pos = ray.pos();
}
while (grid_is_solid(grd(ray.pos())));
void bolt::fire_wall_effect()
{
// Fire only affects wax walls.
if (grd(pos()) != DNGN_WAX_WALL)
{
finish_beam();
return;
}
testpos = ray.pos();
break; // breaks from line tracing
}
if (!is_superhot())
{
// No actual effect.
if (flavour != BEAM_HELLFIRE)
{
if (see_grid(pos()))
{
emit_message(MSGCH_PLAIN,
"The wax appears to soften slightly.");
}
else if (player_can_smell())
emit_message(MSGCH_PLAIN, "You smell warm wax.");
}
}
else
{
// Destroy the wall.
grd(pos()) = DNGN_FLOOR;
if (see_grid(pos()))
emit_message(MSGCH_PLAIN, "The wax bubbles and burns!");
else if (player_can_smell())
emit_message(MSGCH_PLAIN, "You smell burning wax.");
place_cloud(CLOUD_FIRE, pos(), random2(10)+15, whose_kill(), killer());
obvious_effect = true;
}
finish_beam();
}
// bounce
ray_def old_ray = ray;
bolt old_bolt = pbolt;
do
{
do
{
ray.regress();
pbolt.bounce_pos = ray.pos();
}
while (grid_is_solid(grd(ray.pos())));
if (feat == DNGN_ROCK_WALL
|| feat == DNGN_WAX_WALL
|| feat == DNGN_CLEAR_ROCK_WALL)
{
// FIXME: Blood *does* transfer to the floor here?
grd(pos()) = DNGN_FLOOR;
if (player_can_hear(pos()))
{
mpr("You hear a grinding noise.", MSGCH_SOUND);
obvious_effect = true;
}
}
else if (feat == DNGN_ORCISH_IDOL || feat == DNGN_GRANITE_STATUE)
{
grd(pos()) = DNGN_FLOOR;
ray.advance_and_bounce();
pbolt.range_used += 2;
}
while (pbolt.range_used < pbolt.range
&& grid_is_solid(grd(ray.pos())));
// Blood does not transfer onto floor.
if (is_bloodcovered(pos()))
env.map(pos()).property &= ~(FPROP_BLOODY);
if (player_can_hear(pos()))
{
if (!see_grid(pos()))
mpr("You hear a hideous screaming!", MSGCH_SOUND);
else
{
mpr("The statue screams as its substance crumbles away!",
MSGCH_SOUND);
}
}
else if (see_grid(pos()))
mpr("The statue twists and shakes as its substance crumbles away!");
testpos = ray.pos();
} // end else - beam doesn't affect walls
} // endif - are we in a wall wall?
void bolt::affect_wall()
{
if (flavour == BEAM_DIGGING)
digging_wall_effect();
else if (is_fiery())
fire_wall_effect();
else if (flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
nuke_wall_effect();
// At this point, if grd(testpos) is still a wall, we
// couldn't find any path: bouncy, fuzzy, or not - so break.
if (grid_is_solid(grd(testpos)))
break;
if (grid_is_solid(pos()))
finish_beam();
}
const bool was_seen = pbolt.seen;
if (!was_seen && pbolt.range > 0 && !beam_invisible
&& see_grid(testpos))
{
pbolt.seen = true;
}
coord_def bolt::pos() const
{
if (in_explosion_phase || use_target_as_pos)
return target;
else
return ray.pos();
}
if (pbolt.flavour != BEAM_VISUAL && !was_seen && pbolt.seen
&& !pbolt.is_tracer)
void bolt::hit_wall()
{
const dungeon_feature_type feat = grd(pos());
ASSERT( grid_is_solid(feat) );
if (affects_wall(feat))
affect_wall();
else if (is_bouncy(feat) && !in_explosion_phase)
bounce();
else
{
// Affect a creature in the wall, if any.
if (!invalid_monster_index(mgrd(pos())))
affect_monster( &menv[mgrd(pos())] );
// Regress for explosions: blow up one step earlier.
if (is_explosion && !in_explosion_phase)
// Check for "target termination"
// occurs when beam can be targetted at empty
// cell (e.g. a mage wants an explosion to happen
// between two monsters).
void bolt::affect_cell()
{
// Shooting through clouds affects accuracy.
if (env.cgrid(pos()) != EMPTY_CLOUD)
hit = std::max(hit - 2, 0);
fake_flavour();
// In this case, don't affect the cell - players and
// monsters have no chance to dodge or block such
// a beam, and we want to avoid silly messages.
if (testpos == pbolt.target)
beamTerminate = _beam_term_on_target(pbolt, testpos);
const coord_def old_pos = pos();
const bool was_solid = grid_is_solid(grd(pos()));
if (was_solid)
{
const int mid = mgrd(pos());
// Affect the cell, except in the special case noted
// above -- affect() will early out if something gets
// hit and the beam is type 'term on target'.
if (!beamTerminate || !pbolt.is_explosion)
// Some special casing.
if (!invalid_monster_index(mid))
if (pbolt.beam_cancelled)
return;
// We don't want to hit a monster in a wall square twice.
const bool still_wall = (was_solid && old_pos == pos());
if (!still_wall && !invalid_monster_index(mgrd(pos())))
affect_monster( &menv[mgrd(pos())] );
// Check for range termination.
if (pbolt.range_used >= pbolt.range)
beamTerminate = true;
if (!grid_is_solid(grd(pos())))
affect_ground();
}
// Special case - beam was aimed at feet.
if (pbolt.aimed_at_feet)
beamTerminate = true;
// This saves some important things before calling fire().
void bolt::fire()
{
if (is_tracer)
{
bolt boltcopy = *this;
do_fire();
// Reset chaos beams so that it won't be considered an invisible
// enchantment beam for the purposes of animation.
if (pbolt.real_flavour == BEAM_CHAOS)
pbolt.flavour = pbolt.real_flavour;
target = boltcopy.target;
source = boltcopy.source;
aimed_at_spot = boltcopy.aimed_at_spot;
range_used = boltcopy.range_used;
bounces = boltcopy.bounces;
bounce_pos = boltcopy.bounce_pos;
reflections = boltcopy.reflections;
reflector = boltcopy.reflector;
auto_hit = boltcopy.auto_hit;
ray = boltcopy.ray;
}
else
do_fire();
}
// Actually draw the beam/missile/whatever, if the player can see
// the cell.
if (!pbolt.is_tracer && !pbolt.is_enchantment() && see_grid(testpos))
{
// We don't clean up the old position.
// First, most people like to see the full path,
// and second, it is hard to do it right with
// respect to killed monsters, cloud trails, etc.
void bolt::do_fire()
{
initialize_fire();
if (range <= range_used && range > 0)
{
#ifdef DEBUG
mprf(MSGCH_DIAGNOSTICS, "fire_beam() called on already done beam "
"'%s' (item = '%s')", name.c_str(),
item ? item->name(DESC_PLAIN).c_str() : "none");
#endif
return;
}
if (tile_beam != -1 && in_los_bounds(drawpos))
{
tiles.add_overlay(testpos, tile_beam);
delay(pbolt.delay);
}
else
#endif
// bounds check
if (in_los_bounds(drawpos))
{
#ifndef USE_TILE
cgotoxy(drawpos.x, drawpos.y);
put_colour_ch(
pbolt.colour == BLACK ? random_colour()
: element_colour(pbolt.colour),
pbolt.type );
if (item && !is_tracer && flavour == BEAM_MISSILE)
{
const coord_def diff = target - source;
tile_beam = tileidx_item_throw(*item, diff.x, diff.y);
}
#ifdef MISSILE_TRAILS_OFF
// mv: It's not optimal but is usually enough.
if (!pbolt.is_beam)
viewwindow(true, false);
#ifdef WIN32CONSOLE
// Before we start drawing the beam, turn buffering off.
bool oldValue = true;
if (!is_tracer)
oldValue = set_buffering(false);
if (!did_bounce)
ray.advance_through(pbolt.target);
else
ray.advance(true);
} // end- while !beamTerminate
range_used++;
if (range_used >= range)
break;
// Check for explosion. NOTE that for tracers, we have to make a copy
// of target co-ords and then reset after calling this -- tracers should
// never change any non-tracers fields in the beam structure. -- GDL
coord_def targetcopy = pbolt.target;
const bool was_seen = seen;
if (!was_seen && range > 0 && !invisible() && see_grid(pos()))
seen = true;
if (pbolt.is_tracer)
{
pbolt.target = targetcopy;
// Reset chaos beams so that it won't be considered an invisible
// enchantment beam for the purposes of animation.
if (real_flavour == BEAM_CHAOS)
flavour = real_flavour;
// Actually draw the beam/missile/whatever, if the player can see
// the cell.
draw(pos());
// A bounce takes away the meaning from the target.
if (bounces == 0)
ray.advance_through(target);
else
ray.advance(true);
if (!pbolt.is_tracer && !pbolt.msg_generated && !pbolt.obvious_effect
&& YOU_KILL(pbolt.thrower))
{
canned_msg(MSG_NOTHING_HAPPENS);
}
canned_msg(MSG_NOTHING_HAPPENS);
const monsters *mon = &menv[pbolt.beam_source];
if (pbolt.foe_hurt > 0 && !mons_wont_attack(mon)
const monsters *mon = &menv[beam_source];
if (foe_hurt > 0 && !mons_wont_attack(mon)
}
// Monster has probably failed save, now it gets enchanted somehow.
// * Returns MON_RESIST if monster is unaffected due to magic resist.
// * Returns MON_UNAFFECTED if monster is immune to enchantment.
// * Returns MON_AFFECTED in all other cases (already enchanted, etc).
mon_resist_type mons_ench_f2(monsters *monster, bolt &pbolt)
{
switch (pbolt.flavour) // put in magic resistance
{
case BEAM_SLOW:
// try to remove haste, if monster is hasted
if (monster->del_ench(ENCH_HASTE))
{
if (simple_monster_message(monster,
" is no longer moving quickly."))
{
pbolt.obvious_effect = true;
}
return (MON_AFFECTED);
}
// not hasted, slow it
if (!monster->has_ench(ENCH_SLOW)
&& !mons_is_stationary(monster)
&& monster->add_ench(mon_enchant(ENCH_SLOW, 0, _whose_kill(pbolt))))
{
if (!mons_is_paralysed(monster) && !mons_is_petrified(monster)
&& simple_monster_message(monster, " seems to slow down."))
{
pbolt.obvious_effect = true;
}
}
return (MON_AFFECTED);
case BEAM_HASTE:
if (monster->del_ench(ENCH_SLOW))
{
if (simple_monster_message(monster, " is no longer moving slowly."))
pbolt.obvious_effect = true;
return (MON_AFFECTED);
}
// Not slowed, haste it.
if (!monster->has_ench(ENCH_HASTE)
&& !mons_is_stationary(monster)
&& monster->add_ench(ENCH_HASTE))
{
if (!mons_is_paralysed(monster) && !mons_is_petrified(monster)
&& simple_monster_message(monster, " seems to speed up."))
{
pbolt.obvious_effect = true;
}
}
return (MON_AFFECTED);
case BEAM_HEALING:
if (YOU_KILL(pbolt.thrower))
{
if (cast_healing(5 + pbolt.damage.roll(), monster->pos()) > 0)
pbolt.obvious_effect = true;
pbolt.msg_generated = true; // to avoid duplicate "nothing happens"
}
else if (heal_monster( monster, 5 + pbolt.damage.roll(), false ))
{
if (monster->hit_points == monster->max_hit_points)
{
if (simple_monster_message(monster,
"'s wounds heal themselves!"))
{
pbolt.obvious_effect = true;
}
}
else if (simple_monster_message(monster, " is healed somewhat."))
pbolt.obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_PARALYSIS:
_beam_paralyses_monster(pbolt, monster);
return (MON_AFFECTED);
case BEAM_PETRIFY:
_beam_petrifies_monster(pbolt, monster);
return (MON_AFFECTED);
case BEAM_CONFUSION:
if (!mons_class_is_confusable(monster->type))
return (MON_UNAFFECTED);
if (monster->add_ench( mon_enchant(ENCH_CONFUSION, 0,
_whose_kill(pbolt)) ))
{
// Put in an exception for things you won't notice becoming
// confused.
if (simple_monster_message(monster, " appears confused."))
pbolt.obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_INVISIBILITY:
{
// Store the monster name before it becomes an "it" -- bwr
const std::string monster_name = monster->name(DESC_CAP_THE);
if (!monster->has_ench(ENCH_INVIS)
&& monster->add_ench(ENCH_INVIS))
{
// A casting of invisibility erases backlight.
monster->del_ench(ENCH_BACKLIGHT);
// Can't use simple_monster_message() here, since it checks
// for visibility of the monster (and it's now invisible).
// -- bwr
if (mons_near( monster ))
{
mprf("%s flickers %s",
monster_name.c_str(),
player_monster_visible(monster) ? "for a moment."
: "and vanishes!" );
if (!player_monster_visible(monster))
{
// Also turn off autopickup.
Options.autopickup_on = false;
mpr("Deactivating autopickup; reactivate with Ctrl-A.",
MSGCH_WARN);
if (Options.tutorial_left)
{
learned_something_new(TUT_INVISIBLE_DANGER);
Options.tut_seen_invisible = you.num_turns;
}
}
}
pbolt.obvious_effect = true;
}
return (MON_AFFECTED);
}
case BEAM_CHARM:
if (player_will_anger_monster(monster))
{
simple_monster_message(monster, " is repulsed!");
return (MON_OTHER);
}
if (monster->add_ench(ENCH_CHARM))
{
// Put in an exception for fungi, plants and other things
// you won't notice becoming charmed.
if (simple_monster_message(monster, " is charmed."))
pbolt.obvious_effect = true;
}
return (MON_AFFECTED);
default:
break;
}
return (MON_AFFECTED);
}
// degree is ignored.
static void _slow_monster(monsters *mon, int /* degree */)
{
bolt beam;
beam.flavour = BEAM_SLOW;
mons_ench_f2(mon, beam);
if ( beam.real_flavour == BEAM_CHAOS
&& grid_is_solid(static_cast<dungeon_feature_type>(gridtype)) )
{
if (real_flavour == BEAM_CHAOS && grid_is_solid(feat))
if ((beam.flavour == BEAM_FIRE || beam.flavour == BEAM_COLD)
&& gridtype == DNGN_GREEN_CRYSTAL_WALL )
if ((flavour == BEAM_FIRE || flavour == BEAM_COLD)
&& feat == DNGN_GREEN_CRYSTAL_WALL )
cloud_type cl_type;
// This will be the last thing this beam does. Set target_x
// and target_y to hold explosion co'ords.
beam.target = p;
// Generic explosion.
if (beam.is_explosion)
{
_explosion1(beam);
return;
}
if (beam.flavour >= BEAM_POTION_STINKING_CLOUD
&& beam.flavour <= BEAM_POTION_RANDOM)
switch (flavour)
case BEAM_POTION_STEAM:
case BEAM_POTION_GREY_SMOKE:
beam.colour = LIGHTGREY;
break;
case BEAM_POTION_MIASMA:
case BEAM_POTION_BLACK_SMOKE:
return (DARKGREY);
_explosion1(beam);
return;
case BEAM_POTION_RANDOM:
default:
// Leave it the colour of the potion, the clouds will colour
// themselves on the next refresh. -- bwr
return (-1);
cl_type = (beam.flavour == BEAM_MIASMA) ? CLOUD_MIASMA : CLOUD_STINK;
big_cloud( cl_type, _whose_kill(beam), beam.killer(), p, 0, 9 );
int newcolour = _potion_beam_flavour_to_colour(flavour);
if (newcolour >= 0)
colour = newcolour;
explode();
if (beam.name == "freezing blast")
{
big_cloud( CLOUD_COLD, _whose_kill(beam), beam.killer(), p,
random_range(10, 15), 9 );
if (is_tracer)
// special cases - orbs & blasts of cold
if (beam.name == "orb of electricity"
|| beam.name == "metal orb"
|| beam.name == "great blast of cold")
if (name == "blast of poison")
big_cloud(CLOUD_POISON, whose_kill(), killer(), pos(), 0, 7+random2(5));
if (name == "foul vapour")
_explosion1( beam );
return;
const cloud_type cl_type = (flavour == BEAM_MIASMA) ? CLOUD_MIASMA
: CLOUD_STINK;
big_cloud(cl_type, whose_kill(), killer(), pos(), 0, 9);
// Generic - all explosion-type beams can be targeted at empty space,
// and will explode there. This semantic also means that a creature
// in the target cell will have no chance to dodge or block, so we
// DON'T affect() the cell if this function returns true!
if (beam.is_explosion || beam.is_big_cloud)
return (true);
// POISON BLAST
if (beam.name == "blast of poison")
return (true);
// FOUL VAPOR (SWAMP DRAKE)
if (beam.name == "foul vapour")
return (true);
// STINKING CLOUD
if (beam.name == "ball of vapour")
return (true);
if (beam.aimed_at_spot && p == beam.target)
if (is_explosion
|| is_big_cloud
|| aimed_at_spot
|| name == "blast of poison"
|| name == "foul vapour"
|| name == "ball of vapour")
{
if (!item)
item = beam.item;
ASSERT( item != NULL );
ASSERT( is_valid_item(*item) );
#ifdef DEBUG
if (!beam.drop_item)
mprf(MSGCH_DIAGNOSTICS, "beam_drop_object() called when beam.drop_item "
"is false (beam = %s, item = %s)", beam.name.c_str(),
item->name(DESC_PLAIN).c_str());
#endif
if (beam.is_tracer || beam.flavour != BEAM_MISSILE)
return;
ASSERT( item != NULL && is_valid_item(*item) );
if (beam.dropped_item)
{
#ifdef DEBUG
mprf(MSGCH_DIAGNOSTICS, "beam_drop_object() called after object "
"already dropped (beam = %s, item = %s)", beam.name.c_str(),
item->name(DESC_PLAIN).c_str());
#endif
// Conditions: beam is missile and not tracer.
if (is_tracer || flavour != BEAM_MISSILE)
// Conditions: beam is missile and not tracer.
if (YOU_KILL(beam.thrower)
&& !thrown_object_destroyed(item, p, false)
|| MON_KILL(beam.thrower)
&& !mons_thrown_object_destroyed(item, p, false, beam.beam_source))
if (YOU_KILL(thrower) && !thrown_object_destroyed(item, pos(), false)
|| MON_KILL(thrower)
&& !mons_thrown_object_destroyed(item, pos(), false, beam_source))
// Dropped item might have merged into stack
if (is_stackable_item(*item))
{
for (stack_iterator si(p); si; ++si)
{
if (items_stack(*item, *si))
{
beam.item_index = si->index();
break;
}
}
}
else
beam.item_index = igrd(p);
}
else
{
#ifdef DEBUG
mprf(MSGCH_DIAGNOSTICS, "beam_drop_object() unable to drop "
"object (beam = %s, item = %s)", beam.name.c_str(),
item->name(DESC_PLAIN).c_str());
#endif
}
copy_item_to_grid(*item, pos(), 1);
// Extra range used by hitting something.
int rangeUsed = 0;
coord_def p = _p;
if (!in_bounds(_p))
p = beam.pos;
if (!item)
item = beam.item;
if (beam.flavour == BEAM_VISUAL)
{
if (mgrd(p) != NON_MONSTER)
behaviour_event( &menv[mgrd(p)], ME_DISTURB );
return (0);
}
if (grid_is_solid(grd(p)))
{
if (beam.is_tracer) // Tracers always stop on walls.
{
beam.range_used += BEAM_STOP;
return (BEAM_STOP);
}
if (_affects_wall(beam, grd(p)))
rangeUsed += _affect_wall(beam, p);
// If it's still a wall, quit - we can't do anything else to a
// wall (but we still might be able to do something to any
// monster inside the wall). Otherwise effects (like clouds,
// etc.) are still possible.
if (grid_is_solid(grd(p)))
{
int mid = mgrd(p);
if (mid != NON_MONSTER)
{
monsters *mon = &menv[mid];
if (_affect_mon_in_wall(beam, NULL, p))
rangeUsed += _affect_monster( beam, mon, item );
else if (you.can_see(mon))
{
mprf("The %s protects %s from harm.",
raw_feature_description(grd(mon->pos())).c_str(),
mon->name(DESC_NOCAP_THE).c_str());
}
}
beam.range_used += rangeUsed;
return (rangeUsed);
}
}
// grd(p) will NOT be a wall for the remainder of this function.
// If not a tracer, affect items and place clouds.
if (!beam.is_tracer)
{
if (affect_items)
{
const int burn_power = (beam.is_explosion) ? 5 :
(beam.is_beam) ? 3 : 2;
expose_items_to_element(beam.flavour, p, burn_power);
}
rangeUsed += _affect_place_clouds(beam, p);
}
// If player is at this location, try to affect unless term_on_target.
if (_found_player(beam, p))
{
// Done this way so that poison blasts affect the target once (via
// place_cloud) and explosion spells only affect the target once
// (during the explosion phase, not an initial hit during the
// beam phase).
if (!beam.is_big_cloud
&& (!beam.is_explosion || beam.in_explosion_phase))
{
rangeUsed += _affect_player( beam, item, affect_items );
}
if (_beam_term_on_target(beam, p))
{
beam.range_used += BEAM_STOP;
return (BEAM_STOP);
}
}
if (is_tracer)
return;
// If there is a monster at this location, affect it.
// Submerged monsters aren't really there. -- bwr
int mid = mgrd(p);
if (mid != NON_MONSTER)
if (affects_items)
monsters *mon = &menv[mid];
const bool invisible = YOU_KILL(beam.thrower) && !you.can_see(mon);
// Monsters submerged in shallow water can be targeted by beams
// aimed at that spot.
if (mon->alive()
// Don't stop tracers on invisible monsters.
&& (!invisible || !beam.is_tracer)
&& (!mon->submerged()
|| beam.aimed_at_spot && beam.target == mon->pos()
&& grd(mon->pos()) == DNGN_SHALLOW_WATER))
{
if (!beam.is_big_cloud
&& (!beam.is_explosion || beam.in_explosion_phase))
{
rangeUsed += _affect_monster( beam, &menv[mid], item );
}
if (_beam_term_on_target(beam, p))
{
beam.range_used += BEAM_STOP;
return (BEAM_STOP);
}
}
const int burn_power = (is_explosion) ? 5 : (is_beam) ? 3 : 2;
expose_items_to_element(flavour, pos(), burn_power);
affect_place_clouds();
return (beam.name == "bolt of fire"
|| beam.name == "bolt of magma"
|| beam.name.find("hellfire") != std::string::npos
&& beam.in_explosion_phase);
return (name == "bolt of fire"
|| name == "bolt of magma"
|| name.find("hellfire") != std::string::npos
&& in_explosion_phase);
int rangeUsed = 0;
// DIGGING
if (beam.flavour == BEAM_DIGGING)
{
if (grd(p) == DNGN_STONE_WALL
|| grd(p) == DNGN_METAL_WALL
|| grd(p) == DNGN_PERMAROCK_WALL
|| grd(p) == DNGN_CLEAR_STONE_WALL
|| grd(p) == DNGN_CLEAR_PERMAROCK_WALL
|| !in_bounds(p))
{
return (0);
}
if (grd(p) == DNGN_ROCK_WALL || grd(p) == DNGN_CLEAR_ROCK_WALL)
{
grd(p) = DNGN_FLOOR;
// Mark terrain as changed so travel excludes can be updated
// as necessary.
// XXX: This doesn't work for some reason: after digging
// the wrong grids are marked excluded.
set_terrain_changed(p);
// Blood does not transfer onto floor.
if (is_bloodcovered(p))
env.map(p).property &= ~(FPROP_BLOODY);
if (!beam.msg_generated)
{
if (!silenced(you.pos()))
{
mpr("You hear a grinding noise.", MSGCH_SOUND);
beam.obvious_effect = true;
}
beam.msg_generated = true;
}
}
if (in_explosion_phase)
affect_place_explosion_clouds();
const int wgrd = grd(p);
if (wgrd != DNGN_WAX_WALL)
return (0);
if (!_is_superhot(beam))
{
if (beam.flavour != BEAM_HELLFIRE)
{
if (see_grid(p))
{
_beam_mpr(MSGCH_PLAIN,
"The wax appears to soften slightly.");
}
else if (player_can_smell())
_beam_mpr(MSGCH_PLAIN, "You smell warm wax.");
}
return (BEAM_STOP);
}
grd(p) = DNGN_FLOOR;
if (see_grid(p))
_beam_mpr(MSGCH_PLAIN, "The wax bubbles and burns!");
else if (player_can_smell())
_beam_mpr(MSGCH_PLAIN, "You smell burning wax.");
place_cloud(CLOUD_FIRE, p, random2(10) + 15, _whose_kill(beam),
beam.killer());
beam.obvious_effect = true;
cloud_type& ctype = env.cloud[cloudidx].type;
// Polymorph randomly changes clouds in its path
if (flavour == BEAM_POLYMORPH)
ctype = static_cast<cloud_type>(1 + random2(8));
return (BEAM_STOP);
}
// NUKE / DISRUPT
if (beam.flavour == BEAM_DISINTEGRATION || beam.flavour == BEAM_NUKE)
{
int targ_grid = grd(p);
if ((targ_grid == DNGN_ROCK_WALL || targ_grid == DNGN_WAX_WALL
|| targ_grid == DNGN_CLEAR_ROCK_WALL)
&& in_bounds(p))
{
grd(p) = DNGN_FLOOR;
if (!silenced(you.pos()))
{
mpr("You hear a grinding noise.", MSGCH_SOUND);
beam.obvious_effect = true;
}
}
if (targ_grid == DNGN_ORCISH_IDOL
|| targ_grid == DNGN_GRANITE_STATUE)
{
grd(p) = DNGN_FLOOR;
// Blood does not transfer onto floor.
if (is_bloodcovered(p))
env.map(p).property &= ~(FPROP_BLOODY);
if (!silenced(you.pos()))
{
if (!see_grid( p ))
mpr("You hear a hideous screaming!", MSGCH_SOUND);
else
{
mpr("The statue screams as its substance crumbles away!",
MSGCH_SOUND);
}
}
else if (see_grid(p))
mpr("The statue twists and shakes as its substance crumbles away!");
if (targ_grid == DNGN_ORCISH_IDOL
&& beam.beam_source == NON_MONSTER)
{
did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8);
}
beam.obvious_effect = true;
}
return (BEAM_STOP);
}
return (rangeUsed);
}
static int _affect_place_clouds(bolt &beam, const coord_def& p)
{
if (beam.in_explosion_phase)
{
_affect_place_explosion_clouds( beam, p );
return (0); // return value irrelevant for explosions
}
// check for CLOUD HITS
if (env.cgrid(p) != EMPTY_CLOUD) // hit a cloud
{
// polymorph randomly changes clouds in its path
if (beam.flavour == BEAM_POLYMORPH)
{
env.cloud[ env.cgrid(p) ].type =
static_cast<cloud_type>(1 + random2(8));
}
// now exit (all enchantments)
if (beam.is_enchantment())
return (0);
int clouty = env.cgrid(p);
if ((env.cloud[clouty].type == CLOUD_COLD
&& (beam.flavour == BEAM_FIRE
|| beam.flavour == BEAM_LAVA))
|| (env.cloud[clouty].type == CLOUD_FIRE
&& beam.flavour == BEAM_COLD))
if ((ctype == CLOUD_COLD
&& (flavour == BEAM_FIRE || flavour == BEAM_LAVA))
|| (ctype == CLOUD_FIRE && flavour == BEAM_COLD))
// POISON BLAST
if (beam.name == "blast of poison")
place_cloud( CLOUD_POISON, p, random2(4) + 2, _whose_kill(beam),
beam.killer() );
// No clouds here, free to make new ones.
const dungeon_feature_type feat = grd(p);
// FIRE/COLD over water/lava
if (grd(p) == DNGN_LAVA && beam.flavour == BEAM_COLD
|| grid_is_watery(grd(p)) && _is_fiery(beam))
if (name == "blast of poison")
place_cloud(CLOUD_POISON, p, random2(4) + 2, whose_kill(), killer());
// Fire/cold over water/lava
if (feat == DNGN_LAVA && flavour == BEAM_COLD
|| grid_is_watery(feat) && is_fiery())
// GREAT BLAST OF COLD
if (beam.name == "great blast of cold")
place_cloud( CLOUD_COLD, p, random2(5) + 3, _whose_kill(beam),
beam.killer() );
// BALL OF STEAM
if (beam.name == "ball of steam")
place_cloud( CLOUD_STEAM, p, random2(5) + 2, _whose_kill(beam),
beam.killer() );
if (name == "ball of steam")
place_cloud(CLOUD_STEAM, p, random2(5) + 2, whose_kill(), killer());
if (beam.flavour == BEAM_MIASMA)
place_cloud( CLOUD_MIASMA, p, random2(5) + 2, _whose_kill(beam),
beam.killer() );
if (flavour == BEAM_MIASMA)
place_cloud(CLOUD_MIASMA, p, random2(5) + 2, whose_kill(), killer());
// POISON GAS
if (beam.name == "poison gas")
place_cloud( CLOUD_POISON, p, random2(4) + 3, _whose_kill(beam),
beam.killer() );
if (name == "poison gas")
place_cloud(CLOUD_POISON, p, random2(4) + 3, whose_kill(), killer());
// First check: FIRE/COLD over water/lava.
if (grd(p) == DNGN_LAVA && beam.flavour == BEAM_COLD
|| grid_is_watery(grd(p)) && _is_fiery(beam))
// First check: fire/cold over water/lava.
if (grd(p) == DNGN_LAVA && flavour == BEAM_COLD
|| grid_is_watery(grd(p)) && is_fiery())
duration = 1 + random2(4) + random2( (beam.ench_power / 50) + 1 );
place_cloud( CLOUD_STINK, p, duration, _whose_kill(beam) );
const int duration = 1 + random2(4) + random2((ench_power / 50) + 1);
place_cloud( CLOUD_STINK, p, duration, whose_kill(), killer() );
if (beam.reflections > 0)
ouch(dam, beam.reflector, KILLED_BY_REFLECTION,
beam.name.c_str());
else if (beam.bounces > 0)
ouch(dam, NON_MONSTER, KILLED_BY_BOUNCE,
beam.name.c_str());
if (reflections > 0)
ouch(dam, reflector, KILLED_BY_REFLECTION, name.c_str());
else if (bounces > 0)
ouch(dam, NON_MONSTER, KILLED_BY_BOUNCE, name.c_str());
}
else if (MON_KILL(beam.thrower))
{
ouch(dam, beam.beam_source, KILLED_BY_BEAM,
beam.aux_source.c_str());
{
ouch(dam, beam.beam_source, KILLED_BY_WILD_MAGIC,
beam.aux_source.c_str());
}
ouch(dam, beam_source, KILLED_BY_WILD_MAGIC, aux_source.c_str());
// For enchantments, this is already handled in _nasty_beam().
if (beam.is_enchantment())
return (!_nasty_beam(mon, beam));
// For enchantments, this is already handled in nasty_to().
if (is_enchantment())
return (!nasty_to(mon));
// Returns amount of extra range used up by affectation of the player.
static int _affect_player( bolt &beam, item_def *item, bool affect_items )
void bolt::tracer_affect_player()
// Digging -- don't care.
if (beam.flavour == BEAM_DIGGING)
return (0);
// Check for tracer.
if (beam.is_tracer)
// Check whether thrower can see player, unless thrower == player.
if (YOU_KILL(thrower))
// Check whether thrower can see player, unless thrower == player.
if (YOU_KILL(beam.thrower))
{
// Don't ask if we're aiming at ourselves.
if (!beam.aimed_at_feet && !beam.dont_stop_player
&& !_beam_is_harmless_player(beam))
{
if (yesno("That beam is likely to hit you. Continue anyway?",
false, 'n'))
{
beam.fr_count += 1;
beam.fr_power += you.experience_level;
beam.dont_stop_player = true;
}
else
{
beam.beam_cancelled = true;
return (BEAM_STOP);
}
}
}
else if (beam.can_see_invis || !you.invisible()
|| _fuzz_invis_tracer(beam))
// Don't ask if we're aiming at ourselves.
if (!aimed_at_feet && !dont_stop_player && !harmless_to_player())
return (_range_used_on_hit(beam));
}
else if (can_see_invis || !you.invisible() || fuzz_invis_tracer())
{
if (mons_att_wont_attack(attitude))
{
fr_count++;
fr_power += you.experience_level;
}
else
{
foe_count++;
foe_power += you.experience_level;
}
// Trigger an interrupt, so travel will stop on misses
// which generate smoke.
if (!YOU_KILL(beam.thrower))
interrupt_activity(AI_MONSTER_ATTACKS);
bool bolt::misses_player()
{
if (is_explosion || aimed_at_feet || auto_hit || is_enchantment())
return (false);
// BEGIN real beam code
beam.msg_generated = true;
const int dodge = player_evasion();
int real_tohit = hit;
// Monsters shooting at an invisible player are very inaccurate.
if (you.invisible() && !can_see_invis)
real_tohit /= 2;
// Use beamHit, NOT beam.hit, for modification of tohit.. geez!
int beamHit = beam.hit;
if (is_beam)
{
// Beams can be dodged.
if (player_light_armour(true) && !aimed_at_feet && coinflip())
exercise(SK_DODGING, 1);
// Monsters shooting at an invisible player are very inaccurate.
if (you.invisible() && !beam.can_see_invis)
beamHit /= 2;
if (you.duration[DUR_DEFLECT_MISSILES])
real_tohit = random2(real_tohit * 2) / 3;
else if (you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3)
{
real_tohit -= random2(real_tohit / 2);
}
if (beam.is_beam)
{
// Beams can be dodged.
if (player_light_armour(true)
&& !beam.aimed_at_feet && coinflip())
{
exercise(SK_DODGING, 1);
}
const int block = you.shield_bonus();
if (you.duration[DUR_DEFLECT_MISSILES])
beamHit = random2(beamHit * 2) / 3;
else if (you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3)
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d",
testhit, block);
#endif
if (testhit < block)
{
if (is_reflectable(you.shield()))
else if (_beam_is_blockable(beam))
{
// Non-beams can be blocked or dodged.
if (you.shield()
&& !beam.aimed_at_feet
&& player_shield_class() > 0)
{
int exer = one_chance_in(3) ? 1 : 0;
const int hit = random2( beam.hit * 130 / 100
+ you.shield_block_penalty() );
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d",
hit, block);
#endif
if (hit < block)
{
if (_beam_is_reflectable(beam, you.shield()))
{
mprf( "Your %s reflects the %s!",
you.shield()->name(DESC_PLAIN).c_str(),
beam.name.c_str() );
_ident_reflector(you.shield());
_reflect_beam(beam);
}
else
mprf( "You block the %s.", beam.name.c_str() );
you.shield_block_succeeded();
return (BEAM_STOP);
}
if (player_light_armour(true) && !aimed_at_feet && coinflip())
exercise(SK_DODGING, 1);
// Some training just for the "attempt".
if (coinflip())
exercise( SK_SHIELDS, exer );
}
if (you.duration[DUR_DEFLECT_MISSILES])
real_tohit = random2(real_tohit / 2);
else if (you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3)
{
real_tohit = random2(real_tohit);
}
if (player_light_armour(true) && !beam.aimed_at_feet
&& coinflip())
{
exercise(SK_DODGING, 1);
}
// miss message
if (!test_beam_hit(real_tohit, dodge))
{
mprf("The %s misses you.", name.c_str());
return (true);
}
}
return (false);
}
if (you.duration[DUR_DEFLECT_MISSILES])
beamHit = random2(beamHit / 2);
else if (you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3)
{
beamHit = random2(beamHit);
}
void bolt::affect_player_enchantment()
{
if ((has_saving_throw() || flavour == BEAM_POLYMORPH)
&& you_resist_magic(ench_power))
{
// You resisted it.
// miss message
if (!test_beam_hit(beamHit, dodge))
{
mprf("The %s misses you.", beam.name.c_str());
return (0);
}
// Give a message.
bool need_msg = true;
if (thrower != KILL_YOU_MISSILE && !invalid_monster_index(beam_source))
{
monsters *mon = &menv[beam_source];
if (!player_monster_visible(mon))
{
mpr("Something tries to affect you, but you resist.");
need_msg = false;
// BEGIN enchantment beam
if (beam.flavour != BEAM_HASTE
&& beam.flavour != BEAM_INVISIBILITY
&& beam.flavour != BEAM_HEALING
&& beam.flavour != BEAM_POLYMORPH
&& beam.flavour != BEAM_DISPEL_UNDEAD
&& (beam.flavour != BEAM_TELEPORT && beam.flavour != BEAM_BANISH
|| !beam.aimed_at_feet)
&& you_resist_magic( beam.ench_power ))
case BEAM_BACKLIGHT:
if (!you.duration[DUR_INVIS])
bool need_msg = true;
if (beam.thrower != KILL_YOU_MISSILE
&& !invalid_monster_index(beam.beam_source))
{
monsters *mon = &menv[beam.beam_source];
if (!player_monster_visible(mon))
{
mpr("Something tries to affect you, but you resist.");
need_msg = false;
}
}
if (need_msg)
canned_msg(MSG_YOU_RESIST);
if (you.duration[DUR_BACKLIGHT])
mpr("You glow brighter.");
else
mpr("You are outlined in light.");
// You *could* have gotten a free teleportation in the Abyss,
// but no, you resisted.
if (beam.flavour != BEAM_TELEPORT && you.level_type == LEVEL_ABYSS)
xom_is_stimulated(255);
you.duration[DUR_BACKLIGHT] += random_range(15, 35);
if (you.duration[DUR_BACKLIGHT] > 250)
you.duration[DUR_BACKLIGHT] = 250;
_ench_animation( beam.real_flavour );
// these colors are misapplied - see mons_ench_f2() {dlb}
switch (beam.flavour)
else
case BEAM_BACKLIGHT:
if (!you.duration[DUR_INVIS])
{
if (you.duration[DUR_BACKLIGHT])
mpr("You glow brighter.");
else
mpr("You are outlined in light.");
you.duration[DUR_BACKLIGHT] += random_range(3, 5);
if (you.duration[DUR_BACKLIGHT] > 250)
you.duration[DUR_BACKLIGHT] = 250;
beam.obvious_effect = true;
}
else
{
mpr("You feel strangely conspicuous.");
case BEAM_POLYMORPH:
if (MON_KILL(thrower))
{
mpr("Strange energies course through your body.");
you.mutate();
obvious_effect = true;
}
else if (get_ident_type(OBJ_WANDS, WAND_POLYMORPH_OTHER)
== ID_KNOWN_TYPE)
{
mpr("This is polymorph other only!");
}
else
canned_msg( MSG_NOTHING_HAPPENS );
case BEAM_POLYMORPH:
if (MON_KILL(beam.thrower))
{
mpr("Strange energies course through your body.");
you.mutate();
beam.obvious_effect = true;
}
else if (get_ident_type(OBJ_WANDS, WAND_POLYMORPH_OTHER)
== ID_KNOWN_TYPE)
{
mpr("This is polymorph other only!");
}
else
canned_msg( MSG_NOTHING_HAPPENS );
case BEAM_HASTE:
potion_effect( POT_SPEED, ench_power );
contaminate_player( 1, effect_known );
obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_SLOW:
potion_effect( POT_SLOWING, beam.ench_power );
beam.obvious_effect = true;
break;
case BEAM_PARALYSIS:
potion_effect( POT_PARALYSIS, ench_power );
obvious_effect = true;
break;
case BEAM_HASTE:
potion_effect( POT_SPEED, beam.ench_power );
contaminate_player( 1, beam.effect_known );
beam.obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_PETRIFY:
you.petrify( ench_power );
obvious_effect = true;
break;
case BEAM_HEALING:
potion_effect( POT_HEAL_WOUNDS, beam.ench_power );
beam.obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_CONFUSION:
potion_effect( POT_CONFUSION, ench_power );
obvious_effect = true;
break;
case BEAM_PARALYSIS:
potion_effect( POT_PARALYSIS, beam.ench_power );
beam.obvious_effect = true;
break;
case BEAM_INVISIBILITY:
potion_effect( POT_INVISIBILITY, ench_power );
contaminate_player( 1 + random2(2), effect_known );
obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_CONFUSION:
potion_effect( POT_CONFUSION, beam.ench_power );
beam.obvious_effect = true;
break;
// An enemy helping you escape while in the Abyss, or an
// enemy stabilizing a teleport that was about to happen.
if (!mons_att_wont_attack(attitude)
&& you.level_type == LEVEL_ABYSS)
{
xom_is_stimulated(255);
}
case BEAM_INVISIBILITY:
potion_effect( POT_INVISIBILITY, beam.ench_power );
contaminate_player( 1 + random2(2), beam.effect_known );
beam.obvious_effect = true;
nasty = false;
nice = true;
break;
obvious_effect = true;
break;
// An enemy helping you escape while in the Abyss, or an
// enemy stabilizing a teleport that was about to happen.
if (!mons_att_wont_attack(beam.attitude)
&& you.level_type == LEVEL_ABYSS)
{
xom_is_stimulated(255);
}
case BEAM_CHARM:
potion_effect( POT_CONFUSION, ench_power );
obvious_effect = true;
break; // enslavement - confusion?
case BEAM_BLINK:
random_blink(false);
beam.obvious_effect = true;
}
if (you.level_type == LEVEL_ABYSS)
{
mpr("You feel trapped.");
case BEAM_CHARM:
potion_effect( POT_CONFUSION, beam.ench_power );
beam.obvious_effect = true;
break; // enslavement - confusion?
}
you.banished = true;
you.banished_by = zapper();
obvious_effect = true;
break;
case BEAM_BANISH:
if (YOU_KILL(beam.thrower))
{
mpr("This spell isn't strong enough to banish yourself.");
break;
}
if (you.level_type == LEVEL_ABYSS)
{
mpr("You feel trapped.");
break;
}
you.banished = true;
you.banished_by = _beam_zapper(beam);
beam.obvious_effect = true;
case BEAM_PAIN:
if (player_res_torment())
{
mpr("You are unaffected.");
case BEAM_PAIN:
if (player_res_torment())
{
mpr("You are unaffected.");
break;
}
mpr("Pain shoots through your body!");
_beam_ouch(beam.damage.roll(), beam);
beam.obvious_effect = true;
case BEAM_DISPEL_UNDEAD:
if (!you.is_undead)
{
mpr("You are unaffected.");
_beam_ouch(beam.damage.roll(), beam);
beam.obvious_effect = true;
break;
}
internal_ouch(damage.roll());
obvious_effect = true;
break;
_beam_ouch(beam.damage.roll(), beam);
beam.obvious_effect = true;
break;
internal_ouch(damage.roll());
obvious_effect = true;
break;
default:
// _All_ enchantments should be enumerated here!
mpr("Software bugs nibble your toes!");
break;
}
default:
// _All_ enchantments should be enumerated here!
mpr("Software bugs nibble your toes!");
break;
}
beam.fr_hurt++;
if (beam.beam_source == NON_MONSTER)
{
// Beam from player rebounded and hit player.
if (!beam.aimed_at_feet)
xom_is_stimulated(255);
}
else
{
// Beam from an ally or neutral.
xom_is_stimulated(128);
}
// Beam from player rebounded and hit player.
if (!aimed_at_feet)
xom_is_stimulated(255);
// Regardless of affect, we need to know if this is a stopper
// or not - it seems all of the above are.
return (_range_used_on_hit(beam));
if (nice)
{
if (mons_att_wont_attack(attitude))
fr_helped++;
else
{
foe_helped++;
xom_is_stimulated(128);
}
}
// END enchantment beam
// Regardless of effect, we need to know if this is a stopper
// or not - it seems all of the above are.
range_used += range_used_on_hit();
}
void bolt::affect_player()
{
// Digging -- don't care.
if (flavour == BEAM_DIGGING)
return;
if (is_tracer)
{
tracer_affect_player();
return;
if (_nasty_beam(mon, beam))
beam.foe_hurt++;
else if (_nice_beam(mon, beam))
beam.foe_helped++;
if (nasty_to(mon))
foe_hurt++;
else if (nice_to(mon))
foe_helped++;
// Returns amount of range used up by affectation of this monster.
static int _affect_monster(bolt &beam, monsters *mon, item_def *item)
void bolt::tracer_enchantment_affect_monster(monsters* mon)
const int tid = mgrd(mon->pos());
const int mons_type = menv[tid].type;
const int thrower = YOU_KILL(beam.thrower) ? KILL_YOU_MISSILE
: KILL_MON_MISSILE;
const bool submerged = mon->submerged();
int hurt;
int hurt_final;
handle_stop_attack_prompt(mon);
if (!beam_cancelled)
range_used += range_used_on_hit();
}
// digging -- don't care.
if (beam.flavour == BEAM_DIGGING)
return (0);
// Return false if we should skip handling this monster.
bool bolt::determine_damage(monsters* mon, int& preac, int& postac, int& final)
{
// preac: damage before AC modifier
// postac: damage after AC modifier
// final: damage after AC and resists
// All these are invalid if we return false.
// fire storm creates these, so we'll avoid affecting them
if (beam.name == "great blast of fire"
&& mon->type == MONS_FIRE_VORTEX)
{
return (0);
}
// Tracers get the mean.
if (is_tracer)
preac = (damage.num * (damage.size + 1)) / 2;
else
preac = damage.roll();
// Can we see this monster?
if (!beam.can_see_invis && menv[tid].invisible()
|| (thrower == KILL_YOU_MISSILE && !see_grid(mon->pos())))
// The beam will pass overhead unless it's aimed at them.
if (!aimed_at_spot)
return false;
// Electricity is ineffective.
if (flavour == BEAM_ELECTRICITY)
// Can't see this monster, ignore it.
return 0;
if (!is_tracer && see_grid(mon->pos()))
mprf("The %s arcs harmlessly into the water.", name.c_str());
finish_beam();
return false;
if (!mons_atts_aligned(beam.attitude, mons_attitude(mon)))
{
beam.foe_count += 1;
beam.foe_power += mons_power(mons_type);
}
else
{
beam.fr_count += 1;
beam.fr_power += mons_power(mons_type);
}
postac = preac - maybe_random2(1 + mon->ac, !is_tracer);
// Fragmentation has triple AC reduction.
if (flavour == BEAM_FRAG)
{
postac -= maybe_random2(1 + mon->ac, !is_tracer);
postac -= maybe_random2(1 + mon->ac, !is_tracer);
else if ((beam.flavour == BEAM_DISINTEGRATION || beam.flavour == BEAM_NUKE)
&& mons_is_statue(mons_type) && !mons_is_icy(mons_type))
postac = std::max(postac, 0);
// Don't do side effects (beam might miss or be a tracer.)
final = mons_adjust_flavoured(mon, *this, preac, false);
return true;
}
void bolt::handle_stop_attack_prompt(monsters* mon)
{
if ((thrower == KILL_YOU_MISSILE || thrower == KILL_YOU)
&& !is_harmless(mon))
if (!see_grid( mon->pos() ))
mpr("You hear a hideous screaming!", MSGCH_SOUND);
const bool on_target = (target == mon->pos());
if (stop_attack_prompt(mon, true, on_target))
{
beam_cancelled = true;
finish_beam();
}
}
else if (see_grid( mon->pos() ))
{
mpr("The statue twists and shakes as its substance "
"crumbles away!");
beam.obvious_effect = true;
_update_hurt_or_helped(beam, mon);
mon->hurt(beam.agent(), INSTANT_DEATH);
return (BEAM_STOP);
bool hit_woke_orc = false;
if (beam.is_enchantment()) // enchantments: no to-hit check
{
if (beam.is_tracer)
{
if (beam.thrower == KILL_YOU_MISSILE || beam.thrower == KILL_YOU)
{
if (!_beam_is_harmless(beam, mon)
&& (beam.fr_count == 1 && !beam.dont_stop_fr
|| beam.foe_count == 1 && !beam.dont_stop_foe))
{
const bool target = (beam.target == mon->pos());
if (stop_attack_prompt(mon, true, target))
{
beam.beam_cancelled = true;
return (BEAM_STOP);
}
if (beam.fr_count == 1 && !beam.dont_stop_fr)
beam.dont_stop_fr = true;
else
beam.dont_stop_foe = true;
}
}
void bolt::tracer_nonenchantment_affect_monster(monsters* mon)
{
int preac, post, final;
if ( !determine_damage(mon, preac, post, final) )
return;
return (_range_used_on_hit(beam));
}
// Maybe the user wants to cancel at this point.
handle_stop_attack_prompt(mon);
if (beam_cancelled)
return;
// Check only if actual damage.
if (final > 0)
{
// Monster could be hurt somewhat, but only apply the
// monster's power based on how badly it is affected.
// For example, if a fire giant (power 16) threw a
// fireball at another fire giant, and it only took
// 1/3 damage, then power of 5 would be applied to
// foe_power or fr_power.
// BEGIN non-tracer enchantment
// Counting foes is only important for monster tracers.
if (!mons_atts_aligned(attitude, mons_attitude(mon)))
foe_power += 2 * final * mons_power(mon->type) / preac;
else
fr_power += 2 * final * mons_power(mon->type) / preac;
}
// Either way, we could hit this monster, so update range used.
range_used += range_used_on_hit();
}
// Submerged monsters are unaffected by enchantments.
if (submerged)
return (0);
void bolt::tracer_affect_monster(monsters* mon)
{
// Ignore unseen monsters.
if (!can_see_invis && mon->invisible()
|| (YOU_KILL(thrower) && !see_grid(mon->pos())))
{
return;
}
// Nasty enchantments will annoy the monster, and are considered
// naughty (even if a monster might resist).
if (_nasty_beam(mon, beam))
{
if (YOU_KILL(beam.thrower))
{
if (is_sanctuary(mon->pos()) || is_sanctuary(you.pos()))
remove_sanctuary(true);
// Update friend or foe encountered.
if (!mons_atts_aligned(attitude, mons_attitude(mon)))
{
foe_count++;
foe_power += mons_power(mon->type);
}
else
{
fr_count++;
fr_power += mons_power(mon->type);
}
if (you.religion == GOD_BEOGH
&& mons_species(mon->type) == MONS_ORC
&& mons_is_sleeping(mon) && !player_under_penance()
&& you.piety >= piety_breakpoint(2) && mons_near(mon))
{
hit_woke_orc = true;
}
}
behaviour_event(mon, ME_ANNOY, _beam_source(beam));
}
else
behaviour_event(mon, ME_ALERT, _beam_source(beam));
enable_attack_conducts(conducts);
void bolt::enchantment_affect_monster(monsters* mon)
{
// Submerged monsters are unaffected by enchantments.
if (mon->submerged())
return;
// Doing this here so that the player gets to see monsters
// "flicker and vanish" when turning invisible....
_ench_animation( beam.real_flavour, mon );
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
bool hit_woke_orc = false;
// now do enchantment affect
mon_resist_type ench_result = _affect_monster_enchantment(beam, mon);
if (mon->alive())
// Nasty enchantments will annoy the monster, and are considered
// naughty (even if a monster might resist).
if (nasty_to(mon))
{
if (YOU_KILL(thrower))
case MON_RESIST:
if (simple_monster_message(mon, " resists."))
beam.msg_generated = true;
break;
case MON_UNAFFECTED:
if (simple_monster_message(mon, " is unaffected."))
beam.msg_generated = true;
break;
default:
_update_hurt_or_helped(beam, mon);
break;
hit_woke_orc = true;
if (hit_woke_orc)
beogh_follower_convert(mon, true);
return (_range_used_on_hit(beam));
// END non-tracer enchantment
behaviour_event(mon, ME_ANNOY, beam_source_as_target());
// BEGIN non-enchantment (could still be tracer)
if (submerged && !beam.aimed_at_spot)
return (0); // missed me!
enable_attack_conducts(conducts);
// We need to know how much the monster _would_ be hurt by this, before
// we decide if it actually hits.
// Doing this here so that the player gets to see monsters
// "flicker and vanish" when turning invisible....
_ench_animation( real_flavour, mon );
if (see_grid(mon->pos()) && !beam.is_tracer)
{
mprf("The %s arcs harmlessly into the water.",
beam.name.c_str());
}
return (BEAM_STOP);
case MON_RESIST:
if (simple_monster_message(mon, " resists."))
msg_generated = true;
break;
case MON_UNAFFECTED:
if (simple_monster_message(mon, " is unaffected."))
msg_generated = true;
break;
case MON_AFFECTED:
case MON_OTHER: // Should this really be here?
update_hurt_or_helped(mon);
break;
if (beam.is_tracer)
hurt_final -= std::max(mon->ac / 2, 0);
else
hurt_final -= random2(1 + mon->ac);
void bolt::monster_post_hit(monsters* mon, int dmg)
{
if (YOU_KILL(thrower) && mons_near(mon))
print_wounds(mon);
// Don't annoy friendlies or good neutrals if the player's beam
// did no damage. Hostiles will still take umbrage.
if (dmg > 0 || !mons_wont_attack(mon) || !YOU_KILL(thrower))
behaviour_event(mon, ME_ANNOY, beam_source_as_target());
const int raw_damage = hurt_final;
// Handle missile effects.
if (item && item->base_type == OBJ_MISSILES)
{
if (item->special == SPMSL_POISONED)
{
int num_levels = 0;
// ench_power == AUTOMATIC_HIT if this is a poisoned needle.
if (ench_power == AUTOMATIC_HIT
&& x_chance_in_y(90 - 3 * mon->ac, 100))
{
num_levels = 2;
}
else if (random2(dmg) - random2(mon->ac) > 0)
num_levels = 1;
// Check monster resists, _without_ side effects (since the
// beam/missile might yet miss!)
hurt_final = mons_adjust_flavoured( mon, beam, raw_damage, false );
int num_success = 0;
if (YOU_KILL(thrower))
{
const int skill_level = _name_to_skill_level(name);
if (x_chance_in_y(skill_level + 25, 50))
num_success++;
if (x_chance_in_y(skill_level, 50))
num_success++;
}
else
num_success = 1;
#if DEBUG_DIAGNOSTICS
if (!beam.is_tracer)
{
mprf(MSGCH_DIAGNOSTICS,
"Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d",
mon->name(DESC_PLAIN).c_str(), hurt, raw_damage, hurt_final);
if (num_success)
{
if (num_success == 2)
num_levels++;
poison_monster(mon, whose_kill(), num_levels);
}
}
else if (item->special == SPMSL_CURARE)
{
if (ench_power == AUTOMATIC_HIT
&& curare_hits_monster(agent(), mon, whose_kill(), 2)
&& !mon->alive())
{
wake_mimic = false;
}
}
// Now, we know how much this monster would (probably) be
// hurt by this beam.
if (beam.is_tracer)
// Return true if the block succeeded (including reflections.)
bool bolt::attempt_block(monsters* mon)
{
const int shield_block = mon->shield_bonus();
bool rc = false;
if (shield_block > 0)
beam.beam_cancelled = true;
return (BEAM_STOP);
mprf("%s reflects the %s off %s %s!",
mon->name(DESC_CAP_THE).c_str(),
name.c_str(),
mon->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
shield->name(DESC_PLAIN).c_str());
_ident_reflector(shield);
// Check only if actual damage.
if (hurt_final > 0 && hurt > 0)
return (rc);
}
bool bolt::handle_statue_disintegration(monsters* mon)
{
bool rc = false;
if ((flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
&& mons_is_statue(mon->type) && !mons_is_icy(mon->type))
{
rc = true;
// Disintegrate the statue.
if (!silenced(you.pos()))
// Monster could be hurt somewhat, but only apply the
// monster's power based on how badly it is affected.
// For example, if a fire giant (power 16) threw a
// fireball at another fire giant, and it only took
// 1/3 damage, then power of 5 would be applied to
// foe_power or fr_power.
if (!mons_atts_aligned(beam.attitude, mons_attitude(mon)))
{
// Counting foes is only important for monster tracers.
beam.foe_power += 2 * hurt_final * mons_power(mons_type)
/ hurt;
}
if (!see_grid(mon->pos()))
mpr("You hear a hideous screaming!", MSGCH_SOUND);
// Either way, we could hit this monster, so return range used.
return (_range_used_on_hit(beam));
void bolt::affect_monster(monsters* mon)
{
// First some special cases.
// Digging doesn't affect monsters (should it harm earth elementals?)
if (flavour == BEAM_DIGGING)
return;
// Fire storm creates these, so we'll avoid affecting them
if (name == "great blast of fire" && mon->type == MONS_FIRE_VORTEX)
return;
// Handle tracers separately.
if (is_tracer)
{
tracer_affect_monster(mon);
return;
// BEGIN real non-enchantment beam
// Visual - wake monsters.
if (flavour == BEAM_VISUAL)
{
behaviour_event( mon, ME_DISTURB );
return;
}
// Special case: disintegrate (or Shatter) a statue.
// Since disintegration is an enchantment, it has to be handled
// here.
if (handle_statue_disintegration(mon))
return;
if (is_enchantment())
{
// no to-hit check
enchantment_affect_monster(mon);
return;
}
if (mon->submerged() && !aimed_at_spot)
return; // passes overhead
// We need to know how much the monster _would_ be hurt by this,
// before we decide if it actually hits.
int preac, postac, final;
if ( !determine_damage(mon, preac, postac, final) )
return;
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d",
mon->name(DESC_PLAIN).c_str(), preac, postac, final);
#endif
if (!engulfs && _beam_is_blockable(beam))
{
const int shield_block = mon->shield_bonus();
if (shield_block > 0)
{
const int hit = random2( beam.hit * 130 / 100
+ mon->shield_block_penalty() );
if (hit < shield_block)
{
item_def *shield = mon->mslot_item(MSLOT_SHIELD);
if (_beam_is_reflectable(beam, shield))
{
if (you.can_see(mon))
{
mprf("%s reflects the %s off %s %s!",
mon->name(DESC_CAP_THE).c_str(),
beam.name.c_str(),
mon->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
shield->name(DESC_PLAIN).c_str());
_ident_reflector(shield);
}
else if (see_grid(beam.pos))
mprf("The %s bounces off of thin air!",
beam.name.c_str());
_reflect_beam(beam);
}
else
mprf("%s blocks the %s.",
mon->name(DESC_CAP_THE).c_str(),
beam.name.c_str());
mon->shield_block_succeeded();
return (BEAM_STOP);
}
}
}
_update_hurt_or_helped(beam, mon);
if (!engulfs && is_blockable() && attempt_block(mon))
return;
// (not beam).
if (!silenced(you.pos()) && beam.flavour == BEAM_MISSILE
&& YOU_KILL(beam.thrower))
// (not magic beam).
if (!silenced(you.pos()) && flavour == BEAM_MISSILE
&& YOU_KILL(thrower))
// Note that hurt_final was calculated above, so we don't need it again.
// just need to apply flavoured specials (since we called with
// doFlavouredEffects = false above).
hurt_final = mons_adjust_flavoured(mon, beam, raw_damage);
// Apply flavoured specials.
mons_adjust_flavoured(mon, *this, postac, true);
{
if (thrower == KILL_YOU_MISSILE && mons_near(mon))
print_wounds(mon);
// Don't annoy friendlies or good neutrals if the player's beam
// did no damage. Hostiles will still take umbrage.
if (hurt_final > 0 || !mons_wont_attack(mon) || !YOU_KILL(beam.thrower))
behaviour_event(mon, ME_ANNOY, _beam_source(beam));
// Sticky flame.
if (beam.name == "sticky flame")
{
int levels = std::min(4, 1 + random2(mon->hit_dice) / 2);
napalm_monster(mon, _whose_kill(beam), levels);
}
bool wake_mimic = true;
// Handle missile effects.
if (item && item->base_type == OBJ_MISSILES)
{
if (item->special == SPMSL_POISONED)
{
int num_levels = 0;
// ench_power == AUTOMATIC_HIT if this is a poisoned needle.
if (beam.ench_power == AUTOMATIC_HIT
&& x_chance_in_y(90 - 3 * mon->ac, 100))
{
num_levels = 2;
}
else if (random2(hurt_final) - random2(mon->ac) > 0)
num_levels = 1;
int num_success = 0;
if (YOU_KILL(beam.thrower))
{
const int skill_level = _name_to_skill_level(beam.name);
if (x_chance_in_y(skill_level + 25, 50))
num_success++;
if (x_chance_in_y(skill_level, 50))
num_success++;
}
else
num_success = 1;
if (num_success)
{
if (num_success == 2)
num_levels++;
poison_monster(mon, _whose_kill(beam), num_levels);
}
}
else if (item->special == SPMSL_CURARE)
{
if (beam.ench_power == AUTOMATIC_HIT
&& curare_hits_monster(beam.agent(),
mon, _whose_kill(beam), 2)
&& !mon->alive())
{
wake_mimic = false;
}
}
}
if (wake_mimic && mons_is_mimic(mon->type))
mimic_alert(mon);
else if (hit_woke_orc)
beogh_follower_convert(mon, true);
}
monster_post_hit(mon, final);
static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon)
bool enchant_monster_with_flavour(monsters* mon, beam_type flavour, int powc)
{
bolt dummy;
dummy.flavour = flavour;
dummy.ench_power = powc;
dummy.apply_enchantment_to_monster(mon);
return dummy.obvious_effect;
}
mon_resist_type bolt::try_enchant_monster(monsters *mon)
case BEAM_SLOW:
// try to remove haste, if monster is hasted
if (mon->del_ench(ENCH_HASTE))
{
if (simple_monster_message(mon, " is no longer moving quickly."))
obvious_effect = true;
return (MON_AFFECTED);
}
// not hasted, slow it
if (!mon->has_ench(ENCH_SLOW)
&& !mons_is_stationary(mon)
&& mon->add_ench(mon_enchant(ENCH_SLOW, 0, whose_kill())))
{
if (!mons_is_paralysed(mon) && !mons_is_petrified(mon)
&& simple_monster_message(mon, " seems to slow down."))
{
obvious_effect = true;
}
}
return (MON_AFFECTED);
case BEAM_HASTE:
if (mon->del_ench(ENCH_SLOW))
{
if (simple_monster_message(mon, " is no longer moving slowly."))
obvious_effect = true;
return (MON_AFFECTED);
}
// Not slowed, haste it.
if (!mon->has_ench(ENCH_HASTE)
&& !mons_is_stationary(mon)
&& mon->add_ench(ENCH_HASTE))
{
if (!mons_is_paralysed(mon) && !mons_is_petrified(mon)
&& simple_monster_message(mon, " seems to speed up."))
{
obvious_effect = true;
}
}
return (MON_AFFECTED);
case BEAM_HEALING:
if (YOU_KILL(thrower))
{
if (cast_healing(5 + damage.roll(), mon->pos()) > 0)
obvious_effect = true;
msg_generated = true; // to avoid duplicate "nothing happens"
}
else if (heal_monster(mon, 5 + damage.roll(), false))
{
if (mon->hit_points == mon->max_hit_points)
{
if (simple_monster_message(mon, "'s wounds heal themselves!"))
obvious_effect = true;
}
else if (simple_monster_message(mon, " is healed somewhat."))
obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_PARALYSIS:
apply_bolt_paralysis(mon);
return (MON_AFFECTED);
case BEAM_PETRIFY:
apply_bolt_petrify(mon);
return (MON_AFFECTED);
case BEAM_CONFUSION:
if (!mons_class_is_confusable(mon->type))
return (MON_UNAFFECTED);
if (mon->add_ench(mon_enchant(ENCH_CONFUSION, 0, whose_kill())))
{
// FIXME: Put in an exception for things you won't notice
// becoming confused.
if (simple_monster_message(mon, " appears confused."))
obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_INVISIBILITY:
{
// Store the monster name before it becomes an "it" -- bwr
const std::string monster_name = mon->name(DESC_CAP_THE);
if (!mon->has_ench(ENCH_INVIS) && mon->add_ench(ENCH_INVIS))
{
// A casting of invisibility erases backlight.
mon->del_ench(ENCH_BACKLIGHT);
// Can't use simple_monster_message() here, since it checks
// for visibility of the monster (and it's now invisible).
// -- bwr
if (mons_near(mon))
{
mprf("%s flickers %s",
monster_name.c_str(),
player_monster_visible(mon) ? "for a moment."
: "and vanishes!" );
if (!player_monster_visible(mon))
{
// Also turn off autopickup.
Options.autopickup_on = false;
mpr("Deactivating autopickup; reactivate with Ctrl-A.",
MSGCH_WARN);
if (Options.tutorial_left)
{
learned_something_new(TUT_INVISIBLE_DANGER);
Options.tut_seen_invisible = you.num_turns;
}
}
}
obvious_effect = true;
}
return (MON_AFFECTED);
}
case BEAM_CHARM:
if (player_will_anger_monster(mon))
{
simple_monster_message(mon, " is repulsed!");
return (MON_OTHER);
}
if (mon->add_ench(ENCH_CHARM))
{
// FIXME: Put in an exception for fungi, plants and other
// things you won't notice becoming charmed.
if (simple_monster_message(mon, " is charmed."))
obvious_effect = true;
}
return (MON_AFFECTED);
// If it isn't lightning, reduce range by a lot.
if (beam.flavour != BEAM_ELECTRICITY)
return (2);
// Lightning goes through things.
if (flavour == BEAM_ELECTRICITY)
return (0);
// Takes a bolt and refines it for use in the explosion function. Called
// from missile() and beam() in beam.cc. Explosions which do not follow from
// beams (eg scrolls of immolation) bypass this function.
static void _explosion1(bolt &pbolt)
// Takes a bolt and refines it for use in the explosion function.
// Explosions which do not follow from beams (e.g., scrolls of
// immolation) bypass this function.
void bolt::refine_for_explosion()
pbolt.type = dchar_glyph(DCHAR_FIRED_BURST);
pbolt.flavour = BEAM_HELLFIRE;
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_HELLFIRE;
pbolt.type = dchar_glyph(DCHAR_FIRED_BURST);
pbolt.flavour = BEAM_HOLY;
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_HOLY;
pbolt.type = dchar_glyph(DCHAR_FIRED_BURST);
pbolt.flavour = BEAM_FIRE;
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_FIRE;
pbolt.type = dchar_glyph(DCHAR_FIRED_BURST);
pbolt.flavour = BEAM_ELECTRICITY;
pbolt.colour = LIGHTCYAN;
pbolt.damage.num = 1;
ex_size = 2;
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_ELECTRICITY;
colour = LIGHTCYAN;
damage.num = 1;
ex_size = 2;
pbolt.name = "blast of shrapnel";
pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP);
pbolt.flavour = BEAM_FRAG; // Sets it from pure damage to shrapnel
// (which is absorbed extra by armour).
name = "blast of shrapnel";
type = dchar_glyph(DCHAR_FIRED_ZAP);
flavour = BEAM_FRAG; // Sets it from pure damage to shrapnel
// (which is absorbed extra by armour).
pbolt.name = "ice storm";
pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP);
pbolt.colour = WHITE;
ex_size = 2 + (random2( pbolt.ench_power ) > 75);
name = "ice storm";
type = dchar_glyph(DCHAR_FIRED_ZAP);
colour = WHITE;
ex_size = 2 + (random2( ench_power ) > 75);
// explosion() is considered to emanate from beam->target_x, target_y
// and has a radius equal to ex_size. The explosion will respect
// boundaries like walls, but go through/around statues/idols/etc.
//
// For each cell affected by the explosion, affect() is called.
int explosion( bolt &beam, bool hole_in_the_middle,
bool explode_in_wall, bool stop_at_statues,
bool stop_at_walls, bool show_more, bool affect_items)
static sweep_type _radial_sweep(int r)
if (in_bounds(beam.source) && beam.source != beam.target
&& (!explode_in_wall || stop_at_statues || stop_at_walls))
// Center first.
work.push_back( coord_def(0,0) );
result.push_back(work);
for (int rad = 1; rad <= r; ++rad)
ray_def ray;
int max_dist = grid_distance(beam.source, beam.target);
ray.fullray_idx = -1; // to quiet valgrind
find_ray( beam.source, beam.target, true, ray, 0, true );
// Can cast explosions out from statues or walls.
if (ray.pos() == beam.source)
{
max_dist--;
ray.advance(true);
}
work.clear();
bool is_wall = grid_is_wall(grd(ray.pos()));
if (!stop_at_statues && !is_wall)
{
#if DEBUG_DIAGNOSTICS
mpr("Explosion beam passing over a statue or other "
"non-wall solid feature.", MSGCH_DIAGNOSTICS);
#endif
continue;
}
else if (!stop_at_walls && is_wall)
{
#if DEBUG_DIAGNOSTICS
mpr("Explosion beam passing through a wall.",
MSGCH_DIAGNOSTICS);
#endif
continue;
}
#if DEBUG_DIAGNOSTICS
if (!is_wall && stop_at_statues)
{
mpr("Explosion beam stopped by a statue or other "
"non-wall solid feature.", MSGCH_DIAGNOSTICS);
}
else if (is_wall && stop_at_walls)
{
mpr("Explosion beam stopped by a by wall.",
MSGCH_DIAGNOSTICS);
}
else
{
mpr("Explosion beam stopped by someting buggy.",
MSGCH_DIAGNOSTICS);
}
#endif
break;
work.push_back( coord_def(-rad, d) );
work.push_back( coord_def(+rad, d) );
// Backup so we don't explode inside the wall.
if (!explode_in_wall && grid_is_wall(grd(ray.pos())))
{
#if DEBUG_DIAGNOSTICS
int old_x = ray.x();
int old_y = ray.y();
#endif
ray.regress();
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"Can't explode in a solid wall, backing up a step "
"along the beam path from (%d, %d) to (%d, %d)",
old_x, old_y, ray.x(), ray.y());
#endif
work.push_back( coord_def(d, -rad) );
work.push_back( coord_def(d, +rad) );
// Beam is now an explosion.
beam.in_explosion_phase = true;
bool bolt::explode(bool show_more, bool hole_in_the_middle)
{
real_flavour = flavour;
const int r = std::min(ex_size, MAX_EXPLOSION_RADIUS);
in_explosion_phase = true;
beam.target.x, beam.target.y,
beam.type, beam.colour, beam.flavour,
beam.hit, beam.damage.num, beam.damage.size );
pos().x, pos().y, type, colour, flavour, hit, damage.num, damage.size);
noisy(10 + 5 * r, beam.target);
// set map to INT_MAX
explode_map.init(INT_MAX);
// Discover affected cells - recursion is your friend!
// this is done to model an explosion's behaviour around
// corners where a simple 'line of sight' isn't quite
// enough. This might be slow for really big explosions,
// as the recursion runs approximately as R^2.
_explosion_map(beam, coord_def(0, 0), 0, r);
noisy(10 + 5 * r, pos());
// Go through affected cells, drawing effect and
// calling affect() for each. Now, we get a bit
// fancy, drawing all radius 0 effects, then radius 1,
// radius 2, etc. It looks a bit better that way.
// Run DFS to determine which cells are influenced
explosion_map exp_map;
exp_map.init(INT_MAX);
determine_affected_cells(exp_map, coord_def(), 0, r, true, true);
// --------------------- begin boom ---------------
// We get a bit fancy, drawing all radius 0 effects, then radius
// 1, radius 2, etc. It looks a bit better that way.
const std::vector< std::vector<coord_def> > sweep = _radial_sweep(r);
const coord_def centre(9,9);
// do center -- but only if its affected
if (!hole_in_the_middle)
_explosion_cell(beam, coord_def(0, 0), drawing, affect_items);
// do the rest of it
for (int rad = 1; rad <= r; rad ++)
for (siter ci = sweep.begin(); ci != sweep.end(); ++ci)
if (explode_map[-rad+9][ay+9] < INT_MAX)
{
_explosion_cell(beam, coord_def(-rad, ay), drawing,
affect_items);
}
if (explode_map[rad+9][ay+9] < INT_MAX)
{
_explosion_cell(beam, coord_def(rad, ay), drawing,
affect_items);
}
}
const coord_def delta = *cci;
// do top & bottom
for (int ax = -rad; ax <= rad; ax += 1)
{
if (explode_map[ax+9][-rad+9] < INT_MAX)
{
_explosion_cell(beam, coord_def(ax, -rad), drawing,
affect_items);
}
if (explode_map[ax+9][rad+9] < INT_MAX)
{
_explosion_cell(beam, coord_def(ax, rad), drawing,
affect_items);
}
}
if (delta.origin() && hole_in_the_middle)
continue;
// new-- delay after every 'ring' {gdl}
// If we don't refresh curses we won't
// guarantee that the explosion is visible.
if (drawing)
update_screen();
// Only delay on real explosion.
if (!beam.is_tracer && drawing)
delay(50);
if (exp_map(delta + centre) < INT_MAX)
explosion_draw_cell(delta + pos());
}
update_screen();
delay(50);
for ( int i = -9; i <= 9; ++i )
for ( int j = -9; j <= 9; ++j )
if ( explode_map[i+9][j+9]
&& see_grid(beam.target + coord_def(i,j)))
for (siter ci = sweep.begin(); ci != sweep.end(); ++ci)
{
for (viter cci = ci->begin(); cci != ci->end(); ++cci)
{
const coord_def delta = *cci;
if (delta.origin() && hole_in_the_middle)
continue;
if (exp_map(delta + centre) < INT_MAX)
// Duplicate old behaviour - pause after entire explosion
// has been drawn.
if (!beam.is_tracer && cells_seen > 0 && show_more)
// Pause after entire explosion has been drawn.
if (!is_tracer && cells_seen > 0 && show_more)
coord_def realpos = beam.target + p;
if (!drawOnly)
{
// Random/chaos beams: randomize before affect().
if (beam.real_flavour == BEAM_RANDOM)
beam.flavour = static_cast<beam_type>(
random_range(BEAM_FIRE, BEAM_ACID) );
else if (beam.real_flavour == BEAM_CHAOS)
beam.flavour = _chaos_beam_flavour();
affect(beam, realpos, NULL, affect_items);
beam.flavour = beam.real_flavour;
}
// Early out for tracer.
if (beam.is_tracer)
return;
if (drawOnly)
if (see_grid(p))
const coord_def drawpos = grid2view(realpos);
// XXX Don't you always see your own grid?
if (see_grid(realpos) || realpos == you.pos())
{
const coord_def drawpos = grid2view(p);
// bounds check
if (in_los_bounds(drawpos))
{
cgotoxy(drawpos.x, drawpos.y, GOTO_DNGN);
put_colour_ch(
beam.colour == BLACK ? random_colour() : beam.colour,
dchar_glyph( DCHAR_EXPLOSION ) );
}
// bounds check
if (in_los_bounds(drawpos))
{
cgotoxy(drawpos.x, drawpos.y, GOTO_DNGN);
put_colour_ch(
colour == BLACK ? random_colour() : colour,
dchar_glyph(DCHAR_EXPLOSION));
}
// Check to see out of range.
if (p.abs() > r*(r+1))
return;
// Check count.
if (count > 10*r)
return;
const coord_def loc(beam.target + p);
fake_flavour();
target = p;
affect_cell();
flavour = real_flavour;
}
// Make sure we haven't run off the map.
if (!map_bounds(loc))
return;
// Uses DFS
void bolt::determine_affected_cells(explosion_map& m, const coord_def& delta,
int count, int r,
bool stop_at_statues, bool stop_at_walls)
{
const coord_def centre(9,9);
const coord_def loc = pos() + delta;
// Hmm, I think we're ok.
explode_map(p + coord_def(9,9)) = count;
if (grid_is_solid(dngn_feat) && !grid_is_wall(dngn_feat) && stop_at_statues)
return;
// Hmm, I think we're OK.
m(delta + centre) = std::min(count, m(delta + centre));
// Is that cell already covered by a recursion that was closer
// to the center?
if (explode_map(p + coord_def(9,9) + Compass[i]) <= count)
const coord_def new_delta = delta + Compass[i];
if (new_delta.rdist() > centre.rdist())
continue;
// Is that cell already covered?
if (m(new_delta + centre) <= count)
// haste/healing/invisibility
if (beam.flavour == BEAM_HASTE || beam.flavour == BEAM_HEALING
|| beam.flavour == BEAM_INVISIBILITY)
{
return (true);
}
return (false);
return (flavour == BEAM_HASTE
|| flavour == BEAM_HEALING
|| flavour == BEAM_INVISIBILITY);