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);elsereturn x / 2;}
bool is_enchantment() const; // no block/dodge, but mag resistvoid set_target(const dist &);
bool is_enchantment() const; // no block/dodge, use magic resistvoid 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 DEBUGmprf(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_DIAGNOSTICSmprf( 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 valgrindfind_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_DIAGNOSTICSmprf( 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 checkif (in_los_bounds(drawpos)){#ifndef USE_TILEcgotoxy(drawpos.x, drawpos.y);put_colour_ch(colour == BLACK ? random_colour() : element_colour(colour),type);
// initpbolt.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 valgrindfind_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 WIN32CONSOLEbool oldValue = true;if (!pbolt.is_tracer)oldValue = set_buffering(false);#endifwhile (!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();}
// bounceray_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;elsereturn 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;}elsedo_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 DEBUGmprf(MSGCH_DIAGNOSTICS, "fire_beam() called on already done beam ""'%s' (item = '%s')", name.c_str(),item ? item->name(DESC_PLAIN).c_str() : "none");#endifreturn;}
if (tile_beam != -1 && in_los_bounds(drawpos)){tiles.add_overlay(testpos, tile_beam);delay(pbolt.delay);}else#endif// bounds checkif (in_los_bounds(drawpos)){#ifndef USE_TILEcgotoxy(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);elseray.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. -- GDLcoord_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);elseray.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 hastedif (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 itif (!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" -- bwrconst 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).// -- bwrif (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. -- bwrreturn (-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 coldif (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 BLASTif (beam.name == "blast of poison")return (true);// FOUL VAPOR (SWAMP DRAKE)if (beam.name == "foul vapour")return (true);// STINKING CLOUDif (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 DEBUGif (!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());#endifif (beam.is_tracer || beam.flavour != BEAM_MISSILE)return;
ASSERT( item != NULL && is_valid_item(*item) );
if (beam.dropped_item){#ifdef DEBUGmprf(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 stackif (is_stackable_item(*item)){for (stack_iterator si(p); si; ++si){if (items_stack(*item, *si)){beam.item_index = si->index();break;}}}elsebeam.item_index = igrd(p);}else{#ifdef DEBUGmprf(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. -- bwrint 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;// DIGGINGif (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 pathif (flavour == BEAM_POLYMORPH)ctype = static_cast<cloud_type>(1 + random2(8));
return (BEAM_STOP);}// NUKE / DISRUPTif (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 HITSif (env.cgrid(p) != EMPTY_CLOUD) // hit a cloud{// polymorph randomly changes clouds in its pathif (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 BLASTif (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/lavaif (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/lavaif (feat == DNGN_LAVA && flavour == BEAM_COLD|| grid_is_watery(feat) && is_fiery())
// GREAT BLAST OF COLDif (beam.name == "great blast of cold")place_cloud( CLOUD_COLD, p, random2(5) + 3, _whose_kill(beam),beam.killer() );
// BALL OF STEAMif (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 GASif (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 codebeam.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_DIAGNOSTICSmprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d",testhit, block);#endifif (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_DIAGNOSTICSmprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d",hit, block);#endifif (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);}elsemprf( "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 messageif (!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 messageif (!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 beamif (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.");elsempr("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.");elsempr("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!");}elsecanned_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!");}elsecanned_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 themif (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;elsepreac = 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;elsebeam.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;elsefr_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));}elsebehaviour_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 affectmon_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);elsehurt_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++;}elsenum_success = 1;
#if DEBUG_DIAGNOSTICSif (!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 themif (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 checkenchantment_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_DIAGNOSTICSmprf(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);}elsemprf("%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++;}elsenum_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 hastedif (mon->del_ench(ENCH_HASTE)){if (simple_monster_message(mon, " is no longer moving quickly."))obvious_effect = true;return (MON_AFFECTED);}// not hasted, slow itif (!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" -- bwrconst 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).// -- bwrif (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 valgrindfind_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_DIAGNOSTICSmpr("Explosion beam passing over a statue or other ""non-wall solid feature.", MSGCH_DIAGNOSTICS);#endifcontinue;}else if (!stop_at_walls && is_wall){#if DEBUG_DIAGNOSTICSmpr("Explosion beam passing through a wall.",MSGCH_DIAGNOSTICS);#endifcontinue;}#if DEBUG_DIAGNOSTICSif (!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);}#endifbreak;
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_DIAGNOSTICSint old_x = ray.x();int old_y = ray.y();#endifray.regress();#if DEBUG_DIAGNOSTICSmprf(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_MAXexplode_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 influencedexplosion_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 affectedif (!hole_in_the_middle)_explosion_cell(beam, coord_def(0, 0), drawing, affect_items);// do the rest of itfor (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 & bottomfor (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 checkif (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 checkif (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 DFSvoid 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/invisibilityif (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);