diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..a7e7e192da9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.cquery_cache/ +.DS_Store diff --git a/.mailmap b/.mailmap index 59b94c8e7b77..23cc5d25e731 100644 --- a/.mailmap +++ b/.mailmap @@ -36,7 +36,7 @@ galehar galehar elliptic edlothiol -MarvinPA +kate- <601195+semitonal@users.noreply.github.com> SamB |amethyst dolorous @@ -62,6 +62,7 @@ Sage Sage gammafunk wheals +wheals reaverb reaverb N78291 @@ -176,3 +177,4 @@ Yermak <33148474+Mindcrafter@users.noreply.github.com> EMTedronai Alcon Patashu +gmarks diff --git a/.travis.yml b/.travis.yml index 35cbcf1355bd..2d75ddafd3c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,107 @@ language: cpp +dist: xenial compiler: - - clang - gcc + - clang +cache: ccache sudo: required -before_install: - - sudo apt-get update --option Acquire::Retries=100 --option Acquire::http::Timeout="60" - - perl .travis/apt.pl - - if [ "$CXX" = "g++" ]; then export CXX="g++-4.7" CC="gcc-4.7"; fi +addons: + apt: + update: true + retries: true + packages: &basic_deps + - xorg-dev + - python3-yaml + - liblua5.1-0-dev # strictly speaking, only BUILD_ALL builds + - liblua5.1-0-dbg + - gdb + env: - - NOTHING=1 - - UNBRACE=1 - - CHECKWHITE=1 - - FULLDEBUG=1 - - TILES=1 - - TILES=1 FULLDEBUG=1 - - WEBTILES=1 - - WEBTILES=1 FULLDEBUG=1 - - USE_DGAMELAUNCH=1 EXTERNAL_DEFINES=-UTOURNEY - - USE_DGAMELAUNCH=1 WEBTILES=1 EXTERNAL_DEFINES=-UTOURNEY - - TILES=1 BUILD_ALL=1 - - WEBTILES=1 BUILD_ALL=1 - - CROSSCOMPILE=1 + global: + - CCACHE_CPP2=yes + matrix: # anything here is fully crossed with compilers, uses *basic_deps + - NOTHING=1 + - FULLDEBUG=1 + - WEBTILES=1 FULLDEBUG=1 + git: submodules: false + +# the rest of the build matrix, specifying each combo directly. Anything +# without explicit addons here will uses *basic_deps above, and any addons list +# overrides the global one. matrix: - exclude: + include: + # lint-type checks + - env: CHECKWHITE=1 + addons: {apt: {packages: [] }} + - env: UNBRACE=1 + addons: {apt: {packages: [] }} + # the rest of the actual builds. + - compiler: gcc + env: WEBTILES=1 + - compiler: gcc + env: WEBTILES=1 BUILD_ALL=1 + - compiler: gcc + env: TILES=1 + addons: + apt: + packages: + - *basic_deps + - &sdl_deps [libegl1-mesa-dev, libgles2-mesa-dev, libsdl2-dev, libsdl2-image-dev, libsdl2-mixer-dev] - compiler: clang - env: UNBRACE=1 + env: TILES=1 + addons: + apt: + packages: + - *basic_deps + - *sdl_deps + - compiler: gcc + env: TILES=1 FULLDEBUG=1 + addons: + apt: + packages: + - *basic_deps + - *sdl_deps - compiler: clang - env: CHECKWHITE=1 + env: TILES=1 FULLDEBUG=1 + addons: + apt: + packages: + - *basic_deps + - *sdl_deps + - compiler: gcc + env: TILES=1 BUILD_ALL=1 + addons: + apt: + packages: + - *basic_deps + - &sdl_buildall_deps [libegl1-mesa-dev, mesa-common-dev, libglu1-mesa-dev, libasound2-dev, libxss-dev] - compiler: clang - env: CROSSCOMPILE=1 + env: TILES=1 BUILD_ALL=1 + addons: + apt: + packages: + - *basic_deps + - *sdl_buildall_deps + - env: CROSSCOMPILE=1 + addons: + apt: + packages: + - mingw-w64 + - binutils-mingw-w64 + - g++-mingw-w64-i686 + - gcc-mingw-w64-i686 + - nsis + - *sdl_buildall_deps + - compiler: gcc + env: USE_DGAMELAUNCH=1 EXTERNAL_DEFINES=-UTOURNEY + - compiler: gcc + env: USE_DGAMELAUNCH=1 WEBTILES=1 EXTERNAL_DEFINES=-UTOURNEY + +before_install: + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then if [ "$CC" == "clang" ]; then sudo ln -s `which ccache` /usr/lib/ccache/clang; fi; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then if [ "$CXX" == "clang++" ]; then sudo ln -s `which ccache` /usr/lib/ccache/clang++; fi; fi install: perl .travis/deps.pl script: perl .travis/build.pl notifications: diff --git a/.travis/apt.pl b/.travis/apt.pl deleted file mode 100644 index 61a34772acca..000000000000 --- a/.travis/apt.pl +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -run(qw(sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y)); -run(qw(sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y)); - -retry(qw(sudo apt-get update -qq)); - -my @common_libs = qw(xorg-dev python3-yaml); - -if ($ENV{CROSSCOMPILE}) { - retry(qw(sudo apt-get install -qq mingw32 mingw32-binutils mingw32-runtime g++-mingw-w64-i686 gcc-mingw-w64-i686 nsis)); -} - -if ($ENV{CXX} eq "clang++") { - retry(qw(sudo apt-get install -qq libstdc++6-4.7-dev), @common_libs); -} -elsif ($ENV{CXX} eq "g++") { - retry(qw(sudo apt-get install -qq g++-4.7), @common_libs); -} - -sub run { - my @cmd = @_; - - print "Running '@cmd'\n"; - my $ret = system(@cmd); - exit($ret) if $ret; - return; -} - -sub retry { - my (@cmd) = @_; - - my $tries = 5; - my $sleep = 5; - - my $ret; - while ($tries--) { - print "Running '@cmd'\n"; - $ret = system(@cmd); - return if $ret == 0; - print "'@cmd' failed ($ret), retrying in $sleep seconds...\n"; - sleep $sleep; - } - - print "Failed to run '@cmd' ($ret)\n"; - # 1 if there was a signal or system() failed, otherwise the exit status. - exit($ret & 127 ? 1 : $ret >> 8); -} diff --git a/.travis/deps.pl b/.travis/deps.pl index 1427ea2c15c9..ff70825f3572 100644 --- a/.travis/deps.pl +++ b/.travis/deps.pl @@ -2,34 +2,12 @@ use strict; use warnings; -my @deps = qw( - gdb -); - +# this file used to do a bunch of apt stuff, but that's now all done in the +# travis build matrix. if ($ENV{BUILD_ALL} || $ENV{CROSSCOMPILE}) { retry(qw(git fetch --unshallow)); retry(qw(git submodule update --init --recursive)); - - push @deps, qw( - libegl1-mesa-dev - libasound2-dev - libxss-dev - ); } -else { - push @deps, qw( - liblua5.1-0-dev - liblua5.1-0-dbg - ); - - push @deps, qw( - libsdl2-dev - libsdl2-image-dev - libsdl2-mixer-dev - ) if $ENV{TILES} || $ENV{WEBTILES}; -} - -retry(qw(sudo apt-get install), @deps); sub retry { my (@cmd) = @_; diff --git a/LICENSE b/LICENSE index 08656b10161f..9069e9579daa 100644 --- a/LICENSE +++ b/LICENSE @@ -18,7 +18,7 @@ These include: resize-observer-polyfill, and scrollbarwidth worley.{cc,h} * zlib: webserver/static/scripts/contrib/inflate.js -* Apache v2: pcg.cc +* Apache v2, MIT (see comments in the file for details): pcg.cc The majority of Crawl's tiles and artwork are released under the CC0 license (https://creativecommons.org/publicdomain/zero/1.0/), and new submissions are diff --git a/crawl-ref/CREDITS.txt b/crawl-ref/CREDITS.txt index 604191c1fdc9..d09461c02c97 100644 --- a/crawl-ref/CREDITS.txt +++ b/crawl-ref/CREDITS.txt @@ -105,6 +105,7 @@ Matthew Boeh Mike Boutin John Boyle + Josh Braden Daniel Mark Brownrigg Andre Bubel Trent W. Buck @@ -119,11 +120,13 @@ Thom Chiovoloni Anthony Chuah Henry Cipolla + CollinJ Nicholas Connelly Tom Conrad Vitor 'Baconkid' Costa Aaron Curtis Simon Curtis + cut1less Paul Dejean Denzi Lothar Dierkes @@ -140,6 +143,7 @@ Micha Elsner Carl-Oscar Erneholm Erich Erstu + Higor Eurípedes Christopher Evenstar Floodkiller floraline @@ -188,6 +192,7 @@ Vesa Kivimäki Daniel Klein Jon Knapp + Quinten Konyn Ilkka Koskela infiniplex Vambola Kotkas @@ -197,6 +202,7 @@ Lorenzo Lami Lantier Maciej Lapinski + Nikolai Lavsky Lamil Lerran Jordan Lewis Sami Liedes @@ -215,6 +221,7 @@ Paul Maloney Pablo Mansanet Wille Mäntylä + Gabriel Marks Jean Martel James 'Eronarn' Meickle Jacob Meigs @@ -231,9 +238,9 @@ Naruni neofelis nicolae - Nikolai Lavsky Jonathon Nooner NormalPerson7 + Michael Nowack Erkki Nurmi Mattias Nyberg nyra @@ -257,14 +264,17 @@ Iago Porto psywombats pubby + Ashley Elizabeth Raine Edward Randtke Derek Ray RealzHS Terence Reeves Tobias Rehbein Remsleep + Chris Rendle-Short Ryan Riegel RjY + rmcneive Andy Roche rockygargoyle Will Rogers @@ -274,6 +284,7 @@ Sebastian Salman Sandman25 Brett Scarborough + sdynet Roman Sêk Brennan Shacklett ShadyAmish @@ -284,7 +295,6 @@ Robert Shimmin Sigurd Edgar Simo - Skrybe Alex Smith Keanan Smith Richard Soderberg @@ -297,6 +307,7 @@ Jan 'heftig' Steffens Stenella Timothy Stiles + John Stilley Johan Strandell Ben Striegel Derrick Sund @@ -309,8 +320,10 @@ Jared 'Twinge' Tinney Matt Titus Tommy + Sean VanMeter Mikko Vepsäläinen WanderingBlade + Richard Wardin waylon531 Steven Wheeler Thomas Willem @@ -335,7 +348,7 @@ The Dungeon Crawl Stone Soup development team is: Corin Buchanan-Howland James Bulgin Robert Burnham - Chris Campbell + Kate Campbell Matthew Cline Florian Diebold Rax E. Dillon diff --git a/crawl-ref/INSTALL.txt b/crawl-ref/INSTALL.txt index 64748f06d8ad..62ec004b8219 100644 --- a/crawl-ref/INSTALL.txt +++ b/crawl-ref/INSTALL.txt @@ -134,7 +134,7 @@ On Fedora, and possibly other RPM-based systems, you can get the dependencies by running the following as root: dnf install gcc gcc-c++ make bison flex ncurses-devel compat-lua-devel \ - sqlite-devel zlib-devel pkgconfig python-yaml SDL-devel SDL_image-devel \ + sqlite-devel zlib-devel pkgconfig python-yaml SDL2-devel SDL2_image-devel \ libpng-devel freetype-devel dejavu-sans-fonts dejavu-sans-mono-fonts (the last six are needed only for tile builds). As with Debian, this package @@ -310,11 +310,15 @@ from within the MSYS2 Shell. After the packages are installed and gcc runs, you're ready to build crawl! -* To install PyYAML, you can install it from Pip/PyPA: +* To install PyYAML, you can install it from either pacman or from Pip/PyPA: - pacman -S mingw64/mingw-w64-x86_64-python2-pip + pacman -S mingw64/mingw-w64-x86_64-python3-pip pip install pyyaml + OR + + pacman -S mingw-w64-x86_64-python3-yaml + You can verify PyYAML is installed by running `python -m yaml`, which should give an error like "'yaml' is a package and cannot be directly imported" (rather than "No module named yaml"). diff --git a/crawl-ref/docs/changelog.txt b/crawl-ref/docs/changelog.txt index e4e0fd69352e..e07338fa38a1 100644 --- a/crawl-ref/docs/changelog.txt +++ b/crawl-ref/docs/changelog.txt @@ -1,3 +1,160 @@ +Stone Soup 0.24 (20191024) +-------------------------------- + +Highlights +---------- +* Vampire species simplified +* Thrown weapons streamlined +* Fedhas reimagined +* Sif Muna reworked + +Branches, Environment +--------------------- +* Incremental pregeneration: + - This mode generates the dungeon in a stable order for all games regardless + of the path taken by the player, meaning that the behavior of seeds for + online and offline games should now be the same. + - Games using the same seed will see the same dungeon if they are + incrementally pregenerated without requiring the long initial generation + time of full pregeneration as introduced in 0.23. + - Incremental pregeneration is the default mode for new games. + - Servers can now enable seed selection without heavy CPU cost. +* A Trove map requiring many uses of digging has been removed. +* Identification scrolls are no longer used as a potential Trove cost. +* Transporters in vaults now correctly place in the Abyss. +* Fog generation in the Desolation of Salt portal now happens far less often + per turn, greatly reducing slowdown from high CPU usage. +* Random traps no longer place under items placed by vaults. + +Character +--------- +* Formicids can now use their dig ability on diggable statues. +* Manticore barbs now can only be picked out if the player can move, isn't + confused, and isn't asleep. +* Net traps now always trigger when the player steps on them. +* Vampiric weapons no longer have a hunger cost upon wield. +* Vampire simplification: Player vampires no longer eat or drink blood, and + have exactly two blood states. + - Alive: Regenerates quickly, normal HP modifier, no undead bonuses, no + batform, can transform and berserk normally. + - Bloodless: No regeneration with monsters in sight, -20% HP, stealth bonus, + undead resistances, batform, no other transformations or berserk. + - Blood states are transitioned by an ability which costs delay; becoming + bloodless incurs no penalty, becoming alive causes a temporary frail + mutation. + - Bat form causes stat drain. + +Gods +---- +* Fedhas reimagined: + - Piety is gained exclusively from kills instead of through corpse decay. + - Fedhas abilities now create plant allies directly as temporary summons + instead of creating permanent allies using plants. All abilities cost + piety instead of food. + - New abilities: + * 2*: Active 'Wall of Briars' ability to surround the player with briar + patches. Hostile monsters will attack briars to reach you, taking + damage. + * 3*: Active 'Grow Ballistomycete' ability to place a ballistomycete at + any location within radius 2. The ballistomycete will fire spores + that seek out foes and make a 1-radius damaging explosion that + additionally confuses living creatures. + * 4*: Active 'Overgrow' ability to destroy any non-permarock walls in a + 3x3 area, replacing them with plant allies, mostly wandering + mushrooms or burning bushes and sometimes ballistomycetes or oklob + plants. + * 5*: Active 'Grow Oklob' ability to place an oklob plant at any location + within radius 2. + - Fedhas no longer hates any necromancy spells, and allows worship by undead + species. + - Removed abilities: Fungal Bloom, Reproduction, Growth, Evolution, Rain. +* Sif Muna reworked: + - Piety gain is exclusively from kills. + - 'Channel Energy' is available at 1* piety instead of 3*. + - 'Forget Spell' is available at 3* piety instead of 4*. + - New 4* 'Divine Exegesis' ability to cast any spell in your library + regardless of skill training. Receives a spell-power bonus based on + Invocations skill of 1.5 times the spell-power received from equivalent + levels of training in the spell's schools. + - The Divine Energy ability and Sif's miscast protection are removed. +* Trog and Okawaru now can gift unbranded boomerangs and javelins. +* Jiyva's Slimify ability now works properly on skeletons, zombies and + simulacra. +* Torment can now trigger Jiyva jelly spawns and Xom butterfly spawns. + +Interface +--------- +* The 'X' key map mode has zoom support, defaulting to 60% zoom. +* Sealed off clinging and sensed monsters no longer stop autoexplore. +* Equipment melding no longer stops autoexplore and autotravel. +* Armour, jewellery, and weapons can be worn/wielded from the floor. +* Summoner monsters are now highlighted when the cursor is over one of its + summons. +* Traps are now shown in the ctrl-x listing and are indexed in the stash + tracker. +* Scarves are now properly shown on player tiles. +* Species and background selection menus now have tiles. +* Updated visual theme for main menu, high scores, and related screens. + +Items +----- +* New unrand: Staff of Battle, a staff of conjuration that spawns a + battlesphere when a hostile monster is in view. +* Unrandart changes: + - The Storm Bow now has a penetration effect. + - Gyre and Gimble now has the protection brand, enchantment of +7, and no + longer has Dex-3. + - Piercer has been removed, its effect having been merged into Storm Bow. +* Weapons with the holy wrath ego can now be cursed. +* Randart rings no longer generate with both rCorr and *Corrode. +* Throwing weapons have been streamlined. + - Blowguns are removed, needles are replaced with throwing darts. + - Dart effects scale with Throwing and Stealth skill. + - Darts of sleep, paralysis, confusion are removed. + - Darts of frenzy are renamed datura-tipped darts. + - A new dart brand, atropa is added. Atropa-tipped darts cause brief + confusion and longer-lasting blindness in the target. + - Tomahawks are renamed to boomerangs and always return. + - Javelins always penetrate. + - Steel and silver are merged into a single brand, called silver. Silver + ammunition applies the maximum of vorpal bonus damage and the old silver + damage bonus. + - Poison, returning, penetration, and exploding are no longer available + brands for tomahawks and javelins. + +Lua +--- +* Monster AC, EV, MR, MaxHP, and descriptions are now exposed in the CLua + monster.info class. +* A new function 'defeat_mr()' to determine the chance that a given spell + defeats a monster's magic resistance. +* There is a basic seed explorer available in scripts/seed_explorer.lua. + +Monsters +-------- +* Monsters can now use wands of clouds, iceblast, and scattershot. +* Monsters clones created by Mara and rakshasa have the intended reduced HP of + the original monster instead of several times that value. +* A monster having the Dig spell no longer allows it to cast spells out of + LOS. +* Azrael and draconian scorchers can use Call Down Damnation when not at + low HP. +* Player ghosts can be shafted. +* Golden Eyes no longer have randomized spellpower for their gaze. +* Monster airstrike uses the same formula as the player, reducing damage + variance. +* Pikel's slaves no longer drop corpses nor items. + +Spells +------ +* The HP from Death's Door is fixed at time of casting rather than being + updated continuously, hence being subject to changes in spellpower. +* Olgreb's Toxic Radiance now properly triggers ally conducts when cast. +* Olgreb's Toxic Radiance now applies direct damage and poison status in a way + that considers the time of each turn. +* Shroud of Golubria's spellpower is now capped at 50. + + Stone Soup 0.23.2 (20190330) ---------------------------- @@ -207,6 +364,17 @@ Spells ------ * Confusing touch works with a weapon wielded +Stone Soup 0.22.2 (20190327) +---------------------------- + +Bugfix Release +-------------- +* Fix a crash on startup when there are seeded games (from 0.23) in a shared + save directory. +* Let Barachim telefrag at their full range. +* Don't let ball lightnings climb stairs. +* 52 other bugfixes and cleanups in total. + Stone Soup 0.22.1 (20180916) ---------------------------- diff --git a/crawl-ref/docs/crawl_manual.rst b/crawl-ref/docs/crawl_manual.rst index 7cace7ddf148..baeda325862e 100644 --- a/crawl-ref/docs/crawl_manual.rst +++ b/crawl-ref/docs/crawl_manual.rst @@ -41,7 +41,7 @@ You can: - view your past games or those of others - battle ghosts of other players - compete using a common score list -- take part in the annual tournament +- take part in the semiannual tournament - play the most recent development version A full list of available servers and information on how to connect to them can @@ -67,10 +67,13 @@ Game modes are: Dungeon Crawl Start a standard game of Crawl. +Choose game seed + Start a standard game of Crawl with a custom seed (see `Seeded play`_ below). + Tutorial for Dungeon Crawl Start one of several specialised tutorials to learn how to play. -Hints mode for Dungeon Crawl +Hints Mode for Dungeon Crawl Start a standard game of Crawl, modified to provide help as you play. Dungeon Sprint @@ -468,31 +471,30 @@ Seeded play ======================================== Crawl dungeons are determined by a "seed" number used to initialise the game's -random number generator. If you initialise the game, keeping the game version -constant, then the same seed should (within certain parameters) lead to the same -dungeon. In offline games you can view your game's seed with '?V' as well as in -a character file; in online games you normally must finish a game in order to -see the game's seed. There are two seeded modes: - -Without dungeon pregeneration ('pregen_dungeon = false') - If dungeon pregeneration is turned off (the default), every game with the - same seed will have at least the same initial dungeon level and temple - layout. However, the order in which you explore levels after the first one - can lead to multiple possible dungeon layouts, depending on your choices. This - is implicitly how dungeon generation always worked before version 0.23. - -With dungeon pregeneration ('pregen_dungeon = true') - If dungeon pregeneration is turned on, the entire connected dungeon will be - determined by the game seed. Portal vaults and chaotic zones such as the - abyss are not guaranteed to be the same, though. - -To set a game seed, use the 'game_seed' rc file option, or the '-seed' command -line option. +random number generator. You may either let the game choose a seed randomly, +or specify a seed; if you choose a seed this puts the game in "Seeded" mode, +which is scored separately. Playing games with the same seed value, as long as +the game version is constant, should (within certain parameters) lead to the +same dungeon. The entire connected dungeon will be determined by the game +seed, including dungeon layout, monster placement, and items. Portal vaults +and chaotic zones such as the abyss are not guaranteed to be the same, and the +placement of rare unique artefacts may vary depending on certain player +actions. + +To set a game seed, use the "Choose game seed" option from the main menu; you +can also use the 'game_seed' rc file option, or the '-seed' command line +option. In offline games you can view your game's seed with '?V' as well as in +a character file; in online games a randomly chosen seed will only be shown to +you after finishing the game. If you find that the same seed generates distinct parts of a dungeon on the same or different devices, please report it as a bug. However, keep in mind -that upgrading your save game between multiple versions of crawl, or traversing -dungeon levels in different orders, will naturally lead to seed divergence. +that upgrading your save game between multiple versions of crawl will +naturally lead to seed divergence. When playing offline, if you would like to +ensure that your game can be upgraded without divergence, you can set +'pregen_dungeon = full' in your options file. (This will also ensure +completely stable unique artefact placement.) On the other hand, to completely +disable incremental pregeneration, you can set 'pregen_dungeon = false'. Further Help ======================================== @@ -665,7 +667,7 @@ adventures, how they are displayed, and what commands there are to use them: = rings (use 'P'ut on and 'R'emove) " amulets (use 'P'ut on and 'R'emove) \| staves (use 'w'ield) -: spellbooks (use 'r'ead and 'M'emorise and 'z'ap) +: spellbooks (use 'M'emorise and 'z'ap) } miscellaneous (use 'V' for evoking from the inventory) $ gold (use 'g' to pick up) ======= ============= ================================================ @@ -737,6 +739,10 @@ In order to get a description of what an item does, bring up the inventory (with of armours and weapons, but don't expect too much information from examining unidentified items. +In most equipment-related prompts and menus, the ';' key is a shortcut for +"last unequipped item," meaning the armour, jewellery or weapon you most +recently took off or unwielded. + Another useful command is the '{' key, which lets you inscribe items with a comment. You can also inscribe items when looking at your inventory with 'i', simply by pressing the letter of an item. For more details, and how to automate @@ -855,11 +861,6 @@ produce a number of 'chunks', which can be eaten with the 'e' command as above. Some species are happy to eat raw meat at any time, and others cannot eat meat at all. Information on special diets is displayed on the 'A' screen. -Vampires are a special case. Members of this species can try to drink blood -directly from a fresh corpse (use the 'e' command). They can also bottle potions -of blood from corpses instead of chopping corpses into chunks with the 'c' -command. - ? Magical Scrolls ======================================== @@ -883,16 +884,14 @@ the 'q' command. ======================================== Sometimes you will be lucky enough to find a stick which contains stored magical -energies. Wands each have a certain amount of charges, and a wand will cease to -function when its charges run out. You must identify a wand to find out how many -uses it has left. This can be done with a scroll of identify; characters with a -good Evocations skill may also deduce the number of charges simply upon evoking -the wand. Evoking a wand without having fully identified the number of charges -remaining will waste some charges. +energies. Wands each have a certain number of charges, which you immediately +recognise when you pick them up. When you pick up a wand of type you already +have in inventory, the charges from the new wand are merged into the existing +one, and the new wand no longer exists. Beware that when the last charge of a +wand is used, the wand is destroyed. -Wands are aimed in the same way as missile weapons, and you can release the power -of a wand by evoking it with 'V'. See section I for targeting. There are also a -number of wands that may be useful to aim at yourself. +Wands are aimed in the same way as missile weapons, and you can release the +power of a wand by evoking it with 'V'. See section I for targeting. =" Rings and Amulets ======================================== @@ -925,9 +924,10 @@ magic school tend to have no combat powers at all. : Books ======================================== -Most books contain magical spells which your character may be able to learn. You -can read a book with the 'r' command, which lets you access a description of -each spell or memorise spells from it with the 'M' command. +Most books contain magical spells which your character may be able to learn. +Upon picking up a book, all of the spells in it will be added to your spell +library, allowing you to access a description of each spell or memorise spells +from it with the 'M' command. Occasionally you will find manuals of some skill. Carrying these will cause your experience to have twice the effect as usual when used for training that skill. @@ -989,10 +989,9 @@ Air and Earth) and, finally, Poison. A particular spell can belong to (and thus allow training of) up to three areas. Being good in the areas of a spell will improve the casting chance and, in many cases, the effect as well. -Spells are stored in books, which you will occasionally find in the dungeon. You -can read books with 'r' to check what spells they contain; doing so will allow -you to read the individual spells' descriptions. In order to memorise a certain -spell, use the 'M' command. +Spells are stored in books, which you will occasionally find in the dungeon. +Once you have picked up a book and added its contents to your spell library, you +can memorise a spell using the 'M' command. In addition to picking up new spells, your character may also wish to get rid of old ones by reading a scroll of amnesia, which will let you pick a spell to @@ -1066,11 +1065,11 @@ There are some shortcuts while targeting: strokes. At times, it will be useful to switch targets with the '+' or '-' commands, though. -It is possible to target yourself: obviously beneficial effects like hasting or -healing will actually target the cursor on you, leaving to you only the pressing -of '.', Enter, etc. - except if you want to heal or haste someone else. If you -target yourself while firing something harmful (which can be sensible at times), -you will be asked for confirmation. +It is possible to target yourself, and some beneficial effects like invisibility +will automatically target the cursor on you, leaving to you only the pressing +of '.', Enter, etc. - except if you want to aim at someone else. If you target +yourself while firing something harmful (which can be sensible at times), you +will be asked for confirmation. Finally, the ':' key allows you to hide the path of your spell/wand/missile. @@ -1086,8 +1085,7 @@ faith (use the 'a' command - but most gods resent being scorned). Further detail can be seen with '!' while in the '^' screen. To use any powers which your god deems you fit for, access the abilities menu -via the 'a' command; god-given abilities are listed as invocations. The god -Fedhas Madash also has a corpse decay ability specially accessed through 'p'. +via the 'a' command; god-given abilities are listed as invocations. Depending on background, some characters start out religious; others have to pray at an altar to dedicate themselves to a life of servitude. There are altars @@ -1761,17 +1759,14 @@ Vine Stalkers (VS) spells' fuel with each voracious bite. Vampires (Vp) - Vampires are another form of undead, but with a peculiarity: by consuming - fresh blood, they may become alive. A bloodless Vampire has the traits of - an undead (immunity to poisons, negative energy and torment, resistant to - damage from the cold), but cannot regain lost physical attributes or - regenerate from wounds over time - in particular, magical items or spells - which increase the rate of regeneration will not work (though divine ones - will). On the other hand, a Vampire full with blood will regenerate very - quickly, but lose all undead powers. Vampires can never starve. They can - drink from fresh corpses with the 'e' command, or can bottle blood for later - use with 'c'. Upon growing, they learn to transform into quick bats. Unlike - other undead species, they may be mutated normally at all times. + Vampires are another form of undead, but with a peculiarity they may become + alive. A bloodless Vampire has the traits of an undead (immunity to poisons, + negative energy and torment, resistant to damage from the cold), but cannot + physically regenerate when monsters are in sight and are less resilient. On + the other hand, a Vampire full with blood will regenerate very quickly, but + will lose all undead powers. Vampires can never starve. Upon growing, they + learn to transform into quick bats. Unlike other undead species, they may be + mutated normally at all times. Demigods (Dg) Demigods are mortals with some divine or angelic ancestry, however distant; @@ -1975,8 +1970,8 @@ Hunters Assassins An Assassin is a stealthy character who is especially good at killing, using - daggers or blowguns. They start with a dagger, a robe and cloak, a blowgun, - poisoned needles, and a few deadly and rare curare needles. + daggers or darts. They start with a dagger, a robe and cloak, poisoned darts, + and a few deadly and rare curare darts. Adventurer backgrounds ====================== @@ -2047,13 +2042,13 @@ Warpers Warpers specialise in translocation magic, and are experts in traveling long distances and positioning themselves precisely and use this to their advantage in melee or missile combat. They start with a scroll of blinking, the Book of - Spatial Translocations, some dispersal tomahawks, a simple weapon of thier + Spatial Translocations, some dispersal tomahawks, a simple weapon of their choice, and leather armour. Arcane Marksmen Arcane Marksmen are Hunters who use debilitating spells to assist their ranged attacks. They are particularly good at keeping their enemies at a distance. - They begin the game with a the Book of Debilitation, a ranged weapon of their + They begin the game with the Book of Debilitation, a ranged weapon of their choice, and a robe. Enchanters @@ -2187,16 +2182,15 @@ Ranged combat skills There are a number of individual weapon skills for missile weapons: - * Throwing (includes blowguns) + * Throwing * Bows * Crossbows * Slings Throwing is the skill for all things hurled without a launcher: tomahawks, -javelins, nets, etc. The other skills refer to various types of missiles shot -with a launcher. An exception to this are needles: these are launched using -blowguns, an action which uses the Throwing skill. Since stones can be thrown -without launchers to some effect, these skills crosstrain: +javelins, nets, darts, etc. The other skills refer to various types of missiles +shot with a launcher. Since stones can be thrown without launchers to some +effect, these skills crosstrain: * Throwing and Slings @@ -2260,7 +2254,7 @@ Shields Affects the amount of protection you gain by using a shield, and the degree to which it hinders you. For most races, 5/15/25 skill is enough to mitigate the encumbrance of bucklers/shields/large shields respectively, though larger - races need less skill and and smaller races more. + races need less skill and smaller races more. Invocations Affects your ability to call on your god for aid. Those skilled at Invocations @@ -2270,9 +2264,8 @@ Invocations Evocations This skill lets you use wands much more effectively, in terms of both damage - and precision. Furthermore, with high Evocations, you can easily deduce the - number of charges in a wand through usage. Similarly, various other items - that have evocable powers work better for characters trained in this skill. + and precision. Similarly, various other items that have evocable powers work + better for characters trained in this skill. Invocations and Evocations can increase your maximum magical reserves, although both have a smaller effect than Spellcasting in this regard. The @@ -2557,7 +2550,7 @@ e synonymous to y. r - Read a scroll or book. + Read a scroll. M Memorise a spell from a book. @@ -2640,7 +2633,7 @@ will deselect it (except for ',' and '-', obviously). & Select all carrion and inedible food. -\+ or : +: Select all books. / @@ -2676,6 +2669,10 @@ will deselect it (except for ',' and '-', obviously). will toggle the next item. This can be repeated, quickly selecting several subsequent items). +; + Select last unequipped. Selects the equipment (armour, jewellery, or weapon) + you last took off or unwielded. + Level map ('X') ======================================== diff --git a/crawl-ref/docs/develop/levels/advanced.txt b/crawl-ref/docs/develop/levels/advanced.txt index 55d5d488e60b..7dfaf9435d6a 100644 --- a/crawl-ref/docs/develop/levels/advanced.txt +++ b/crawl-ref/docs/develop/levels/advanced.txt @@ -114,6 +114,10 @@ two contexts: 1) An initial compilation phase before the game starts. 2) The actual mapgen phase when the dungeon builder is at work. +There are a number of caveats that go with phase 1. You can detect this phase +by using the `is_validating()` call or an `e.is_validating()` call where `e` is +the current map (`_G`). + In context (1), you will not get useful answers from the Crawl Lua API in general, because the game hasn't started. This is generally ignorable (as in the case above) because the compilation phase just @@ -156,6 +160,16 @@ Lua code can detect the compile phase using crawl.game_started() which returns true only when the player has started a game (and will return false when the map is being initially compiled). +In phase (1), you should avoid conditionals that impact tags randomly, and if +you are using any form of tag randomization, wrap it in a validating check. For +example: + +: if not is_validating() and crawl.coinflip() then +TAGS: no_pool_fixup +: end + +See `rng_guidelines.md` for more details on this particular caveat. + For more details on the available Lua API and syntax, see the Lua reference section. @@ -261,10 +275,14 @@ a. Abyssal vaults must be small: no larger than 28x23. 23x23 or smaller vaults are best so that they can be rotated and fit in anywhere on the level. -b. The player may never get a chance to explore unique abyss vaults - (vaults without the "allow_dup" tag). The player may be teleported - away to a different area of the abyss, or never notice your vault - before it shifts away. +b. Unique vaults (vaults without the "allow_dup" tag) are tracked + separately for the abyss, so if a map's DEPTH spec allows it to + place both inside and outside of the abyss, it may appear up to + two times even if unique. The same applies to unique tags. + +c. The player may never get a chance to explore unique abyss vaults. + The player may be teleported away to a different area of the abyss, + or never notice your vault before it shifts away. Unique vaults that the player never sees will be reused by the abyss builder, but it is still possible for the player to see an @@ -273,9 +291,9 @@ b. The player may never get a chance to explore unique abyss vaults can finish exploring it. Once any square of a unique vault has been seen by the player in - the abyss, that vault cannot be reused. + the abyss, that vault cannot be reused within the abyss. -c. Timers and other listeners in an abyssal vault may be discarded at +d. Timers and other listeners in an abyssal vault may be discarded at any time when the abyss shifts and the vault is destroyed. You can still use timers to modify your vaults, but be aware that the timer may be removed at any time as the abyss shifts away from your diff --git a/crawl-ref/docs/develop/levels/guidelines.md b/crawl-ref/docs/develop/levels/guidelines.md index c3efaa727c2b..6b3bea5c0562 100644 --- a/crawl-ref/docs/develop/levels/guidelines.md +++ b/crawl-ref/docs/develop/levels/guidelines.md @@ -3,6 +3,7 @@ 1. [Guidelines for D:1 arrival vaults](#guidelines-for-d1-arrival-vaults) 2. [Guidelines for creating serial vaults](#guidelines-for-creating-serial-vaults) 3. [Guidelines for creating ghost vaults](#guidelines-for-creating-ghost-vaults) +4. [Guidelines for no_tele_into](#guidelines-for-no_tele_into) ## Guidelines for D:1 arrival vaults @@ -186,3 +187,36 @@ CHANCE directly in the vault. For ghost vault rooms for Vaults, instead set the Other tags we generally require for ghost vaults are `no_tele_into` and `no_trap_gen`. If you use `allow_dup` in your vault, also use `luniq_player_ghost` to avoid multiple vault placement on the same level. + + +## Guidelines for no_tele_into + +The `no_tele_into` KPROP prevents teleports landing you on the tagged locations. + +Example: + +``` +NAME: example_vault +KPROP: - = no_tele_into +SUBST: - = . +MAP +xxxxxx ++.m--x +xxxxxx +ENDMAP +``` + +Teleports will never land the player behind the glass wall. + +Don't overuse this property. It's a hidden mechanic not exposed to the player. + +Good places to use `no_tele_into`: + +* Vaults which need the player to enter in a controlled manner to understand/enjoy. For example `gammafunk_steamed_eel`. +* Teleport closets: areas the player cannot escape without a scroll of teleportation (or similar). For example `lemuel_altar_in_water`. +* Egregiously dangerous/unfair situations. For example `chequers_guarded_unrand_ignorance` (four orange crystal statues). + +Bad places to use `no_tele_into`: + +* Any old runed door / transporter vault. It's fine for players to teleport into tough or scary situations. +* Islands: areas the player can also reach with flight or similar tools. `no_tele_into` would be an incomplete solution. It's better to place a hatch/shaft, which solves all cases. diff --git a/crawl-ref/docs/develop/levels/syntax.txt b/crawl-ref/docs/develop/levels/syntax.txt index 1b087ea1c346..14c435b699a6 100644 --- a/crawl-ref/docs/develop/levels/syntax.txt +++ b/crawl-ref/docs/develop/levels/syntax.txt @@ -327,7 +327,8 @@ TAGS: Tags go on a TAGS: line and are space-separated. You can have several property to every square of the vault. See KPROP: below. * "allow_dup": Vaults are normally used only once per game. If you have a vault that can be used more than once, use allow_dup to tell - the dungeon builder that the vault can be reused. + the dungeon builder that the vault can be reused. This is tracked + separately for the abyss and the rest of the dungeon. * "chance_FOO": Maps can be tagged chance_ with any unique suffix to indicate that if the map's CHANCE roll is made, one of the maps tagged chance_FOO should be picked. @@ -379,7 +380,8 @@ TAGS: Tags go on a TAGS: line and are space-separated. You can have several * "uniq_BAR": (uniq_ with any suffix) specifies that only one of the vaults with this tag can be used in a game. In particular, late-Dungeon encompass vaults have the tag uniq_d_encompass, so - that only one will occur per game. + that only one will occur per game. This is tracked separately + for the abyss and the rest of the dungeon. * "luniq": specifies that this vault can be used only once on a given level. "luniq" is only relevant when used with "allow_dup". * "luniq_BAR": (luniq_ with any suffix) specifies that only one @@ -634,9 +636,9 @@ ITEM: (list of items, separated by comma) will be created instead. Staff unrands, since they have no corresponding type that can be transformed into a randart, are replaced by an appropriate magical staff. (E.g. a staff of poison.) - The lua function you.unrands() ('you.unrands("scythe of curses")') - can be used to check whether an unrand has already been seen, if - you don't want to use the default fallback. + You should not use `you.unrands` to check whether an unrand has been + seen in normal levelgen scenarios, as it will impact seeding. Some + details about default fallbacks can be specified in art-data.txt. Randart spell books ------------------- diff --git a/crawl-ref/docs/develop/release/debian.txt b/crawl-ref/docs/develop/release/debian.txt index 02000e2f3f5a..62046ef88d10 100644 --- a/crawl-ref/docs/develop/release/debian.txt +++ b/crawl-ref/docs/develop/release/debian.txt @@ -12,219 +12,219 @@ packages: https://www.debian.org/doc/manuals/maint-guide/index.en.html +1. Software Prerequisites and setup -0.1. Software Prerequisites +The cowbuilder and pbuilderrc can be set up once and re-used for each DCSS +release. The cowbuilder directory should be updated before - Install the cowbuilder and pbuilder packages: +See the following documentation if you have trouble with cowbuilder or +pbuilder during any of these steps: - sudo apt-get install cowbuilder +https://wiki.debian.org/cowbuilder +https://wiki.debian.org/PbuilderTricks - See the following websites if you have trouble with cowbuilder or pbuilder - during any of these steps: +1.1 Set up your pbuilderrc - https://wiki.debian.org/cowbuilder - https://wiki.debian.org/PbuilderTricks +We use the cowbuilder program to create the copy-on-write chroot directories, +and use pbuilder to build the packages in the chroot. A pbuilderrc file is +needed to tell pbuilder to use the cowbuilder system and to set downstream +shell variables. To use the example pbuilderrc file in this directory, copy it +to ~/.pbuilderrc or (as root) to /etc/pbuilderrc after making any +modifications. - Set up your DEBFULLNAME and DEBEMAIL shell variables using the name and - email you use in the git mailmap. This can be done in the shell or in your - pbuilderrc file (see below). To set them in the shell: +You can edit this file to set the variables DEBFULLNAME and DEBEMAIL to your +name and email. You can also set and export these variable directly in your +shell. - export DEBFULLNAME="yourname" - export DEBEMAIL="your@email" +In the example pbuilderrc, all pbuilder-related data go in +/var/cache/pbuilder. If you need to change this location, you'll need to modify +at least BASEPATH, BUILDPLACE, BUILDRESULT, and APTCACHE. +1.2 Install the cowbuilder and pbuilder packages -0.2 Release Prerequisites +This should get you the necessary packages: - Before building the packages, you should have the release version tagged - with all commits included, including final `docs/changelog.txt' entry for - the version and a commit updating the `crawl-ref/source/debian/changelog' - Debian changelog. + sudo apt-get install cowbuilder debhelper - To update the Debian changelog, add an entry at the top of this file in the - following format. +1.3 Create a .cow chroot directory for each architecture. - crawl (2:VER-1) unstable; urgency=low +You'll need a cowbuilder chroot directory for each architecture you want to +build. If you're using the example pbuilderrc, we use the scheme +`DIST-ARCH.cow'. For example, when building based on debian stable, we'd use: - * Major bugfix release + /var/cache/pbuilder/stable-amd64.cow + /var/cache/pbuilder/stable-i386.cow - -- devname TIMESTAMP +To create these, run the following: - You can copy the previous entry, update the version, and use output from the - `date' command to update the timestamp. A command like the following will - give you a valid timestamp: + sudo cowbuilder --create --architecture amd64 \ + --basepath /var/cache/pbuilder/stable-amd64.cow + sudo cowbuilder --create --architecture i386 \ + --basepath /var/cache/pbuilder/stable-i386.cow - date +'%a, %d %b %Y %R:%S %z' +In order for these to run succesfully in docker, you will need to be running +the docker image with --privileged. - The entire entry must mach the format of previous entries; note the lines - following the first `crawl' line have leading spaces. Incorrect formatting - can cause debuild and hence pbuilder to fail. +This chroot directory needs to be created only once for each OS/ARCH/DIST +combination on which you what to build the package. If It doesn't need to be +recreated if you're only building a different DCSS version, but if you're using +a different value for any of OS, ARCH, or DIST, you'll need to create a +corresponding .cow chroot. - Once the file is updated, commit the change to the repository so that it's - included in the release version tag. +2 Steps needed before release and before building the packages +The changelog update described in 2.1 should be done before you make the +release tag. If you forget, you can apply the changelog changes in the copy of +the debian directory you make in section 3.2. -1. Make a copy of the of the source packages and extract +2.1 Update and commit the Debian changelog - The source packages should be made using the `package-source' target. Run - the following from the source directory if you haven't yet: +To update the Debian changelog, add an entry at the top of this file in the +following format. - make package-source +crawl (2:VERSION-1) unstable; urgency=low - This will make several files in the toplevel repo dir, but you specifically - need the `stone_soup-VER-nodeps.tar.xz' file. Copy this to a location where - you'd like to prepare the packages, then exactract the source directory and - rename the original file. Using version 0.17 as an example and with - ~/crawl-deb as my staging directory: + * Major bugfix release - mkdir -p ~/crawl-deb - cp stone_soup-0.17.0-nodeps.tar.xz ~/crawl-deb - cd ~/crawl-deb - tar Jxf stone_soup-0.17.0.tar.xz - mv stone_soup-0.17.0 crawl-0.17.0 - mv stone_soup-0.17.0-nodeps.tar.xz crawl-0.17.0.orig.tar.xz + -- devname TIMESTAMP - Note that the name formats of crawl-VER and crawl-VER.orig.EXT for the - source directory and archive are specifically looked for by pbuilder, so - these file renaming steps are required. +You can copy the previous entry, update the version, and use output from the +`date' command to update the timestamp. A command like the following will +give you a valid timestamp: + date +'%a, %d %b %Y %R:%S %z' -2. Copy and update the debian directory in the source directory +The entire entry must match the format of previous entries; note the lines +following the first `crawl' line have leading spaces. Incorrect formatting +can cause debuild and hence pbuilder to fail. - We need the crawl-ref/source/debian directory to be at the top level to - build the package. Using 0.17.0 as an example, and assuming we're already in - our staging directory with the unpacked source: +Once the file is updated, commit the change to the repository so that it's +included in the release version tag. - cd crawl-0.17.0 - cp -r source/debian . +2.2 Updating the cow chroots +It's good to update your cow chroot directory with the latest security/bugfix +updates to its packages: -3. Set up your pbuilderrc + sudo cowbuilder --update --architecture amd64 \ + --basepath /var/cache/pbuilder/stable-amd64.cow + sudo cowbuilder --update --architecture i386 \ + --basepath /var/cache/pbuilder/stable-i386.cow - We use the cowbuilder program to create the copy-on-write chroot - directories, and use pbuilder to build the packages in the chroot. A - pbuilderrc file is needed to tell pbuilder to use the cowbuilder system and - to set downstream shell variables. To use the example pbuilderrc file in - this directory, copy it to ~/.pbuilderrc or (as root) to /etc/pbuilderrc - after making any modifications. +3. Making the Debian packages - You can edit this file to set DEBFULLNAME and DEBEMAIL to your name and - email if you didn't do that in your shell already. In the example - pbuilderrc, all pbuilder-related data go in /var/cache/pbuilder. To change - this, you'll need to modify at least BASEPATH, BUILDPLACE, BUILDRESULT, and - APTCACHE. +At this point the release should be tagged. +3.1 Make a copy of the of the source packages and extract -4. Create a .cow chroot directory for each architecture. +The source packages should be made using the `package-source' target. Run +the following from the source directory if you haven't yet: - You'll need a cowbuilder chroot directory for each architecture you want to - build. If you're using the example pbuilderrc, we use the scheme - `DIST-ARCH.cow'. For example, when building based on debian stable, we'd - use: + make package-source - /var/cache/pbuilder/stable-amd64.cow - /var/cache/pbuilder/stable-i386.cow +This will make several files in the toplevel repo dir, but you specifically +need the `stone_soup-VERSION-nodeps.tar.xz' file. Copy this to a location where +you'd like to prepare the packages, then extract the source directory and +rename the original file. Using version 0.17 as an example and with +~/crawl-deb as my staging directory: - To create these, run the following: + mkdir -p ~/crawl-deb + cp stone_soup-0.17.0-nodeps.tar.xz ~/crawl-deb + cd ~/crawl-deb + tar Jxf stone_soup-0.17.0-nodeps.tar.xz + mv stone_soup-0.17.0 crawl-0.17.0 + mv stone_soup-0.17.0-nodeps.tar.xz crawl_0.17.0.orig.tar.xz - sudo cowbuilder --create --architecture amd64 \ - --basepath /var/cache/pbuilder/stable-amd64.cow - sudo cowbuilder --create --architecture i386 \ - --basepath /var/cache/pbuilder/stable-i386.cow +Note that the name formats of crawl-VERSION and crawl_VERSION.orig.EXT for +the source directory and archive are specifically looked for by pbuilder. If +you receive errors, check that your source directory and archive follow this +format, using `crawl-` as a prefix for the directory but `crawl_` as a +prefix for the source archive. - This chroot directory needs to be created only once for each OS/ARCH/DIST - combination on which you what to build the package. It doesn't need to be - recreated if you're only building a different DCSS version, but if you're - using a different value for any of OS, ARCH, or DIST, you'll need to create - a corresponding .cow chroot. +3.2 Copy and update the debian directory in the source directory +We need the crawl-ref/source/debian directory to be at the top level to +build the package. Using 0.17.0 as an example, and assuming we're already in +our staging directory with the unpacked source: -5. Build the packages + cd crawl-0.17.0 + cp -r source/debian . - Asuming your pbuilderrc is based on the example one in this directory, you - need to set some of the shell variables ARCH, DIST, and OS to set up - downstream variables to build the packages for the architectures (e.g. i386 - or amd64) you want and against the distribution you want (e.g. stable or - testing for Debian), and possibly based on the OS you want (debian or - ubuntu) if you want to e.g. build against debian on an ubunutu host system. +3.3 Build the packages - Run pdebuild from the `crawl-VER' source directory you made above. The - simplest build situation is making packages for the same architecture and - against the same distribution as your host system. Using our 0.17.0 example - building against debian stable on a debian stable system for amd64, simply: +Assuming your pbuilderrc is based on the example one in this directory, you +need to set some of the shell variables ARCH, DIST, and OS downstream +variables. The pbuilderrc uses these to build the packages for the +architectures (e.g. i386 or amd64) you want and against the distribution you +want (e.g. stable or testing for Debian), and based on the OS you want (e.g. +debian or ubuntu). - sudo pdebuild +Run pdebuild from the `crawl-VERSION' source directory you made above. The +simplest build situation is making packages for the same architecture and +against the same distribution as your host system. Using our 0.17.0 example +building against debian stable on a debian stable system for amd64, simply: - To build the i386 package: + sudo pdebuild - sudo ARCH=i386 pdebuild +Then to build the i386 package: - If you're on ubuntu, building against debian stable, you might need: + sudo ARCH=i386 pdebuild - sudo OS=debian DIST=stable pdebuild - sudo OS=debian DIST=stable ARCH=i386 pdebuild +If you're on ubuntu, building against debian stable, you might need: - Once the package building is finished, the results well be in - /var/cache/pbuilder/result, if you're using the example pbuilderrc. + sudo OS=debian DIST=stable pdebuild + sudo OS=debian DIST=stable ARCH=i386 pdebuild +Once the package building is finished, the results will be in +/var/cache/pbuilder/result, if you're using the example pbuilderrc. -6. Test install the packages +3.4 Upload files to CDO - Test the built packages in /var/cache/pbuilder/result by installing them; - you'll need to install the crawl_VER_ARCH.deb, crawl-tiles_VER_ARCH.deb, and - crawl-common_VER.deb packages. For example: +You'll need to upload all the files produced in /var/cache/pbuilder/result (not +just the deb and dsc files) to CDO. The staging directory +~/upload/VERSION/deb-files can be used for this. - dpkg -i /var/cache/pbuilder/results/crawl_0.17.0-1_amd64.deb \ - /var/cache/pbuilder/results/crawl-tiles_0.17.0-1_amd64.deb \ - /var/cache/pbuilder/results/crawl-common_0.17.0-1_amd64.deb \ - /var/cache/pbuilder/results/crawl-tiles-data_0.17.0-1_amd64.deb +3.5 Install a new repo component (major release only) - Run the console version with `crawl' and the tiles version with - `crawl-tiles' to see if they work. +For point releases, this step should be skipped. +For major releases, we make a new release component for all releases of that +version. If you're logged into CDO, edit the `deb/conf/distributions` file, +adding the new version in the "Components:" field before the final entry for +"trunk": -7. Upload files to CDO, install into the repo, and test +Components: 0.10 0.11 0.12 0.13 0.14 0.15 0.16 0.17 trunk - You'll need to upload the deb files above in addition to the - `crawl_VER-1.dsc' file to the crawl account on CDO. The staging directory - ~/upload//deb-files can be used for this - We install each release into a component named after the major version of - the release. If you're logged into CDO, edit the `deb/conf/distributions` - file, adding the new version in the "Components:" field before the final - entry for "trunk". Example entry with 0.17 added: +3.6 Install the debian packages into the repository - Components: 0.10 0.11 0.12 0.13 0.14 0.15 0.16 0.17 trunk +Install the .deb files and the .dsc file using reprepro. An example using 0.17 +and `~/upload/0.17.0/deb-files' as a staging directory: - Then install the .deb files and the .dsc file using reprepro. An example - using 0.17 and `~/upload/0.17.0/deb-files' as a staging directory: + cd ~/deb + for i in ../upload/0.17.0/deb-files/*.deb + do reprepro -C 0.17 includedeb crawl "$i"; done + for i in ../upload/0.17.0/deb-files/*.dsc + do reprepro -C 0.17 includedsc crawl "$i"; done - cd ~/deb - for i in ../upload/0.17.0/deb-files/*.deb - do reprepro -C 0.17 includedeb crawl "$i"; done - for i in ../upload/0.17.0/deb-files/*.dsc - do reprepro -C 0.17 includedsc crawl "$i"; done +Note that the other files produced by pdebuild in /var/cache/pbuilder/result +(section 3.3) must be present in the same directory as the dsc file you +install. - Make sure to use the right major version number in those -C arguments. +Note: If you messed something up and need to upload a new build of packages +for a version already installed in the repo, you can remove the installed +debs with a command like the following (run from ~/deb): - Note: If you messed something up and need to upload a new build of packages - for a version already installed in the repo, you can remove the installed - debs with a command like the following (run from ~/deb): + reprepro -C 0.17 remove crawl crawl crawl-common crawl-tiles crawl-tiles-data - reprepro -C 0.17 remove crawl crawl crawl-common crawl-tiles crawl-tiles-data +Test that the repository packages are working by following the apt instructions +on the download page to install and run them. - To test that the repository is working, follow the apt instructions on the - download page to add the DCSS repo and signing key, being sure to use the - correct crawl major version in sources.list. Then update your apt-cache with - `apt-get update`, install the crawl and crawl-tiles packages on your system, - and run them. - - -8. Update the download page - - The version in the Linux example command to add the repository URL should be - updated. See release.txt for details on keeping the CDO website in sync with - the website repository. +3.6 Update the download page +The version in the Linux example command to add the repository URL should be +updated. See release.txt for details on keeping the CDO website in sync with +the website repository. At this point, the repository should be working and the packages ready for users to install with apt. diff --git a/crawl-ref/docs/develop/rng_guidelines.md b/crawl-ref/docs/develop/rng_guidelines.md index 181a5bb6a0a0..3e18dee1e220 100644 --- a/crawl-ref/docs/develop/rng_guidelines.md +++ b/crawl-ref/docs/develop/rng_guidelines.md @@ -195,7 +195,19 @@ example, don't use lua's `math.rand`, or C stdlib's `rand`. Try to avoid vault design elements that will lead to variable .des caches; any random choices made here will be outside of levelgen rng, and potentially set at the time the player first opens crawl. Especially try to avoid -determining a set of tags for a map randomly using lua `tags` calls. +determining a set of tags for a map randomly using lua `tags` calls. If you +must randomize tags, wrap your conditionals in a validating check. For lua, +this usually looks like: + + if not e.is_validating() and crawl.one_chance_in(25) then + e.tags("no_monster_gen") + end + +and for conditionalizing the TAGS field in a .des file, this looks like: + + : if not is_validating() and crawl.coinflip() then + TAGS: no_pool_fixup + : end ## 4. Dealing with seed divergence diff --git a/crawl-ref/docs/develop/species_creation.md b/crawl-ref/docs/develop/species_creation.md index 32db6ca7b98a..8f0c88f093b5 100644 --- a/crawl-ref/docs/develop/species_creation.md +++ b/crawl-ref/docs/develop/species_creation.md @@ -12,8 +12,8 @@ You can view existing files to get a sense of the format, it's quite straightfor | key | type | required | description | | ---- | ---- | -------- | ----------- | -| enum | `string` | **Yes** | Set the species enum. Must match the pattern `SP_[A-Z]+`. | -| monster | `string` | **Yes** | Species' corresponding monster. Must match the pattern `MONS_[A-Z]+`. | +| enum | `string` | **Yes** | Set the species enum. Must match the pattern `SP_[A-Z_]+`. | +| monster | `string` | **Yes** | Species' corresponding monster. Must match the pattern `MONS_[A-Z_]+`. | | name | `string` | **Yes** | Species name, like 'Deep Dwarf' or 'Felid'. Required. Must be unique. | | short_name | `string` | No | Two letter species code. Must be unique (excepting special cases like Draconian sub-species). Defaults to the first two letters of the name. | | adjective | `string` | No | Species adjective, like 'Dwarven' or 'Feline'. Defaults to `$name`. | @@ -44,4 +44,4 @@ You probably don't need or want to use these. | key | type | required | description | | ---- | ---- | -------- | ----------- | | TAG_MAJOR_VERSION | `int` | No | Wrap the species in `#if TAG_MAJOR_VERSION == [...]` to deprecate it. See existing deprecated species for examples. | -| create_enum | `boolean` | No | Set `true` if the species already has an enum in species-type.h. Any new species should not set this (and will fail to compile if you try). Defaults to `false`. | +| create_enum | `boolean` | No | Set `false` if the species has a hardcoded enum in species-type.h. New species should use the default value. Defaults to `true`. | diff --git a/crawl-ref/docs/options_guide.txt b/crawl-ref/docs/options_guide.txt index be8466bf78d1..23540949de6d 100644 --- a/crawl-ref/docs/options_guide.txt +++ b/crawl-ref/docs/options_guide.txt @@ -122,7 +122,7 @@ The contents of this text are: 4-b Items and Kills. kill_map, dump_kill_places, dump_kill_breakdowns, dump_item_origins, dump_item_origin_price, dump_message_count, - dump_order, dump_book_spells + dump_order 4-c Notes. user_note_prefix, note_items, note_monsters, note_hp_percent, note_skill_levels, note_all_skill_levels, note_skill_max, @@ -410,14 +410,17 @@ game_seed = none "-seed", which will override any rc file setting. This value is an unsigned 64 bit integer. -pregen_dungeon = false - When set to true, the game will pregenerate the entire connected dungeon - when starting a new character. This leads to more deterministic dungeon - generation relative to a particular game seed. If this is false, levels - will be generated as you enter them, and the order in which you traverse - the dungeon will lead to different dungeons for the same game seed. With - this set to true, you still may encounter variation in portal vaults, - the abyss, pandemonium, and ziggurats. +pregen_dungeon = incremental + When set to `true` or `full`, the game will pregenerate the entire + connected dungeon when starting a new character. This leads to + deterministic dungeon generation relative to a particular game seed, at + the cost of a slow game start. If set to `incremental`, the game will + generate levels as needed so that it always generates them in the same + order, also producing a deterministic dungeon. You still may encounter + variation in bazaars, the abyss, pandemonium, and ziggurats, and for + incremental pregeneration, artefacts. When set to `false` or `classic`, + the game will generate all levels on level entry, as was the rule before + 0.23. Some servers may disallow full pregeneration. 2- File System. ================ @@ -491,10 +494,8 @@ autopickup = $?!+"/% ! Potions + or : Books | Staves - \ Rods 0 Orbs } Misc. items - X Corpses $ Gold Note that _whether_ items are picked up automatically or not, is controlled by the in-game toggle Ctrl-A. Also note that picking @@ -511,13 +512,13 @@ autopickup_exceptions ^= don't-pickup-regex, ... be subject to autopickup. An example: - autopickup_exceptions += and the match expression is significant, so the following won't work: - autopickup_exceptions += < ebony casket + autopickup_exceptions += < box of beasts autopickup_exceptions replace the older ban_pickup. Using autopickup_exceptions += >uselessness, >inaccuracy @@ -529,8 +530,7 @@ autopickup_exceptions ^= don't-pickup-regex, ... autopickup_exceptions += uselessness, inaccuracy You can use multiple autopickup_exceptions lines. Some examples: - autopickup_exceptions += inaccuracy, scrolls? of paper - autopickup_exceptions += immolation, curse (armour|weapon) + autopickup_exceptions += inaccuracy, immolation autopickup_exceptions += uselessness, noise, torment Unless you clear the list of exceptions, you won't need to set @@ -1047,7 +1047,6 @@ easy_eat_chunks = false If this is set to true then when using the (e)at command, the game will automatically determine the oldest chunk that is safe to eat, and eat it without prompting. - You will always be prompted to eat harmful chunks. auto_eat_chunks = true Setting this option to true will allow you to automatically eat a chunk @@ -1293,7 +1292,7 @@ autofight_hunger_stop_undead = false autofight_throw = false If your quiver contains a throwable item, autofight will throw it at enemies out of melee range. Without this option, only a wielded - launcher (a bow, crossbow, sling or blowgun) will be considered. + launcher (a bow, crossbow or sling) will be considered. autofight_throw_nomove = true This works same as above, except only for ===hit_closest_nomove rather @@ -1454,12 +1453,13 @@ show_game_time = true By default, the counter in the stat area displays elapsed game time. Most actions take one unit of time, but some are quicker (putting on a ring, wielding a weapon, ...) and others are slower (swinging a weapon - with low skill, changing armour, ...). The duration of the last action - is displayed in parenthesis, after the time display. + with low skill, changing armour, ...). When set to false, the counter will display player turns instead, which is the number of actions taken regardless of their duration. It is this turn count which is used for scoring (and this turn count is always visible on the % overview screen). + The duration of the last action is displayed in parenthesis, after the + time/player turns display. equip_bar = false When set to true, this option replaces the noise bar with an @@ -1737,8 +1737,8 @@ fire_items_start = a Sets the first inventory item to consider when selecting missiles to fire. The default is a. -fire_order = launcher, return -fire_order += javelin / tomahawk / stone / rock / net +fire_order = launcher +fire_order += javelin / boomerang / stone / rock / net / dart fire_order += inscribed (Ordered list option) Controls the order of items autoselected for firing. Items @@ -1751,7 +1751,7 @@ fire_order += inscribed firing. 'launcher' refers to firing the appropriate missile for the - wielded weapon (i.e. crossbow, bow, sling, blowgun). You'll almost + wielded weapon (i.e. crossbow, bow, sling). You'll almost certainly want it first, as it'll be ignored when you're not wielding a ranged weapon. 'return' refers to (identified) weapons of returning. @@ -2037,12 +2037,21 @@ tile_map_pixels = 0 downside is that travelling by clicking on the minimap becomes much harder. If set to zero, it will auto-size the minimap. +tile_viewport_scale = 1.0 + A scale factor to apply to the dungeon view. The minimum value is 0.2 + and the maximum value is determined by resolution; zoom will be capped + so that at a minimum, your line of sight is always visible. This can + also be adjusted in-game in local tiles with ctrl-'-' and ctrl-'='. + +tile_map_scale = 0.6 + A scale factor to apply to the dungeon view in map mode (X). This can + also be adjusted in map mode in tiles with '{' and '}', as well as in + local tiles with ctrl-'-' and ctrl-'='. + tile_cell_pixels = 32 - The width and height of tiles in the dungeon view. If you want - to see the tiles in all their glory and have a large - resolution, set this to 48 or even 64; but note that tiles - will be automatically scaled down to fit the visible area on - the screen. + The width and height of tiles in the dungeon view at 1.0 scale, in + (logical) pixels. This is a legacy option, and it is recommended that + you adjust scaling via tile_viewport_scale instead. tile_filter_scaling = false Used in conjunction with tile_cell_pixels. Setting it to true filters @@ -2276,11 +2285,6 @@ dump_order += monlist,kills,notes,skill_gains,action_counts in trunk builds of Crawl (not releases or pre-release betas), appearing between "notes" and "skill_gains". -dump_book_spells = true - By default all randart spellbooks in inventory will have all their - spells listed in the dump. If this option is set to true, spells will - also be dumped for non-randart spellbooks. - 4-c Notes. -------------- diff --git a/crawl-ref/settings/colemak_command_keys.txt b/crawl-ref/settings/colemak_command_keys.txt index 978c507637ce..e8e28e225da3 100644 --- a/crawl-ref/settings/colemak_command_keys.txt +++ b/crawl-ref/settings/colemak_command_keys.txt @@ -63,6 +63,9 @@ bindkey = [U] CMD_MAP_FIND_EXCLUDED bindkey = [^U] CMD_TOGGLE_TRAVEL_SPEED bindkey = [^U] CMD_MAP_CLEAR_EXCLUDES +# fix overwrite on ^U +bindkey = [F] CMD_MAP_UNFORGET + # replace (i) with (y) bindkey = [y] CMD_DISPLAY_INVENTORY bindkey = [Y] CMD_DISPLAY_SPELLS diff --git a/crawl-ref/source/Android.mk b/crawl-ref/source/Android.mk index 51a87461e89c..2da426698935 100644 --- a/crawl-ref/source/Android.mk +++ b/crawl-ref/source/Android.mk @@ -257,6 +257,7 @@ CRAWLSRC = ability.cc \ notes.cc \ orb.cc \ ouch.cc \ + outer-menu.cc \ output.cc \ package.cc \ pattern.cc \ diff --git a/crawl-ref/source/Application.mk b/crawl-ref/source/Application.mk index 17f513a2f505..c5645afa5562 100644 --- a/crawl-ref/source/Application.mk +++ b/crawl-ref/source/Application.mk @@ -1,4 +1,4 @@ APP_STL=gnustl_static APP_CPPFLAGS += -std=c++11 APP_ABI := armeabi-v7a arm64-v8a -NDK_TOOLCHAIN_VERSION=4.8 +NDK_TOOLCHAIN_VERSION=4.9 diff --git a/crawl-ref/source/MSVC/crawl.vcxproj b/crawl-ref/source/MSVC/crawl.vcxproj index e18497dcd914..6431485ac605 100644 --- a/crawl-ref/source/MSVC/crawl.vcxproj +++ b/crawl-ref/source/MSVC/crawl.vcxproj @@ -687,6 +687,7 @@ perl.exe "util/gen-cflg.pl" compflag.h "<UNKNOWN>" "<UNKNOWN>" + @@ -874,7 +875,6 @@ perl.exe "util/gen-cflg.pl" compflag.h "<UNKNOWN>" "<UNKNOWN>" - @@ -918,6 +918,7 @@ perl.exe "util/gen-cflg.pl" compflag.h "<UNKNOWN>" "<UNKNOWN>" + @@ -1085,6 +1086,7 @@ perl.exe "util/gen-cflg.pl" compflag.h "<UNKNOWN>" "<UNKNOWN>" + diff --git a/crawl-ref/source/MSVC/crawl.vcxproj.filters b/crawl-ref/source/MSVC/crawl.vcxproj.filters index 439b2809a94e..1c12a6bfb42e 100644 --- a/crawl-ref/source/MSVC/crawl.vcxproj.filters +++ b/crawl-ref/source/MSVC/crawl.vcxproj.filters @@ -1092,9 +1092,6 @@ h - - h - h @@ -1218,6 +1215,9 @@ h + + h + h diff --git a/crawl-ref/source/Makefile b/crawl-ref/source/Makefile index 7f0b86353940..03a4f7f5faf6 100644 --- a/crawl-ref/source/Makefile +++ b/crawl-ref/source/Makefile @@ -109,6 +109,13 @@ include Makefile.obj STDFLAG = -std=c++11 CFOTHERS := -pipe $(EXTERNAL_FLAGS) +# Build with FORCE_SSE=y to get better seed stability on 32 bit x86 builds. It +# is not recommended to do this unless you are building with contrib lua. +# On any 64bit builds where the arch supports it, (at least) sse2 is implied. +ifdef FORCE_SSE +CFOTHERS += -mfpmath=sse -msse2 +endif + CFWARN := CFWARN_L := -Wall -Wformat-security -Wundef @@ -167,7 +174,7 @@ ARCH := $(HOST) ifdef CROSSHOST ARCH := $(CROSSHOST) - ifneq (,$($shell which $(ARCH)-pkg-config 2>/dev/null)) + ifneq (,$(shell which $(ARCH)-pkg-config 2>/dev/null)) PKGCONFIG = $(ARCH)-pkg-config else ifneq (,$(wildcard /usr/$(ARCH)/lib/pkgconfig)) @@ -254,10 +261,17 @@ ifneq (,$(findstring CYGWIN,$(uname_S))) endif ifdef BUILD_ALL - BUILD_FREETYPE = YesPlease + ifdef TILES + BUILD_FREETYPE = YesPlease + BUILD_SDL2 = YesPlease + BUILD_SDL2IMAGE = YesPlease + BUILD_LIBPNG = YesPlease + endif + ifdef WEBTILES + BUILD_LIBPNG = YesPlease + endif + BUILD_PCRE = YesPlease - BUILD_SDL2 = YesPlease - BUILD_SDL2IMAGE = YesPlease ifdef SOUND ifndef WIN32 BUILD_SDL2MIXER = YesPlease @@ -265,7 +279,6 @@ ifdef BUILD_ALL endif BUILD_SQLITE = YesPlease BUILD_LUA = YesPlease - BUILD_LIBPNG = YesPlease BUILD_ZLIB = YesPlease endif @@ -1189,7 +1202,10 @@ TITLEIMGS = denzi_dragon denzi_kitchen_duty denzi_summoner \ psiweapon_kiku white_noise_entering_the_dungeon \ white_noise_grabbing_the_orb pooryurik_knight \ psiweapon_roxanne baconkid_gastronok baconkid_mnoleg \ - peileppe_bloax_eye baconkid_duvessa_dowan + peileppe_bloax_eye baconkid_duvessa_dowan ploomutoo_ijyb \ + froggy_thunder_fist_nikola froggy_rune_and_run_failed_on_dis \ + froggy_natasha_and_boris froggy_jiyva_felid \ + froggy_goodgod_tengu_gold Cws_Minotauros STATICFILES = $(TILEIMAGEFILES:%=webserver/game_data/static/%.png) \ webserver/static/stone_soup_icon-32x32.png \ @@ -1749,9 +1765,9 @@ ifneq ($(GAME),crawl.exe) @echo "This is only for Windows; please specify CROSSHOST.";false endif +$(MAKE) clean - +$(MAKE) TILES=y DESTDIR=build-win SAVEDIR='~/crawl' install + +$(MAKE) TILES=y DESTDIR=build-win FORCE_SSE=y SAVEDIR='~/crawl' install mv build-win/crawl.exe build-win/crawl-tiles.exe - +$(MAKE) TILES= DESTDIR=build-win SAVEDIR='~/crawl' install + +$(MAKE) TILES= DESTDIR=build-win FORCE_SSE=y SAVEDIR='~/crawl' install mv build-win/crawl.exe build-win/crawl-console.exe mv build-win/docs/CREDITS.txt build-win/ echo $(SRC_VERSION) >build-win/version.txt @@ -1762,11 +1778,11 @@ ifneq ($(GAME),crawl.exe) @echo "This is only for Windows; please specify CROSSHOST.";false endif +$(MAKE) clean - +$(MAKE) TILES=y DESTDIR=build-win/stone_soup-tiles-$(MAJOR_VERSION) install + +$(MAKE) TILES=y DESTDIR=build-win/stone_soup-tiles-$(MAJOR_VERSION) FORCE_SSE=y install cp -p $(ZIP_DOCS) build-win/stone_soup-tiles-$(MAJOR_VERSION)/ cd build-win && zip -9r ../stone_soup-$(SRC_VERSION)-tiles-win32.zip * rm -rf build-win - +$(MAKE) DESTDIR=build-win/stone_soup-$(MAJOR_VERSION) install + +$(MAKE) DESTDIR=build-win/stone_soup-$(MAJOR_VERSION) FORCE_SSE=y install cp -p $(ZIP_DOCS) build-win/stone_soup-$(MAJOR_VERSION)/ cd build-win && zip -9r ../stone_soup-$(SRC_VERSION)-win32.zip * rm -rf build-win diff --git a/crawl-ref/source/Makefile.obj b/crawl-ref/source/Makefile.obj index 7c4010e21953..75f74a62e18e 100644 --- a/crawl-ref/source/Makefile.obj +++ b/crawl-ref/source/Makefile.obj @@ -175,6 +175,7 @@ ng-wanderer.o \ notes.o \ orb.o \ ouch.o \ +outer-menu.o \ output.o \ package.o \ pattern.o \ @@ -196,8 +197,8 @@ random.o \ random-var.o \ ranged-attack.o \ ray.o \ -rot.o \ religion.o \ +rot.o \ scroller.o \ shopping.o \ shout.o \ diff --git a/crawl-ref/source/ability-type.h b/crawl-ref/source/ability-type.h index 599e138898d4..c8c0dbac1d0f 100644 --- a/crawl-ref/source/ability-type.h +++ b/crawl-ref/source/ability-type.h @@ -42,6 +42,8 @@ enum ability_type #endif // Vampires ABIL_TRAN_BAT, + ABIL_EXSANGUINATE, + ABIL_REVIVIFY, #if TAG_MAJOR_VERSION == 34 ABIL_BOTTLE_BLOOD, #endif @@ -117,8 +119,11 @@ enum ability_type // Sif Muna ABIL_SIF_MUNA_CHANNEL_ENERGY = 1070, ABIL_SIF_MUNA_FORGET_SPELL, +#if TAG_MAJOR_VERSION == 34 ABIL_SIF_MUNA_DIVINE_ENERGY, ABIL_SIF_MUNA_STOP_DIVINE_ENERGY, +#endif + ABIL_SIF_MUNA_DIVINE_EXEGESIS, // Trog ABIL_TROG_BERSERK = 1080, ABIL_TROG_REGEN_MR, @@ -168,12 +173,10 @@ enum ability_type ABIL_JIYVA_SLIMIFY, ABIL_JIYVA_CURE_BAD_MUTATION, // Fedhas - ABIL_FEDHAS_SUNLIGHT = 1140, - ABIL_FEDHAS_RAIN, - ABIL_FEDHAS_PLANT_RING, - ABIL_FEDHAS_SPAWN_SPORES, - ABIL_FEDHAS_EVOLUTION, - ABIL_FEDHAS_FUNGAL_BLOOM, + ABIL_FEDHAS_WALL_OF_BRIARS = 1140, + ABIL_FEDHAS_GROW_BALLISTOMYCETE, + ABIL_FEDHAS_OVERGROW, + ABIL_FEDHAS_GROW_OKLOB, // Cheibriados ABIL_CHEIBRIADOS_TIME_STEP = 1151, ABIL_CHEIBRIADOS_TIME_BEND, diff --git a/crawl-ref/source/ability.cc b/crawl-ref/source/ability.cc index 07d836c9e975..bc83fe7f4668 100644 --- a/crawl-ref/source/ability.cc +++ b/crawl-ref/source/ability.cc @@ -61,6 +61,7 @@ #include "prompt.h" #include "religion.h" #include "skills.h" +#include "spl-book.h" #include "spl-cast.h" #include "spl-clouds.h" #include "spl-damage.h" @@ -311,12 +312,16 @@ static const ability_def Ability_List[] = 0, 0, 125, 0, {fail_basis::xl, 30, 1}, abflag::breath }, { ABIL_BREATHE_STEAM, "Breathe Steam", 0, 0, 75, 0, {fail_basis::xl, 20, 1}, abflag::breath }, - { ABIL_TRAN_BAT, "Bat Form", - 2, 0, 0, 0, {fail_basis::xl, 45, 2}, abflag::starve_ok }, - { ABIL_BREATHE_ACID, "Breathe Acid", 0, 0, 125, 0, {fail_basis::xl, 30, 1}, abflag::breath }, + { ABIL_TRAN_BAT, "Bat Form", + 2, 0, 0, 0, {fail_basis::xl, 45, 2}, abflag::starve_ok }, + { ABIL_EXSANGUINATE, "Exsanguinate", + 0, 0, 0, 0, {}, abflag::delay}, + { ABIL_REVIVIFY, "Revivify", + 0, 0, 0, 0, {}, abflag::delay}, + { ABIL_FLY, "Fly", 3, 0, 100, 0, {fail_basis::xl, 42, 3}, abflag::none }, { ABIL_STOP_FLYING, "Stop Flying", 0, 0, 0, 0, {}, abflag::starve_ok }, { ABIL_DAMNATION, "Hurl Damnation", @@ -430,14 +435,12 @@ static const ability_def Ability_List[] = {fail_basis::invo, 90, 2, 5}, abflag::hostile }, // Sif Muna - { ABIL_SIF_MUNA_DIVINE_ENERGY, "Divine Energy", - 0, 0, 0, 0, {fail_basis::invo}, abflag::instant | abflag::starve_ok }, - { ABIL_SIF_MUNA_STOP_DIVINE_ENERGY, "Stop Divine Energy", - 0, 0, 0, 0, {fail_basis::invo}, abflag::instant | abflag::starve_ok }, - { ABIL_SIF_MUNA_FORGET_SPELL, "Forget Spell", - 0, 0, 0, 8, {fail_basis::invo}, abflag::none }, { ABIL_SIF_MUNA_CHANNEL_ENERGY, "Channel Magic", 0, 0, 200, 2, {fail_basis::invo, 60, 4, 25}, abflag::none }, + { ABIL_SIF_MUNA_FORGET_SPELL, "Forget Spell", + 0, 0, 0, 8, {fail_basis::invo}, abflag::none }, + { ABIL_SIF_MUNA_DIVINE_EXEGESIS, "Divine Exegesis", + 0, 0, 0, 12, {fail_basis::invo, 80, 4, 25}, abflag::none }, // Trog { ABIL_TROG_BERSERK, "Berserk", @@ -511,18 +514,14 @@ static const ability_def Ability_List[] = 0, 0, 0, 15, {fail_basis::invo}, abflag::none }, // Fedhas - { ABIL_FEDHAS_FUNGAL_BLOOM, "Fungal Bloom", - 0, 0, 0, 0, {fail_basis::invo}, abflag::none }, - { ABIL_FEDHAS_SUNLIGHT, "Sunlight", - 2, 0, 50, 0, {fail_basis::invo, 30, 6, 20}, abflag::none }, - { ABIL_FEDHAS_EVOLUTION, "Evolution", - 2, 0, 0, 0, {fail_basis::invo, 30, 6, 20}, abflag::rations_or_piety }, - { ABIL_FEDHAS_PLANT_RING, "Growth", - 2, 0, 0, 0, {fail_basis::invo, 40, 5, 20}, abflag::rations }, - { ABIL_FEDHAS_SPAWN_SPORES, "Reproduction", - 4, 0, 100, 1, {fail_basis::invo, 60, 4, 25}, abflag::none }, - { ABIL_FEDHAS_RAIN, "Rain", - 4, 0, 150, 4, {fail_basis::invo, 70, 4, 25}, abflag::none }, + { ABIL_FEDHAS_WALL_OF_BRIARS, "Wall of Briars", + 3, 0, 50, 2, {fail_basis::invo, 30, 6, 20}, abflag::none}, + { ABIL_FEDHAS_GROW_BALLISTOMYCETE, "Grow Ballistomycete", + 4, 0, 100, 4, {fail_basis::invo, 60, 4, 25}, abflag::none }, + { ABIL_FEDHAS_OVERGROW, "Overgrow", + 8, 0, 200, 12, {fail_basis::invo, 70, 5, 20}, abflag::none}, + { ABIL_FEDHAS_GROW_OKLOB, "Grow Oklob", + 6, 0, 150, 6, {fail_basis::invo, 80, 4, 25}, abflag::none }, // Cheibriados { ABIL_CHEIBRIADOS_TIME_BEND, "Bend Time", @@ -759,6 +758,8 @@ static string _nemelex_card_text(ability_type ability) return make_stringf("(%d in deck)", cards); } +static const int VAMPIRE_BAT_FORM_STAT_DRAIN = 2; + const string make_cost_description(ability_type ability) { const ability_def& abil = get_ability_def(ability); @@ -772,6 +773,15 @@ const string make_cost_description(ability_type ability) if (ability == ABIL_HEAL_WOUNDS) ret += make_stringf(", Permanent MP (%d left)", get_real_mp(false)); + if (ability == ABIL_TRAN_BAT) + { + ret += make_stringf(", Stat Drain (%d each)", + VAMPIRE_BAT_FORM_STAT_DRAIN); + } + + if (ability == ABIL_REVIVIFY) + ret += ", Frailty"; + if (abil.hp_cost) ret += make_stringf(", %d HP", abil.hp_cost.cost(you.hp_max)); @@ -1014,11 +1024,6 @@ ability_type fixup_ability(ability_type ability) else return ability; - case ABIL_SIF_MUNA_DIVINE_ENERGY: - if (you.attribute[ATTR_DIVINE_ENERGY]) - return ABIL_SIF_MUNA_STOP_DIVINE_ENERGY; - return ability; - case ABIL_ASHENZARI_TRANSFER_KNOWLEDGE: if (you.species == SP_GNOLL) return ABIL_NON_ABILITY; @@ -1196,8 +1201,8 @@ void no_ability_msg() mpr("You can't untransform!"); else { - ASSERT(you.hunger_state > HS_SATIATED); - mpr("Sorry, you're too full to transform right now."); + ASSERT(you.vampire_alive); + mpr("Sorry, you cannot become a bat while alive."); } } else if (you.get_mutation_level(MUT_TENGU_FLIGHT) @@ -1333,6 +1338,22 @@ static bool _check_ability_possible(const ability_def& abil, bool quiet = false) return false; } } + else if ((abil.ability == ABIL_EXSANGUINATE + || abil.ability == ABIL_REVIVIFY) + && you.form != transformation::none) + { + if (feat_dangerous_for_form(transformation::none, env.grid(you.pos()))) + { + if (!quiet) + { + mprf("Becoming %s right now would cause you to %s!", + abil.ability == ABIL_EXSANGUINATE ? "bloodless" : "alive", + env.grid(you.pos()) == DNGN_LAVA ? "burn" : "drown"); + } + + return false; + } + } if ((abil.ability == ABIL_EVOKE_BERSERK || abil.ability == ABIL_TROG_BERSERK) @@ -1538,6 +1559,9 @@ static bool _check_ability_possible(const ability_def& abil, bool quiet = false) } return true; + case ABIL_SIF_MUNA_DIVINE_EXEGESIS: + return can_cast_spells(); + case ABIL_ASHENZARI_TRANSFER_KNOWLEDGE: if (!trainable_skills(true)) { @@ -1547,26 +1571,6 @@ static bool _check_ability_possible(const ability_def& abil, bool quiet = false) } return true; - case ABIL_FEDHAS_EVOLUTION: - return fedhas_check_evolve_flora(quiet); - - case ABIL_FEDHAS_SPAWN_SPORES: - { - const int retval = fedhas_check_corpse_spores(quiet); - if (retval <= 0) - { - if (!quiet) - { - if (retval == 0) - mpr("No corpses are in range."); - else - canned_msg(MSG_OK); - } - return false; - } - return true; - } - case ABIL_SPIT_POISON: case ABIL_BREATHE_FIRE: case ABIL_BREATHE_FROST: @@ -1838,6 +1842,87 @@ static bool _cleansing_flame_affects(const actor *act) return act->res_holy_energy() < 3; } +static string _vampire_str_int_info_blurb(string stats_affected) +{ + return make_stringf("This will reduce your %s to zero. ", + stats_affected.c_str()); +} + +/* + * Create a string which informs the player of the consequences of bat form. + * + * @param str_affected Whether the player will cause strength stat zero by + * Bat Form's stat drain ability cost. + * @param dex_affected Whether the player will cause dexterity stat zero by + * Bat Form's stat drain ability cost, disregarding Bat Form's dexterity boost. + * @param int_affected Whether the player will cause intelligence stat zero by + * Bat Form's stat drain ability cost. + * @returns The string prompt to give the player. + */ +static string _vampire_bat_transform_prompt(bool str_affected, bool dex_affected, + bool intel_affected) +{ + string prompt = ""; + + if (str_affected && intel_affected) + prompt += _vampire_str_int_info_blurb("strength and intelligence"); + else if (str_affected) + prompt += _vampire_str_int_info_blurb("strength"); + else if (intel_affected) + prompt += _vampire_str_int_info_blurb("intelligence"); + + // Bat form's dexterity boost will keep a vampire's dexterity above zero until + // they untransform. + if (dex_affected) + prompt += "This will reduce your dexterity to zero once you untransform. "; + + prompt += "Continue?"; + + return prompt; +} + +static bool _stat_affected_by_bat_form_stat_drain(int stat_value) +{ + // We check whether the stat is greater than zero to avoid prompting if a + // stat is already zero. + return 0 < stat_value && stat_value <= VAMPIRE_BAT_FORM_STAT_DRAIN; +} + +/* + * Give the player a chance to cancel a bat form transformation which could + * cause their stats to be drained to zero. + * + * @returns Whether the player canceled the transformation. + */ +static bool _player_cancels_vampire_bat_transformation() +{ + + bool str_affected = _stat_affected_by_bat_form_stat_drain(you.strength()); + bool dex_affected = _stat_affected_by_bat_form_stat_drain(you.dex()); + bool intel_affected = _stat_affected_by_bat_form_stat_drain(you.intel()); + + // Don't prompt if there's no risk of stat-zero + if (!str_affected && !dex_affected && !intel_affected) + return false; + + string prompt = _vampire_bat_transform_prompt(str_affected, dex_affected, + intel_affected); + + bool proceed_with_transformation = yesno(prompt.c_str(), false, 'n'); + + if (!proceed_with_transformation) + canned_msg(MSG_OK); + + return !proceed_with_transformation; +} + +static void _cause_vampire_bat_form_stat_drain() +{ + lose_stat(STAT_STR, VAMPIRE_BAT_FORM_STAT_DRAIN); + lose_stat(STAT_INT, VAMPIRE_BAT_FORM_STAT_DRAIN); + lose_stat(STAT_DEX, VAMPIRE_BAT_FORM_STAT_DRAIN); +} + /* * Use an ability. * @@ -2100,7 +2185,9 @@ static spret _do_ability(const ability_def& abil, bool fail) if (!invis_allowed()) return spret::abort; fail_check(); +#if TAG_MAJOR_VERSION == 34 surge_power(you.spec_evoke()); +#endif potionlike_effect(POT_INVISIBILITY, player_adjust_evoc_power( you.skill(SK_EVOCATIONS, 2) + 5)); @@ -2130,7 +2217,9 @@ static spret _do_ability(const ability_def& abil, bool fail) } else { +#if TAG_MAJOR_VERSION == 34 surge_power(you.spec_evoke()); +#endif fly_player( player_adjust_evoc_power(you.skill(SK_EVOCATIONS, 2) + 30)); } @@ -2280,7 +2369,7 @@ static spret _do_ability(const ability_def& abil, bool fail) } fail_check(); cleansing_flame(10 + you.skill_rdiv(SK_INVOCATIONS, 7, 6), - CLEANSING_FLAME_INVOCATION, you.pos(), &you); + cleansing_flame_source::invocation, you.pos(), &you); break; } @@ -2551,23 +2640,13 @@ static spret _do_ability(const ability_def& abil, bool fail) &you); break; - case ABIL_SIF_MUNA_DIVINE_ENERGY: - simple_god_message(" will now grant you divine energy when your " - "reserves of magic are depleted."); - mpr("You will briefly lose access to your magic after casting a " - "spell in this manner."); - you.attribute[ATTR_DIVINE_ENERGY] = 1; - break; - - case ABIL_SIF_MUNA_STOP_DIVINE_ENERGY: - simple_god_message(" stops granting you divine energy."); - you.attribute[ATTR_DIVINE_ENERGY] = 0; - break; - case ABIL_SIF_MUNA_FORGET_SPELL: fail_check(); if (cast_selective_amnesia() <= 0) + { + canned_msg(MSG_OK); return spret::abort; + } break; case ABIL_SIF_MUNA_CHANNEL_ENERGY: @@ -2578,6 +2657,12 @@ static spret _do_ability(const ability_def& abil, bool fail) break; } + case ABIL_SIF_MUNA_DIVINE_EXEGESIS: + { + return divine_exegesis(fail); + break; + } + case ABIL_ELYVILON_LIFESAVING: fail_check(); if (you.duration[DUR_LIFESAVING]) @@ -2755,46 +2840,60 @@ static spret _do_ability(const ability_def& abil, bool fail) end_recall(); break; - case ABIL_FEDHAS_FUNGAL_BLOOM: - fedhas_fungal_bloom(); - return spret::success; - - case ABIL_FEDHAS_SUNLIGHT: - return fedhas_sunlight(fail); - - case ABIL_FEDHAS_PLANT_RING: + case ABIL_FEDHAS_WALL_OF_BRIARS: fail_check(); - if (!fedhas_plant_ring_from_rations()) + if (!fedhas_wall_of_briars()) return spret::abort; break; - case ABIL_FEDHAS_RAIN: - fail_check(); - if (!fedhas_rain(you.pos())) - { - canned_msg(MSG_NOTHING_HAPPENS); - return spret::abort; - } + case ABIL_FEDHAS_GROW_BALLISTOMYCETE: + { + return fedhas_grow_ballistomycete(fail); + break; + } - case ABIL_FEDHAS_SPAWN_SPORES: + case ABIL_FEDHAS_OVERGROW: { fail_check(); - const int num = fedhas_corpse_spores(); - ASSERT(num > 0); + + if (!fedhas_overgrow()) + return spret::abort; + break; } - case ABIL_FEDHAS_EVOLUTION: - return fedhas_evolve_flora(fail); + case ABIL_FEDHAS_GROW_OKLOB: + { + return fedhas_grow_oklob(fail); + + break; + } case ABIL_TRAN_BAT: + { + if (_player_cancels_vampire_bat_transformation()) + return spret::abort; fail_check(); if (!transform(100, transformation::bat)) { crawl_state.zero_turns_taken(); return spret::abort; } + + _cause_vampire_bat_form_stat_drain(); + + break; + } + + case ABIL_EXSANGUINATE: + fail_check(); + start_delay(5); + break; + + case ABIL_REVIVIFY: + fail_check(); + start_delay(5); break; case ABIL_JIYVA_CALL_JELLY: @@ -3128,8 +3227,7 @@ static spret _do_ability(const ability_def& abil, bool fail) fail_check(); if (yesno("Really renounce your faith, foregoing its fabulous benefits?", false, 'n') - && yesno("Are you sure you won't change your mind later?", - false, 'n')) + && yesno("Are you sure?", false, 'n')) { excommunication(true); } @@ -3413,11 +3511,16 @@ vector your_talents(bool check_confused, bool include_unusable) _add_talent(talents, draconian_breath(you.species), check_confused); } - if (you.species == SP_VAMPIRE && you.experience_level >= 3 - && you.hunger_state <= HS_SATIATED - && you.form != transformation::bat) + if (you.species == SP_VAMPIRE) { - _add_talent(talents, ABIL_TRAN_BAT, check_confused); + if (!you.vampire_alive) + { + if (you.experience_level >= 3 && you.form != transformation::bat) + _add_talent(talents, ABIL_TRAN_BAT, check_confused); + _add_talent(talents, ABIL_REVIVIFY, check_confused); + } + else + _add_talent(talents, ABIL_EXSANGUINATE, check_confused); } if (you.racial_permanent_flight() && !you.attribute[ATTR_PERM_FLIGHT]) @@ -3534,7 +3637,7 @@ vector your_talents(bool check_confused, bool include_unusable) } // Try to find a free hotkey for i, starting from Z. - for (int k = 51; k >= 0; ++k) + for (int k = 51; k >= 0; --k) { const int kkey = index_to_letter(k); bool good_key = true; @@ -3549,7 +3652,7 @@ vector your_talents(bool check_confused, bool include_unusable) if (good_key) { - tal.hotkey = k; + tal.hotkey = kkey; you.ability_letter_table[k] = tal.which; break; } diff --git a/crawl-ref/source/abyss.cc b/crawl-ref/source/abyss.cc index c5641145d5e9..3bddb0e13b4e 100644 --- a/crawl-ref/source/abyss.cc +++ b/crawl-ref/source/abyss.cc @@ -20,6 +20,7 @@ #include "colour.h" #include "coordit.h" #include "dbg-scan.h" +#include "dbg-util.h" #include "delay.h" #include "dgn-overview.h" #include "dgn-proclayouts.h" @@ -28,6 +29,7 @@ #include "god-passive.h" // passive_t::slow_abyss #include "hiscores.h" #include "item-prop.h" +#include "item-status-flag-type.h" #include "items.h" #include "libutil.h" #include "mapmark.h" @@ -589,6 +591,8 @@ static void _abyss_lose_monster(monster& mons) // make sure we don't end up with an invalid hep ancestor else if (hepliaklqana_ancestor() == mons.mid) { + simple_monster_message(mons, " is pulled into the Abyss.", + MSGCH_BANISHMENT); remove_companion(&mons); you.duration[DUR_ANCESTOR_DELAY] = random_range(50, 150); //~5-15 turns } @@ -627,7 +631,8 @@ static void _place_displaced_monsters() if (mon->alive() && !mon->find_home_near_place(mon->pos())) { maybe_bloodify_square(mon->pos()); - if (you.can_see(*mon)) + // hep messaging is done in _abyss_lose_monster + if (you.can_see(*mon) && hepliaklqana_ancestor() != mon->mid) { simple_monster_message(*mon, " is pulled into the Abyss.", MSGCH_BANISHMENT); @@ -667,6 +672,7 @@ static void _push_items() if (!_pushy_feature(grd(*di))) { int j = i; + ASSERT(!testbits(item.flags, ISFLAG_SUMMONED)); move_item_to_grid(&j, *di, true); break; } @@ -1056,8 +1062,11 @@ static level_id _get_random_level() vector levels; for (branch_iterator it; it; ++it) { - if (it->id == BRANCH_ABYSS || it->id == BRANCH_SHOALS) + if (it->id == BRANCH_VESTIBULE || it->id == BRANCH_ABYSS + || it->id == BRANCH_SHOALS) + { continue; + } for (int j = 1; j <= brdepth[it->id]; ++j) { const level_id id(it->id, j); @@ -1373,7 +1382,7 @@ static int _abyss_place_vaults(const map_bitmask &abyss_genlevel_mask) const map_def *map = random_map_in_depth(level_id::current(), extra); if (map) { - if (_abyss_place_map(map) && !map->has_tag("extra")) + if (_abyss_place_map(map) && !map->is_extra_vault()) { extra = true; @@ -1432,11 +1441,11 @@ static void _generate_area(const map_bitmask &abyss_genlevel_mask) static void _initialize_abyss_state() { - abyssal_state.major_coord.x = get_uint32() & 0x7FFFFFFF; - abyssal_state.major_coord.y = get_uint32() & 0x7FFFFFFF; - abyssal_state.seed = get_uint32() & 0x7FFFFFFF; + abyssal_state.major_coord.x = rng::get_uint32() & 0x7FFFFFFF; + abyssal_state.major_coord.y = rng::get_uint32() & 0x7FFFFFFF; + abyssal_state.seed = rng::get_uint32() & 0x7FFFFFFF; abyssal_state.phase = 0.0; - abyssal_state.depth = get_uint32() & 0x7FFFFFFF; + abyssal_state.depth = rng::get_uint32() & 0x7FFFFFFF; abyssal_state.destroy_all_terrain = false; abyssal_state.level = _get_random_level(); abyss_sample_queue = sample_queue(ProceduralSamplePQCompare()); @@ -1446,7 +1455,7 @@ void set_abyss_state(coord_def coord, uint32_t depth) { abyssal_state.major_coord = coord; abyssal_state.depth = depth; - abyssal_state.seed = get_uint32() & 0x7FFFFFFF; + abyssal_state.seed = rng::get_uint32() & 0x7FFFFFFF; abyssal_state.phase = 0.0; abyssal_state.destroy_all_terrain = true; abyss_sample_queue = sample_queue(ProceduralSamplePQCompare()); @@ -1488,7 +1497,19 @@ static void abyss_area_shift() // And allow monsters in transit another chance to return. place_transiting_monsters(); + auto &vault_list = you.vault_list[level_id::current()]; +#ifdef DEBUG + vault_list.push_back("[shift]"); +#endif + const auto &level_vaults = level_vault_names(); + vault_list.insert(vault_list.end(), + level_vaults.begin(), level_vaults.end()); + + check_map_validity(); + // TODO: should dactions be rerun at this point instead? That would cover + // this particular case... + gozag_detect_level_gold(false); } void destroy_abyss() @@ -1667,6 +1688,7 @@ void abyss_morph() _abyss_apply_terrain(abyss_genlevel_mask, true); _place_displaced_monsters(); _push_items(); + // TODO: does gozag gold detection need to be here too? los_changed(); } @@ -1711,6 +1733,15 @@ void abyss_teleport() stop_delay(true); forget_map(false); clear_excludes(); + gozag_detect_level_gold(false); + auto &vault_list = you.vault_list[level_id::current()]; +#ifdef DEBUG + vault_list.push_back("[tele]"); +#endif + const auto &level_vaults = level_vault_names(); + vault_list.insert(vault_list.end(), + level_vaults.begin(), level_vaults.end()); + more(); } diff --git a/crawl-ref/source/ac-type.h b/crawl-ref/source/ac-type.h index 4464860b0519..8e36a6b45653 100644 --- a/crawl-ref/source/ac-type.h +++ b/crawl-ref/source/ac-type.h @@ -1,12 +1,12 @@ #pragma once -enum ac_type +enum class ac_type { - AC_NONE, + none, // These types block small amounts of damage, hardly affecting big hits. - AC_NORMAL, - AC_HALF, - AC_TRIPLE, + normal, + half, + triple, // This one stays fair over arbitrary splits. - AC_PROPORTIONAL, + proportional, }; diff --git a/crawl-ref/source/acquire.cc b/crawl-ref/source/acquire.cc index 301064df0f95..3043c09047a8 100644 --- a/crawl-ref/source/acquire.cc +++ b/crawl-ref/source/acquire.cc @@ -407,13 +407,6 @@ static int _acquirement_food_subtype(bool /*divine*/, int& quantity) // Food is a little less predictable now. - bwr if (you.species == SP_GHOUL) type_wanted = FOOD_CHUNK; - else if (you.species == SP_VAMPIRE) - { - // Vampires really don't want any OBJ_FOOD but OBJ_CORPSES - // but it's easier to just give them a potion of blood - // class type is set elsewhere - type_wanted = POT_BLOOD; - } else type_wanted = FOOD_RATION; @@ -422,8 +415,6 @@ static int _acquirement_food_subtype(bool /*divine*/, int& quantity) // giving more of the lower food value items if (type_wanted == FOOD_CHUNK) quantity += 2 + random2avg(10, 2); - else if (type_wanted == POT_BLOOD) - quantity = 8 + random2(5); return type_wanted; } @@ -545,7 +536,7 @@ static int _acquirement_missile_subtype(bool /*divine*/, int & /*quantity*/) skill = i; } - missile_type result = MI_TOMAHAWK; + missile_type result = MI_BOOMERANG; switch (skill) { @@ -558,8 +549,8 @@ static int _acquirement_missile_subtype(bool /*divine*/, int & /*quantity*/) // Choose from among all usable missile types. vector > missile_weights; - missile_weights.emplace_back(MI_TOMAHAWK, 50); - missile_weights.emplace_back(MI_NEEDLE, 75); + missile_weights.emplace_back(MI_BOOMERANG, 50); + missile_weights.emplace_back(MI_DART, 75); if (you.body_size() >= SIZE_MEDIUM) missile_weights.emplace_back(MI_JAVELIN, 100); @@ -771,10 +762,6 @@ static int _find_acquirement_subtype(object_class_type &class_wanted, if (class_wanted == OBJ_MISCELLANY) class_wanted = random_choose(OBJ_WANDS, OBJ_MISCELLANY); - // Vampires acquire blood, not food. - if (class_wanted == OBJ_FOOD && you.species == SP_VAMPIRE) - class_wanted = OBJ_POTIONS; - if (_subtype_finders[class_wanted]) type_wanted = (*_subtype_finders[class_wanted])(divine, quantity); @@ -1239,7 +1226,11 @@ int acquirement_create_item(object_class_type class_wanted, ASSERT(class_wanted != OBJ_RANDOM); const bool divine = (agent == GOD_OKAWARU || agent == GOD_XOM - || agent == GOD_TROG || agent == GOD_PAKELLAS); + || agent == GOD_TROG +#if TAG_MAJOR_VERSION == 34 + || agent == GOD_PAKELLAS +#endif + ); int thing_created = NON_ITEM; int quant = 1; #define MAX_ACQ_TRIES 40 @@ -1512,12 +1503,10 @@ bool acquirement(object_class_type class_wanted, int agent, { OBJ_BOOKS, "Book" }, { OBJ_STAVES, "Staff " }, { OBJ_MISCELLANY, "Evocable" }, - { OBJ_FOOD, 0 }, // amended below + { OBJ_FOOD, "Food" }, // amended below { OBJ_GOLD, 0 }, }; ASSERT(acq_classes[6].type == OBJ_FOOD); - acq_classes[6].name = you.species == SP_VAMPIRE ? "Blood": - "Food"; string gold_text = make_stringf("Gold (you have $%d)", you.gold); ASSERT(acq_classes[7].type == OBJ_GOLD); acq_classes[7].name = gold_text.c_str(); diff --git a/crawl-ref/source/activity-interrupt-type.h b/crawl-ref/source/activity-interrupt-type.h index eb118c08d2e3..95806175b3ad 100644 --- a/crawl-ref/source/activity-interrupt-type.h +++ b/crawl-ref/source/activity-interrupt-type.h @@ -1,26 +1,27 @@ #pragma once // Be sure to change activity_interrupt_names in delay.cc to match! -enum activity_interrupt_type +enum class activity_interrupt { - AI_FORCE_INTERRUPT = 0, // Forcibly kills any activity that can be - // interrupted. - AI_KEYPRESS, - AI_FULL_HP, // Player is fully healed - AI_FULL_MP, // Player has recovered all mp - AI_ANCESTOR_HP, // Player's ancestor is fully healed - AI_HUNGRY, // Hunger increased - AI_MESSAGE, // Message was displayed - AI_HP_LOSS, - AI_STAT_CHANGE, - AI_SEE_MONSTER, - AI_MONSTER_ATTACKS, - AI_TELEPORT, - AI_HIT_MONSTER, // Player hit monster (invis or - // mimic) during travel/explore. - AI_SENSE_MONSTER, - AI_MIMIC, + force = 0, // Forcibly kills any activity that can be + // interrupted. + keypress, // Not currently used + full_hp, // Player is fully healed + full_mp, // Player has recovered all mp + ancestor_hp, // Player's ancestor is fully healed + hungry, // Hunger increased + message, // Message was displayed + hp_loss, + stat_change, + see_monster, + monster_attacks, + teleport, + hit_monster, // Player hit invis monster during travel/explore. + sense_monster, + mimic, // Always the last. - NUM_AINTERRUPTS + COUNT }; +constexpr int NUM_ACTIVITY_INTERRUPTS + = static_cast(activity_interrupt::COUNT); diff --git a/crawl-ref/source/actor.cc b/crawl-ref/source/actor.cc index 64b7dd31bb38..75672e819af7 100644 --- a/crawl-ref/source/actor.cc +++ b/crawl-ref/source/actor.cc @@ -347,23 +347,23 @@ int actor::apply_ac(int damage, int max_damage, ac_type ac_rule, int saved = 0; switch (ac_rule) { - case AC_NONE: + case ac_type::none: return damage; // no GDR, too - case AC_PROPORTIONAL: + case ac_type::proportional: ASSERT(stab_bypass == 0); saved = damage - apply_chunked_AC(damage, ac); saved = max(saved, div_rand_round(max_damage * gdr, 100)); return max(damage - saved, 0); - case AC_NORMAL: + case ac_type::normal: saved = random2(1 + ac); break; - case AC_HALF: + case ac_type::half: saved = random2(1 + ac) / 2; ac /= 2; gdr /= 2; break; - case AC_TRIPLE: + case ac_type::triple: saved = random2(1 + ac); saved += random2(1 + ac); saved += random2(1 + ac); diff --git a/crawl-ref/source/actor.h b/crawl-ref/source/actor.h index 8442b7aaad6e..350e74cb3c2c 100644 --- a/crawl-ref/source/actor.h +++ b/crawl-ref/source/actor.h @@ -18,13 +18,13 @@ #define CLING_KEY "clinging" // 'is creature clinging' property key -enum ev_ignore_bit +enum class ev_ignore { - EV_IGNORE_NONE = 0, - EV_IGNORE_HELPLESS = 1<<0, - EV_IGNORE_UNIDED = 1<<1, + none = 0, + helpless = 1<<0, + unided = 1<<1, }; -DEF_BITFIELD(ev_ignore_type, ev_ignore_bit); +DEF_BITFIELD(ev_ignore_type, ev_ignore); struct bolt; @@ -260,9 +260,10 @@ class actor virtual int armour_class(bool calc_unid = true) const = 0; virtual int gdr_perc() const = 0; - int apply_ac(int damage, int max_damage = 0, ac_type ac_rule = AC_NORMAL, - int stab_bypass = 0, bool for_real = true) const; - virtual int evasion(ev_ignore_type ign = EV_IGNORE_NONE, + int apply_ac(int damage, int max_damage = 0, + ac_type ac_rule = ac_type::normal, int stab_bypass = 0, + bool for_real = true) const; + virtual int evasion(ev_ignore_type ign = ev_ignore::none, const actor *attacker = nullptr) const = 0; virtual bool shielded() const = 0; virtual int shield_bonus() const = 0; diff --git a/crawl-ref/source/adjust.cc b/crawl-ref/source/adjust.cc index 727f6db4ddbe..57771704d857 100644 --- a/crawl-ref/source/adjust.cc +++ b/crawl-ref/source/adjust.cc @@ -51,7 +51,8 @@ void adjust_item(int from_slot) if (from_slot == -1) { - from_slot = prompt_invent_item("Adjust which item?", MT_INVLIST, -1); + from_slot = prompt_invent_item("Adjust which item?", + menu_type::invlist, OSEL_ANY); if (prompt_failed(from_slot)) return; @@ -59,8 +60,8 @@ void adjust_item(int from_slot) } const int to_slot = prompt_invent_item("Adjust to which letter? ", - MT_INVLIST, - -1, OPER_ANY, + menu_type::invlist, + OSEL_ANY, OPER_ANY, invprompt_flag::unthings_ok | invprompt_flag::manual_list); if (to_slot == PROMPT_ABORT @@ -238,4 +239,6 @@ void swap_inv_slots(int from_slot, int to_slot, bool verbose) if (to_count > 0) last_pickup[from_slot] = to_count; } + if (you.last_unequip == from_slot) + you.last_unequip = to_slot; } diff --git a/crawl-ref/source/android-project/jni/src/Android.mk b/crawl-ref/source/android-project/jni/src/Android.mk index 2a3ec1bdb16e..e64674808528 100644 --- a/crawl-ref/source/android-project/jni/src/Android.mk +++ b/crawl-ref/source/android-project/jni/src/Android.mk @@ -194,6 +194,7 @@ LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ $(CRAWL_PATH)/orb.cc \ $(CRAWL_PATH)/ouch.cc \ $(CRAWL_PATH)/output.cc \ + $(CRAWL_PATH)/outer-menu.cc \ $(CRAWL_PATH)/package.cc \ $(CRAWL_PATH)/pattern.cc \ $(CRAWL_PATH)/pcg.cc \ diff --git a/crawl-ref/source/areas.cc b/crawl-ref/source/areas.cc index f3f1c5d27849..b3248f4a3fbf 100644 --- a/crawl-ref/source/areas.cc +++ b/crawl-ref/source/areas.cc @@ -95,7 +95,7 @@ static void _actor_areas(actor *a) if ((r = a->silence_radius()) >= 0) { - _agrid_centres.emplace_back(AREA_SILENCE, a->pos(), r); + _agrid_centres.emplace_back(area_centre_type::silence, a->pos(), r); for (radius_iterator ri(a->pos(), r, C_SQUARE); ri; ++ri) _set_agrid_flag(*ri, areaprop::silence); @@ -104,7 +104,7 @@ static void _actor_areas(actor *a) if ((r = a->halo_radius()) >= 0) { - _agrid_centres.emplace_back(AREA_HALO, a->pos(), r); + _agrid_centres.emplace_back(area_centre_type::halo, a->pos(), r); for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_DEFAULT); ri; ++ri) _set_agrid_flag(*ri, areaprop::halo); @@ -113,7 +113,7 @@ static void _actor_areas(actor *a) if ((r = a->liquefying_radius()) >= 0) { - _agrid_centres.emplace_back(AREA_LIQUID, a->pos(), r); + _agrid_centres.emplace_back(area_centre_type::liquid, a->pos(), r); for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_SOLID); ri; ++ri) { @@ -129,7 +129,7 @@ static void _actor_areas(actor *a) if ((r = a->umbra_radius()) >= 0) { - _agrid_centres.emplace_back(AREA_UMBRA, a->pos(), r); + _agrid_centres.emplace_back(area_centre_type::umbra, a->pos(), r); for (radius_iterator ri(a->pos(), r, C_SQUARE, LOS_DEFAULT); ri; ++ri) _set_agrid_flag(*ri, areaprop::umbra); @@ -145,6 +145,9 @@ static void _actor_areas(actor *a) */ static void _update_agrid() { + // sanitize rng in case this gets indirectly called by the builder. + rng::generator gameplay(rng::GAMEPLAY); + if (no_areas) { _agrid_valid = true; @@ -163,7 +166,7 @@ static void _update_agrid() if (player_has_orb() && !you.pos().origin()) { const int r = 2; - _agrid_centres.emplace_back(AREA_ORB, you.pos(), r); + _agrid_centres.emplace_back(area_centre_type::orb, you.pos(), r); for (radius_iterator ri(you.pos(), r, C_SQUARE, LOS_DEFAULT); ri; ++ri) _set_agrid_flag(*ri, areaprop::orb); no_areas = false; @@ -172,7 +175,7 @@ static void _update_agrid() if (you.duration[DUR_QUAD_DAMAGE]) { const int r = 2; - _agrid_centres.emplace_back(AREA_QUAD, you.pos(), r); + _agrid_centres.emplace_back(area_centre_type::quad, you.pos(), r); for (radius_iterator ri(you.pos(), r, C_SQUARE); ri; ++ri) { @@ -185,7 +188,8 @@ static void _update_agrid() if (you.duration[DUR_DISJUNCTION]) { const int r = 4; - _agrid_centres.emplace_back(AREA_DISJUNCTION, you.pos(), r); + _agrid_centres.emplace_back(area_centre_type::disjunction, + you.pos(), r); for (radius_iterator ri(you.pos(), r, C_SQUARE); ri; ++ri) { @@ -195,13 +199,6 @@ static void _update_agrid() no_areas = false; } - if (!env.sunlight.empty()) - { - for (const auto &entry : env.sunlight) - _set_agrid_flag(entry.first, areaprop::halo); - no_areas = false; - } - // TODO: update sanctuary here. _agrid_valid = true; @@ -211,22 +208,22 @@ static area_centre_type _get_first_area(const coord_def& f) { areaprops a = _agrid(f); if (a & areaprop::sanctuary_1) - return AREA_SANCTUARY; + return area_centre_type::sanctuary; if (a & areaprop::sanctuary_2) - return AREA_SANCTUARY; + return area_centre_type::sanctuary; if (a & areaprop::silence) - return AREA_SILENCE; + return area_centre_type::silence; if (a & areaprop::halo) - return AREA_HALO; + return area_centre_type::halo; if (a & areaprop::umbra) - return AREA_UMBRA; + return area_centre_type::umbra; // liquid is always applied; actual_liquid is on top // of this. If we find the first, we don't care about // the second. if (a & areaprop::liquid) - return AREA_LIQUID; + return area_centre_type::liquid; - return AREA_NONE; + return area_centre_type::none; } coord_def find_centre_for(const coord_def& f, area_centre_type at) @@ -248,11 +245,11 @@ coord_def find_centre_for(const coord_def& f, area_centre_type at) // Unspecified area type; settle for the first valid one. // We checked for no aprop a bit ago. - if (at == AREA_NONE) + if (at == area_centre_type::none) at = _get_first_area(f); // on the off chance that there is an error, assert here - ASSERT(at != AREA_NONE); + ASSERT(at != area_centre_type::none); for (const area_centre &a : _agrid_centres) { diff --git a/crawl-ref/source/areas.h b/crawl-ref/source/areas.h index 491ccaca4ccc..8f56d3bf20a1 100644 --- a/crawl-ref/source/areas.h +++ b/crawl-ref/source/areas.h @@ -1,18 +1,18 @@ #pragma once -enum area_centre_type +enum class area_centre_type { - AREA_NONE, - AREA_SANCTUARY, - AREA_SILENCE, - AREA_HALO, - AREA_LIQUID, - AREA_ORB, - AREA_UMBRA, - AREA_QUAD, - AREA_DISJUNCTION, + none, + sanctuary, + silence, + halo, + liquid, + orb, + umbra, + quad, + disjunction, #if TAG_MAJOR_VERSION == 34 - AREA_HOT, + hot, #endif }; @@ -25,7 +25,8 @@ void create_sanctuary(const coord_def& center, int time); bool remove_sanctuary(bool did_attack = false); void decrease_sanctuary_radius(); -coord_def find_centre_for (const coord_def& f, area_centre_type at = AREA_NONE); +coord_def find_centre_for(const coord_def& f, + area_centre_type at = area_centre_type::none); bool silenced(const coord_def& p); diff --git a/crawl-ref/source/arena.cc b/crawl-ref/source/arena.cc index a64f75186f12..a4283e62c4f6 100644 --- a/crawl-ref/source/arena.cc +++ b/crawl-ref/source/arena.cc @@ -15,6 +15,7 @@ #include "dungeon.h" #include "end.h" #include "food.h" +#include "initfile.h" #include "item-name.h" #include "item-status-flag-type.h" #include "items.h" @@ -22,12 +23,14 @@ #include "los.h" #include "macro.h" #include "maps.h" +#include "menu.h" #include "message.h" #include "misc.h" #include "mgen-data.h" #include "mon-death.h" #include "mon-pick.h" #include "mon-tentacle.h" +#include "newgame-def.h" #include "ng-init.h" #include "spl-miscast.h" #include "state.h" @@ -46,18 +49,129 @@ using namespace ui; #define ARENA_VERBOSE +// wrap a message tee around a file ptr, which can be null. +// for a more general purpose application you'd want this to handle opening +// and closing the file too, but that would require some restructuring of the +// arena. +class arena_message_tee : message_tee +{ +public: + arena_message_tee(FILE **_file) : message_tee(), file(_file) { } + + ~arena_message_tee() + { + if (*file) + fflush(*file); + } + + void append(const string &s, msg_channel_type ch = MSGCH_PLAIN) + { + if (Options.arena_dump_msgs && *file) + { + if (!s.size()) + return; + string prefix; + switch (ch) + { + case MSGCH_DIAGNOSTICS: + prefix = "DIAG: "; + if (Options.arena_dump_msgs_all) + break; + return; + + // Ignore messages generated while the user examines + // the arnea. + case MSGCH_PROMPT: + case MSGCH_MONSTER_TARGET: + case MSGCH_FLOOR_ITEMS: + case MSGCH_EXAMINE: + case MSGCH_EXAMINE_FILTER: + return; + + // If a monster-damage message ends with '!' it's a + // death message, otherwise it's an examination message + // and should be skipped. + case MSGCH_MONSTER_DAMAGE: + if (s[s.size() - 1] != '!') + return; + break; + + case MSGCH_ERROR: prefix = "ERROR: "; break; + case MSGCH_WARN: prefix = "WARN: "; break; + case MSGCH_SOUND: prefix = "SOUND: "; break; + + case MSGCH_TALK_VISUAL: + case MSGCH_TALK: prefix = "TALK: "; break; + default: break; + } + formatted_string fs = formatted_string::parse_string(s); + fprintf(*file, "%s%s", prefix.c_str(), fs.tostring().c_str()); + fflush(*file); + } + } + +private: + FILE **file; +}; + extern void world_reacts(); +static void _results_popup(string msg, bool error=false) +{ + // TODO: shared code here with end.cc + linebreak_string(msg, 79); + +#ifdef USE_TILE_WEB + tiles_crt_popup show_as_popup; + tiles.set_ui_state(UI_CRT); +#endif + + if (error) + { + msg = string("Arena error:\n\n") + + replace_all(msg, "<", "<<"); + msg += ""; + } + else + msg = string("Arena results:\n\n") + msg; + + msg += "\n\nHit any key to continue, " + "ctrl-p for the full log."; + + auto prompt_ui = make_shared( + formatted_string::parse_string(msg)); + bool done = false; + prompt_ui->on(Widget::slots.event, [&](wm_event ev) { + if (ev.type == WME_KEYDOWN) + { + if (ev.key.keysym.sym == CONTROL('P')) + replay_messages(); + else + done = true; + } + return done; + }); + + mouse_control mc(MOUSE_MODE_MORE); + auto popup = make_shared(prompt_ui); + ui::run_layout(move(popup), done); +} + namespace arena { + static bool skipped_arena_ui = true; // whether this is an interactive session static void write_error(const string &error); struct arena_error : public runtime_error { - explicit arena_error(const string &msg) : runtime_error(msg) {} - explicit arena_error(const char *msg) : runtime_error(msg) {} + explicit arena_error(const string &msg, bool _fatal=true) + : runtime_error(msg), fatal(_fatal) {} + explicit arena_error(const char *msg, bool _fatal=true) + : runtime_error(msg), fatal(_fatal) {} + bool fatal; }; #define arena_error_f(...) arena_error(make_stringf(__VA_ARGS__)) +#define arena_error_nonfatal_f(...) arena_error(make_stringf(__VA_ARGS__), false) // A faction is just a big list of monsters. Monsters will be dropped // around the appropriate marker. @@ -143,6 +257,7 @@ namespace arena static FILE *file = nullptr; static level_id place(BRANCH_DEPTHS, 1); + static string arena_log; static void adjust_spells(monster* mons, bool no_summons, bool no_animate) { @@ -310,7 +425,7 @@ namespace arena { const string err = fact.members.add_mons(monster, false); if (!err.empty()) - throw arena_error(err); + throw arena_error(err, false); } } @@ -334,7 +449,10 @@ namespace arena summon_throttle = strip_number_tag(spec, "summon_throttle:"); if (real_summons && respawn) - throw arena_error("Can't set real_summons and respawn at same time."); + { + throw arena_error("Can't set real_summons and respawn at same time.", + false); + } if (summon_throttle <= 0) summon_throttle = INT_MAX; @@ -368,7 +486,7 @@ namespace arena } catch (const bad_level_id &err) { - throw arena_error_f("Bad place '%s': %s", + throw arena_error_nonfatal_f("Bad place '%s': %s", arena_place.c_str(), err.what()); } @@ -385,7 +503,8 @@ namespace arena if (factions.size() != 2) { - throw arena_error_f("Expected arena monster spec \"xxx v yyy\", " + throw arena_error_nonfatal_f( + "Expected arena monster spec \"xxx v yyy\", " "but got \"%s\"", spec.c_str()); } @@ -396,7 +515,7 @@ namespace arena } catch (const arena_error &err) { - throw arena_error_f("Bad monster spec \"%s\": %s", + throw arena_error_nonfatal_f("Bad monster spec \"%s\": %s", spec.c_str(), err.what()); } @@ -587,59 +706,6 @@ namespace arena return faction_a.active_members > 0 && faction_b.active_members > 0; } - static void dump_messages() - { - if (!Options.arena_dump_msgs || file == nullptr) - return; - - vector messages; - vector channels; - get_recent_messages(messages, channels); - - for (unsigned int i = 0; i < messages.size(); i++) - { - string msg = messages[i]; - int chan = channels[i]; - - string prefix; - switch (chan) - { - case MSGCH_DIAGNOSTICS: - prefix = "DIAG: "; - if (Options.arena_dump_msgs_all) - break; - continue; - - // Ignore messages generated while the user examines - // the arnea. - case MSGCH_PROMPT: - case MSGCH_MONSTER_TARGET: - case MSGCH_FLOOR_ITEMS: - case MSGCH_EXAMINE: - case MSGCH_EXAMINE_FILTER: - continue; - - // If a monster-damage message ends with '!' it's a - // death message, otherwise it's an examination message - // and should be skipped. - case MSGCH_MONSTER_DAMAGE: - if (msg[msg.length() - 1] != '!') - continue; - break; - - case MSGCH_ERROR: prefix = "ERROR: "; break; - case MSGCH_WARN: prefix = "WARN: "; break; - case MSGCH_SOUND: prefix = "SOUND: "; break; - - case MSGCH_TALK_VISUAL: - case MSGCH_TALK: prefix = "TALK: "; break; - } - msg = prefix + msg; - - fprintf(file, "%s\n", msg.c_str()); - } - } - // Try to prevent random luck from letting one spawner fill up the // arena with so many monsters that the other spawner can never get // back on even footing. @@ -789,6 +855,7 @@ namespace arena { viewwindow(); clear_messages(true); + { cursor_control coff(false); while (fight_is_on() && !contest_cancelled) @@ -810,9 +877,8 @@ namespace arena do_respawn(faction_a); do_respawn(faction_b); balance_spawners(); - ui_delay(Options.view_delay); + ui::delay(Options.view_delay); clear_messages(); - dump_messages(); ASSERT(you.pet_target == MHITNOT); } viewwindow(); @@ -821,8 +887,8 @@ namespace arena if (contest_cancelled) { mpr("Canceled contest at user request"); + ui::delay(Options.view_delay); clear_messages(); - dump_messages(); return; } @@ -897,7 +963,6 @@ namespace arena mprf(msg.c_str(), faction_a.won ? faction_a.desc.c_str() : faction_b.desc.c_str()); - dump_messages(); } static void global_setup(const string& arena_teams) @@ -911,35 +976,16 @@ namespace arena memset(banned_glyphs, 0, sizeof(banned_glyphs)); arena_type = ""; place = level_id(BRANCH_DEPTHS, 1); + arena_log = ""; // [ds] Turning off view_lock crashes arena. Options.view_lock_x = Options.view_lock_y = true; teams = arena_teams; // Set various options from the arena spec's tags - try - { - parse_monster_spec(); - } - catch (const arena_error &error) - { - write_error(error.what()); - game_ended_with_error(error.what()); - } - - if (file != nullptr) - end(0, false, "Results file already open"); - file = fopen("arena.result", "w"); - - if (file != nullptr) - { - string spec = find_monster_spec(); - fprintf(file, "%s\n", spec.c_str()); - - if (Options.arena_dump_msgs || Options.arena_list_eq) - fprintf(file, "========================================\n"); - } + parse_monster_spec(); // may throw an arena_error + crawl_view.init_geometry(); expand_mlist(5); for (monster_type i = MONS_0; i < NUM_MONSTERS; ++i) @@ -958,6 +1004,7 @@ namespace arena fclose(file); file = nullptr; + arena_log = ""; } static void write_results() @@ -1023,22 +1070,29 @@ namespace arena { write_error(error.what()); game_ended_with_error(error.what()); + continue; } do_fight(); if (trials_done < total_trials) - ui_delay(Options.view_delay * 5); + ui::delay(Options.view_delay * 5); } while (!contest_cancelled && trials_done < total_trials); + ui::delay(Options.view_delay * 5); + if (total_trials > 0) { - mprf("Final score: %s (%d); %s (%d) [%d ties]", + string outcome = make_stringf( + "Final score: %s (%d); %s (%d) [%d ties]", faction_a.desc.c_str(), team_a_wins, faction_b.desc.c_str(), trials_done - team_a_wins - ties, ties); + mpr(outcome); + if (!skipped_arena_ui) + _results_popup(outcome); } - ui_delay(Options.view_delay * 5); + ui::pop_layout(); write_results(); @@ -1359,7 +1413,7 @@ int arena_cull_items() // Arrows/needles/etc on the floor is just clutter. if (item.base_type != OBJ_MISSILES || item.sub_type == MI_JAVELIN - || item.sub_type == MI_TOMAHAWK + || item.sub_type == MI_BOOMERANG || item.sub_type == MI_THROWING_NET) { ammo.push_back(idx); @@ -1411,19 +1465,120 @@ static void _init_arena() initialise_item_descriptions(); } -NORETURN void run_arena(const string& teams) +static void _choose_arena_teams(newgame_def& choice, + const string &default_arena_teams) +{ +#ifdef USE_TILE_WEB + tiles_crt_popup show_as_popup; +#endif + + if (!choice.arena_teams.empty()) + return; + arena::skipped_arena_ui = false; + clear_message_store(); + + char buf[80]; + resumable_line_reader reader(buf, sizeof(buf)); + bool done = false; + bool cancel = false; + auto prompt_ui = make_shared(); + auto popup = make_shared(prompt_ui); + + popup->on(Widget::slots.event, [&](wm_event ev) { + if (ev.type != WME_KEYDOWN) + return false; + const int key = reader.putkey(ev.key.keysym.sym); + if (key == -1) + return true; + cancel = !!key; + return done = true; + }); + + ui::push_layout(move(popup)); + while (!done && !crawl_state.seen_hups) + { + string hlbuf = formatted_string(buf).to_colour_string(); + if (hlbuf.find(" v ") != string::npos) + hlbuf = "" + replace_all(hlbuf, " v ", " v ") + ""; + + formatted_string prompt; + prompt.cprintf("Enter your choice of teams:\n\n "); + prompt += formatted_string::parse_string(hlbuf); + + prompt.cprintf("\n\n"); + if (!default_arena_teams.empty()) + prompt.cprintf("Enter - %s\n", default_arena_teams.c_str()); + prompt.cprintf("\n"); + prompt.cprintf("Examples:\n"); + prompt.cprintf(" Sigmund v Jessica\n"); + prompt.cprintf(" 99 orc v the Royal Jelly\n"); + prompt.cprintf(" 20-headed hydra v 10 kobold ; scimitar ego:flaming"); + prompt_ui->set_text(prompt); + + ui::pump_events(); + } + ui::pop_layout(); + + if (cancel || crawl_state.seen_hups) + { + arena::global_shutdown(); + game_ended(crawl_state.bypassed_startup_menu + ? game_exit::death : game_exit::abort); + } + choice.arena_teams = buf; + if (choice.arena_teams.empty()) + choice.arena_teams = default_arena_teams; +} + +NORETURN void run_arena(const newgame_def& choice, const string &default_arena_teams) { - _init_arena(); + ASSERT(crawl_state.game_is_arena()); + + newgame_def arena_choice = choice; + string last_teams = default_arena_teams; + if (arena::file != nullptr) + end(0, false, "Results file already open"); + // would be more elegant if arena_message_tee handled file open/close, but + // that would need a bunch of refactoring of how the file is handled here. + arena::file = fopen("arena.result", "w"); + arena_message_tee log(&arena::file); + + do + { + try + { + _choose_arena_teams(arena_choice, last_teams); + write_newgame_options_file(arena_choice); + _init_arena(); - ASSERT(!crawl_state.arena_suspended); + ASSERT(!crawl_state.arena_suspended); #ifdef WIZARD - // The player has wizard powers for the duration of the arena. - unwind_bool wiz(you.wizard, true); + // The player has wizard powers for the duration of the arena. + unwind_bool wiz(you.wizard, true); #endif - arena::global_setup(teams); - arena::simulate(); - arena::global_shutdown(); - game_ended(game_exit::death); // there is only death in the arena + arena::global_setup(arena_choice.arena_teams); + arena::simulate(); + arena::global_shutdown(); + game_ended(game_exit::death); // there is only death in the arena + } + catch (const arena::arena_error &error) + { + if (error.fatal || arena::skipped_arena_ui) + { + arena::write_error(error.what()); + game_ended_with_error(error.what()); + } + else + { + mprf(MSGCH_ERROR, "%s", error.what()); + _results_popup(error.what(), true); + last_teams = arena_choice.arena_teams; + arena_choice.arena_teams = ""; + // fallthrough + } + } + } + while (true); } diff --git a/crawl-ref/source/arena.h b/crawl-ref/source/arena.h index 95afedcddd47..33404a027a40 100644 --- a/crawl-ref/source/arena.h +++ b/crawl-ref/source/arena.h @@ -13,7 +13,9 @@ struct mgen_data; struct coord_def; -NORETURN void run_arena(const string& teams); +struct newgame_def; + +NORETURN void run_arena(const newgame_def& choice, const string &default_arena_teams); monster_type arena_pick_random_monster(const level_id &place); diff --git a/crawl-ref/source/art-data.txt b/crawl-ref/source/art-data.txt index 79ddecb3b44b..f505a72dfa48 100644 --- a/crawl-ref/source/art-data.txt +++ b/crawl-ref/source/art-data.txt @@ -123,6 +123,11 @@ # PLUS: The pluses of the artefact. +# FALLBACK: if an artifact has already generated, the builder instead usually +# creates a randart with the same base type. This field allows overriding the +# base type for certain special cases (e.g. if a randart's special properties +# put it on par with a much stronger base type). + ##### # Explanation of normal fields: @@ -160,6 +165,7 @@ ENUM: DUMMY1 NAME: DUMMY UNRANDART 1 OBJ: OBJ_UNASSIGNED/250 +FALLBACK: OBJ_UNASSIGNED/250 PLUS: 250 COLOUR: BLACK @@ -177,6 +183,7 @@ BRAND: SPWPN_VORPAL NAME: Wrath of Trog OBJ: OBJ_WEAPONS/WPN_BATTLEAXE +FALLBACK: OBJ_WEAPONS/WPN_EXECUTIONERS_AXE PLUS: +8 COLOUR: ETC_BLOOD TILE: spwpn_wrath_of_trog @@ -187,6 +194,7 @@ BRAND: SPWPN_ANTIMAGIC NAME: mace of Variability OBJ: OBJ_WEAPONS/WPN_GREAT_MACE +FALLBACK: OBJ_WEAPONS/OBJ_RANDOM INSCRIP: chain chaos PLUS: +7 COLOUR: ETC_RANDOM @@ -207,6 +215,7 @@ BRAND: SPWPN_VORPAL NAME: sword of Power OBJ: OBJ_WEAPONS/WPN_GREAT_SWORD +FB_BRAND: SPWPN_VORPAL PLUS: +0 # Set on wield COLOUR: RED TILE: spwpn_sword_of_power @@ -235,6 +244,7 @@ VALUE: 1000 NAME: Vampire's Tooth OBJ: OBJ_WEAPONS/WPN_QUICK_BLADE # it's a quick blade made from a tooth -> no TYPE +FB_BRAND: SPWPN_VAMPIRISM PLUS: +12 COLOUR: ETC_BONE TILE: spwpn_vampires_tooth @@ -270,8 +280,9 @@ COLOUR: ETC_BONE TILE: spwpn_sword_of_zonguldrok TILE_EQ: zonguldrok BRAND: SPWPN_REAPING +FB_BRAND: SPWPN_PAIN VALUE: 800 -BOOL: evil, corpse_violating +BOOL: evil NAME: sword of Cerebov APPEAR: great serpentine sword @@ -313,7 +324,7 @@ PLUS: +3 COLOUR: ETC_JEWEL TILE: urand_faerie TILE_EQ: faerie_dragon_armour -BOOL: nogen, unided, no_upgrade +BOOL: nogen, no_upgrade NAME: demon blade "Bloodbane" OBJ: OBJ_WEAPONS/WPN_DEMON_BLADE @@ -405,6 +416,8 @@ COLOUR: ETC_SLIME TILE: urand_punk TILE_EQ: punk BRAND: SPWPN_ACID +# random fallback brand +FB_BRAND: SPWPN_NORMAL BOOL: rCorr NAME: longbow "Zephyr" @@ -428,6 +441,7 @@ STR: 7 NAME: glaive of the Guard OBJ: OBJ_WEAPONS/WPN_GLAIVE +FALLBACK: OBJ_WEAPONS/WPN_BARDICHE PLUS: +8 COLOUR: ETC_ELECTRICITY TILE: urand_guard @@ -443,6 +457,7 @@ PLUS: +10 COLOUR: ETC_HOLY TILE: unrand_zealot_sword TILE_EQ: zealot_sword +# fallback places a randart eudemon blade! BRAND: SPWPN_HOLY_WRATH EV: 3 ANGRY: 5 @@ -451,6 +466,7 @@ LIFE: 1 NAME: arbalest "Damnation" INSCRIP: damnation, OBJ: OBJ_WEAPONS/WPN_ARBALEST +FB_BRAND: SPWPN_FLAMING PLUS: +6 COLOUR: ETC_FIRE TILE: urand_damnation @@ -492,6 +508,7 @@ COLOUR: ETC_UNHOLY TILE: urand_botono TILE_EQ: botono BRAND: SPWPN_REAPING +FB_BRAND: SPWPN_PAIN HP: -6 LIFE: 1 BOOL: poison, nogen @@ -517,6 +534,8 @@ MAGIC: 1 NAME: Elemental Staff OBJ: OBJ_WEAPONS/WPN_STAFF +FALLBACK: OBJ_WEAPONS/WPN_LAJATANG +FB_BRAND: SPWPN_FREEZING PLUS: +3 COLOUR: DARKGREY TILE: urand_elemental @@ -539,6 +558,7 @@ BRAND: SPWPN_VORPAL BOOL: seeinv INSCRIP: Acc+∞ +# TAG_MAJOR_VERSION == 34 NAME: longbow "Piercer" OBJ: OBJ_WEAPONS/WPN_LONGBOW PLUS: +7 @@ -547,12 +567,15 @@ TILE: urand_piercer TILE_EQ: great_bow BRAND: SPWPN_PENETRATION EV: -2 +BOOL: nogen # TAG_MAJOR_VERSION == 34 ENUM: BLOWGUN_ASSASSIN NAME: blowgun of the Assassin INSCRIP: stab, OBJ: OBJ_WEAPONS/WPN_BLOWGUN +# fallback is just to prevent errors +FALLBACK: OBJ_WEAPONS/WPN_LONGBOW PLUS: +6 COLOUR: ETC_DEATH TILE: urand_blowgun @@ -562,6 +585,8 @@ BOOL: inv, tilerim, nogen NAME: lance "Wyrmbane" OBJ: OBJ_WEAPONS/WPN_SPEAR +FALLBACK: OBJ_WEAPONS/WPN_DEMON_TRIDENT +FB_BRAND: SPWPN_VORPAL INSCRIP: slay drac, TYPE: lance BASE_DAM: +2 @@ -587,6 +612,7 @@ STEALTH: 1 NAME: plutonium sword OBJ: OBJ_WEAPONS/WPN_TRIPLE_SWORD +FB_BRAND: SPWPN_CHAOS PLUS: +11 COLOUR: ETC_MUTAGENIC TILE: urand_plutonium @@ -598,6 +624,7 @@ VALUE: 1000 NAME: great mace "Undeadhunter" INSCRIP: disrupt, OBJ: OBJ_WEAPONS/WPN_GREAT_MACE +FB_BRAND: SPWPN_HOLY_WRATH PLUS: +7 COLOUR: LIGHTGREY TILE: urand_undeadhunter @@ -606,6 +633,7 @@ LIFE: 1 NAME: whip "Snakebite" OBJ: OBJ_WEAPONS/WPN_WHIP +FALLBACK: OBJ_WEAPONS/WPN_DEMON_WHIP INSCRIP: curare, PLUS: +8 COLOUR: DARKGREY @@ -654,6 +682,7 @@ COLOUR: BLUE TILE: urand_storm_bow TILE_EQ: storm_bow BRAND: SPWPN_ELECTROCUTION +INSCRIP: penet NAME: large shield of Ignorance OBJ: OBJ_ARMOUR/ARM_LARGE_SHIELD @@ -676,6 +705,7 @@ DEX: 4 NAME: cloak of the Thief OBJ: OBJ_ARMOUR/ARM_CLOAK +FB_BRAND: SPARM_INVISIBILITY INSCRIP: +Fog PLUS: +2 COLOUR: ETC_DARK @@ -697,6 +727,7 @@ BOOL: nogen NAME: crown of Dyrovepreva OBJ: OBJ_ARMOUR/ARM_HAT +FB_BRAND: SPARM_SEE_INVISIBLE PLUS: +3 COLOUR: ETC_JEWEL TILE: urand_dyrovepreva @@ -710,6 +741,7 @@ PLUS: +2 COLOUR: DARKGREY TILE: urand_bear TILE_EQ: bear +# TODO: why isn't this brand working on the fallback? BRAND: SPARM_SPIRIT_SHIELD BOOL: berserk MAGIC: 2 @@ -717,6 +749,8 @@ LIFE: 1 NAME: robe of Misfortune OBJ: OBJ_ARMOUR/ARM_ROBE +# try to make it bad, this is the only bad SPARM: +FB_BRAND: SPARM_PONDEROUSNESS PLUS: 5 COLOUR: LIGHTMAGENTA TILE: urand_misfortune @@ -737,6 +771,7 @@ BOOL: fly, nogen ENUM: BOOTS_ASSASSIN NAME: boots of the Assassin OBJ: OBJ_ARMOUR/ARM_BOOTS +FB_BRAND: SPARM_STEALTH INSCRIP: Detection Stab+ PLUS: +2 COLOUR: BROWN @@ -748,6 +783,7 @@ VALUE: 600 ENUM: LEAR NAME: Lear's hauberk OBJ: OBJ_ARMOUR/ARM_CHAIN_MAIL +FALLBACK: OBJ_ARMOUR/ARM_PLATE_ARMOUR PLUS: 27 COLOUR: ETC_GOLD TILE: urand_lear @@ -756,6 +792,9 @@ BOOL: special NAME: skin of Zhor OBJ: OBJ_ARMOUR/ARM_ANIMAL_SKIN +# because animal skins count as "mundane" use something else to ensure a randart +# is generated; this isn't a great match: +FALLBACK: OBJ_ARMOUR/ARM_STEAM_DRAGON_ARMOUR PLUS: +4 COLOUR: BROWN TILE: urand_zhor @@ -767,6 +806,7 @@ INSCRIP: Hibernate ENUM: SALAMANDER NAME: salamander hide armour OBJ: OBJ_ARMOUR/ARM_LEATHER_ARMOUR +FB_BRAND: SPARM_FIRE_RESISTANCE PLUS: +3 COLOUR: ETC_FIRE TILE: urand_salamander @@ -824,6 +864,7 @@ BOOL: seeinv NAME: robe of Night OBJ: OBJ_ARMOUR/ARM_ROBE +# there's not a very appropriate fallback brand that works on robes... PLUS: +5 INSCRIP: Dark COLOUR: ETC_DARK @@ -890,6 +931,7 @@ BOOL: poison NAME: shield of the Gong OBJ: OBJ_ARMOUR/ARM_SHIELD +FALLBACK: OBJ_ARMOUR/ARM_LARGE_SHIELD PLUS: +18 COLOUR: ETC_GOLD TILE: urand_gong @@ -996,6 +1038,7 @@ COLOUR: ETC_UNHOLY TILE: spwpn_demon_axe TILE_EQ: demon_axe BRAND: SPWPN_VORPAL +FB_BRAND: SPWPN_PAIN BOOL: evil, seeinv, fly, curse NAME: lightning scales @@ -1026,6 +1069,7 @@ TILE: urand_vitality NAME: autumn katana OBJ: OBJ_WEAPONS/WPN_LONG_SWORD +FALLBACK: OBJ_WEAPONS/WPN_DOUBLE_SWORD TYPE: katana TILE: urand_katana TILE_EQ: katana_slant @@ -1040,6 +1084,8 @@ BRAND: SPWPN_VORPAL NAME: shillelagh "Devastator" INSCRIP: shatter OBJ: OBJ_WEAPONS/WPN_CLUB +FALLBACK: OBJ_WEAPONS/WPN_EVENINGSTAR +FB_BRAND: SPWPN_VORPAL TYPE: shillelagh TILE: urand_shillelagh TILE_EQ: shillelagh @@ -1142,6 +1188,8 @@ BOOL: nogen NAME: arc blade INSCRIP: discharge, OBJ: OBJ_WEAPONS/WPN_RAPIER +FALLBACK: OBJ_WEAPONS/WPN_QUICK_BLADE +FB_BRAND: SPWPN_ELECTROCUTION PLUS: +8 COLOUR: ETC_ELECTRICITY TILE: urand_arc_blade @@ -1162,6 +1210,8 @@ MAGIC: 1 NAME: lajatang of Order INSCRIP: silver, OBJ: OBJ_WEAPONS/WPN_LAJATANG +# not really comparable... +FB_BRAND: SPWPN_HOLY_WRATH COLOUR: ETC_SILVER TILE: urand_order TILE_EQ: order @@ -1188,12 +1238,14 @@ TILE_EQ: orange_crystal PLUS: 8 INT: 3 BRAND: SPARM_ARCHMAGI +FB_BRAND: SPWPN_NORMAL BOOL: clarity ENUM: MAJIN NAME: Majin-Bo INSCRIP: Archmagi OBJ: OBJ_WEAPONS/WPN_QUARTERSTAFF +FALLBACK: OBJ_WEAPONS/WPN_LAJATANG PLUS: +6 COLOUR: ETC_UNHOLY TILE: spwpn_majin @@ -1209,9 +1261,9 @@ TYPE: pair of quick blades COLOUR: ETC_ENCHANT TILE: urand_gyre TILE_EQ: gyre -PLUS: 5 -BRAND: SPWPN_VORPAL -DEX: -3 +PLUS: 7 +BRAND: SPWPN_PROTECTION +FB_BRAND: SPWPN_VORPAL ENUM: ETHERIC_CAGE NAME: Maxwell's etheric cage @@ -1320,6 +1372,15 @@ TILE: urand_rift TILE_EQ: rift BRAND: SPWPN_DISTORTION +ENUM: BATTLE +NAME: staff of Battle +OBJ: OBJ_WEAPONS/WPN_STAFF +PLUS: +0 +COLOUR: ETC_MAGIC +TILE: urand_staff_of_battle +TILE_EQ: battle_staff +VALUE: 800 + # This entry must always be last. ENUM: DUMMY2 NAME: DUMMY UNRANDART 2 diff --git a/crawl-ref/source/art-func.h b/crawl-ref/source/art-func.h index a8a762ee7b86..5b062d0fe27d 100644 --- a/crawl-ref/source/art-func.h +++ b/crawl-ref/source/art-func.h @@ -165,7 +165,7 @@ static void _CURSES_world_reacts(item_def *item) { // don't spam messages for ash worshippers if (one_chance_in(30) && !have_passive(passive_t::want_curses)) - curse_an_item(true); + curse_an_item(); } static void _CURSES_melee_effects(item_def* weapon, actor* attacker, @@ -371,20 +371,20 @@ static void _SINGING_SWORD_melee_effects(item_def* weapon, actor* attacker, actor* defender, bool mondied, int dam) { - int tension = get_tension(GOD_NO_GOD); int tier; if (attacker->is_player()) - tier = max(1, min(4, 1 + tension / 20)); + tier = max(1, min(4, 1 + get_tension(GOD_NO_GOD) / 20)); // Don't base the sword on player state when the player isn't wielding it. else tier = 1; - dprf(DIAG_COMBAT, "Singing sword tension: %d, tier: %d", tension, tier); - if (silenced(attacker->pos())) tier = 0; + dprf(DIAG_COMBAT, "Singing sword tension: %d, tier: %d", + attacker->is_player() ? get_tension(GOD_NO_GOD) : -1, tier); + // Not as spammy at low tension. Max chance reached at tier 3, allowing // tier 0 to have a high chance so that the sword is likely to express its // unhappiness with being silenced. @@ -398,7 +398,7 @@ static void _SINGING_SWORD_melee_effects(item_def* weapon, actor* attacker, const int loudness[] = {0, 0, 20, 30, 40}; - item_noise(*weapon, msg, loudness[tier]); + item_noise(*weapon, *attacker, msg, loudness[tier]); if (tier < 1) return; // Can't cast when silenced. @@ -522,7 +522,8 @@ static bool _WUCAD_MU_evoke(item_def *item, bool* did_work, bool* unevokable) static void _VAMPIRES_TOOTH_equip(item_def *item, bool *show_msgs, bool unmeld) { - if (you.undead_state() == US_ALIVE && !you_foodless()) + if (you.undead_state() == US_ALIVE + && (you.species == SP_VAMPIRE || !you_foodless())) { _equip_mpr(show_msgs, "You feel a strange hunger, and smell blood in the air..."); @@ -558,10 +559,7 @@ static void _ZONGULDROK_melee_effects(item_def* weapon, actor* attacker, actor* defender, bool mondied, int dam) { if (attacker->is_player()) - { did_god_conduct(DID_EVIL, 3); - did_god_conduct(DID_CORPSE_VIOLATION, 3); - } } /////////////////////////////////////////////////// @@ -1066,6 +1064,8 @@ static void _ORDER_melee_effects(item_def* item, actor* attacker, mpr(msg); defender->hurt(attacker, silver_dam); } + else if (dam > 0) + defender->hurt(attacker, 1 + random2(dam) / 3); } } @@ -1101,6 +1101,7 @@ static void _FIRESTARTER_melee_effects(item_def* weapon, actor* attacker, /////////////////////////////////////////////////// +#if TAG_MAJOR_VERSION == 34 static void _CHILLY_DEATH_equip(item_def *item, bool *show_msgs, bool unmeld) { _equip_mpr(show_msgs, "The dagger glows with an icy blue light!"); @@ -1134,9 +1135,11 @@ static void _CHILLY_DEATH_melee_effects(item_def* weapon, actor* attacker, } } } +#endif /////////////////////////////////////////////////// +#if TAG_MAJOR_VERSION == 34 static void _FLAMING_DEATH_equip(item_def *item, bool *show_msgs, bool unmeld) { _equip_mpr(show_msgs, "The scimitar bursts into red hot flame!"); @@ -1163,6 +1166,7 @@ static void _FLAMING_DEATH_melee_effects(item_def* weapon, actor* attacker, } } } +#endif /////////////////////////////////////////////////// @@ -1281,6 +1285,7 @@ static void _ETHERIC_CAGE_world_reacts(item_def *item) /////////////////////////////////////////////////// +#if TAG_MAJOR_VERSION == 34 static void _ETERNAL_TORMENT_equip(item_def *item, bool *show_msgs, bool unmeld) { calc_hp(); @@ -1297,6 +1302,7 @@ static void _ETERNAL_TORMENT_unequip(item_def *item, bool *show_msgs) { calc_hp(); } +#endif /////////////////////////////////////////////////// @@ -1342,8 +1348,11 @@ static void _FROSTBITE_melee_effects(item_def* weapon, actor* attacker, static void _LEECH_equip(item_def *item, bool *show_msgs, bool unmeld) { - if (you.undead_state() == US_ALIVE && !you_foodless()) + if (you.undead_state() == US_ALIVE + && (you.species == SP_VAMPIRE || !you_foodless())) + { _equip_mpr(show_msgs, "You feel a powerful hunger."); + } else if (you.species != SP_VAMPIRE) _equip_mpr(show_msgs, "You feel very empty."); // else let player-equip.cc handle message @@ -1420,3 +1429,22 @@ static void _ZHOR_world_reacts(item_def *item) cast_englaciation(30, false); } } + +//////////////////////////////////////////////////// + +// XXX: Staff of Battle giving a boost to conjuration spells is hardcoded in +// player_spec_conj(). + +static void _BATTLE_unequip(item_def *item, bool *show_msgs) +{ + end_battlesphere(find_battlesphere(&you), false); +} + +static void _BATTLE_world_reacts(item_def *item) +{ + if (!find_battlesphere(&you) && there_are_monsters_nearby(true, true, false)) + { + your_spells(SPELL_BATTLESPHERE, 0, false); + did_god_conduct(DID_SPELL_CASTING, 1); + } +} diff --git a/crawl-ref/source/artefact-prop-type.h b/crawl-ref/source/artefact-prop-type.h index 775115021a1f..e06f072ca34b 100644 --- a/crawl-ref/source/artefact-prop-type.h +++ b/crawl-ref/source/artefact-prop-type.h @@ -19,6 +19,9 @@ enum artefact_prop_type ARTP_SEE_INVISIBLE, ARTP_INVISIBLE, ARTP_FLY, +#if TAG_MAJOR_VERSION > 34 + ARTP_FOG, +#endif ARTP_BLINK, ARTP_BERSERK, ARTP_NOISE, diff --git a/crawl-ref/source/artefact.cc b/crawl-ref/source/artefact.cc index 2df00a5714f8..08b471dfcfae 100644 --- a/crawl-ref/source/artefact.cc +++ b/crawl-ref/source/artefact.cc @@ -162,12 +162,6 @@ static bool _god_fits_artefact(const god_type which_god, const item_def &item, } break; - case GOD_ASHENZARI: - // Cursed god: no holy wrath (since that brand repels curses). - if (brand == SPWPN_HOLY_WRATH) - return false; - break; - case GOD_DITHMENOS: // No reducing stealth. if (artefact_property(item, ARTP_STEALTH) < 0) @@ -380,6 +374,7 @@ static map> jewellery_artps = { { RING_POISON_RESISTANCE, { { ARTP_POISON, 1 } } }, { RING_LIFE_PROTECTION, { { ARTP_NEGATIVE_ENERGY, 1 } } }, { RING_PROTECTION_FROM_MAGIC, { { ARTP_MAGIC_RESISTANCE, 1 } } }, + { RING_RESIST_CORROSION, { { ARTP_RCORR, 1 } } }, { RING_FIRE, { { ARTP_FIRE, 1 }, { ARTP_COLD, -1 } } }, { RING_ICE, { { ARTP_COLD, 1 }, { ARTP_FIRE, -1 } } }, @@ -409,7 +404,7 @@ static void _populate_jewel_intrinsic_artps(const item_def &item, return; const bool id_props = item_ident(item, ISFLAG_KNOW_PROPERTIES) - || item_ident(item, ISFLAG_KNOW_TYPE); + || item_ident(item, ISFLAG_KNOW_TYPE); for (const auto &fake_artp : *props) { @@ -465,6 +460,12 @@ static void _add_randart_weapon_brand(const item_def &item, { const int item_type = item.sub_type; + if (!is_weapon_brand_ok(item_type, item_props[ARTP_BRAND], true)) + item_props[ARTP_BRAND] = SPWPN_NORMAL; + + if (item_props[ARTP_BRAND] != SPWPN_NORMAL) + return; + if (is_range_weapon(item)) { item_props[ARTP_BRAND] = random_choose_weighted( @@ -660,6 +661,9 @@ static const artefact_prop_data artp_data[] = []() { return 1; }, nullptr, 0, 0 }, { "+Fly", ARTP_VAL_BOOL, 15, // ARTP_FLY, []() { return 1; }, nullptr, 0, 0 }, +#if TAG_MAJOR_VERSION > 34 + { "+Fog", ARTP_VAL_BOOL, 0, nullptr, nullptr, 0, 0 }, // ARTP_FOG, +#endif { "+Blink", ARTP_VAL_BOOL, 15, // ARTP_BLINK, []() { return 1; }, nullptr, 0, 0 }, { "+Rage", ARTP_VAL_BOOL, 15, // ARTP_BERSERK, @@ -1436,7 +1440,7 @@ static bool _randart_is_redundant(const item_def &item, break; case RING_SLAYING: - provides = ARTP_SLAYING; + provides = ARTP_SLAYING; break; case RING_SEE_INVISIBLE: @@ -1479,6 +1483,10 @@ static bool _randart_is_redundant(const item_def &item, provides = ARTP_MAGIC_RESISTANCE; break; + case RING_RESIST_CORROSION: + provides = ARTP_RCORR; + break; + case AMU_RAGE: provides = ARTP_BERSERK; break; @@ -1514,8 +1522,7 @@ static bool _randart_is_conflicting(const item_def &item, { if (item.base_type == OBJ_WEAPONS && get_weapon_brand(item) == SPWPN_HOLY_WRATH - && (is_demonic(item) - || proprt[ARTP_CURSE])) + && is_demonic(item)) { return true; } @@ -1541,8 +1548,14 @@ static bool _randart_is_conflicting(const item_def &item, conflicts = ARTP_PREVENT_SPELLCASTING; break; + case RING_RESIST_CORROSION: + conflicts = ARTP_CORRODE; + break; + case RING_TELEPORTATION: +#if TAG_MAJOR_VERSION == 34 case RING_TELEPORT_CONTROL: +#endif conflicts = ARTP_PREVENT_TELEPORTATION; break; diff --git a/crawl-ref/source/artefact.h b/crawl-ref/source/artefact.h index f8e71fcbc2cb..92f3acf8bb0e 100644 --- a/crawl-ref/source/artefact.h +++ b/crawl-ref/source/artefact.h @@ -28,7 +28,7 @@ enum unrand_flag_type UNRAND_FLAG_EVIL = 0x08, UNRAND_FLAG_UNCLEAN = 0x10, UNRAND_FLAG_CHAOTIC = 0x20, - UNRAND_FLAG_CORPSE_VIOLATING = 0x40, + // = 0x40, // was UNRAND_FLAG_CORPSE_VIOLATING UNRAND_FLAG_NOGEN = 0x80, UNRAND_FLAG_RANDAPP =0x100, UNRAND_FLAG_UNIDED =0x200, @@ -52,6 +52,9 @@ struct unrandart_entry object_class_type base_type; uint8_t sub_type; + object_class_type fallback_base_type; + uint8_t fallback_sub_type; + int fallback_brand; short plus; short plus2; colour_t colour; diff --git a/crawl-ref/source/attack.cc b/crawl-ref/source/attack.cc index 0ba6ed1ab76b..46710314a3c7 100644 --- a/crawl-ref/source/attack.cc +++ b/crawl-ref/source/attack.cc @@ -381,7 +381,8 @@ void attack::init_attack(skill_type unarmed_skill, int attack_number) if (attacker->is_monster()) { mon_attack_def mon_attk = mons_attack_spec(*attacker->as_monster(), - attack_number); + attack_number, + false); attk_type = mon_attk.type; attk_flavour = mon_attk.flavour; @@ -440,7 +441,10 @@ void attack::alert_defender() && !attacker->as_monster()->wont_attack()) { if (defender->is_player()) - interrupt_activity(AI_MONSTER_ATTACKS, attacker->as_monster()); + { + interrupt_activity(activity_interrupt::monster_attacks, + attacker->as_monster()); + } if (you.pet_target == MHITNOT && env.sanctuary_time <= 0) you.pet_target = attacker->mindex(); } @@ -1167,8 +1171,8 @@ int attack::player_apply_misc_modifiers(int damage) int attack::get_weapon_plus() { if (weapon->base_type == OBJ_STAVES - || weapon->sub_type == WPN_BLOWGUN #if TAG_MAJOR_VERSION == 34 + || weapon->sub_type == WPN_BLOWGUN || weapon->base_type == OBJ_RODS #endif ) @@ -1348,7 +1352,7 @@ int attack::apply_defender_ac(int damage, int damage_max) const stab_bypass = random2(div_rand_round(stab_bypass, 100 * stab_bonus)); } int after_ac = defender->apply_ac(damage, damage_max, - AC_NORMAL, stab_bypass); + ac_type::normal, stab_bypass); dprf(DIAG_COMBAT, "AC: att: %s, def: %s, ac: %d, gdr: %d, dam: %d -> %d", attacker->name(DESC_PLAIN, true).c_str(), defender->name(DESC_PLAIN, true).c_str(), diff --git a/crawl-ref/source/attribute-type.h b/crawl-ref/source/attribute-type.h index 3686328386e6..e27116166183 100644 --- a/crawl-ref/source/attribute-type.h +++ b/crawl-ref/source/attribute-type.h @@ -82,17 +82,20 @@ enum attribute_type ATTR_DIVINE_AC, // Divine AC bonus (Qazlal). #endif ATTR_GOZAG_GOLD_USED, // Gold spent for Gozag abilities. +#if TAG_MAJOR_VERSION == 34 ATTR_BONE_ARMOUR, // Current amount of bony armour (from the spell) +#endif ATTR_LAST_FLIGHT_STATUS, // Whether SPARM_FLIGHT should be restored after form change ATTR_GOZAG_FIRST_POTION, // Gozag's free first usage of Potion Petition. ATTR_STAT_LOSS_XP, // Unmodified XP needed for stat recovery. #if TAG_MAJOR_VERSION == 34 ATTR_SURGE_REMOVED, // Was surge power applied to next evocation. ATTR_PAKELLAS_EXTRA_MP, // MP to be collected to get a !magic from P -#endif ATTR_DIVINE_ENERGY, // Divine energy from Sif to cast with no MP. +#endif ATTR_SERPENTS_LASH, // Remaining instant movement actions. ATTR_HEAVENLY_STORM, // Strength of Heavenly Storm slaying. ATTR_WALL_JUMP_READY, // Ready to perform a wall jump. + ATTR_DEATHS_DOOR_HP, // How much HP we should have under Death's Door NUM_ATTRIBUTES }; diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc index c748fe9ec9ac..e67237a24477 100644 --- a/crawl-ref/source/beam.cc +++ b/crawl-ref/source/beam.cc @@ -2555,43 +2555,6 @@ void bolt::affect_ground() if (is_tracer) return; - // Spore explosions might spawn a fungus. The spore explosion - // covers 21 tiles in open space, so the expected number of spores - // produced is the x in x_chance_in_y() in the conditional below. - if (is_explosion && flavour == BEAM_SPORE - && agent() && !agent()->is_summoned()) - { - if (env.grid(pos()) == DNGN_FLOOR) - env.pgrid(pos()) |= FPROP_MOLD; - - if (x_chance_in_y(2, 21) - && mons_class_can_pass(MONS_BALLISTOMYCETE, env.grid(pos())) - && !actor_at(pos())) - { - beh_type beh = attitude_creation_behavior(attitude); - // A friendly spore or hyperactive can exist only with Fedhas - // in which case the inactive ballistos spawned should be - // good_neutral to avoid hidden piety costs of Fedhas abilities - if (beh == BEH_FRIENDLY) - beh = BEH_GOOD_NEUTRAL; - - const god_type god = agent()->deity(); - - if (create_monster(mgen_data(MONS_BALLISTOMYCETE, - beh, - pos(), - MHITNOT, - MG_FORCE_PLACE, - god))) - { - remove_mold(pos()); - if (you.see_cell(pos())) - mpr("A fungus suddenly grows."); - - } - } - } - affect_place_clouds(); } @@ -2622,7 +2585,7 @@ bool bolt::can_affect_wall(const coord_def& p, bool map_knowledge) const return true; } - // Temporary trees (from Summon Forest) can't be burned/distintegrated. + // Temporary trees (from Summon Forest) can't be burned. if (feat_is_tree(wall) && is_temp_terrain(p)) return false; @@ -3761,9 +3724,12 @@ void bolt::affect_player() if (!YOU_KILL(thrower)) { if (agent() && agent()->is_monster()) - interrupt_activity(AI_MONSTER_ATTACKS, agent()->as_monster()); + { + interrupt_activity(activity_interrupt::monster_attacks, + agent()->as_monster()); + } else - interrupt_activity(AI_MONSTER_ATTACKS); + interrupt_activity(activity_interrupt::monster_attacks); } if (flavour == BEAM_MISSILE && item) @@ -4017,11 +3983,11 @@ int bolt::apply_AC(const actor *victim, int hurted) switch (flavour) { case BEAM_DAMNATION: - ac_rule = AC_NONE; break; + ac_rule = ac_type::none; break; case BEAM_ELECTRICITY: - ac_rule = AC_HALF; break; + ac_rule = ac_type::half; break; case BEAM_FRAG: - ac_rule = AC_TRIPLE; break; + ac_rule = ac_type::triple; break; default: ; } @@ -4186,6 +4152,19 @@ void bolt::handle_stop_attack_prompt(monster* mon) beam_cancelled = true; finish_beam(); } + // Handle enslaving monsters when OTR is up: give a prompt for attempting + // to enslave monsters that don't have rPois with Toxic status. + else if (flavour == BEAM_ENSLAVE && you.duration[DUR_TOXIC_RADIANCE] + && mon->res_poison() <= 0) + { + string verb = make_stringf("enslave %s", mon->name(DESC_THE).c_str()); + if (otr_stop_summoning_prompt(verb)) + { + beam_cancelled = true; + finish_beam(); + prompted = true; + } + } if (prompted) { @@ -4420,7 +4399,6 @@ void bolt::monster_post_hit(monster* mon, int dmg) // did no damage. Hostiles will still take umbrage. if (dmg > 0 || !mon->wont_attack() || !YOU_KILL(thrower)) { - bool was_asleep = mon->asleep(); special_missile_type m_brand = SPMSL_FORBID_BRAND; if (item && item->base_type == OBJ_MISSILES) m_brand = get_ammo_brand(*item); @@ -4433,11 +4411,6 @@ void bolt::monster_post_hit(monster* mon, int dmg) if (!mon->alive()) return; } - - - // Don't allow needles of sleeping to awaken monsters. - if (m_brand == SPMSL_SLEEP && was_asleep && !mon->asleep()) - mon->put_to_sleep(agent(), 0); } if (YOU_KILL(thrower) && !mon->wont_attack() && !mons_is_firewood(*mon)) diff --git a/crawl-ref/source/beam.h b/crawl-ref/source/beam.h index 0d6006c20429..5dc1dfe2d967 100644 --- a/crawl-ref/source/beam.h +++ b/crawl-ref/source/beam.h @@ -105,7 +105,7 @@ struct bolt // FROST etc so that we can change mulch rate // Do we draw animations? bool animate = bool(Options.use_animations & UA_BEAM); - ac_type ac_rule = AC_NORMAL; // How defender's AC affects damage. + ac_type ac_rule = ac_type::normal; // How defender's AC affects damage. #ifdef DEBUG_DIAGNOSTICS bool quiet_debug = false; // Disable any debug spam. #endif @@ -245,6 +245,7 @@ struct bolt private: void affect_wall(); void digging_wall_effect(); + void growth_wall_effect(); void burn_wall_effect(); void affect_ground(); void affect_place_clouds(); diff --git a/crawl-ref/source/branch-data.h b/crawl-ref/source/branch-data.h index bd4835fa8994..873a7001b7b4 100644 --- a/crawl-ref/source/branch-data.h +++ b/crawl-ref/source/branch-data.h @@ -13,353 +13,353 @@ const Branch branches[NUM_BRANCHES] = // travel shortcut, runes, ambient noise level { BRANCH_DUNGEON, NUM_BRANCHES, 0, 0, 15, 0, - BFLAG_NONE, + brflag::none, NUM_FEATURES, DNGN_EXIT_DUNGEON, NUM_FEATURES, "Dungeon", "the Dungeon", "D", nullptr, LIGHTGREY, BROWN, - 'D', {}, BRANCH_NOISE_NORMAL }, + 'D', {}, branch_noise::normal }, { BRANCH_TEMPLE, BRANCH_DUNGEON, 4, 7, 1, 5, - BFLAG_NO_ITEMS, + brflag::no_items, DNGN_ENTER_TEMPLE, DNGN_EXIT_TEMPLE, NUM_FEATURES, "Temple", "the Ecumenical Temple", "Temple", nullptr, LIGHTGREY, BROWN, - 'T', {}, BRANCH_NOISE_NORMAL }, + 'T', {}, branch_noise::normal }, { BRANCH_ORC, BRANCH_DUNGEON, 9, 12, 2, 10, - BFLAG_SPOTTY, + brflag::spotty, DNGN_ENTER_ORC, DNGN_EXIT_ORC, NUM_FEATURES, "Orcish Mines", "the Orcish Mines", "Orc", nullptr, BROWN, BROWN, - 'O', {}, BRANCH_NOISE_NORMAL }, + 'O', {}, branch_noise::normal }, { BRANCH_ELF, BRANCH_ORC, 2, 2, 3, 15, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_ELF, DNGN_EXIT_ELF, NUM_FEATURES, "Elven Halls", "the Elven Halls", "Elf", nullptr, WHITE, ETC_ELVEN_BRICK, - 'E', {}, BRANCH_NOISE_NORMAL }, + 'E', {}, branch_noise::normal }, #if TAG_MAJOR_VERSION == 34 { BRANCH_DWARF, BRANCH_ELF, -1, -1, 0, 17, - BFLAG_NONE, + brflag::none, DNGN_ENTER_DWARF, DNGN_EXIT_DWARF, NUM_FEATURES, "Dwarven Hall", "the Dwarven Hall", "Dwarf", nullptr, BROWN, BROWN, - 'K', {}, BRANCH_NOISE_NORMAL }, + 'K', {}, branch_noise::normal }, #endif { BRANCH_LAIR, BRANCH_DUNGEON, 8, 11, 6, 10, - BFLAG_NONE, + brflag::none, DNGN_ENTER_LAIR, DNGN_EXIT_LAIR, NUM_FEATURES, "Lair", "the Lair of Beasts", "Lair", nullptr, GREEN, BROWN, - 'L', {}, BRANCH_NOISE_NORMAL }, + 'L', {}, branch_noise::normal }, { BRANCH_SWAMP, BRANCH_LAIR, 2, 4, 4, 15, - BFLAG_DANGEROUS_END | BFLAG_SPOTTY, + brflag::dangerous_end | brflag::spotty, DNGN_ENTER_SWAMP, DNGN_EXIT_SWAMP, NUM_FEATURES, "Swamp", "the Swamp", "Swamp", nullptr, BROWN, BROWN, - 'S', { RUNE_SWAMP }, BRANCH_NOISE_NORMAL }, + 'S', { RUNE_SWAMP }, branch_noise::normal }, { BRANCH_SHOALS, BRANCH_LAIR, 2, 4, 4, 15, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_SHOALS, DNGN_EXIT_SHOALS, NUM_FEATURES, "Shoals", "the Shoals", "Shoals", nullptr, BROWN, BROWN, - 'A', { RUNE_SHOALS }, BRANCH_NOISE_LOUD }, + 'A', { RUNE_SHOALS }, branch_noise::loud }, { BRANCH_SNAKE, BRANCH_LAIR, 2, 4, 4, 15, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_SNAKE, DNGN_EXIT_SNAKE, NUM_FEATURES, "Snake Pit", "the Snake Pit", "Snake", nullptr, LIGHTGREEN, YELLOW, - 'P', { RUNE_SNAKE }, BRANCH_NOISE_NORMAL }, + 'P', { RUNE_SNAKE }, branch_noise::normal }, { BRANCH_SPIDER, BRANCH_LAIR, 2, 4, 4, 15, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_SPIDER, DNGN_EXIT_SPIDER, NUM_FEATURES, "Spider Nest", "the Spider Nest", "Spider", nullptr, BROWN, YELLOW, - 'N', { RUNE_SPIDER }, BRANCH_NOISE_NORMAL }, + 'N', { RUNE_SPIDER }, branch_noise::normal }, { BRANCH_SLIME, BRANCH_LAIR, 5, 6, 5, 17, - BFLAG_NO_ITEMS | BFLAG_DANGEROUS_END | BFLAG_SPOTTY, + brflag::no_items | brflag::dangerous_end | brflag::spotty, DNGN_ENTER_SLIME, DNGN_EXIT_SLIME, NUM_FEATURES, "Slime Pits", "the Pits of Slime", "Slime", nullptr, GREEN, BROWN, - 'M', { RUNE_SLIME }, BRANCH_NOISE_QUIET }, + 'M', { RUNE_SLIME }, branch_noise::quiet }, { BRANCH_VAULTS, BRANCH_DUNGEON, 13, 14, 5, 19, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_VAULTS, DNGN_EXIT_VAULTS, NUM_FEATURES, "Vaults", "the Vaults", "Vaults", nullptr, LIGHTGREY, BROWN, - 'V', { RUNE_VAULTS }, BRANCH_NOISE_NORMAL }, + 'V', { RUNE_VAULTS }, branch_noise::normal }, #if TAG_MAJOR_VERSION == 34 { BRANCH_BLADE, BRANCH_VAULTS, 3, 4, 1, 21, - BFLAG_NO_ITEMS, + brflag::no_items, DNGN_ENTER_BLADE, DNGN_EXIT_BLADE, NUM_FEATURES, "Hall of Blades", "the Hall of Blades", "Blade", nullptr, LIGHTGREY, BROWN, - 'B', {}, BRANCH_NOISE_QUIET }, + 'B', {}, branch_noise::quiet }, #endif { BRANCH_CRYPT, BRANCH_VAULTS, 2, 3, 3, 19, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_CRYPT, DNGN_EXIT_CRYPT, NUM_FEATURES, "Crypt", "the Crypt", "Crypt", nullptr, LIGHTGREY, BROWN, - 'C', {}, BRANCH_NOISE_QUIET }, + 'C', {}, branch_noise::quiet }, { BRANCH_TOMB, BRANCH_CRYPT, 3, 3, 3, 21, - BFLAG_ISLANDED | BFLAG_DANGEROUS_END | BFLAG_NO_SHAFTS, + brflag::islanded | brflag::dangerous_end | brflag::no_shafts, DNGN_ENTER_TOMB, DNGN_EXIT_TOMB, NUM_FEATURES, "Tomb", "the Tomb of the Ancients", "Tomb", nullptr, BROWN, BROWN, - 'W', { RUNE_TOMB }, BRANCH_NOISE_QUIET }, + 'W', { RUNE_TOMB }, branch_noise::quiet }, #if TAG_MAJOR_VERSION > 34 { BRANCH_DEPTHS, BRANCH_DUNGEON, 15, 15, 5, 22, - BFLAG_NONE, + brflag::none, DNGN_ENTER_DEPTHS, DNGN_EXIT_DEPTHS, NUM_FEATURES, "Depths", "the Depths", "Depths", nullptr, LIGHTGREY, BROWN, - 'U', {}, BRANCH_NOISE_NORMAL }, + 'U', {}, branch_noise::normal }, #endif { BRANCH_VESTIBULE, NUM_BRANCHES, 27, 27, 1, 27, - BFLAG_NO_ITEMS, + brflag::no_items, DNGN_ENTER_HELL, DNGN_EXIT_HELL, NUM_FEATURES, "Hell", "the Vestibule of Hell", "Hell", "Welcome to Hell!\nPlease enjoy your stay.", LIGHTGREY, LIGHTRED, - 'H', {}, BRANCH_NOISE_NORMAL }, + 'H', {}, branch_noise::normal }, { BRANCH_DIS, BRANCH_VESTIBULE, 1, 1, 7, 28, - BFLAG_NO_ITEMS | BFLAG_DANGEROUS_END, + brflag::no_items | brflag::dangerous_end, DNGN_ENTER_DIS, DNGN_ENTER_HELL, DNGN_ENTER_HELL, "Dis", "the Iron City of Dis", "Dis", nullptr, CYAN, BROWN, - 'I', { RUNE_DIS }, BRANCH_NOISE_NORMAL }, + 'I', { RUNE_DIS }, branch_noise::normal }, { BRANCH_GEHENNA, BRANCH_VESTIBULE, 1, 1, 7, 28, - BFLAG_NO_ITEMS | BFLAG_DANGEROUS_END, + brflag::no_items | brflag::dangerous_end, DNGN_ENTER_GEHENNA, DNGN_ENTER_HELL, DNGN_ENTER_HELL, "Gehenna", "Gehenna", "Geh", nullptr, BROWN, RED, - 'G', { RUNE_GEHENNA }, BRANCH_NOISE_NORMAL }, + 'G', { RUNE_GEHENNA }, branch_noise::normal }, { BRANCH_COCYTUS, BRANCH_VESTIBULE, 1, 1, 7, 28, - BFLAG_NO_ITEMS | BFLAG_DANGEROUS_END, + brflag::no_items | brflag::dangerous_end, DNGN_ENTER_COCYTUS, DNGN_ENTER_HELL, DNGN_ENTER_HELL, "Cocytus", "Cocytus", "Coc", nullptr, LIGHTBLUE, LIGHTCYAN, - 'X', { RUNE_COCYTUS }, BRANCH_NOISE_NORMAL }, + 'X', { RUNE_COCYTUS }, branch_noise::normal }, { BRANCH_TARTARUS, BRANCH_VESTIBULE, 1, 1, 7, 28, - BFLAG_NO_ITEMS | BFLAG_DANGEROUS_END, + brflag::no_items | brflag::dangerous_end, DNGN_ENTER_TARTARUS, DNGN_ENTER_HELL, DNGN_ENTER_HELL, "Tartarus", "Tartarus", "Tar", nullptr, MAGENTA, MAGENTA, - 'Y', { RUNE_TARTARUS }, BRANCH_NOISE_NORMAL }, + 'Y', { RUNE_TARTARUS }, branch_noise::normal }, { BRANCH_ZOT, BRANCH_DEPTHS, 5, 5, 5, 27, - BFLAG_DANGEROUS_END, + brflag::dangerous_end, DNGN_ENTER_ZOT, DNGN_EXIT_ZOT, NUM_FEATURES, "Zot", "the Realm of Zot", "Zot", "Welcome to the Realm of Zot!\n" "You feel the power of the Orb interfering with translocations here.", BLACK, BLACK, // set per-map - 'Z', {}, BRANCH_NOISE_NORMAL }, + 'Z', {}, branch_noise::normal }, #if TAG_MAJOR_VERSION == 34 { BRANCH_FOREST, BRANCH_VAULTS, 2, 3, 5, 19, - BFLAG_SPOTTY, + brflag::spotty, DNGN_ENTER_FOREST, DNGN_EXIT_FOREST, NUM_FEATURES, "Forest", "the Enchanted Forest", "Forest", nullptr, BROWN, BROWN, - 'F', {}, BRANCH_NOISE_NORMAL }, + 'F', {}, branch_noise::normal }, #endif { BRANCH_ABYSS, NUM_BRANCHES, -1, -1, 5, 24, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_MAP, + brflag::no_x_level_travel | brflag::no_map, DNGN_ENTER_ABYSS, DNGN_EXIT_ABYSS, DNGN_FLOOR, // can't get trapped in abyss "Abyss", "the Abyss", "Abyss", nullptr, BLACK, BLACK, // set specially - 'J', { RUNE_ABYSSAL }, BRANCH_NOISE_NORMAL }, + 'J', { RUNE_ABYSSAL }, branch_noise::normal }, { BRANCH_PANDEMONIUM, NUM_BRANCHES, -1, -1, 1, 24, - BFLAG_NO_XLEV_TRAVEL, + brflag::no_x_level_travel, DNGN_ENTER_PANDEMONIUM, DNGN_EXIT_PANDEMONIUM, DNGN_TRANSIT_PANDEMONIUM, "Pandemonium", "Pandemonium", "Pan", "You enter the halls of Pandemonium!\n" "To return, you must find a gate leading back.", BLACK, BLACK, // set specially 'R', { RUNE_DEMONIC, RUNE_MNOLEG, RUNE_LOM_LOBON, RUNE_CEREBOV, - RUNE_GLOORX_VLOQ }, BRANCH_NOISE_NORMAL }, + RUNE_GLOORX_VLOQ }, branch_noise::normal }, { BRANCH_ZIGGURAT, BRANCH_DEPTHS, 1, 5, 27, 27, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_ZIGGURAT, DNGN_EXIT_ZIGGURAT, DNGN_FLOOR, "Ziggurat", "a ziggurat", "Zig", "You land on top of a ziggurat so tall you cannot make out the ground.", BLACK, BLACK, - 'Q', {}, BRANCH_NOISE_NORMAL }, + 'Q', {}, branch_noise::normal }, #if TAG_MAJOR_VERSION == 34 { BRANCH_LABYRINTH, NUM_BRANCHES, -1, -1, 1, 15, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_LABYRINTH, DNGN_EXIT_LABYRINTH, DNGN_EXIT_THROUGH_ABYSS, "Labyrinth", "a Labyrinth", "Lab", "You enter a labyrinth!", BLACK, BLACK, - '0', {}, BRANCH_NOISE_NORMAL }, + '0', {}, branch_noise::normal }, #endif { BRANCH_BAZAAR, NUM_BRANCHES, -1, -1, 1, 18, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_BAZAAR, DNGN_EXIT_BAZAAR, NUM_FEATURES, "Bazaar", "a bazaar", "Bazaar", "You enter an inter-dimensional bazaar!", BLUE, YELLOW, - '1', {}, BRANCH_NOISE_NORMAL }, + '1', {}, branch_noise::normal }, { BRANCH_TROVE, NUM_BRANCHES, -1, -1, 1, 18, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_TROVE, DNGN_EXIT_TROVE, NUM_FEATURES, "Trove", "a treasure trove", "Trove", "You enter a treasure trove!", DARKGREY, BLUE, - '2', {}, BRANCH_NOISE_NORMAL }, + '2', {}, branch_noise::normal }, { BRANCH_SEWER, NUM_BRANCHES, -1, -1, 1, 4, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_SEWER, DNGN_EXIT_SEWER, NUM_FEATURES, "Sewer", "a sewer", "Sewer", "You enter a sewer!", LIGHTGREY, BLUE, - '3', {}, BRANCH_NOISE_NORMAL }, + '3', {}, branch_noise::normal }, { BRANCH_OSSUARY, NUM_BRANCHES, -1, -1, 1, 6, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_OSSUARY, DNGN_EXIT_OSSUARY, NUM_FEATURES, "Ossuary", "an ossuary", "Ossuary", "You enter an ossuary!", WHITE, YELLOW, - '4', {}, BRANCH_NOISE_NORMAL }, + '4', {}, branch_noise::normal }, { BRANCH_BAILEY, NUM_BRANCHES, -1, -1, 1, 11, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_BAILEY, DNGN_EXIT_BAILEY, NUM_FEATURES, "Bailey", "a bailey", "Bailey", "You enter a bailey!", WHITE, LIGHTRED, - '5', {}, BRANCH_NOISE_NORMAL }, + '5', {}, branch_noise::normal }, #if TAG_MAJOR_VERSION > 34 { BRANCH_GAUNTLET, NUM_BRANCHES, -1, -1, 1, 15, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_GAUNTLET, DNGN_EXIT_GAUNTLET, DNGN_EXIT_THROUGH_ABYSS, "Gauntlet", "a Gauntlet", "Gauntlet", "You enter a gauntlet!", BLACK, BLACK, - '6', {}, BRANCH_NOISE_NORMAL }, + '6', {}, branch_noise::normal }, #endif { BRANCH_ICE_CAVE, NUM_BRANCHES, -1, -1, 1, 15, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_ICE_CAVE, DNGN_EXIT_ICE_CAVE, NUM_FEATURES, "Ice Cave", "an ice cave", "IceCv", "You enter an ice cave!", BLUE, WHITE, #if TAG_MAJOR_VERSION == 34 - '6', {}, BRANCH_NOISE_NORMAL }, + '6', {}, branch_noise::normal }, #endif #if TAG_MAJOR_VERSION > 34 - '7', {}, BRANCH_NOISE_NORMAL }, + '7', {}, branch_noise::normal }, #endif { BRANCH_VOLCANO, NUM_BRANCHES, -1, -1, 1, 14, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_VOLCANO, DNGN_EXIT_VOLCANO, NUM_FEATURES, "Volcano", "a volcano", "Volcano", "You enter a volcano!", RED, RED, #if TAG_MAJOR_VERSION == 34 - '7', {}, BRANCH_NOISE_NORMAL }, + '7', {}, branch_noise::normal }, #endif #if TAG_MAJOR_VERSION > 34 - '8', {}, BRANCH_NOISE_NORMAL }, + '8', {}, branch_noise::normal }, #endif { BRANCH_WIZLAB, NUM_BRANCHES, -1, -1, 1, 24, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_WIZLAB, DNGN_EXIT_WIZLAB, NUM_FEATURES, "Wizlab", "a wizard's laboratory", "WizLab", "You enter a wizard's laboratory!", LIGHTGREY, BROWN, // set per-map #if TAG_MAJOR_VERSION == 34 - '8', {}, BRANCH_NOISE_NORMAL }, + '8', {}, branch_noise::normal }, #endif #if TAG_MAJOR_VERSION > 34 - '9', {}, BRANCH_NOISE_NORMAL }, + '9', {}, branch_noise::normal }, #endif #if TAG_MAJOR_VERSION == 34 { BRANCH_DEPTHS, BRANCH_DUNGEON, 15, 15, 5, 22, - BFLAG_NONE, + brflag::none, DNGN_ENTER_DEPTHS, DNGN_EXIT_DEPTHS, NUM_FEATURES, "Depths", "the Depths", "Depths", nullptr, LIGHTGREY, BROWN, - 'U', {}, BRANCH_NOISE_NORMAL }, + 'U', {}, branch_noise::normal }, #endif { BRANCH_DESOLATION, NUM_BRANCHES, -1, -1, 1, 20, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_DESOLATION, DNGN_EXIT_DESOLATION, NUM_FEATURES, // TODO "Desolation", "the Desolation of Salt", "Desolation", "You enter a great desolation of salt!", LIGHTGREY, BROWN, // TODO #if TAG_MAJOR_VERSION == 34 - '9', {}, BRANCH_NOISE_LOUD }, + '9', {}, branch_noise::loud }, #endif #if TAG_MAJOR_VERSION > 34 - '0', {}, BRANCH_NOISE_LOUD }, + '0', {}, branch_noise::loud }, #endif #if TAG_MAJOR_VERSION == 34 { BRANCH_GAUNTLET, NUM_BRANCHES, -1, -1, 1, 15, - BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS, + brflag::no_x_level_travel | brflag::no_items, DNGN_ENTER_GAUNTLET, DNGN_EXIT_GAUNTLET, DNGN_EXIT_THROUGH_ABYSS, "Gauntlet", "a Gauntlet", "Gauntlet", "You enter a gauntlet!", BLACK, BLACK, - '!', {}, BRANCH_NOISE_NORMAL }, + '!', {}, branch_noise::normal }, #endif }; diff --git a/crawl-ref/source/branch.cc b/crawl-ref/source/branch.cc index 842f058d2d08..99410ce521f7 100644 --- a/crawl-ref/source/branch.cc +++ b/crawl-ref/source/branch.cc @@ -112,7 +112,7 @@ branch_iterator::branch_iterator(branch_iterator_type type) : const branch_type* branch_iterator::branch_order() const { - if (iter_type == BRANCH_ITER_DANGER) + if (iter_type == branch_iterator_type::danger) return danger_branch_order; return logical_branch_order; } @@ -188,7 +188,7 @@ bool is_random_subbranch(branch_type branch) bool is_connected_branch(const Branch *branch) { - return !(branch->branch_flags & BFLAG_NO_XLEV_TRAVEL); + return !testbits(branch->branch_flags, brflag::no_x_level_travel); } bool is_connected_branch(branch_type branch) @@ -224,11 +224,11 @@ int ambient_noise(branch_type branch) { switch (branches[branch].ambient_noise) { - case BRANCH_NOISE_NORMAL: + case branch_noise::normal: return 0; - case BRANCH_NOISE_QUIET: + case branch_noise::quiet: return -BRANCH_NOISE_AMOUNT; - case BRANCH_NOISE_LOUD: + case branch_noise::loud: return BRANCH_NOISE_AMOUNT; default: die("Invalid noise level!"); diff --git a/crawl-ref/source/branch.h b/crawl-ref/source/branch.h index 5998f25a7983..805a3504dee6 100644 --- a/crawl-ref/source/branch.h +++ b/crawl-ref/source/branch.h @@ -12,24 +12,25 @@ #define BRANCH_NOISE_AMOUNT 6 -enum branch_flag_type +enum class brflag { - BFLAG_NONE = 0, - - BFLAG_NO_MAP = (1 << 0), // Can't be magic mapped or remembered. - BFLAG_ISLANDED = (1 << 1), // May have isolated zones with no stairs. - BFLAG_NO_XLEV_TRAVEL = (1 << 2), // Can't cross-level travel to or from it. - BFLAG_NO_ITEMS = (1 << 3), // Branch gets no random items. - BFLAG_DANGEROUS_END = (1 << 4), // bottom level is more dangerous than normal - BFLAG_SPOTTY = (1 << 5), // Connect vaults with more open paths, not hallways. - BFLAG_NO_SHAFTS = (1 << 6), // Don't generate random shafts. + none = 0, + + no_map = (1 << 0), // Can't be magic mapped or remembered. + islanded = (1 << 1), // May have isolated zones with no stairs. + no_x_level_travel = (1 << 2), // Can't cross-level travel to or from it. + no_items = (1 << 3), // Branch gets no random items. + dangerous_end = (1 << 4), // bottom level is more dangerous than normal + spotty = (1 << 5), // Connect vaults with more open paths, not hallways. + no_shafts = (1 << 6), // Don't generate random shafts. }; +DEF_BITFIELD(branch_flags_t, brflag); -enum branch_noise_level +enum class branch_noise { - BRANCH_NOISE_NORMAL, - BRANCH_NOISE_QUIET, - BRANCH_NOISE_LOUD, + normal, + quiet, + loud, }; struct Branch @@ -43,7 +44,7 @@ struct Branch int numlevels; // depth of the branch int absdepth; // base item generation/etc depth - uint32_t branch_flags; + branch_flags_t branch_flags; dungeon_feature_type entry_stairs; dungeon_feature_type exit_stairs; @@ -56,19 +57,19 @@ struct Branch colour_t rock_colour; int travel_shortcut; // Which key to press for travel. vector runes; // Contained rune(s) (if any). - branch_noise_level ambient_noise; // affects noise loudness + branch_noise ambient_noise; // affects noise loudness }; -enum branch_iterator_type +enum class branch_iterator_type { - BRANCH_ITER_LOGICAL, - BRANCH_ITER_DANGER, + logical, + danger, }; class branch_iterator { public: - branch_iterator(branch_iterator_type type = BRANCH_ITER_LOGICAL); + branch_iterator(branch_iterator_type type = branch_iterator_type::logical); operator bool() const; const Branch* operator*() const; diff --git a/crawl-ref/source/butcher.cc b/crawl-ref/source/butcher.cc index 84ff776447c8..0f31f9540f1e 100644 --- a/crawl-ref/source/butcher.cc +++ b/crawl-ref/source/butcher.cc @@ -43,28 +43,20 @@ */ static bool _start_butchering(item_def& corpse) { - const bool bottle_blood = - you.species == SP_VAMPIRE - && can_bottle_blood_from_corpse(corpse.mon_type); - if (is_forbidden_food(corpse)) { - mprf("It would be a sin to %sbutcher this!", - bottle_blood ? "bottle or " : ""); + mprf("It would be a sin to butcher this!"); return false; } // Yes, 0 is correct (no "continue butchering" stage). - if (bottle_blood) - start_delay(0, corpse); - else - start_delay(0, corpse); + start_delay(0, corpse); you.turn_is_over = true; return true; } -void finish_butchering(item_def& corpse, bool bottling) +void finish_butchering(item_def& corpse) { ASSERT(corpse.base_type == OBJ_CORPSES); ASSERT(corpse.sub_type == CORPSE_BODY); @@ -72,22 +64,10 @@ void finish_butchering(item_def& corpse, bool bottling) const bool was_intelligent = corpse_intelligence(corpse) >= I_HUMAN; const bool was_same_genus = is_player_same_genus(corpse.mon_type); - if (bottling) - { - mpr("You bottle the corpse's blood."); - - if (mons_skeleton(corpse.mon_type) && one_chance_in(3)) - turn_corpse_into_skeleton_and_blood_potions(corpse); - else - turn_corpse_into_blood_potions(corpse); - } - else - { - mprf("You butcher %s.", - corpse.name(DESC_THE).c_str()); + mprf("You butcher %s.", + corpse.name(DESC_THE).c_str()); - butcher_corpse(corpse); - } + butcher_corpse(corpse); if (was_same_genus) did_god_conduct(DID_CANNIBALISM, 2); @@ -126,9 +106,6 @@ void butchery(item_def* specific_corpse) return; } - const bool bottle_blood = you.species == SP_VAMPIRE; - const char * butcher_verb = bottle_blood ? "bottle" : "butcher"; - vector all_corpses; if (specific_corpse) @@ -140,7 +117,7 @@ void butchery(item_def* specific_corpse) if (all_corpses.empty()) { - mprf("There isn't anything to %s here.", butcher_verb); + mprf("There isn't anything to butcher here."); return; } if (you_foodless(false)) @@ -167,13 +144,10 @@ void butchery(item_def* specific_corpse) if (all_corpses.size() == 1) { mprf("%s %s.", all_corpses[0]->name(DESC_THE).c_str(), - bottle_blood ? "doesn't have any blood" : "isn't edible"); + "isn't edible"); } else - { - mprf("There isn't anything %s to %s here.", - bottle_blood ? "with blood" : "edible", butcher_verb); - } + mprf("There isn't anything edible to butcher here."); return; } @@ -188,14 +162,13 @@ void butchery(item_def* specific_corpse) { if (edible_corpses.size() == 1) { - mprf("It would be a sin to %s %s!", butcher_verb, + mprf("It would be a sin to butcher %s!", edible_corpses[0]->name(DESC_THE).c_str()); } else { - mprf("It would be a sin to %s any of the %scorpses here!", - butcher_verb, - (seen_inedible ? (bottle_blood ? "bloody " : "edible ") : "")); + mprf("It would be a sin to butcher any of the %scorpses here!", + seen_inedible ? "edible " : ""); } return; } @@ -206,8 +179,8 @@ void butchery(item_def* specific_corpse) // Butcher pre-chosen corpse, if found, or if there is only one corpse. if (specific_corpse || corpse_qualities.size() == 1 - && Options.confirm_butcher != CONFIRM_ALWAYS - || Options.confirm_butcher == CONFIRM_NEVER) + && Options.confirm_butcher != confirm_butcher_type::always + || Options.confirm_butcher == confirm_butcher_type::never) { //XXX: this assumes that we're not being called from a delay ourselves. if (_start_butchering(*corpse_qualities[0].first)) @@ -224,9 +197,8 @@ void butchery(item_def* specific_corpse) meat.push_back(entry.first); vector selected = - select_items(meat, bottle_blood ? "Choose a corpse to bottle" - : "Choose a corpse to butcher", - false, MT_ANY, _butcher_menu_title); + select_items(meat, "Choose a corpse to butcher", + false, menu_type::any, _butcher_menu_title); redraw_screen(); for (SelItem sel : selected) if (_start_butchering(const_cast(*sel.item))) @@ -257,11 +229,8 @@ void butchery(item_def* specific_corpse) // Shall we butcher this corpse? do { - const bool can_bottle = - can_bottle_blood_from_corpse(it->mon_type); mprf(MSGCH_PROMPT, - "%s %s? [(y)es/(n)o/(a)ll/(q)uit/?]", - can_bottle ? "Bottle" : "Butcher", + "Butcher %s? [(y)es/(n)o/(a)ll/(q)uit/?]", corpse_name.c_str()); repeat_prompt = false; @@ -305,7 +274,7 @@ void butchery(item_def* specific_corpse) // No point in displaying this if the player pressed 'a' above. if (!to_eat && !butcher_all && !all_done) - mprf("There isn't anything else to %s here.", butcher_verb); + mprf("There isn't anything else to butcher here."); #endif //XXX: this assumes that we're not being called from a delay ourselves. @@ -365,12 +334,12 @@ void turn_corpse_into_chunks(item_def &item, bool bloodspatter) item.quantity = stepdown_value(item.quantity, 4, 4, 12, 12); // Don't mark it as dropped if we are forcing autopickup of chunks. - if (you.force_autopickup[OBJ_FOOD][FOOD_CHUNK] <= 0 + if (you.force_autopickup[OBJ_FOOD][FOOD_CHUNK] <= AP_FORCE_NONE && is_bad_food(item)) { item.flags |= ISFLAG_DROPPED; } - else if (you.species != SP_VAMPIRE) + else clear_item_pickup_flags(item); // Initialise timer depending on corpse age @@ -413,61 +382,3 @@ void butcher_corpse(item_def &item, bool skeleton, bool chunks) } } } - -bool can_bottle_blood_from_corpse(monster_type mons_class) -{ - return you.species == SP_VAMPIRE && mons_has_blood(mons_class); -} - -int num_blood_potions_from_corpse(monster_type mons_class) -{ - // Max. amount is about one third of the max. amount for chunks. - const int max_chunks = max_corpse_chunks(mons_class); - - // Max. amount is about one third of the max. amount for chunks. - int pot_quantity = max_chunks / 3; - pot_quantity = stepdown_value(pot_quantity, 2, 2, 6, 6); - - if (pot_quantity < 1) - pot_quantity = 1; - - return pot_quantity; -} - -// If autopickup is active, the potions are auto-picked up after creation. -void turn_corpse_into_blood_potions(item_def &item) -{ - ASSERT(item.base_type == OBJ_CORPSES); - - const item_def corpse = item; - const monster_type mons_class = corpse.mon_type; - - ASSERT(can_bottle_blood_from_corpse(mons_class)); - - item.base_type = OBJ_POTIONS; - item.sub_type = POT_BLOOD; - item_colour(item); - clear_item_pickup_flags(item); - item.props.clear(); - - item.quantity = num_blood_potions_from_corpse(mons_class); - - // Initialise timer depending on corpse age - init_perishable_stack(item, - item.freshness * ROT_TIME_FACTOR + FRESHEST_BLOOD); -} - -void turn_corpse_into_skeleton_and_blood_potions(item_def &item) -{ - item_def blood_potions = item; - - if (mons_skeleton(item.mon_type)) - turn_corpse_into_skeleton(item); - - int o = get_mitm_slot(); - if (o != NON_ITEM) - { - turn_corpse_into_blood_potions(blood_potions); - copy_item_to_grid(blood_potions, you.pos()); - } -} diff --git a/crawl-ref/source/butcher.h b/crawl-ref/source/butcher.h index 624173031419..52040f5c22ca 100644 --- a/crawl-ref/source/butcher.h +++ b/crawl-ref/source/butcher.h @@ -8,14 +8,10 @@ #include "maybe-bool.h" void butchery(item_def* specific_corpse = nullptr); -void finish_butchering(item_def& corpse, bool bottling); +void finish_butchering(item_def& corpse); void maybe_drop_monster_hide(const item_def &corpse); bool turn_corpse_into_skeleton(item_def &item); void turn_corpse_into_chunks(item_def &item, bool bloodspatter = true); void butcher_corpse(item_def &item, bool skeleton = true, bool chunks = true); -bool can_bottle_blood_from_corpse(monster_type mons_class); -int num_blood_potions_from_corpse(monster_type mons_class); -void turn_corpse_into_blood_potions(item_def &item); -void turn_corpse_into_skeleton_and_blood_potions(item_def &item); diff --git a/crawl-ref/source/chardump.cc b/crawl-ref/source/chardump.cc index c2d0b9711653..c238c037ac27 100644 --- a/crawl-ref/source/chardump.cc +++ b/crawl-ref/source/chardump.cc @@ -212,13 +212,18 @@ static void _sdump_header(dump_params &par) #endif par.text += " character file.\n\n"; - if (you.game_is_seeded + if (you.fully_seeded #ifdef DGAMELAUNCH - && par.se // for online games, only show seed for a dead char + && (par.se // for online games, show seed for a dead char + || you.wizard + || crawl_state.type == GAME_TYPE_CUSTOM_SEED) #endif ) { - par.text += make_stringf("Game seed: %" PRIu64 "\n\n", crawl_state.seed); + par.text += make_stringf( + "Game seed: %" PRIu64 ", levelgen mode: %s\n\n", + crawl_state.seed, you.deterministic_levelgen + ? "deterministic" : "classic"); } } @@ -809,12 +814,8 @@ static void _sdump_inventory(dump_params &par) if (origin_describable(item) && _dump_item_origin(item)) text += "\n" " (" + origin_desc(item) + ")"; - if (is_dumpable_artefact(item) - || Options.dump_book_spells - && item.base_type == OBJ_BOOKS) - { + if (is_dumpable_artefact(item)) text += chardump_desc(item); - } else text += "\n"; } @@ -1512,27 +1513,12 @@ static const char* hunger_names[] = }; COMPILE_CHECK(ARRAYSZ(hunger_names) == HS_ENGORGED + 1); -// Must match the order of hunger_state_t enums -static const char* thirst_names[] = -{ - "bloodless", - "bloodless", - "near bloodless", - "very thirsty", - "thirsty", - "not thirsty", - "full", - "very full", - "almost alive", -}; -COMPILE_CHECK(ARRAYSZ(thirst_names) == HS_ENGORGED + 1); - const char *hunger_level() { ASSERT(you.hunger_state <= HS_ENGORGED); if (you.species == SP_VAMPIRE) - return thirst_names[you.hunger_state]; + return you.vampire_alive ? "alive" : "bloodless"; return hunger_names[you.hunger_state]; } diff --git a/crawl-ref/source/cio.cc b/crawl-ref/source/cio.cc index b1aa3f7b17b1..cdf902e9d219 100644 --- a/crawl-ref/source/cio.cc +++ b/crawl-ref/source/cio.cc @@ -94,7 +94,7 @@ int unmangle_direction_keys(int keyin, KeymapContext keymap, cprintf("CTRL"); keyin = getchm(keymap); // return control-key - keyin = CONTROL(toupper(_numpad2vi(keyin))); + keyin = CONTROL(toupper_safe(_numpad2vi(keyin))); } else if (keyin == '/') { @@ -102,7 +102,7 @@ int unmangle_direction_keys(int keyin, KeymapContext keymap, cprintf("SHIFT"); keyin = getchm(keymap); // return shift-key - keyin = toupper(_numpad2vi(keyin)); + keyin = toupper_safe(_numpad2vi(keyin)); } } diff --git a/crawl-ref/source/cio.h b/crawl-ref/source/cio.h index 34eeae010291..d241c4b79b13 100644 --- a/crawl-ref/source/cio.h +++ b/crawl-ref/source/cio.h @@ -224,19 +224,16 @@ class cursor_control { public: cursor_control(bool cursor_enabled) - : cstate(is_cursor_enabled()), smartcstate(is_smart_cursor_enabled()) + : cstate(is_cursor_enabled()) { - enable_smart_cursor(false); set_cursor_enabled(cursor_enabled); } ~cursor_control() { set_cursor_enabled(cstate); - enable_smart_cursor(smartcstate); } private: bool cstate; - bool smartcstate; }; enum edit_mode diff --git a/crawl-ref/source/cleansing-flame-source-type.h b/crawl-ref/source/cleansing-flame-source-type.h index 4b2b34a78d6c..d8b3dce7a8f9 100644 --- a/crawl-ref/source/cleansing-flame-source-type.h +++ b/crawl-ref/source/cleansing-flame-source-type.h @@ -1,9 +1,10 @@ #pragma once -enum cleansing_flame_source_type +// Cleansing flame sources +enum class cleansing_flame_source { - CLEANSING_FLAME_GENERIC = -1, - CLEANSING_FLAME_SPELL = -2, // SPELL_FLAME_OF_CLEANSING - CLEANSING_FLAME_INVOCATION = -3, // ABIL_TSO_CLEANSING_FLAME - CLEANSING_FLAME_TSO = -4, // TSO effect + generic = -1, + spell = -2, // SPELL_FLAME_OF_CLEANSING + invocation = -3, // ABIL_TSO_CLEANSING_FLAME + tso = -4, // TSO effect }; diff --git a/crawl-ref/source/cluautil.cc b/crawl-ref/source/cluautil.cc index 056b4b3a5b73..03ec096d35ff 100644 --- a/crawl-ref/source/cluautil.cc +++ b/crawl-ref/source/cluautil.cc @@ -9,22 +9,22 @@ int push_activity_interrupt(lua_State *ls, activity_interrupt_data *t) { switch (t->apt) { - case AIP_HP_LOSS: + case ai_payload::hp_loss: { lua_pushnumber(ls, t->ait_hp_loss_data->hp); lua_pushnumber(ls, t->ait_hp_loss_data->hurt_type); return 1; } - case AIP_INT: + case ai_payload::int_payload: lua_pushnumber(ls, *t->int_data); break; - case AIP_STRING: + case ai_payload::string_payload: lua_pushstring(ls, t->string_data); break; - case AIP_MONSTER: + case ai_payload::monster: push_monster(ls, t->mons_data); break; - case AIP_NONE: + case ai_payload::none: lua_pushnil(ls); break; } diff --git a/crawl-ref/source/cmd-keys.h b/crawl-ref/source/cmd-keys.h index 9a2045263592..b3fe0987e2c5 100644 --- a/crawl-ref/source/cmd-keys.h +++ b/crawl-ref/source/cmd-keys.h @@ -377,6 +377,17 @@ // zoom functions {CK_NUMPAD_PLUS, CMD_ZOOM_IN}, {CK_NUMPAD_MINUS, CMD_ZOOM_OUT}, +#elif defined(USE_TILE_LOCAL) +// no good webtiles keys available for the main view case, and browser zoom +// already more or less accomplishes this. +{'=' - SDLK_a + 1, CMD_ZOOM_IN}, // Believe it or not, this is how we map SDL +{'-' - SDLK_a + 1, CMD_ZOOM_OUT}, // ctrl scancodes... +{'=' - SDLK_a + 1, CMD_MAP_ZOOM_IN}, +{'-' - SDLK_a + 1, CMD_MAP_ZOOM_OUT}, +#endif +#ifdef USE_TILE +{'}', CMD_MAP_ZOOM_IN}, +{'{', CMD_MAP_ZOOM_OUT}, #endif {'\0', CMD_NO_CMD} diff --git a/crawl-ref/source/colour.cc b/crawl-ref/source/colour.cc index 4cafa456ae0e..0ba7afa6da9a 100644 --- a/crawl-ref/source/colour.cc +++ b/crawl-ref/source/colour.cc @@ -199,11 +199,12 @@ static int _etc_elemental(int, const coord_def& loc) int get_disjunct_phase(const coord_def& loc) { static int turns = you.num_turns; - static coord_def centre = find_centre_for(loc, AREA_DISJUNCTION); + static coord_def centre = find_centre_for(loc, + area_centre_type::disjunction); if (turns != you.num_turns || (centre-loc).abs() > 15) { - centre = find_centre_for(loc, AREA_DISJUNCTION); + centre = find_centre_for(loc, area_centre_type::disjunction); turns = you.num_turns; } @@ -236,11 +237,11 @@ static int _etc_disjunction(int, const coord_def& loc) static int _etc_liquefied(int, const coord_def& loc) { static int turns = you.num_turns; - static coord_def centre = find_centre_for(loc, AREA_LIQUID); + static coord_def centre = find_centre_for(loc, area_centre_type::liquid); if (turns != you.num_turns || (centre-loc).abs() > 15) { - centre = find_centre_for(loc, AREA_LIQUID); + centre = find_centre_for(loc, area_centre_type::liquid); turns = you.num_turns; } diff --git a/crawl-ref/source/command-type.h b/crawl-ref/source/command-type.h index b09380bdbe68..590f93ba4a2f 100644 --- a/crawl-ref/source/command-type.h +++ b/crawl-ref/source/command-type.h @@ -137,16 +137,15 @@ enum command_type #endif #ifdef USE_TILE + CMD_ZOOM_IN, + CMD_ZOOM_OUT, + CMD_EDIT_PLAYER_TILE, CMD_MIN_TILE = CMD_EDIT_PLAYER_TILE, CMD_MAX_TILE = CMD_EDIT_PLAYER_TILE, #endif #ifdef TOUCH_UI - // zoom on dungeon - CMD_ZOOM_IN, - CMD_ZOOM_OUT, - // bring up the on-screen keyboard if needed CMD_SHOW_KEYBOARD, #endif @@ -222,6 +221,11 @@ enum command_type CMD_MAP_FORGET, CMD_MAP_UNFORGET, +#ifdef USE_TILE + CMD_MAP_ZOOM_IN, + CMD_MAP_ZOOM_OUT, +#endif + CMD_MAP_EXIT_MAP, CMD_MAX_OVERMAP = CMD_MAP_EXIT_MAP, diff --git a/crawl-ref/source/command.cc b/crawl-ref/source/command.cc index 14e8dfd652fb..e5895e14b92d 100644 --- a/crawl-ref/source/command.cc +++ b/crawl-ref/source/command.cc @@ -95,15 +95,18 @@ static string _get_version_features() string result; if (crawl_state.need_save #ifdef DGAMELAUNCH - && you.wizard + && (you.wizard || crawl_state.type == GAME_TYPE_CUSTOM_SEED) #endif ) { - if (you.game_is_seeded) + if (you.fully_seeded) { - result += make_stringf("Game seed: %" PRIu64, crawl_state.seed); + result += make_stringf( + "Game seed: %" PRIu64 ", levelgen mode: %s", + crawl_state.seed, you.deterministic_levelgen + ? "deterministic" : "classic"); if (Version::history_size() > 1) - result += " (game has been upgraded, seed may be affected)"; + result += " (seed may be affected by game upgrades)"; } else result += "Game is not seeded."; @@ -207,7 +210,7 @@ static void _print_version() auto vbox = make_shared(Widget::VERT); #ifdef USE_TILE_LOCAL - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif auto title_hbox = make_shared(Widget::HORZ); @@ -218,30 +221,28 @@ static void _print_version() #endif auto title = make_shared(formatted_string::parse_string(info)); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 10}); + title->set_margin_for_sdl(0, 0, 0, 10); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->set_margin_for_crt({0, 0, 1, 0}); - title_hbox->set_margin_for_sdl({0, 0, 20, 0}); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_crt(0, 0, 1, 0); + title_hbox->set_margin_for_sdl(0, 0, 20, 0); vbox->add_child(move(title_hbox)); auto scroller = make_shared(); auto content = formatted_string::parse_string(feats + "\n\n" + changes); auto text = make_shared(move(content)); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(move(text)); vbox->add_child(scroller); auto popup = make_shared(vbox); bool done = false; - popup->on(Widget::slots.event, [&done, &vbox](wm_event ev) { - if (ev.type != WME_KEYDOWN) - return false; - done = !vbox->on_event(ev); - return true; + popup->on(Widget::slots.event, [&done, &scroller](wm_event ev) { + if (scroller->on_event(ev)) + return true; + return done = ev.type == WME_KEYDOWN; }); #ifdef USE_TILE_WEB @@ -465,33 +466,14 @@ static void _handle_FAQ() title->colour = YELLOW; FAQmenu.set_title(title); - const int width = get_number_of_cols(); - for (unsigned int i = 0, size = question_keys.size(); i < size; i++) { const char letter = index_to_letter(i); - string question = getFAQ_Question(question_keys[i]); - // Wraparound if the question is longer than fits into a line. - linebreak_string(question, width - 4); - vector fss; - formatted_string::parse_string_to_multiple(question, fss); - - MenuEntry *me; - for (unsigned int j = 0; j < fss.size(); j++) - { - if (j == 0) - { - me = new MenuEntry(question, MEL_ITEM, 1, letter); - me->data = &question_keys[i]; - } - else - { - question = " " + fss[j].tostring(); - me = new MenuEntry(question, MEL_ITEM, 1); - } - FAQmenu.add_entry(me); - } + trim_string_right(question); + MenuEntry *me = new MenuEntry(question, MEL_ITEM, 1, letter); + me->data = &question_keys[i]; + FAQmenu.add_entry(me); } while (true) @@ -519,44 +501,9 @@ static void _handle_FAQ() return; } -/////////////////////////////////////////////////////////////////////////// -// Manual menu highlighter. - -class help_highlighter : public MenuHighlighter -{ -public: - help_highlighter(string = ""); - int entry_colour(const MenuEntry *entry) const override; -private: - text_pattern pattern; - string get_species_key() const; -}; - -help_highlighter::help_highlighter(string highlight_string) : - pattern(highlight_string.empty() ? get_species_key() : highlight_string) -{ -} - -int help_highlighter::entry_colour(const MenuEntry *entry) const -{ - return !pattern.empty() && pattern.matches(entry->text) ? WHITE : -1; -} - -// To highlight species in aptitudes list. ('?%') -string help_highlighter::get_species_key() const -{ - string result = species_name(you.species); - // The table doesn't repeat the word "Draconian". - if (you.species != SP_BASE_DRACONIAN && species_is_draconian(you.species)) - strip_tag(result, "Draconian"); - - result += " "; - return result; -} //////////////////////////////////////////////////////////////////////////// -int show_keyhelp_menu(const vector &lines, - int hotkey, string highlight_string) +int show_keyhelp_menu(const vector &lines) { int flags = FS_PREWRAPPED_TEXT | FS_EASY_EXIT; formatted_scroller cmd_help(flags); @@ -564,7 +511,7 @@ int show_keyhelp_menu(const vector &lines, cmd_help.set_more(); for (unsigned i = 0; i < lines.size(); ++i) - cmd_help.add_formatted_string(lines[i], true); + cmd_help.add_formatted_string(lines[i], i < lines.size()-1); cmd_help.show(); @@ -573,7 +520,8 @@ int show_keyhelp_menu(const vector &lines, void show_specific_help(const string &key) { - const string help = getHelpString(key); + string help = getHelpString(key); + trim_string_right(help); vector formatted_lines; for (const string &line : split_string("\n", help, false, true)) formatted_lines.push_back(formatted_string::parse_string(line)); @@ -606,6 +554,11 @@ void show_interlevel_travel_depth_help() show_specific_help("interlevel-travel.depth.prompt"); } +void show_interlevel_travel_altar_help() +{ + show_specific_help("interlevel-travel.altar.prompt"); +} + void show_stash_search_help() { show_specific_help("stash-search.prompt"); @@ -914,9 +867,9 @@ static void _add_formatted_keyhelp(column_composer &cols) 1, "Dungeon Interaction and Information:\n"); - _add_insert_commands(cols, 1, "%/% : Open/Close door", + _add_insert_commands(cols, 1, "%/% : Open/Close door", { CMD_OPEN_DOOR, CMD_CLOSE_DOOR }); - _add_insert_commands(cols, 1, "%/% : use staircase", + _add_insert_commands(cols, 1, "%/% : use staircase", { CMD_GO_UPSTAIRS, CMD_GO_DOWNSTAIRS }); _add_command(cols, 1, CMD_INSPECT_FLOOR, "examine occupied tile and"); @@ -939,6 +892,10 @@ static void _add_formatted_keyhelp(column_composer &cols) _add_command(cols, 1, CMD_TOGGLE_TRAVEL_SPEED, "set your travel speed to your"); cols.add_formatted(1, " slowest ally\n", false); +#ifdef USE_TILE_LOCAL + _add_insert_commands(cols, 1, "%/% : zoom out/in", + { CMD_ZOOM_OUT, CMD_ZOOM_IN }); +#endif cols.add_formatted( 1, @@ -961,16 +918,8 @@ static void _add_formatted_keyhelp(column_composer &cols) "Item Interaction:\n"); _add_command(cols, 1, CMD_INSCRIBE_ITEM, "inscribe item", 2); - { - const bool vampire = you.species == SP_VAMPIRE; - string butcher = vampire ? "bottle blood from" - : "Chop up"; - _add_command(cols, 1, CMD_BUTCHER, butcher + " a corpse on floor", 2); - string interact = (you.species == SP_VAMPIRE ? "drain corpses" - : "Eat food"); - interact += " (tries floor first)\n"; - _add_command(cols, 1, CMD_EAT, interact, 2); - } + _add_command(cols, 1, CMD_BUTCHER, "Chop up a corpse on floor", 2); + _add_command(cols, 1, CMD_EAT, "Eat food (tries floor first) \n", 2); _add_command(cols, 1, CMD_FIRE, "Fire next appropriate item", 2); _add_command(cols, 1, CMD_THROW_ITEM_NO_QUIVER, "select an item and Fire it", 2); _add_command(cols, 1, CMD_QUIVER_ITEM, "select item slot to be Quivered", 2); @@ -1207,7 +1156,7 @@ static int _get_help_section(int section, formatted_string &header_out, formatte text += formatted_string(buf); if (next_is_hotkey && (isaupper(buf[0]) || isadigit(buf[0]))) { - int hotkey = tolower(buf[0]); + int hotkey = tolower_safe(buf[0]); hotkeys[hotkey] = count_occurrences(text.tostring(), "\n"); } diff --git a/crawl-ref/source/command.h b/crawl-ref/source/command.h index cbb4f3a22d1a..c858157a44e0 100644 --- a/crawl-ref/source/command.h +++ b/crawl-ref/source/command.h @@ -21,6 +21,7 @@ void show_levelmap_help(); void show_targeting_help(); void show_interlevel_travel_branch_help(); void show_interlevel_travel_depth_help(); +void show_interlevel_travel_altar_help(); void show_stash_search_help(); void show_butchering_help(); void show_skill_menu_help(); @@ -28,8 +29,7 @@ void show_spell_library_help(); void show_help(int section = CK_HOME, string highlight_string = ""); -int show_keyhelp_menu(const vector &lines, - int hotkey = 0, string highlight_string = ""); +int show_keyhelp_menu(const vector &lines); // XXX: Actually defined in main.cc; we may want to move this to command.cc. void process_command(command_type cmd); diff --git a/crawl-ref/source/conduct-type.h b/crawl-ref/source/conduct-type.h index f52462dbbb0b..8cbc5c3ae224 100644 --- a/crawl-ref/source/conduct-type.h +++ b/crawl-ref/source/conduct-type.h @@ -8,7 +8,6 @@ enum conduct_type DID_ATTACK_HOLY, DID_ATTACK_NEUTRAL, DID_ATTACK_FRIEND, - DID_FRIEND_DIED, DID_KILL_LIVING, DID_KILL_UNDEAD, DID_KILL_DEMON, @@ -33,10 +32,6 @@ enum conduct_type DID_KILL_SLIME, // Jiyva DID_KILL_PLANT, // Fedhas DID_HASTY, // Cheibriados - DID_CORPSE_VIOLATION, // Fedhas (Necromancy involving - // corpses/chunks). - DID_ROT_CARRION, // Fedhas (a corpse rotted) - DID_SOULED_FRIEND_DIED, // Zin DID_ATTACK_IN_SANCTUARY, // Zin DID_KILL_NONLIVING, DID_EXPLORATION, // Ashenzari, wrath timers diff --git a/crawl-ref/source/confirm-butcher-type.h b/crawl-ref/source/confirm-butcher-type.h index 06103357fece..d5bb96308f35 100644 --- a/crawl-ref/source/confirm-butcher-type.h +++ b/crawl-ref/source/confirm-butcher-type.h @@ -1,8 +1,8 @@ #pragma once -enum confirm_butcher_type +enum class confirm_butcher_type { - CONFIRM_NEVER, - CONFIRM_ALWAYS, - CONFIRM_AUTO, + never, + always, + normal, }; diff --git a/crawl-ref/source/confirm-level-type.h b/crawl-ref/source/confirm-level-type.h deleted file mode 100644 index 47fb74037a84..000000000000 --- a/crawl-ref/source/confirm-level-type.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -enum confirm_level_type -{ - CONFIRM_NONE_EASY, - CONFIRM_SAFE_EASY, - CONFIRM_ALL_EASY, -}; diff --git a/crawl-ref/source/confirm-prompt-type.h b/crawl-ref/source/confirm-prompt-type.h index 286d910896aa..6202792204f3 100644 --- a/crawl-ref/source/confirm-prompt-type.h +++ b/crawl-ref/source/confirm-prompt-type.h @@ -1,8 +1,8 @@ #pragma once -enum confirm_prompt_type +enum class confirm_prompt_type { - CONFIRM_CANCEL, // automatically answer 'no', i.e. disallow - CONFIRM_PROMPT, // prompt - CONFIRM_NONE, // automatically answer 'yes' + cancel, // automatically answer 'no', i.e. disallow + prompt, // prompt + none, // automatically answer 'yes' }; diff --git a/crawl-ref/source/ctest.cc b/crawl-ref/source/ctest.cc index f4db4e814011..84c032426e73 100644 --- a/crawl-ref/source/ctest.cc +++ b/crawl-ref/source/ctest.cc @@ -30,6 +30,7 @@ #include "item-name.h" #include "jobs.h" #include "libutil.h" +#include "mapdef.h" #include "maps.h" #include "message.h" #include "mon-pick.h" @@ -124,7 +125,8 @@ static void run_test(const string &file) return; ++ntests; - fprintf(stderr, "Running test #%d: '%s'.\n", ntests, file.c_str()); + if (!crawl_state.script) + fprintf(stderr, "Running test #%d: '%s'.\n", ntests, file.c_str()); mprf(MSGCH_DIAGNOSTICS, "Running %s %d: %s", activity, ntests, file.c_str()); flush_prev_message(); @@ -153,7 +155,8 @@ static void _run_test(const string &name, void (*func)()) if (!_has_test(name)) return; - fprintf(stderr, "Running test #%d: '%s'.\n", ntests, name.c_str()); + if (!crawl_state.script) + fprintf(stderr, "Running test #%d: '%s'.\n", ntests, name.c_str()); ++ntests; try @@ -212,6 +215,10 @@ void run_tests() } } +#ifdef DEBUG_TAG_PROFILING + tag_profile_out(); +#endif + if (crawl_state.test_list) end(0); cio_cleanup(); @@ -219,8 +226,14 @@ void run_tests() fprintf(stderr, "%s error: %s\n", activity, fe.second.c_str()); const int code = failures.empty() ? 0 : 1; - end(code, false, "%d %ss, %d succeeded, %d failed", - ntests, activity, nsuccess, (int)failures.size()); + // scripts are responsible for printing their own errors + if (crawl_state.script && ntests == 1) + end(code, false); + else + { + end(code, false, "%d %ss, %d succeeded, %d failed", + ntests, activity, nsuccess, (int)failures.size()); + } } #endif // DEBUG_TESTS diff --git a/crawl-ref/source/daction-type.h b/crawl-ref/source/daction-type.h index 409647303739..4ce4ffec579a 100644 --- a/crawl-ref/source/daction-type.h +++ b/crawl-ref/source/daction-type.h @@ -25,9 +25,8 @@ enum daction_type #if TAG_MAJOR_VERSION == 34 DACT_HOLY_PETS_GO_NEUTRAL, DACT_ALLY_TROG, -#endif - DACT_RECLAIM_DECKS, +#endif DACT_REAUTOMAP, DACT_REMOVE_JIYVA_ALTARS, DACT_PIKEL_SLAVES, diff --git a/crawl-ref/source/dactions.cc b/crawl-ref/source/dactions.cc index d971d8fbfa45..1278798dc73c 100644 --- a/crawl-ref/source/dactions.cc +++ b/crawl-ref/source/dactions.cc @@ -13,6 +13,7 @@ #include "decks.h" #include "dungeon.h" #include "god-companions.h" // hepliaklqana_ancestor +#include "god-passive.h" #include "items.h" #include "libutil.h" #include "mapmark.h" @@ -47,9 +48,11 @@ static const char *daction_names[] = #else "slimes allow another conversion attempt", #endif +#if TAG_MAJOR_VERSION == 34 "holy beings go neutral", "Trog's gifts go hostile", "reclaim decks", +#endif "reapply passive mapping", "remove Jiyva altars", "Pikel's slaves go good-neutral", @@ -66,8 +69,8 @@ static const char *daction_names[] = "bribe timeout", "remove Gozag shops", "apply Gozag bribes", - "Makhleb's servants go hostile", #if TAG_MAJOR_VERSION == 34 + "Makhleb's servants go hostile", "make all monsters hate you", #endif "ancestor vanishes", @@ -293,9 +296,11 @@ static void _apply_daction(daction_type act) } break; +#if TAG_MAJOR_VERSION == 34 case DACT_RECLAIM_DECKS: reclaim_decks_on_level(); break; +#endif case DACT_REAUTOMAP: reautomap_level(); break; @@ -312,33 +317,8 @@ static void _apply_daction(daction_type act) item.freshness = 1; // thoroughly rotten break; case DACT_GOLD_ON_TOP: - { - for (rectangle_iterator ri(0); ri; ++ri) - { - for (stack_iterator j(*ri); j; ++j) - { - if (j->base_type == OBJ_GOLD) - { - bool detected = false; - int dummy = j->index(); - unlink_item(dummy); - move_item_to_grid(&dummy, *ri, true); - if (!env.map_knowledge(*ri).item() - || env.map_knowledge(*ri).item()->base_type != OBJ_GOLD) - { - detected = true; - } - update_item_at(*ri, true); - - // The gold might be beneath deep water. - if (detected && env.map_knowledge(*ri).item()) - env.map_knowledge(*ri).flags |= MAP_DETECTED_ITEM; - break; - } - } - } + gozag_detect_level_gold(false); break; - } case DACT_REMOVE_GOZAG_SHOPS: { vector markers = env.markers.get_all(MAT_FEATURE); diff --git a/crawl-ref/source/dat/database/help.txt b/crawl-ref/source/dat/database/help.txt index 780534e46243..3966fc4b51ee 100644 --- a/crawl-ref/source/dat/database/help.txt +++ b/crawl-ref/source/dat/database/help.txt @@ -22,8 +22,14 @@ You can quickly select items by type by pressing: / wands | magical staves ? scrolls : books " amulets = rings } miscellaneous % food Global selects: , select all * invert all - deselect all + ; select last unequipped Note that for dropping, the , command uses the drop_filter option, which narrows the range of items to be (de)selected. The default are useless items. + +Disabling autopickup via the drop menu +On the drop menu, items you select after pressing \ will be marked with a * +instead of the usual +. Autopickup for these items will be disabled when you +leave the menu. To return to the normal selection mode, press \ a second time. %%%% known-menu @@ -110,12 +116,13 @@ that contain the spell. You can search for artefact item properties (such as /prevents.*teleport) to find artefacts that have the property. -You can search by some additional item properties: artefact or artifact will -find identified artefacts, ego or branded will find non-artefacts with a -brand and unidentified items which may be branded, throwable will find things -you can throw, dropped will find items you have dropped, in_shop will find -items in shops, and (if the autopickup_search option is enabled) autopickup will find -items that would be automatically picked up. +You can search by some additional item properties: artefact or +artifact will find identified artefacts, ego or branded +will find non-artefacts with a brand and unidentified items which may be +branded, throwable will find things you can throw, dropped will +find items you have dropped, in_shop will find items in shops, and (if +the autopickup_search option is enabled) autopickup will find items that +would be automatically picked up. Skill names (such as Polearms or Long Blades) will find all weapons that train that skill. You can also look for melee weapon and ranged weapon. @@ -126,10 +133,10 @@ Use armour && !!body to list all armour pieces except for body armour. Finding Dungeon Features: -You can search for dungeon features by name: all shops will be found by a -search for shop (including shops that do not have "shop" in their name); -other dungeon features can also be found by name: fountain, spear trap, -altar, etc. You can also search for altars by deity name: Zin. +You can search for dungeon features by name: all shops will be found by a search +for shop (including shops that do not have "shop" in their name); other +dungeon features can also be found by name: dispersal trap, altar, +etc. You can also search for altars by deity name: Zin. Non-regex operators: @@ -155,6 +162,7 @@ level-map [ or ] : Examine the next higher or lower level G : Go to another level by branch and depth o : Move the cursor to the next autoexplore target. +{ or } : Zoom out or in (tiles only). Travel exclusions e : Create a travel exclusion, change its radius, or remove it. @@ -201,6 +209,7 @@ interlevel-travel.branch.prompt Ctrl-P : Travel to a level in the branch above this one. * : Show available waypoints (if any are set). 0-9 : Go to the numbered waypoint. + _ : Show available altars (if any are discovered). %%%% interlevel-travel.depth.prompt @@ -214,6 +223,16 @@ interlevel-travel.depth.prompt $ : Change default to deepest visited level in this branch. ^ : Change default to the entrance to the current branch. %%%% +interlevel-travel.altar.prompt + +Interlevel Travel (go to an altar): + Use the shortcut letter (the initial of a god, with TSO being replaced by 1) + to select the altar for travel. + + Once you select a god, you will begin travelling to the nearst altar. + + Enter : Repeat last interlevel travel. +%%%% butchering Butchering diff --git a/crawl-ref/source/dat/database/miscname.txt b/crawl-ref/source/dat/database/miscname.txt index 929c4044c808..24c219f6efdc 100644 --- a/crawl-ref/source/dat/database/miscname.txt +++ b/crawl-ref/source/dat/database/miscname.txt @@ -19,6 +19,10 @@ dull faint +gleaming + +luminous + mottled pale @@ -112,31 +116,65 @@ glowing_colour_name # random uselessness: "You smell XXX." smell_name +a campfire + +a dewy forest on a spring morning + baking bread +bat guano + burning hair +cherry blossoms + +clean mountain air + coffee dragon dung fire and brimstone +halfling feet + +honeysuckles + +lavender + +leather and polished metal + +meat roasting over an open fire + mustard pepper +pipe weed + +roast spriggan + +rotten fruit + +rotten potatoes + salt something weird sulphur +sweaty leather boots + tea -the scent of fear +the stench of fear + +wet warg wet wool + +wet yak %%%% # This doesn't necessarily have to make sense. For example, a name of a # fruit or an insect will work here. @@ -144,23 +182,45 @@ _roaring_animal_name_ albino dragon +centaur + +dwarf + dragon eggplant frog +gnoll + +grizzly bear + human +komodo dragon + +kobold + millipede +ogre + +orc + pill bug +polar bear + slug +troll + wumpus yak + +yaktaur %%%% # random uselessness: "You hear XXX." sound_name @@ -177,19 +237,23 @@ a very strange noise indeed a voice calling someone else's name +a witch cackle + roaring flames snatches of song -the distant roaring of an enraged @_roaring_animal_name_@ the chiming of a distant gong +the distant roaring of an enraged @_roaring_animal_name_@ + +the ringing of an enormous bell + the sound of one hand clapping the tinkle of a tiny bell -the ringing of an enormous bell whispers in an ancient language @@ -256,16 +320,53 @@ Seething terrors besiege your sanity! Lucidity slithers from your feeble grasp. %%%% +# How great is the Orb of Zot? +_great_adj_ + +awesome + +fabled + +fabulous + +legendary + +transcendent +%%%% welcome_spam Will you prevail where others failed? Will you find the Orb of Zot? -The bosom of this dungeon contains the fabled artefact, the Orb of Zot. +The bosom of this dungeon contains the @_great_adj_@ artefact, the Orb of Zot. -Will you be the one to retrieve the fabulous Orb of Zot from the depths? +Will you be the one to retrieve the @_great_adj_@ Orb of Zot from the depths? It is said that the Orb of Zot exists deep within this dungeon. %%%% +# things that surround us on Halloween +_halloween_things_ + +demonic hallucinations + +disembodied spirits + +ghosts and spectres + +grim specetres + +horrible apparitions +%%%% +# things you don't want a witch to turn you into +_lowly_ + +newt + +slug + +toad + +worm +%%%% welcome_spam Halloween @welcome_spam@ Be careful! Halloween tonight. @@ -274,9 +375,9 @@ The monsters here aren't big trick-or-treat fans. If they won't give you the Orb A terrible geas has sent you after the Orb of Zot. What a horrible night to have a curse! -As you descend in search of the Orb, ghosts and spectres flock about you. This place is haunted! +As you descend in search of the Orb, @_halloween_things_@ flock about you. This place is haunted! -You hear a witch cackle, and suddenly arrive in the dungeon. If you don't find the Orb of Zot, you'll turn into a toad! +You hear a witch cackle, and suddenly arrive in the dungeon. If you don't find the Orb of Zot, you'll turn into a @_lowly_@! %%%% hell_effect_quiet diff --git a/crawl-ref/source/dat/database/monspeak.txt b/crawl-ref/source/dat/database/monspeak.txt index 73e56f6943a9..f1ba72545ff0 100644 --- a/crawl-ref/source/dat/database/monspeak.txt +++ b/crawl-ref/source/dat/database/monspeak.txt @@ -639,8 +639,6 @@ VISUAL:@The_monster@ ponders the situation. @The_monster@ says, "Why is everything spinning?" @The_monster@ screams, "I'll kill you anyway!" - -VISUAL:@The_monster@ covers @possessive@ eyes. %%%% _confused_humanoid_rare_ @@ -3538,8 +3536,6 @@ w:50 @The_monster@ says, "Don't you get tired of all these old geezers telling you what to do?" -@The_monster@ says, "Heaven on Earth? Sounds overrated." - @The_monster@ says, "No way as a way? Sounds like nonsense." %%%% No God Donald @@ -5438,7 +5434,7 @@ _Nessos_rare_ @The_monster@ says, "Even if you were a mare, I'd kill you!" -@The_monster@ shouts, "I'm half man! Half horse! And all stone-cold KILLER!" +@The_monster@ shouts, "I'm half human! Half horse! And all stone-cold KILLER!" w:1 @The_monster@ says, "Hold still for a moment, will you? I've got a trick shot I want to try here." diff --git a/crawl-ref/source/dat/database/pl/miscname.txt b/crawl-ref/source/dat/database/pl/miscname.txt index 8f33be579950..d9eb14a01f77 100644 --- a/crawl-ref/source/dat/database/pl/miscname.txt +++ b/crawl-ref/source/dat/database/pl/miscname.txt @@ -8,13 +8,8 @@ %%%% # "dust glows XXX", "eyes glow XXX", "hands glow XXX", " glows XXX". # If a language needs these keys split, please say so. -# -# Also used by Tome of Destruction below. #glowing_colour_name %%%% -# Tome of Destruction: "The book opens to a page covered in XXX." -#writing_name -%%%% # random uselessness: "You smell XXX." #smell_name %%%% diff --git a/crawl-ref/source/dat/defaults/autopickup_exceptions.txt b/crawl-ref/source/dat/defaults/autopickup_exceptions.txt index ccec8572f96c..47e0b063bcbd 100644 --- a/crawl-ref/source/dat/defaults/autopickup_exceptions.txt +++ b/crawl-ref/source/dat/defaults/autopickup_exceptions.txt @@ -70,11 +70,32 @@ ae += [^n]identified.*spellbook : return it.stacks() or nil : end) -# Excluding amulets as you only need one of each. (If you know the subtype -# that means you already have one of it.). Some items may already be excluded -# as bad_item, e.g. inaccuracy. -ae += amulet of (nothing|inaccuracy|the gourmand|regeneration|harm) -ae += amulet of (rage|guardian spirit|faith|magic regeneration|the acrobat) - -# Likewise for some rings. -ae += ring of (see invisible|flight|poison resistance|resist corrosion) +# Excluding most amulets as you only need one of each. Likewise for some +# rings. Some items may already be excluded as bad_item, e.g. inaccuracy. +:add_autopickup_func(function (it, name) +: if (not it.class(true) == "jewellery") or it.artefact then +: return +: end +: local itsubtype = it.subtype() +: if itsubtype == "amulet of faith" +: or itsubtype == "amulet of guardian spirit" +: or itsubtype == "amulet of harm" +: or itsubtype == "amulet of inaccuracy" +: or itsubtype == "amulet of magic regeneration" +: or itsubtype == "amulet of nothing" +: or itsubtype == "amulet of rage" +: or itsubtype == "amulet of regeneration" +: or itsubtype == "amulet of the acrobat" +: or itsubtype == "amulet of the gourmand" +: or itsubtype == "ring of flight" +: or itsubtype == "ring of poison resistance" +: or itsubtype == "ring of resist corrosion" +: or itsubtype == "ring of see invisible" then +: for inv in iter.invent_iterator:new(items.inventory()) do +: if inv.class(true) == "jewellery" and inv.subtype() == itsubtype then +: return false +: end +: end +: end +: return +:end) diff --git a/crawl-ref/source/dat/defaults/menu_colours.txt b/crawl-ref/source/dat/defaults/menu_colours.txt index f40146a811d6..da064ebf42ad 100644 --- a/crawl-ref/source/dat/defaults/menu_colours.txt +++ b/crawl-ref/source/dat/defaults/menu_colours.txt @@ -68,6 +68,7 @@ menu_colour += pickup:green:god gift # Highlight (partly) selected items menu_colour += inventory:white:\w \+\s menu_colour += inventory:white:\w \#\s +menu_colour += inventory:white:\w \*\s # Not really menu. diff --git a/crawl-ref/source/dat/defaults/messages.txt b/crawl-ref/source/dat/defaults/messages.txt index ec654edb9acc..4d5c90bb03dc 100644 --- a/crawl-ref/source/dat/defaults/messages.txt +++ b/crawl-ref/source/dat/defaults/messages.txt @@ -62,9 +62,9 @@ msc += $warning:settles down msc += lightcyan:LOW MAGIC WARNING msc += $warning:fails to return msc += $warning:no longer ripe -msc += $warning:This .* contains the .* runes? of Zot. -msc += $warning:They guard the .* runes? of Zot. -msc += $warning:Beware, you cannot shaft yourself on this level. +msc += $warning:This .* contains the .* runes? of Zot +msc += $warning:They guard the .* runes? of Zot +msc += $warning:Beware\, you cannot shaft yourself on this level # Unimportant messages and combat clutter # @@ -91,7 +91,7 @@ msc += mute:Uskayaw will no longer force your foes to share their pain\. force_more_message += You have reached level force_more_message += Your scales start -force_more_message += You fall through a shaft +force_more_message += You (fall|are sucked) into a shaft force_more_message += You fall into the water! force_more_message += You fall into the lava! force_more_message += You focus on prolonging your flight @@ -104,7 +104,7 @@ force_more_message += You don't have the energy to cast that spell force_more_message += This is a scroll of acquirement ## Reduce chance of draining because flight or form runs out: -force_more_message += Careful! +force_more_message += ^Careful! # Announcements of timed portal vaults: force_more_message += interdimensional caravan @@ -126,7 +126,7 @@ force_more_message += You have lost your religion force_more_message += is wielding.*distortion # dancing weapons require special handling... force_more_message += there is a.*distortion -force_more_message += of distortion comes into view. +force_more_message += of distortion comes into view # Banishment force_more_message += You are cast into the Abyss diff --git a/crawl-ref/source/dat/defaults/misc.txt b/crawl-ref/source/dat/defaults/misc.txt index 8240419662a6..2ef03d4ea93b 100644 --- a/crawl-ref/source/dat/defaults/misc.txt +++ b/crawl-ref/source/dat/defaults/misc.txt @@ -12,5 +12,5 @@ sort_menus += inv: true : equipped, charged note_items += acquirement, running, of Zot note_messages += Your scales start note_messages += protects you from harm -note_messages += You (fall through|are sucked into) a shaft +note_messages += You (fall|are sucked) into a shaft note_monsters += orb of fire, ancient lich, Sigmund diff --git a/crawl-ref/source/dat/defaults/runrest_messages.txt b/crawl-ref/source/dat/defaults/runrest_messages.txt index 9f406ee25c40..794e6accb756 100644 --- a/crawl-ref/source/dat/defaults/runrest_messages.txt +++ b/crawl-ref/source/dat/defaults/runrest_messages.txt @@ -30,7 +30,7 @@ stop += emerges from the mists of memory ignore += You found a web! stop += found.*trap stop += You have blundered into a Zot trap -stop += You fall through a shaft +stop += You (are sucked|fall) into a shaft and drop stop += A sentinel's mark forms upon you\. # Enchantments diff --git a/crawl-ref/source/dat/des/altar/lugonu_bribe.des b/crawl-ref/source/dat/des/altar/lugonu_bribe.des index 4b21785727b0..17f55bd9b413 100644 --- a/crawl-ref/source/dat/des/altar/lugonu_bribe.des +++ b/crawl-ref/source/dat/des/altar/lugonu_bribe.des @@ -27,24 +27,21 @@ function callback.tgw_lugonu_bribe_lugonu_item (data, triggerable, local item = { ["No God"]="amulet of faith ident:type", Ashenzari="ring of see invisible ident:type", - Beogh="scroll of fear ident:type q:3-6 /\ - scroll of immolation ident:type q:3-6", + Beogh="orc corpse", Cheibriados="potion of haste ident:type q:2-4 /\ good_item quick blade", - Dithmenos="lamp of fire w:5 /\ - good_item " .. weapon .. " ego:flaming ident:type", - Elyvilon="book of Necromancy w:5 /\ - book of Death w:5 / book of Unlife w:5", + Dithmenos="ring of attention ident:type /\ + scroll of noise ident:type q:2-4", + Elyvilon="book of Necromancy / book of Death / book of Unlife", Fedhas="randbook owner:Lugonu spells:necromutation numspells:1 /\ randbook owner:Lugonu spells:simulacrum&animate_dead \ numspells:2", Gozag="amulet of the gourmand ident:type /\ randbook owner:Lugonu spells:corpse_rot&animate_dead numspells:2", - Jiyva="stone q:10-20 / large rock q:2-4", - Kikubaaqudgha="randbook owner:Lugonu spells:dispel_undead \ - numspells:1 / rat corpse w:5 / kobold corpse w:5", - Pakellas="staff of energy / crystal ball of energy w:5", - ["the Shining One"]="Young Poisoner's Handbook /\ + Kikubaaqudgha="rat corpse / kobold corpse", + ["the Shining One"]="good_item " .. weapon .. " ego:pain ident:type /\ + good_item " .. weapon .. " ego:draining ident:type /\ + good_item " .. weapon .. " ego:vampirism ident:type /\ potion of invisibility ident:type q:3-6", Trog="manual of Spellcasting / manual of Air Magic w:5 /\ manual of Earth Magic w:5 / manual of Fire Magic w:5 /\ @@ -55,10 +52,9 @@ function callback.tgw_lugonu_bribe_lugonu_item (data, triggerable, Xom="manual of Invocations", Yredelemnul= "scroll of holy word ident:type q:2-4 /\ good_item " .. weapon .. " ego:holy_wrath ident:type", - Zin="good_item " .. weapon .. " ego:draining ident:type /\ - good_item " .. weapon .. " ego:pain ident:type /\ - good_item " .. weapon .. " ego:chaos ident:type /\ - wand of polymorph ident:type", + Zin="good_item " .. weapon .. " ego:chaos ident:type /\ + wand of polymorph ident:type /\ + potion of lignification ident:type q:2-4", } data.triggered = true if item[you.god()] ~= nil then diff --git a/crawl-ref/source/dat/des/arrival/twisted.des b/crawl-ref/source/dat/des/arrival/twisted.des index 751dde2cf6c3..5590f4bfdc44 100644 --- a/crawl-ref/source/dat/des/arrival/twisted.des +++ b/crawl-ref/source/dat/des/arrival/twisted.des @@ -556,7 +556,7 @@ SHUFFLE: {}, XXT COLOUR: ' : brown / white FTILE: ' = floor_dirt SUBST: '=., X=x, T:tttT, T=t x:1 -: if crawl.coinflip() then +: if not is_validating() and crawl.coinflip() then TAGS: no_pool_fixup : end WEIGHT: 3 diff --git a/crawl-ref/source/dat/des/branches/abyss.des b/crawl-ref/source/dat/des/branches/abyss.des index bc3760e83747..fac37bb321b9 100644 --- a/crawl-ref/source/dat/des/branches/abyss.des +++ b/crawl-ref/source/dat/des/branches/abyss.des @@ -68,7 +68,7 @@ ENDMAP NAME: marbit_kobold_abyss_entry TAGS: no_monster_gen nolayout_encompass WEIGHT: 5 -MONS: big kobold ; tomahawk ego:dispersal, kobold demonologist +MONS: big kobold ; boomerang ego:dispersal, kobold demonologist : abyss_entry(_G, '_') MAP xxxxxxxxxxxxxxx @@ -1459,7 +1459,7 @@ KMONS: 6 = lurking horror # Note: if anything in these two item sets becomes useful for more then a tiny # fraction of characters finding the abyssal rune, remove or replace them. KITEM: 4d = scroll of random uselessness w:15 q:1 / ration w:1 / \ - club w:6 / stone q:1 / needle q:1 ego:confusion / \ + club w:6 / stone q:1 / dart q:1 ego:atropa / \ mundane animal skin / mundane ring mail / gold q:1 KITEM: 5 = wand of random effects / wand of flame / whip not_cursed w:2 / \ hat not_cursed mundane w:5 diff --git a/crawl-ref/source/dat/des/branches/elf.des b/crawl-ref/source/dat/des/branches/elf.des index 6766fd64fdf2..5e9122d1f041 100644 --- a/crawl-ref/source/dat/des/branches/elf.des +++ b/crawl-ref/source/dat/des/branches/elf.des @@ -1275,11 +1275,7 @@ ENDMAP NAME: minmay_guarded_unrand_folly DEPTH: Elf ORIENT: float -: if you.unrands("robe_of_folly") then -ITEM: robe randart no_pickup -: else ITEM: robe_of_folly no_pickup -: end MONS: deep elf sorcerer / deep elf annihilator NSUBST: 1 = 2:2 / 5=1.. / . SUBST: 2 = 1. diff --git a/crawl-ref/source/dat/des/branches/lair.des b/crawl-ref/source/dat/des/branches/lair.des index 5db90565581b..733c44d7f3ff 100644 --- a/crawl-ref/source/dat/des/branches/lair.des +++ b/crawl-ref/source/dat/des/branches/lair.des @@ -88,7 +88,8 @@ function beast_lair_populate_mons_large(e) end function beast_lair_populate_mons_small(e) - if crawl.one_chance_in(25) then + -- don't do this while validating because this call changes the tags + if not e.is_validating() and crawl.one_chance_in(25) then beast_lair_populate_mons_large(e) else local monster_list = {} @@ -2378,13 +2379,7 @@ NAME: minmay_guarded_unrand_snakebite TAGS: ruin DEPTH: Lair ORIENT: southeast -: if you.unrands("snakebite") then -# A randart venom demon whip is unlikely to be useful -# for someone with snakebite, so just do this -ITEM: demon whip good_item -: else ITEM: snakebite no_pickup -: end KMONS: A = adder KMONS: B = water moccasin KMONS: C = black mamba diff --git a/crawl-ref/source/dat/des/branches/spider.des b/crawl-ref/source/dat/des/branches/spider.des index af80061672bb..28fe3eafbdcd 100644 --- a/crawl-ref/source/dat/des/branches/spider.des +++ b/crawl-ref/source/dat/des/branches/spider.des @@ -413,7 +413,7 @@ MAP ...xxxxW.Wxxwwx4xxxxxxxxxxwW.Wxx.. .3xxxxxW.2xxxxxwwxxxxxxxxxwxxW.Wxx... .xxxxx7.WxxxxxxxxwwxxxxxxwxxxxW7Wxxx. - .x4xx.WWxxxxxxx2...w.22.wxxxxxxW.www. + .x4xx.WWxxxxxxx2>..w.22.wxxxxxxW.www. .xxw.WXxxxxxx2..2|x2w..w.xxxxwwwW.Wx. ..xx7WXxxxxx2..2.x2www.w..2wwwxxxW7Wx. ..xxW.Wxxxxx2.x...ww11wwxwww2xxxxxXW.x. diff --git a/crawl-ref/source/dat/des/branches/swamp.des b/crawl-ref/source/dat/des/branches/swamp.des index d15764dd40ad..72159022288c 100644 --- a/crawl-ref/source/dat/des/branches/swamp.des +++ b/crawl-ref/source/dat/des/branches/swamp.des @@ -1613,7 +1613,7 @@ ENDMAP NAME: cheibrodos_swamp_pet_leeches TAGS: no_item_gen no_monster_gen DEPTH: Swamp, !Swamp:$ -MONS: vampire ; potion of blood . long sword . robe, tyrant leech +MONS: vampire ; long sword . robe, tyrant leech KPROP: e' = no_tele_into KPROP: .21 = bloody / w:30 nothing KPROP: e = bloody diff --git a/crawl-ref/source/dat/des/branches/temple_compat.des b/crawl-ref/source/dat/des/branches/temple_compat.des index 37d7f9e369d8..3e20a0edc1f6 100644 --- a/crawl-ref/source/dat/des/branches/temple_compat.des +++ b/crawl-ref/source/dat/des/branches/temple_compat.des @@ -713,7 +713,7 @@ ENDMAP NAME: minmay_temple_bridge_a_8 TAGS: no_pool_fixup SUBST: A : TUV -: if crawl.one_chance_in(10) then +: if not is_validating() and crawl.one_chance_in(10) then SUBST: w = l : set_border_fill_type("endless_lava") : else @@ -759,7 +759,7 @@ ENDMAP NAME: minmay_temple_bridge_b_8 TAGS: no_pool_fixup SUBST: A : TUV -: if crawl.one_chance_in(10) then +: if not is_validating() and crawl.one_chance_in(10) then SUBST: w = l : set_border_fill_type("endless_lava") : else @@ -805,7 +805,7 @@ ENDMAP NAME: minmay_temple_bridge_c_8 TAGS: no_pool_fixup SUBST: A : TUV -: if crawl.one_chance_in(10) then +: if not is_validating() and crawl.one_chance_in(10) then SUBST: w = l : set_border_fill_type("endless_lava") : else @@ -851,7 +851,7 @@ ENDMAP NAME: minmay_temple_bridge_d_8 TAGS: no_pool_fixup SUBST: A : TUV -: if crawl.one_chance_in(10) then +: if not is_validating() and crawl.one_chance_in(10) then SUBST: w = l : set_border_fill_type("endless_lava") : else diff --git a/crawl-ref/source/dat/des/branches/tomb.des b/crawl-ref/source/dat/des/branches/tomb.des index 6bf7507344f2..c0c854216513 100644 --- a/crawl-ref/source/dat/des/branches/tomb.des +++ b/crawl-ref/source/dat/des/branches/tomb.des @@ -98,11 +98,11 @@ function tomb_1_centre_setup(e) end --[[ -Setup Tomb:1 hall stairs. Vaults should place an P glyph for the hallway entry +Setup Tomb:1 hall stairs. Vaults should place a P glyph for the hallway entry destination, a Q glyph for the first hall exit (near P), and an R glyph for the second hall exit (to the second set of rooms on Tomb:2). Any '.' glyphs have a small chance of placing a '3' guardian or an appropriate trap type. About 7 '3' -and about 6 traps on-average. +and about 6 traps on average. ]] function tomb_1_stairs_setup(e) e.lua_marker("P", hatch_dest_name("tomb1_hall_entry")) @@ -469,7 +469,7 @@ SUBVAULT: B : tomb_1_hall_stairs MONS: greater mummy / nothing MONS: sphinx / nothing MONS: obsidian statue / orange crystal statue -SHUFFLE: ([{ +NSUBST: ( = 1:( / *:. # Set up decorative statues. : if crawl.coinflip() then SUBST: I = I:900 5, N = N:900 5 @@ -494,7 +494,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -xxxxxxxxx(.............................[..............................{xxxxxxxxx +xxxxxxxxx(.............................(..............................(xxxxxxxxx xxxxxxxxx..............................................................xxxxxxxxx xxxxxxxxx..............................................................xxxxxxxxx xxxxxxxxx..............................................................xxxxxxxxx diff --git a/crawl-ref/source/dat/des/branches/vaults_rooms_ghost.des b/crawl-ref/source/dat/des/branches/vaults_rooms_ghost.des index 0218d63b45d8..b59f10bce25c 100644 --- a/crawl-ref/source/dat/des/branches/vaults_rooms_ghost.des +++ b/crawl-ref/source/dat/des/branches/vaults_rooms_ghost.des @@ -183,7 +183,7 @@ KITEM: P = robe cursed plus:-8 randart ident:all ITEM: quick blade good_item w:5 / rapier good_item / scimitar good_item / \ double sword good_item w:5 / demon blade good_item w:5 / \ triple sword good_item w:5 / morningstar good_item / \ - eveningstar good_item w:5 / demon whip good_item w:5 \ + eveningstar good_item w:5 / demon whip good_item w:5 / \ dire flail good_item / great mace good_item / broad axe good_item / \ battleaxe good_item / executioner's axe good_item w:5 / \ demon trident good_item w:5 / glaive good_item / \ diff --git a/crawl-ref/source/dat/des/branches/vaults_rooms_hard.des b/crawl-ref/source/dat/des/branches/vaults_rooms_hard.des index 1ba12adabe5a..8448a1ff5008 100644 --- a/crawl-ref/source/dat/des/branches/vaults_rooms_hard.des +++ b/crawl-ref/source/dat/des/branches/vaults_rooms_hard.des @@ -1276,11 +1276,7 @@ ENDMAP NAME: minmay_guarded_unrand_guard_vaults TAGS: vaults_hard vaults_orient_n WEIGHT: 3 -: if you.unrands("glaive_of_the_guard") then -ITEM: bardiche randart no_pickup -: else ITEM: glaive_of_the_guard no_pickup -: end SUBST: b : bxcmG.T NSUBST: 3:9 / 4=9. / ., 8 = 2:8 / 6=8.. / . MAP @@ -1304,15 +1300,9 @@ ENDMAP NAME: minmay_guarded_unrand_secular_launcher TAGS: vaults_hard vaults_orient_s WEIGHT: 3 -: if you.unrands("sniper") and you.unrands("piercer") then -ITEM: triple crossbow randart no_pickup / longbow randart no_pickup -: elseif you.unrands("sniper") then -ITEM: piercer no_pickup -: elseif you.unrands("piercer") then -ITEM: sniper no_pickup -: else -ITEM: piercer no_pickup / sniper no_pickup -: end +# n.b. if one of these has generated, this may fall back on a randart. If both +# have generated, it is guaranteed to. +ITEM: storm_bow no_pickup / sniper no_pickup NSUBST: 3:9 / 6=9. / ., 8 = 2:8 / 6=8.. / . MAP vvvvv diff --git a/crawl-ref/source/dat/des/builder/alphashops.des b/crawl-ref/source/dat/des/builder/alphashops.des index 594d91aca948..c6987a0f0f85 100644 --- a/crawl-ref/source/dat/des/builder/alphashops.des +++ b/crawl-ref/source/dat/des/builder/alphashops.des @@ -67,9 +67,9 @@ elseif s == "B" then i = { "bardiche", "battleaxe", "bolt", "book of battle", "book of beasts", "book of burglary", "box of beasts", "broad axe", "pair of boots", "potion of berserk rage", - "blowgun", "potion of blood", "potion of brilliance", + "potion of brilliance", "scroll of blinking", "scroll of brand weapon", - "manual of bows", "buckler" } + "manual of bows", "buckler", "boomerang" } elseif s == "C" then i = { "book of callings", "book of cantrips", "club", "book of changes", "book of conjurations", "centaur barding", @@ -82,7 +82,7 @@ "book of dreams", "book of the dragon", "dagger", "demon blade", "demon trident", "demon whip", "dire flail", "double sword", "ring of dexterity", "manual of dodging", "wand of disintegration", - "staff of death", "wand of digging" } + "staff of death", "wand of digging", "dart" } elseif s == "E" then i = { "book of enchantments", "manual of evocations", "eveningstar", "ring of evasion", "executioner's axe", @@ -127,7 +127,7 @@ "potion of mutation", "scroll of magic mapping", } elseif s == "N" then i = { "book of necromancy", "naga barding", - "necronomicon", "needle", "scroll of noise", "manual of necromancy" } + "necronomicon", "scroll of noise", "manual of necromancy" } elseif s == "O" then i = { "any" } @@ -163,7 +163,7 @@ "book of transfigurations", "ring of teleportation", "scroll of teleportation", "triple sword", "triple crossbow", "troll leather armour", "manual of throwing", - "manual of translocations", "manual of transmutations", "tomahawk", + "manual of translocations", "manual of transmutations", "trident", "throwing net" } elseif s == "U" then i = { "book of unlife", "manual of unarmed combat" } diff --git a/crawl-ref/source/dat/des/builder/layout_cc.des b/crawl-ref/source/dat/des/builder/layout_cc.des index 55e798603a44..0bfac37dae69 100644 --- a/crawl-ref/source/dat/des/builder/layout_cc.des +++ b/crawl-ref/source/dat/des/builder/layout_cc.des @@ -92,6 +92,8 @@ TAGS: overwritable layout allow_dup unrand layout_type_swamp dgn.layout_swamp() -- Prevent tele closets + -- TODO: there's also code in dungeon.cc (in _build_vault_impl) that does + -- something very similar; do we really need both? zonify.grid_fill_water_zones(1,"tree") }} diff --git a/crawl-ref/source/dat/des/builder/shops.des b/crawl-ref/source/dat/des/builder/shops.des index facf74d9cb51..f1666cd00253 100644 --- a/crawl-ref/source/dat/des/builder/shops.des +++ b/crawl-ref/source/dat/des/builder/shops.des @@ -306,8 +306,7 @@ TAGS: no_monster_gen transparent DEPTH: D:4-, Depths MONS: ball python w:5 / adder / water moccasin, place:Snake:1 KFEAT: v = general shop type:Serpentskin suffix:Sales count:7 ; \ - w:8 blowgun | w:8 needle ego:poisoned | w:4 needle ego:curare | \ - w:14 tomahawk ego:poisoned | \ + w:8 dart ego:poisoned | w:4 dart ego:curare | \ w:3 ring of poison resistance | w:6 randbook disc:poison | \ w:2 staff of poison | w:4 any weapon ego:venom | \ w:4 any armour ego:poison_resistance | w:1 swamp dragon scales @@ -360,7 +359,7 @@ DEPTH: D:12-, Depths KMONS: 1 = vampire / bat KMONS: 2 = vampire / vampire mage / vampire knight KFEAT: v = general shop type:Blood suffix:Bar count:7 ; \ - w:70 potion of blood | w:10 any weapon ego:vampirism | \ + w:70 nothing | w:10 any weapon ego:vampirism | \ w:9 randbook disc:necromancy | w:1 book of annihilations SUBST: ? = 2.Y : if you.in_branch("D") then @@ -386,7 +385,7 @@ SUBST: G : YlG : "type:Evil suffix:Emporium", "type:Profane suffix:Products", : "type:Sacrilege suffix:Store", "type:Wicked suffix:Wares"}) : kfeat("s = general shop " .. shopname .. " ; \ -: potion of blood | any weapon ego:draining w:15 | any weapon ego:pain | \ +: any weapon ego:draining w:15 | any weapon ego:pain | \ : any weapon ego:vampirism | any weapon ego:chaos w:1 | demon blade w:5 | \ : demon trident w:5 | demon whip w:5 | scroll of torment | staff of death | \ : book of necromancy | book of death | book of unlife | \ @@ -412,7 +411,7 @@ TAGS: no_monster_gen no_trap_gen transparent SHUFFLE: _s KFEAT: s = general shop type:Hallowed suffix:Reliquary ; \ potion of heal wounds | potion of curing | \ - tomahawk ego:silver w:2 | javelin ego:silver w:2 | \ + boomerang ego:silver w:2 | javelin ego:silver w:2 | \ any weapon ego:holy_wrath | scroll of holy word |\ amulet of faith w:2 | ring of positive energy w:5 |\ any armour ego:positive_energy w:5 @@ -466,8 +465,7 @@ KFEAT: s = general shop type:Demolitions suffix:Depot ; \ randbook disc:conjuration | book of the tempests | book of clouds | \ wand of iceblast | wand of flame | wand of disintegration | \ wand of clouds | wand of scattershot | staff of conjuration | \ - large rock ego:exploding | javelin ego:exploding | wand of acid | \ - tomahawk ego:exploding | fan of gales | lightning rod | lamp of fire | \ + wand of acid | fan of gales | lightning rod | lamp of fire | \ phial of floods | scroll of immolation | book of flames | book of fire | \ book of air | manual of conjurations | manual of evocations FTILE: ' = floor_rough_red @@ -711,7 +709,7 @@ local kstr = local i = 0 while i < itemCount do local itm = items[crawl.random2(#items) + 1] - if not (chosen[itm] or you.unrands(itm)) then + if not (chosen[itm]) then i = i + 1 chosen[itm] = true kstr = kstr .. itm diff --git a/crawl-ref/source/dat/des/portals/bailey.des b/crawl-ref/source/dat/des/portals/bailey.des index f02606854f8c..caf6a8e6b8ed 100644 --- a/crawl-ref/source/dat/des/portals/bailey.des +++ b/crawl-ref/source/dat/des/portals/bailey.des @@ -67,15 +67,15 @@ end -- Axes. function axe_returning(e) - e.mons("generate_awake kobold ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3 / \ - generate_awake hobgoblin ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3") + e.mons("generate_awake kobold ; boomerang ident:type q:4 | \ + boomerang ident:type q:3 / \ + generate_awake hobgoblin ; boomerang ident:type q:4 | \ + boomerang ident:type q:3") end function kobold_axe_returning(e) - e.mons("generate_awake kobold ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3") + e.mons("generate_awake kobold ; boomerang ident:type q:4 | \ + boomerang ident:type q:3") end function easy_axe_fighter(e) @@ -186,12 +186,12 @@ WEIGHT: 5 : local rnd = crawl.random2(2) : if rnd == 0 then MONS: goblin ; hand axe / hobgoblin ; hand axe / orc ; hand axe -MONS: goblin ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3 / \ - hobgoblin ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3 / \ - orc ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3 +MONS: goblin ; boomerang ident:type q:4 | \ + boomerang ident:type q:3 / \ + hobgoblin ; boomerang ident:type q:4 | \ + boomerang ident:type q:3 / \ + orc ; boomerang ident:type q:4 | \ + boomerang ident:type q:3 SUBST: 1 = 122 : bailey_portal_axe(_G) : elseif rnd == 1 then @@ -215,8 +215,8 @@ NAME: enter_bailey_5 TAGS: transparent : local rnd = crawl.random2(2) : if rnd == 0 then -MONS: goblin ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3 +MONS: goblin ; boomerang ident:type q:4 | \ + boomerang ident:type q:3 : bailey_portal_axe(_G) : elseif rnd == 1 then MONS: goblin ; spear @@ -320,8 +320,8 @@ TAGS: transparent WEIGHT: 1 : local rnd = crawl.random2(2) : if rnd == 0 then -MONS: orc ; tomahawk ego:returning ident:type q:3 | \ - tomahawk ego:returning ident:type q:4 . hand axe +MONS: orc ; boomerang ident:type q:3 | \ + boomerang ident:type q:4 . hand axe MONS: gnoll ; hand axe / goblin ; hand axe : bailey_portal_axe(_G) : elseif rnd == 1 then @@ -570,8 +570,8 @@ SUBST: N = nc SUBST: H = W. # the following KMONS duplicates monster 1 from kobold_axe_returning KFEAT: ( = exit_bailey -KMONS: ( = generate_awake kobold ; tomahawk ego:returning ident:type q:4 | \ - tomahawk ego:returning ident:type q:3 +KMONS: ( = generate_awake kobold ; boomerang ident:type q:4 | \ + boomerang ident:type q:3 # : kobold_axe_returning(_G) : easy_axe_fighter(_G) @@ -943,8 +943,8 @@ ORIENT: encompass TAGS: bailey_polearm no_item_gen no_monster_gen no_pool_fixup patrolling : orc_with_polearm(_G) : orc_warrior_with_polearm(_G) -MONS: centaur ; javelin ego:returning ident:type q:2 | \ - javelin ego:returning ident:type q:3 . spear | trident | halberd . \ +MONS: centaur ; javelin ident:type q:2 | \ + javelin ident:type q:3 . spear | trident | halberd . \ ring mail | scale mail | chain mail NSUBST: _ = 4:2 / 5:1 / *:. NSUBST: ; = 6:2 / 6:1 / *:. diff --git a/crawl-ref/source/dat/des/portals/bazaar.des b/crawl-ref/source/dat/des/portals/bazaar.des index 479f184e2bd0..8eff5f73cf41 100644 --- a/crawl-ref/source/dat/des/portals/bazaar.des +++ b/crawl-ref/source/dat/des/portals/bazaar.des @@ -554,6 +554,7 @@ NAME: bazaar_minmay_generic_island TAGS: no_rotate ORIENT: encompass WEIGHT: 20 +KPROP: w = no_tele_into SUBST: w : wl SHUFFLE: Aa/Bb/Cc/Dd, Cc/Ee, Dd/Ff COLOUR: EeFf = yellow diff --git a/crawl-ref/source/dat/des/portals/desolation.des b/crawl-ref/source/dat/des/portals/desolation.des index 660b644654b7..633e6e8b0789 100644 --- a/crawl-ref/source/dat/des/portals/desolation.des +++ b/crawl-ref/source/dat/des/portals/desolation.des @@ -138,7 +138,8 @@ KMONS: O = saltling band SUBST: . = ;. FTILE: ;OP = FLOOR_SALT MARKER: P = lua:fog_machine { cloud_type = "salt", walk_dist=3, \ - pow_max=6, delay=0, size=5, spread_rate=3, } + pow_min=6, pow_max=20, \ + delay=5, size=5, spread_rate=5, } MAP ....... .;;;;;. @@ -177,8 +178,9 @@ TAGS: transparent no_monster_gen SUBST: C = c;, - = ; .:50 KMONS: O = saltling band FTILE: ;Os = FLOOR_SALT -MARKER: O = lua:fog_machine { cloud_type = "salt", walk_dist = 4, size = 3, \ - pow_max = 6, delay = 10 } +MARKER: O = lua:fog_machine { cloud_type = "salt", walk_dist=3, \ + pow_min=6, pow_max=20, \ + delay=5, size=5, spread_rate=5, } MAP ........... .-;-;--;--. @@ -965,7 +967,8 @@ ORIENT: float NSUBST: - = 2:c / 2:x / 2=c. / 2=x. / 4:% / 1:G. / 2:1 / 2:2 / 1:S / *:. SUBST: C = c.., c = ccccx MARKER: S = lua:fog_machine { cloud_type = "salt", walk_dist=3, \ - pow_max=6, delay=0, size=5, spread_rate=3, } + pow_min=6, pow_max=20, \ + delay=5, size=5, spread_rate=5, } : desolation_ruins_setup(_G) MAP C.........C. @@ -1011,7 +1014,8 @@ TAGS: desolation_ruin transparent ORIENT: float NSUBST: c = 4:- / *=cccx-, - = 7:% / 2:x / 2:2 / 2:1 / *:., . = S / . MARKER: S = lua:fog_machine { cloud_type = "salt", walk_dist=3, \ - pow_max=6, delay=0, size=5, spread_rate=3, } + pow_min=6, pow_max=20, \ + delay=5, size=5, spread_rate=5, } : desolation_ruins_setup(_G) MAP ............ @@ -1309,7 +1313,8 @@ MONS: saltling band : set_border_fill_type('endless_salt') : set_feature_name("granite_statue", "ruined idol") MARKER: S = lua:fog_machine { cloud_type = "salt", walk_dist=3, \ - pow_max=6, delay=0, size=5, spread_rate=3, } + pow_min=6, pow_max=20, \ + delay=5, size=15, spread_rate=10, } MAP PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP diff --git a/crawl-ref/source/dat/des/portals/gauntlet.des b/crawl-ref/source/dat/des/portals/gauntlet.des index 779a98199ac9..f560b0104628 100644 --- a/crawl-ref/source/dat/des/portals/gauntlet.des +++ b/crawl-ref/source/dat/des/portals/gauntlet.des @@ -1146,7 +1146,7 @@ dgn.delayed_decay_extra(_G, "Q", "halfling skeleton", "randbook disc:translocation / book of spatial translocations" .. " / book of the warp" .. ", scroll of blinking / scroll of teleportation q:2" .. - " / tomahawk ego:dispersal" .. + " / boomerang ego:dispersal" .. ", dagger good_item / short sword good_item / rapier good_item" .. " / long sword good_item / double sword / demon blade" .. " / hunting sling randart / fustibalus good_item" .. diff --git a/crawl-ref/source/dat/des/portals/ossuary.des b/crawl-ref/source/dat/des/portals/ossuary.des index 5149f42341ed..f4248d395e01 100644 --- a/crawl-ref/source/dat/des/portals/ossuary.des +++ b/crawl-ref/source/dat/des/portals/ossuary.des @@ -357,7 +357,7 @@ NAME: ossuary_tomb_2 WEIGHT: 40 ORIENT: encompass TAGS: no_item_gen no_monster_gen no_rotate -KFEAT: ~ = arrow trap w:2 / needle trap +KFEAT: ~ = arrow trap w:2 / dart trap KFEAT: ^ = arrow trap / floor w:5 # Number of 1's is 53, number of ~'s is 57. : if crawl.one_chance_in(10) then @@ -556,7 +556,7 @@ MONS: gnoll zombie / hobgoblin zombie / orc zombie / \ human zombie / kobold zombie / goblin zombie / \ big kobold zombie / centaur zombie / scorpion zombie w:20 ITEM: any scroll / any potion / any jewellery w:1 / nothing -KFEAT: ~ = needle trap / arrow trap / spear trap w:5 +KFEAT: ~ = dart trap / arrow trap / spear trap w:5 SUBST: 1 = 1. SUBST: 3 = 3. : ossuary_setup_features(_G) @@ -1110,7 +1110,7 @@ MONS: rat zombie / quokka zombie / adder zombie /\ big kobold zombie w:1 / human zombie w:1 / elf zombie w:1 / orc zombie w:1 MONS: generate_awake mummy ITEM: any scroll / any potion / gold w:4 -KFEAT: ~ = arrow trap / needle trap +KFEAT: ~ = arrow trap / dart trap : ossuary_setup_features(_G) MAP ccccc ccc ccc ccc @@ -1214,7 +1214,7 @@ KMONS: 3 = goblin zombie / hobgoblin zombie / kobold zombie / gnoll zombie w:2 big kobold zombie w:1 / human zombie w:1 / elf zombie w:1 / orc zombie w:1 /\ nothing w:36 KITEM: 12d = any potion / any scroll / any jewellery w:1 -KFEAT: . = needle trap / arrow trap / spear trap w:5 / floor w:600 +KFEAT: . = dart trap / arrow trap / spear trap w:5 / floor w:600 NSUBST: d = 1:1 / 3:2 / 4:. / *:d : ossuary_setup_features(_G) MAP diff --git a/crawl-ref/source/dat/des/portals/sewer.des b/crawl-ref/source/dat/des/portals/sewer.des index 78118ba5d864..06d1d6e20fce 100644 --- a/crawl-ref/source/dat/des/portals/sewer.des +++ b/crawl-ref/source/dat/des/portals/sewer.des @@ -724,7 +724,7 @@ KMONS: 4 = Purgy, crocodile ITEM: potion of flight / potion of agility / potion of heal wounds / \ potion of invisibility / potion of mutation / potion of degeneration /\ scroll of blinking / scroll of teleportation / scroll of fear / \ - scroll of random uselessness / ration / potion of blood / \ + scroll of random uselessness / ration / \ nothing w:140 : sewer_setup(_G) MAP @@ -852,7 +852,7 @@ KMONS: 2 = purgy, crocodile ITEM: potion of flight / potion of agility / potion of heal wounds / \ potion of invisibility / potion of mutation / potion of degeneration / \ scroll of blinking / scroll of teleportation / scroll of fear / \ - scroll of random uselessness / ration / potion of blood / \ + scroll of random uselessness / ration / \ nothing w:140 NSUBST: e = 3:* / 9:d / *:. NSUBST: 0 = 5:1 / 1:2 / *:. diff --git a/crawl-ref/source/dat/des/portals/trove.des b/crawl-ref/source/dat/des/portals/trove.des index 901c6764ddc1..c9e03e7f2b69 100644 --- a/crawl-ref/source/dat/des/portals/trove.des +++ b/crawl-ref/source/dat/des/portals/trove.des @@ -97,7 +97,6 @@ function trove.get_trove_item(e, value, base_item) -- currently 20% armour, 15% weapon, 50% consumable, 15% miscellaneous local prices = { {base="scroll", type="acquirement", quant=2}, - {base="scroll", type="identify", quant=12+d(4)+d(4)+d(4)}, {base="miscellaneous", type="rune of Zot", quant=3, name="slimy rune of Zot"}, {base="miscellaneous", type="rune of Zot", quant=11, name="abyssal rune of Zot"}, {base="miscellaneous", type="horn of Geryon"} } @@ -194,7 +193,10 @@ function trove.get_toll_nopiety() end function trove.portal(e) - local toll = trove.get_trove_toll(e) + -- dependent on player stats (skills, race, etc) so needs to be isolated + -- from gameplay rng. + local toll = crawl.rng_wrap(function () return trove.get_trove_toll(e) end, + "subgenerator") local function stair () return trove_marker { diff --git a/crawl-ref/source/dat/des/portals/wizlab.des b/crawl-ref/source/dat/des/portals/wizlab.des index dd3d44609557..2a8aac3c26fe 100644 --- a/crawl-ref/source/dat/des/portals/wizlab.des +++ b/crawl-ref/source/dat/des/portals/wizlab.des @@ -881,11 +881,7 @@ KMONS: & = col:mutagenic primal ox-sting-weird mutant beast \ name:Cigotuvi's_Monster name_replace tile:mons_cigotuvis_monster ITEM: potion of mutation ITEM: potion of mutation q:1, potion of mutation -: if you.unrands("hat of the alchemist") then -ITEM: any jewellery randart -: else ITEM: any jewellery randart / w:5 hat_of_the_alchemist -: end ITEM: randbook disc:transmutation disc2:necromancy owner:Cigotuvi ITEM: book of transfigurations / randbook disc:transmutation ITEM: staff of death, scroll of summoning @@ -1314,19 +1310,19 @@ ENDMAP NAME: wizlab_golubria TAGS: no_pool_fixup KMONS: 1 = spatial vortex w:7 / blink frog / insubstantial wisp w:3 / nothing w:5 -KMONS: 2 = deep elf mage w:5 ; tomahawk ego:dispersal w:20 | tomahawk w:5 | nothing / \ - rakshasa w:5 ; tomahawk ego:dispersal w:20 | tomahawk w:5 | nothing / \ - boggart ; tomahawk ego:dispersal w:20 | tomahawk w:5 | nothing / \ - wizard ; tomahawk ego:dispersal w:20 | tomahawk w:5 | nothing +KMONS: 2 = deep elf mage w:5 ; boomerang ego:dispersal w:20 | boomerang w:5 | nothing / \ + rakshasa w:5 ; boomerang ego:dispersal w:20 | boomerang w:5 | nothing / \ + boggart ; boomerang ego:dispersal w:20 | boomerang w:5 | nothing / \ + wizard ; boomerang ego:dispersal w:20 | boomerang w:5 | nothing KMONS: 3 = rakshasa w:5 ; whip ego:distortion | whip ego:vorpal w:6 | dagger \ ego:distortion | dagger ego:vorpal w:6 | spear ego:distortion | \ - spear ego:vorpal w:6 . tomahawk ego:dispersal / \ + spear ego:vorpal w:6 . boomerang ego:dispersal / \ boggart w:5 ; whip ego:distortion | whip ego:vorpal w:6 | dagger \ ego:distortion | dagger ego:vorpal w:6 | spear ego:distortion | \ - spear ego:vorpal w:6 . tomahawk ego:dispersal / \ + spear ego:vorpal w:6 . boomerang ego:dispersal / \ wizard ; whip ego:distortion | whip ego:vorpal w:6 | dagger \ ego:distortion | dagger ego:vorpal w:6 | spear ego:distortion | \ - spear ego:vorpal w:6 . tomahawk ego:dispersal . robe + spear ego:vorpal w:6 . boomerang ego:dispersal . robe KMONS: 04 = large abomination w:15 / small abomination w:15 / unseen horror w:5 / \ thrashing horror w:30 / skeleton w:30 / freezing wraith w:5 / \ hellwing / chaos spawn w:5 / orange demon / ice devil / \ @@ -1351,7 +1347,7 @@ KITEM: d = scroll of blinking / scroll of teleportation q:3 w:15 / \ any scroll w:1 KITEM: e = any ring KITEM: f = book of spatial translocations w:5 / book of the warp w:5 / \ - akashic record w:5 / manual of translocations w:5 / \ + manual of translocations w:5 / \ randbook disc:translocation numspells:5 / \ randbook disc:translocation owner:Golubria numspells:7 \ spells:shroud_of_golubria&passage_of_golubria diff --git a/crawl-ref/source/dat/des/serial/gnoll_camp.des b/crawl-ref/source/dat/des/serial/gnoll_camp.des index 68f6392ed942..c284cc30ebf7 100644 --- a/crawl-ref/source/dat/des/serial/gnoll_camp.des +++ b/crawl-ref/source/dat/des/serial/gnoll_camp.des @@ -29,7 +29,7 @@ TAGS: serial_gnoll_camp patrolling no_pool_fixup WEIGHT: 8 KMASK: w = no_monster_gen MONS: gnoll -MONS: gnoll ; stone q:10 | tomahawk q:3 | throwing net q:3 / nothing +MONS: gnoll ; stone q:10 | boomerang q:3 | throwing net q:3 / nothing MONS: gnoll shaman MONS: gnoll sergeant MONS: jackal / hound / wolf w:1 @@ -56,7 +56,7 @@ NAME: gnoll_camp_medium_02 TAGS: serial_gnoll_camp patrolling WEIGHT: 8 MONS: gnoll -MONS: gnoll ; stone q:10 | tomahawk q:3 | throwing net q:3 +MONS: gnoll ; stone q:10 | boomerang q:3 | throwing net q:3 MONS: gnoll shaman MONS: gnoll sergeant MONS: jackal w:20 / hound / wolf w:1 / nothing w:5 @@ -141,7 +141,7 @@ MONS: gnoll MONS: gnoll shaman MONS: gnoll sergeant MONS: jackal / hound / wolf w:1 -MONS: gnoll ; stone q:10 | tomahawk q:3 | throwing net q:3 +MONS: gnoll ; stone q:10 | boomerang q:3 | throwing net q:3 MAP ....... .41.14. diff --git a/crawl-ref/source/dat/des/sprint/arena_sprint.des b/crawl-ref/source/dat/des/sprint/arena_sprint.des index 5e30424c3ec9..eea0df1224c9 100644 --- a/crawl-ref/source/dat/des/sprint/arena_sprint.des +++ b/crawl-ref/source/dat/des/sprint/arena_sprint.des @@ -437,7 +437,7 @@ function arena_sprint_get_monster_set(round, boss) "moth of wrath / w:20 stone giant", "ball lightning", "orb spider", "hungry ghost / harpy", "shining eye", "tormentor", - "skeletal warrior / w:5 skeletal warrior ; blowgun . needle ego:confusion" + "skeletal warrior / w:5 skeletal warrior ; dart ego:atropa" } elseif round <= 26 then mon_set = { @@ -503,7 +503,7 @@ function arena_sprint_get_monster_set(round, boss) "neqoxec", "smoke demon", "catoblepas", "moth of wrath / w:20 ettin", "orb spider", "fire crab", "shining eye", "tormentor", "tentacled monstrosity", - "skeletal warrior / skeletal warrior ; blowgun . needle ego:confusion", + "skeletal warrior / skeletal warrior ; dart ego:atropa", "unseen horror / lorocyproca / shadow wraith / ghost moth", "curse toe / w:20 wandering mushroom / w:4 murray", "reaper / hell beast / geryon w:1", @@ -1096,8 +1096,7 @@ KFEAT: R = general shop type:Premium suffix:Goods use_all count:11 greed:20 ; \ potion of haste q:5 | potion of haste q:5 |\ crystal ball of energy KFEAT: J = general shop type:Miscellaneous suffix:Merchandise count:14 greed:15 use_all ; \ - ration q:20 | potion of blood q:5 | potion of blood q:5 |\ - potion of blood q:5 | potion of blood q:5 | ring of poison resistance |\ + ration q:20 | ring of poison resistance |\ ring of protection from fire | ring of protection from cold |\ ring of protection from magic | amulet of regeneration |\ amulet of the gourmand | amulet of harm | amulet of reflection |\ diff --git a/crawl-ref/source/dat/des/sprint/menkaure.des b/crawl-ref/source/dat/des/sprint/menkaure.des index 2bb310041393..903a1e4ce9db 100644 --- a/crawl-ref/source/dat/des/sprint/menkaure.des +++ b/crawl-ref/source/dat/des/sprint/menkaure.des @@ -27,7 +27,7 @@ TAGS: sprint no_item_gen no_trap_gen no_pool_fixup no_rotate no_hmirror no_vmi ORIENT: encompass SHUFFLE: 12 SUBST: ' = '...... -KFEAT: ' = net trap / needle trap / bolt trap / spear trap / arrow trap +KFEAT: ' = net trap / dart trap / bolt trap / spear trap / arrow trap MONS: Menkaure, crocodile, kraken, flying skull MARKER: a = lua:fog_machine { \ pow_min = 1, pow_max = 2, delay = 3, \ @@ -722,7 +722,7 @@ TAGS: sprint2_transparent_cluster no_item_gen no_trap_gen no_monster_gen no_po MARKER: + = lua:restrict_door() MONS: oklob plant, orc warlord / Nikola / draconian knight, toenail golem, the Lernaean hydra SUBST: ' = '. -KFEAT: ' = net trap / needle trap / bolt trap / spear trap / arrow trap +KFEAT: ' = net trap / dart trap / bolt trap / spear trap / arrow trap KFEAT: , = book shop KFEAT: f = any shop NSUBST: | = 1:a / 2:b / 1:d / 6:$ / *:| @@ -765,7 +765,7 @@ KITEM: b = scroll of acquirement q:1 / potion of magic q:9 / potion of haste q: KITEM: h = potion of flight q:3 MONS: Nessos, jelly, sixfirhy, Gastronok, orc sorcerer, dire elephant SUBST: . = '..... -KFEAT: ' = net trap / needle trap / bolt trap / spear trap / arrow trap +KFEAT: ' = net trap / dart trap / bolt trap / spear trap / arrow trap KFEAT: q = book shop KFEAT: i = any shop MAP @@ -843,7 +843,7 @@ SUBST: e = . KITEM: Z = Orb of Zot NSUBST: a = 3:1 / 3:2 / 27:$ / *:' SUBST: ' = '........ -KFEAT: ' = net trap / needle trap / bolt trap / spear trap / arrow trap +KFEAT: ' = net trap / dart trap / bolt trap / spear trap / arrow trap KITEM: $ = gold q:27 MONS: quicksilver dragon, Lorocyproca, ballistomycete spore COLOUR: X = magenta diff --git a/crawl-ref/source/dat/des/sprint/sprint_mu.des b/crawl-ref/source/dat/des/sprint/sprint_mu.des index eb9c1dbb6258..fcbef82cca23 100644 --- a/crawl-ref/source/dat/des/sprint/sprint_mu.des +++ b/crawl-ref/source/dat/des/sprint/sprint_mu.des @@ -513,8 +513,7 @@ KMONS: g = tyrant leech KMONS: A = lindwurm KMONS: h = redback KMONS: i = hydra -KMONS: j = patrolling spriggan ; blowgun . q:5 needle ego:sleep | \ - q:5 needle ego:paralysis | q:5 needle ego:frenzy +KMONS: j = patrolling spriggan ; q:5 dart ego:atropa | q:5 dart ego:datura KMONS: k = patrolling spriggan druid KMONS: q = electric eel KFEAT: q = deep_water @@ -817,26 +816,26 @@ KMONS: a = patrolling Sigmund hd:15 hp:100 \ iskenderun's_mystic_blast.16.wizard \ ; scythe ego:distortion KMONS: b = patrolling Edmund hp:150 ; great mace ego:freezing \ - . blowgun . needle ego:curare q:7 + . dart ego:curare q:7 : elseif d2rnd == 1 then KMONS: a = patrolling Dowan hd:15 hp:100 \ spells:bolt_of_cold.11.wizard;corona.11.wizard;blink_range.11.wizard;\ bolt_of_fire.11.wizard;;haste_other.11.wizard;minor_healing.11.wizard \ ; morningstar ego:distortion KMONS: b = patrolling Duvessa hp:150 ; great sword ego:freezing \ - . blowgun . needle ego:curare q:7 + . dart ego:curare q:7 : elseif d2rnd == 2 then KMONS: a = patrolling Harold hd:15 hp:100 \ spells:poison_arrow.16.wizard;teleport_other.16.wizard;\ minor_healing.16.wizard;slow.16.wizard \ ; dire flail ego:distortion KMONS: b = patrolling Joseph hd:5 hp:150 ; quarterstaff ego:freezing \ - . blowgun . needle ego:curare q:7 + . dart ego:curare q:7 : else KMONS: a = patrolling Donald ; war axe ego:holy_wrath . scale mail \ . shield ego:reflection KMONS: b = patrolling Frances ; war axe ego:holy_wrath \ - . blowgun . needle ego:curare q:7 + . dart ego:curare q:7 :end KITEM: & = lamp of fire, scroll of acquirement q:1 ident:all, \ scroll of acquirement q:1 ident:all / w:15 scroll of torment ident:all, \ @@ -1048,7 +1047,7 @@ KMONS: 7 = patrolling simulacrum SUBST: 1 = 1122334455667 KITEM: 1234567 = star_item / nothing KFEAT: ^ = zot trap -KFEAT: - = spear trap / needle trap / net trap / blade trap / bolt trap +KFEAT: - = spear trap / dart trap / net trap / blade trap / bolt trap NSUBST: ~ = 1:^ / 10:- / *:. SUBST: ! = + @@ -1070,7 +1069,7 @@ MONS: patrolling iron golem, patrolling necromancer MONS: patrolling hell knight, patrolling skeletal warrior KMONS: 5 = lich KITEM: 5 = star_item, star_item, star_item, star_item, star_item, star_item -KFEAT: - = spear trap / needle trap / net trap / blade trap / bolt trap +KFEAT: - = spear trap / dart trap / net trap / blade trap / bolt trap NSUBST: . = 6:- / *:. MAP @@ -1088,7 +1087,7 @@ ENDMAP NAME: sprint_mu_crypt_lich TAGS: sprint_mu_crypt no_rotate no_hmirror no_vmirror MONS: ancient lich, skeletal warrior -KFEAT: - = spear trap / needle trap / net trap / blade trap / bolt trap +KFEAT: - = spear trap / dart trap / net trap / blade trap / bolt trap NSUBST: . = 10:- / *:. SUBST: : = . @@ -1110,7 +1109,7 @@ NAME: sprint_mu_tomb_spiral TAGS: sprint_mu_tomb no_rotate no_hmirror no_vmirror MONS: patrolling greater mummy, patrolling mummy priest MONS: patrolling guardian mummy, patrolling sphinx -KFEAT: - = needle trap / net trap / blade trap / bolt trap / spear trap / zot trap +KFEAT: - = dart trap / net trap / blade trap / bolt trap / spear trap / zot trap SUBST: . = -----.................332 KITEM: $ = golden rune of zot KITEM: ? = scroll of acquirement ident:all q:5 diff --git a/crawl-ref/source/dat/des/sprint/zigsprint.des b/crawl-ref/source/dat/des/sprint/zigsprint.des index 4875197f5856..c374ab8363a6 100644 --- a/crawl-ref/source/dat/des/sprint/zigsprint.des +++ b/crawl-ref/source/dat/des/sprint/zigsprint.des @@ -796,7 +796,7 @@ KITEM: R = obsidian_axe ident:all, \ dark_maul ident:all, \ sniper ident:all, \ majin-bo ident:all -KITEM: T = piercer ident:all, \ +KITEM: T = storm_bow ident:all, \ singing_sword ident:all, \ mace_of_variability ident:all, \ sword_of_power ident:all, \ @@ -810,10 +810,18 @@ KITEM: S = lightning rod ident:all KFEAT: ^ = granite_statue KFEAT: & = granite_statue COLOUR: & = magenta + +# Don't place the guaranteed artefacts (represented by glyphs R and T) any +# lower in this subvault. They are placed on the top line to ensure they are +# generated before any superb_items in the sprint. If the superb_items were +# generated before the guaranteed artefacts, they would be able to generate the +# guaranteed artefacts elsewhere in the sprint, which would prevent them from +# spawning in the initial room. + MAP ^g^h^j&A&k^l^m^ -f.............n -^.........RT..^ +f.........RT..n +^.............^ e..HG.........o ^..IF.........^ d..JE.....S...q diff --git a/crawl-ref/source/dat/des/tutorial/lesson2.des b/crawl-ref/source/dat/des/tutorial/lesson2.des index 8b3d63f2b975..8ded75d477fa 100644 --- a/crawl-ref/source/dat/des/tutorial/lesson2.des +++ b/crawl-ref/source/dat/des/tutorial/lesson2.des @@ -100,10 +100,10 @@ FTILE: deABCDEF = tutorial_pad MONS: training dummy hp:2 ; stone q:10 MONS: rat always_corpse MONS: worm -ITEM: tomahawk mundane q:10 +ITEM: boomerang mundane q:10 ITEM: shortbow mundane not_cursed ITEM: arrow mundane q:30 -MARKER: d = lua:tutorial2.msg("tomahawks") +MARKER: d = lua:tutorial2.msg("boomerangs") MARKER: A = lua:tutorial2.msg("throwing") MARKER: B = lua:tutorial2.msg("resting_reminder") MARKER: e = lua:tutorial2.msg("wield_bow") diff --git a/crawl-ref/source/dat/des/variable/arcadia.des b/crawl-ref/source/dat/des/variable/arcadia.des index b2533d5161bf..ea4d0e848c23 100644 --- a/crawl-ref/source/dat/des/variable/arcadia.des +++ b/crawl-ref/source/dat/des/variable/arcadia.des @@ -36,7 +36,7 @@ KMONS: N = spriggan ; robe . quarterstaff KMONS: O = dire elephant / death yak / emperor scorpion / anaconda w:5 KMONS: Q = bush KMONS: R = plant -ITEM: dagger / short sword / rapier / flail / whip / blowgun / shortbow +ITEM: dagger / short sword / rapier / flail / whip / shortbow ITEM: robe / cloak / hat / buckler / steam dragon scales w:1 ITEM: ration q:3 KFEAT: _ = altar_trog diff --git a/crawl-ref/source/dat/des/variable/d_encompass.des b/crawl-ref/source/dat/des/variable/d_encompass.des index a0f09d3aa439..f8a80f33a7ee 100644 --- a/crawl-ref/source/dat/des/variable/d_encompass.des +++ b/crawl-ref/source/dat/des/variable/d_encompass.des @@ -158,7 +158,7 @@ KMONS: t = draconian knight / draconian scorcher / draconian annihilator : end : end : end -: table.sort(kmonsters) +: util.sort(kmonsters) : kmons("s = " .. table.concat(kmonsters, " / ")) KITEM: ? = any ring randart w:390 / ring_of_the_octopus_king KITEM: ! = ring of slaying / ring of wizardry / any good_item ring no_uniq w:75 diff --git a/crawl-ref/source/dat/des/variable/float.des b/crawl-ref/source/dat/des/variable/float.des index fd6db25cbe80..e1a101a7bb30 100644 --- a/crawl-ref/source/dat/des/variable/float.des +++ b/crawl-ref/source/dat/des/variable/float.des @@ -3245,11 +3245,7 @@ KMONS: 2 = shock serpent / storm dragon ITEM: book of air / book of the sky / manual of air magic / \ staff of air ident:type / lightning rod w:15 / fan of gales w:15 / \ storm dragon scales w:5 -: if you.unrands("arc blade") then -KITEM: 2 = quick blade ego:electrocution randart ident:type -: else -KITEM: 2 = arc_blade -: end +KITEM: 2 = arc_blade ident:type : minmay_elemental_octagon_setup(_G) MAP xxx@xxx @@ -3277,11 +3273,7 @@ KMONS: 2 = stone giant / iron dragon ITEM: book of geomancy / book of the earth / manual of earth magic / \ staff of earth ident:type / ring of protection ident:type / \ wand of scattershot ident:type / crystal plate armour w:5 -: if you.unrands("devastator") then -KITEM: 2 = eveningstar randart -: else KITEM: 2 = devastator -: end : minmay_elemental_octagon_setup(_G) MAP xxx@xxx @@ -3309,15 +3301,9 @@ KMONS: 2 = frost giant / ice dragon ITEM: book of frost / book of ice / manual of ice magic / \ wand of iceblast ident:type / staff of cold ident:type / \ ring of ice ident:type / phial of floods / ice dragon scales w:5 -: if you.unrands("frostbite") then -KITEM: 2 = executioner's axe ego:freezing randart ident:type / \ - battleaxe ego:freezing randart ident:type / \ - broad axe ego:freezing randart ident:type -: else -KITEM: 2 = frostbite / \ +KITEM: 2 = frostbite ident:type / \ battleaxe ego:freezing randart ident:type / \ broad axe ego:freezing randart ident:type -: end : minmay_elemental_octagon_setup(_G) MAP xxx@xxx @@ -3345,11 +3331,7 @@ KMONS: 2 = fire giant / fire dragon ITEM: book of flames / book of fire / manual of fire magic / \ staff of fire ident:type / ring of fire ident:type / lamp of fire / \ fire dragon scales w:5 -: if you.unrands("firestarter") then -KITEM: 2 = great mace ego:flaming randart ident:type -: else -KITEM: 2 = firestarter -: end +KITEM: 2 = firestarter ident:type : minmay_elemental_octagon_setup(_G) MAP xxx@xxx @@ -3374,12 +3356,7 @@ ORIENT: float DEPTH: Depths, Crypt MONS: necromancer / vampire mage MONS: lich / ancient lich w:5 -: if you.unrands("majin-bo") then -ITEM: quarterstaff randart ego:vampirism ident:type no_pickup \ - / good_item lajatang no_pickup -: else -ITEM: majin-bo no_pickup -: end +ITEM: majin-bo no_pickup ident:type ITEM: any magical staff / any book NSUBST: d = 1:d / *:e NSUBST: 1 = 4:1 / 8=1... / . @@ -3408,11 +3385,7 @@ NAME: minmay_guarded_unrand_sceptre_of_torment ORIENT: float DEPTH: Depths, Crypt MONS: mummy priest, greater mummy -: if you.unrands("sceptre_of_torment") then -KITEM: d = any weapon good_item randart no_pickup -: else -KITEM: d = sceptre_of_torment no_pickup -: end +KITEM: d = sceptre_of_torment no_pickup ident:type KMONS: d = greater mummy MAP ccccccccccc@ccccccccccc @@ -3432,11 +3405,7 @@ NAME: minmay_guarded_unrand_sword_of_power ORIENT: float DEPTH: D:13- MONS: vault guard -: if you.unrands("sword_of_power") then -ITEM: any weapon good_item randart no_pickup -: else ITEM: sword_of_power no_pickup -: end MAP vvvvvvvv vv......vv @@ -3454,11 +3423,7 @@ ENDMAP NAME: minmay_guarded_unrand_guard DEPTH: D:12-, Depths:1-4 ORIENT: float -: if you.unrands("glaive_of_the_guard") then -ITEM: bardiche randart no_pickup -: else ITEM: glaive_of_the_guard no_pickup -: end SUBST: b : bxcmG.T NSUBST: 3:9 / 4=9. / ., 8 = 2:8 / 6=8.. / . MAP @@ -3957,18 +3922,28 @@ xx...xx xxxxxx .@. ENDMAP -NAME: cheibrodos_wizards_tower -TAGS: no_item_gen no_monster_gen no_pool_fixup -DEPTH: D:7-12, Elf +NAME: cheibrodos_wizards_tower +TAGS: no_pool_fixup +DEPTH: D:7-, Elf ORIENT: float -: if you.branch() == "Elf" then -MONS: deep elf sorcerer / deep elf annihilator +ITEM: robe good_item / cloak good_item / scarf / pair of boots good_item \ + / pair of gloves good_item / hat good_item +ITEM: randbook w:40 / any magical staff +: if you.in_branch("D") then +MONS: wizard / orc sorcerer, deep elf annihilator +: if you.depth() < 10 then +NSUBST: " = 1 / 1... / ., - = d / *% / %$.. / . +: elseif you.depth() < 13 then +NSUBST: " = 1 / 1. / 2... / ., - = de / |* / %$ / . +: else +NSUBST: " = 1 / 2=1. / 2. / ., - = d / e / |* / %$ / . +: end : else -MONS: wizard / orc sorcerer +MONS: deep elf sorcerer / deep elf annihilator +NSUBST: " = 2=1 / 4=1. / ., - = d / e / 2=|* / *% / %$.. / . : end SHUFFLE: AB / CD / EF -SUBST: A = ., B = W, CDEF = w -NSUBST: " = 2:1 / 2=1., - = 1=*| / 2=*% / 2=%. +SUBST: A = ., B = W, CDEF = w MAP ....@.... ..wwFEFww.. @@ -8502,6 +8477,7 @@ DEPTH: D:8-, Depths ORIENT: float KMONS: 8 = 8 KITEM: 8 = | +KPROP: 8 = no_tele_into SUBST: c : cvb, x : xcvb MAP xxxxxx@xx @@ -9108,13 +9084,7 @@ NAME: emtedronai_trogs_sanctum TAGS: no_monster_gen no_trap_gen no_tele_into no_item_gen DEPTH: D:13-, Depths ORIENT: float -: if not you.unrands("necklace_of_bloodlust") then -KITEM: _ = necklace of bloodlust no_pickup -: elseif not you.unrands("wrath_of_trog") then -KITEM: _ = wrath of trog no_pickup -: else -KITEM: _ = nothing -: end +KITEM: _ = necklace of bloodlust no_pickup / wrath of trog no_pickup ITEM: manual of armour w:30 / manual of fighting w:30 / \ manual of long blades / manual of axes / manual of maces & flails / \ manual of polearms / manual of dodging / manual of shields diff --git a/crawl-ref/source/dat/des/variable/ghost.des b/crawl-ref/source/dat/des/variable/ghost.des index 394a04bc259b..9db25944a6f7 100644 --- a/crawl-ref/source/dat/des/variable/ghost.des +++ b/crawl-ref/source/dat/des/variable/ghost.des @@ -812,7 +812,7 @@ KITEM: P = robe cursed plus:-8 randart ident:all ITEM: quick blade good_item w:5 / rapier good_item / scimitar good_item / \ double sword good_item w:5 / demon blade good_item w:5 / \ triple sword good_item w:5 / morningstar good_item / \ - eveningstar good_item w:5 / demon whip good_item w:5 \ + eveningstar good_item w:5 / demon whip good_item w:5 / \ dire flail good_item / great mace good_item / broad axe good_item / \ battleaxe good_item / executioner's axe good_item w:5 / \ demon trident good_item w:5 / glaive good_item / \ @@ -1078,9 +1078,8 @@ ENDMAP NAME: biasface_ghost_orc_armoury DEPTH: D:3-, Orc -ITEM: pair of boots / helmet / hat / pair of gloves / cloak -ITEM: pair of boots good_item / helmet good_item / hat good_item / \ - pair of gloves good_item / cloak good_item +:item(dgn.aux_armour) +:item(dgn.good_aux_armour) ITEM: any armour, any armour good_item, any weapon, any weapon good_item ITEM: any jewellery, any jewellery good_item KMONS: O = player ghost @@ -1550,9 +1549,9 @@ KMONS: pq = plant / fungus KFEAT: q = W KMONS: O = player ghost MONS: kobold, big kobold, kobold demonologist -KMONS: D = kobold ; blowgun . needle ego:curare | needle ego:poisoned . dagger -KMONS: E = big kobold ; blowgun . needle ego:curare | \ - needle ego:poisoned w:5 . dagger | short sword | whip +KMONS: D = kobold ; dart ego:curare | dart ego:poisoned . dagger +KMONS: E = big kobold ; dart ego:curare | \ + dart ego:poisoned w:5 . dagger | short sword | whip KMONS: F = kobold ; wand of flame | wand of acid | wand of polymorph | \ wand of enslavement | wand of paralysis | \ wand of disintegration . dagger @@ -1998,7 +1997,7 @@ NSUBST: R = R / - {{ kmons("q = kobold; " .. wrathful_weapon("kobold", crawl.one_chance_in(3) and "randart" or "good_item") .. - " . blowgun good_item . needle ego:curare | needle ego:poisoned w:5") + " . dart ego:curare | dart ego:poisoned w:5") kmons("r = gnoll sergeant; " .. wrathful_weapon("sergeant", crawl.one_chance_in(3) and "randart" or "good_item") .. " . leather armour good_item | ring mail good_item" .. @@ -2090,7 +2089,7 @@ kmons("q = deep elf master archer; longbow randart " .. MONS: deep elf blademaster / deep elf master archer SUBST: fg = |*, h = *, i = $%, j= - # There aren't any good lower-tier, trog-appropriate warrior types. So use all -# blademasters and master archers, but reduce the reduce monster count. +# blademasters and master archers, but reduce the monster count. NSUBST: - = 1 / 2=1. / 2=L / q / 3=2. / . : elseif you.in_branch("Depths") then MONS: tengu warrior / deep troll / spriggan berserker / stone giant / ettin diff --git a/crawl-ref/source/dat/des/variable/grated_community.des b/crawl-ref/source/dat/des/variable/grated_community.des index 5e2092a80363..3b2fd43724a6 100644 --- a/crawl-ref/source/dat/des/variable/grated_community.des +++ b/crawl-ref/source/dat/des/variable/grated_community.des @@ -132,7 +132,7 @@ KMONS: 2 = generate_awake spectral hound KMONS: 3 = generate_awake spectral wolf KMONS: 4 = generate_awake spectral warg KMONS: V = vampire knight / vampire mage -KITEM: V = potion of blood / q:2 potion of blood / q:3 potion of blood, any good_item +KITEM: V = any good_item KFEAT: g = iron_grate KFEAT: _ = altar_kikubaaqudgha KFEAT: ! = fountain_blood diff --git a/crawl-ref/source/dat/des/variable/mini_monsters.des b/crawl-ref/source/dat/des/variable/mini_monsters.des index 8c14fb673533..9bc9c256497e 100644 --- a/crawl-ref/source/dat/des/variable/mini_monsters.des +++ b/crawl-ref/source/dat/des/variable/mini_monsters.des @@ -883,11 +883,7 @@ ITEM: Young Poisoner's Handbook / book of Changes / \ randbook numspells:4 title:Nature \ spells:beastly_appendage&sting&sticks_to_snakes&spider_form KMASK: 'f = opaque -: if crawl.coinflip() then -KITEM: f = javelin ego:poisoned q:4-6 / tomahawk ego:poisoned -: else -KITEM: f = blowgun / w:5 nothing, needle ego:poisoned / needle ego:curare / needle ego:paralysis -: end +KITEM: f = dart ego:poisoned / dart ego:curare / dart ego:atropa KITEM: g = any potion q:2-3 MONS: plant, oklob sapling, ball python MONS: adder, oklob plant, water moccasin @@ -938,11 +934,7 @@ ITEM: Young Poisoner's Handbook / book of Changes / \ spells:beastly_appendage&summon_small_mammal&call_canine_familiar&spider_form / \ randbook numspells:4 title:Nature \ spells:beastly_appendage&sting&sticks_to_snakes&spider_form -: if crawl.coinflip() then -KITEM: f = javelin ego:poisoned q:4-6 / tomahawk ego:poisoned -: else -KITEM: f = blowgun / w:5 nothing, needle ego:poisoned / needle ego:curare / needle ego:paralysis -: end +KITEM: f = dart ego:poisoned / dart ego:curare / dart ego:atropa KITEM: g = any potion q:2 / any potion q:3 MONS: plant, oklob sapling, ball python MONS: adder, oklob plant, water moccasin @@ -1298,10 +1290,10 @@ ENDMAP NAME: minmay_unpleasant_kobolds TAGS: transparent DEPTH: D:6- -MONS: kobold ; tomahawk ego:dispersal q:2 / w:3 big kobold ; tomahawk ego:dispersal q:4 -MONS: kobold ; tomahawk ego:exploding q:2 / w:3 big kobold ; tomahawk ego:exploding q:4 -MONS: kobold ; blowgun . needle ego:curare q:1 / \ - w:3 big kobold ; blowgun . needle ego:curare q:2 +MONS: kobold ; boomerang ego:dispersal q:2 / w:3 big kobold ; boomerang ego:dispersal q:4 +MONS: kobold ; boomerang ego:silver q:2 / w:3 big kobold ; boomerang ego:silver q:4 +MONS: kobold ; dart ego:curare q:1 / \ + w:3 big kobold ; dart ego:curare q:2 : if you.absdepth() > 13 then SUBST: 1 = 3 : elseif you.absdepth() > 11 then @@ -2267,7 +2259,7 @@ DEPTH: D:12-, Elf, !Elf:$ MONS: red devil ; spear | trident . ring mail | scale mail MONS: red devil ; spear | trident . buckler MONS: red devil ; spear good_item | trident good_item -MONS: red devil ; spear | trident . tomahawk +MONS: red devil ; spear | trident . boomerang SHUFFLE: 1234 KMASK: l = no_monster_gen MAP @@ -2828,20 +2820,6 @@ c.....c.....c.....c cccbcccc.@.ccccbccc ENDMAP -NAME: evilmike_surprise_oklob -DEPTH: D:9-, Elf -WEIGHT: 5 -KMASK: x = no_wall_fixup -KMONS: 1 = oklob plant -KITEM: 1 = star_item / nothing -KITEM: | = gold, superb_item -MAP -ccccccccccc -c1x|mmm...c -ccccccccc.c -ccccccccc@c -ENDMAP - NAME: hangedman_tree_tricks DEPTH: D:8-, !D:$ TAGS: extra no_monster_gen @@ -4554,9 +4532,9 @@ MAP ....n|n.... .cccc+cccc. .c8^n_n^8c. -.cccc.cccc. +.cc^c.c^cc. .c8^n.n^8c. -.cccc.cccc. +.cc^c.c^cc. .c8^n.n^8c. .cccc+cccc. ........... @@ -4853,6 +4831,7 @@ ENDMAP NAME: minmay_hidden_floor DEPTH: D:12-, Depths SUBST: 9 = 9999., 8 = 8888. +KPROP: 8*$ = no_tele_into MAP @ cccccnccccc @@ -4860,7 +4839,7 @@ c.........c c.ccc9ccc.c c.c8ncn8c.c c.cn***nc.c -@.9c*.*c9.n@ +@.9c*$*c9.n@ cncn***nc.c c.=8ncn8c.c c.ccc9ccc.c @@ -6730,11 +6709,7 @@ DEPTH: D:12- MONS: deep elf mage / ogre mage / wizard / \ kobold demonologist w:2 / vampire mage w:2 / naga mage w:2 NSUBST: 1 = 6:1 / 10=1.... / . -: if you.unrands("glaive of prune") then -ITEM: any weapon good_item randart no_pickup -: else ITEM: glaive_of_prune no_pickup -: end SUBST: " : .:50 cvbnG SUBST: ' : .:50 cvbnG SUBST: ? : .:50 cvbnG @@ -6769,12 +6744,7 @@ TAGS: transparent DEPTH: D:8- MONS: chaos spawn KFEAT: _ = altar_xom -: if you.unrands("mace of variability") then -# could be a launcher! -ITEM: any weapon good_item randart ego:chaos ident:type no_pickup -: else -ITEM: mace_of_variability no_pickup -: end +ITEM: mace_of_variability no_pickup ident:type # walls are carefully placed and guarantee no islands SUBST: x = xcvbmGwWlTUVYt+ <:4 >:4 A:2 1:160 NSUBST: 1 = 2:1 / 2:% / 3=1%...... / . @@ -6798,12 +6768,8 @@ TAGS: no_monster_gen no_trap_gen transparent MONS: worker ant / soldier ant MONS: killer bee MONS: adder / water moccasin -: if you.unrands("staff_of_olgreb") then -ITEM: young poisoner's handbook / ring of poison resistance ident:type / \ - staff of poison ident:type / swamp dragon scales -: else -ITEM: staff_of_olgreb no_pickup -: end +# TODO: built in fallback for staff of olgreb is kind of dumb +ITEM: staff_of_olgreb no_pickup ident:type NSUBST: 1 = 5:1 / 6=1.. / ., 2 = 5:2 / 9=2.. / ., 3 = 5:3 / 6=1.. / . MAP ... @@ -6841,11 +6807,7 @@ MONS: vampire mosquito / tyrant leech / vampire, vampire knight : else MONS: vampire knight, vampire mage : end -: if you.unrands("vampires_tooth") then -ITEM: quick blade randart no_pickup -: else ITEM: vampires_tooth no_pickup -: end MAP ......... .ccc.ccc. @@ -6863,11 +6825,7 @@ TAGS: transparent DEPTH: D:14-, Depths MONS: spriggan berserker NSUBST: 1 = 3:1 / 9=1...... / . -: if you.unrands("wrath_of_trog") then -ITEM: executioner's axe randart ego:antimagic ident:type no_pickup -: else -ITEM: wrath_of_trog no_pickup -: end +ITEM: wrath_of_trog no_pickup ident:type MAP ....... ..""""".. @@ -6895,12 +6853,7 @@ NSUBST: d = 1:d / *:* MONS: skeletal warrior NSUBST: d = 1:d / *:% : end -: if you.unrands("skullcrusher") then -ITEM: giant club randart ego:speed ident:type no_pickup / \ - giant spiked club randart no_pickup -: else -ITEM: skullcrusher no_pickup -: end +ITEM: skullcrusher no_pickup ident:type NSUBST: ' = 12:1 / 14=1...... / . SUBST: " = 1., ! = 2. MAP @@ -6934,11 +6887,7 @@ NAME: minmay_guarded_unrand_finisher TAGS: transparent DEPTH: D:7-13 MONS: wraith -: if you.unrands("finisher") then -ITEM: scythe randart ego:speed ident:type no_pickup -: else -ITEM: finisher no_pickup -: end +ITEM: finisher no_pickup ident:type SUBST: 1 = 1. NSUBST: A = 1:. / *:c, B = 1:. / *:c SUBST: c = c .:2 @@ -6962,12 +6911,7 @@ NAME: minmay_guarded_unrand_resistance DEPTH: D:9-14 TAGS: no_monster_gen no_trap_gen transparent KMASK: lw = opaque -: if you.unrands("shield_of_resistance") then -ITEM: buckler randart no_pickup / shield randart no_pickup / \ - large shield randart no_pickup -: else ITEM: shield_of_resistance no_pickup -: end MONS: fire dragon, ice dragon MAP ........... @@ -6986,34 +6930,28 @@ ENDMAP NAME: chequers_guarded_unrand_ignorance DEPTH: D:7-12, Orc TAGS: no_monster_gen no_trap_gen transparent -: if you.unrands("large_shield_of_ignorance") then -ITEM: large shield randart no_pickup -: else ITEM: large_shield_of_ignorance no_pickup -: end MONS: orange crystal statue -MAP -........... -.cccc=cccc. -.c1.....1c. -.c.c...c.c. -.c.......c. -.n...d...n. -.c.......c. -.c.c...c.c. -.c1.....1c. -.cncc=cccc. -........... +KPROP: .d1 = no_tele_into +SUBST: - = . +MAP +----------- +-cccc=cccc- +-c1.....1c- +-c.c...c.c- +-c.......c- +-n...d...n- +-c.......c- +-c.c...c.c- +-c1.....1c- +-cncc=cccc- +----------- ENDMAP NAME: chequers_guarded_unrand_mask_of_the_dragon DEPTH: Zot, !Zot:$ TAGS: no_monster_gen no_trap_gen -: if you.unrands("mask_of_the_dragon") then -ITEM: hat randart no_pickup -: else ITEM: mask_of_the_dragon no_pickup -: end # Shuffle one transporter destination location NSUBST: r = 1:R / *:- # Randomly swap which transporter is the entrance and the loot locations. @@ -7031,6 +6969,7 @@ MARKER: R = lua:transp_loc("mask_of_the_dragon_exit") MARKER: S = lua:transp_dest_loc("mask_of_the_dragon_exit") KMASK: @" = !opaque KFEAT: " = floor +KPROP: -$*|rQDEFH = no_tele_into MAP """"""""" """""""""ccccccc" @@ -7058,11 +6997,7 @@ ENDMAP NAME: brannock_guarded_unrand_thermic_engine DEPTH: Depths TAGS: no_monster_gen no_item_gen transparent -: if you.unrands("maxwell's_thermic_engine") then -ITEM: demon blade randart no_pickup -: else ITEM: maxwell's_thermic_engine no_pickup -: end MONS: ice devil, blizzard demon, ice fiend MONS: sun demon, balrug, brimstone fiend # 6-2-1, 9 total demons for each side @@ -7086,11 +7021,7 @@ TAGS: no_monster_gen no_item_gen transparent DEPTH: D:5-8 ORIENT: float MONS: rat, river rat, hell rat -: if you.unrands("ratskin_cloak") then -ITEM: cloak randart -: else ITEM: ratskin_cloak -:end NSUBST: - = 8:1 / *:. NSUBST: ' = 6:1 / 4:2 / 2:23 / 1:3 / *:. MAP diff --git a/crawl-ref/source/dat/descript/ability.txt b/crawl-ref/source/dat/descript/ability.txt index ebe3078270d0..98035fa94827 100644 --- a/crawl-ref/source/dat/descript/ability.txt +++ b/crawl-ref/source/dat/descript/ability.txt @@ -14,7 +14,8 @@ Randomly translocates the user a short distance. Breathe Dispelling Energy ability Breathes a bolt of dispelling energy at a targeted creature, possibly removing -some of its enchantments. +some of its enchantments. The range of the bolt is decreased by one for every +creature it strikes. %%%% Breathe Fire ability @@ -51,20 +52,32 @@ for a short time. It may also splash onto other nearby creatures on impact. Breathe Steam ability Breathes a jet of steam at a targeted location, which will scald any creatures -it hits and will also obscure vision. +it hits and will also obscure vision. The range of the jet is decreased by one +for every creature it strikes. %%%% Breathe Noxious Fumes ability -Breathes a blast of noxious fumes at a targeted creature. +Breathes a blast of noxious fumes at a targeted creature. The range of the blast +is decreased by one for every creature it strikes. %%%% Bat Form ability Transforms the user into a swift-moving vampire bat, increasing evasion but -substantially weakening melee attacks. +substantially weakening melee attacks. The transformation drains the user's +attributes. While tranformed, any equipped weapons, armour and rings are melded, and the user becomes unable to cast spells. %%%% +Exsanguinate ability + +Remove all of your blood, transitioning to a bloodless state. +%%%% +Revivify ability + +Return to life, transitioning from bloodless to alive. This process renders +the user temporarily frail. +%%%% Fly ability Starts flight. During flight you can safely cross water and similar obstacles. @@ -248,6 +261,8 @@ skeletons cannot leave the level they were created on. %%%% Drain Life ability +# Note: part of this description duplicates the Drain Life spell description. + Drains the life force of any nearby creatures, healing the user depending on the damage dealt. The amount of life force drained is increased by Invocations skill. @@ -297,16 +312,6 @@ Summons a major demon. Failing to use this ability correctly will turn the demon hostile. %%%% # Sif Muna -Divine Energy ability - -Calls on Sif Muna for divine energy. While active, Sif Muna will allow you to -cast spells even with insufficient reserves of magic. Doing so will cause you -to briefly lose access to your magic after the spell is cast. -%%%% -Stop Divine Energy ability - -Stops requesting divine energy from Sif Muna, no longer allowing you to cast -spells without sufficient magical reserves. %%%% Channel Magic ability @@ -317,6 +322,12 @@ Forget Spell ability Removes a known spell from memory, so as to free memory to learn others. %%%% +Divine Exegesis ability + +Casts any spell from your spell library with guaranteed success. You receive a +bonus to spell power based on your Invocations skill that is worth 50% more +than equivalent levels of training in the relevant skills of the spell itself. +%%%% # Trog Berserk ability @@ -470,47 +481,33 @@ Bring an orc corpse back to life. The orc will be an ally, even if it was not already one before it died. %%%% # Fedhas -Fungal Bloom ability +Wall of Briars ability -Call up Fedhas to accelerate the natural process of decay, transforming all -corpses nearby into fast-growing, short-lived toadstools. Zombies, ghouls, -and necrophages are considered walking corpses, and are affected accordingly; -zombies rot into skeletons, and ghouls and necrophages rot away entirely, -granting no experience. +Grow short-lived briar patches around you. Monsters attacking these briars will +be damaged by sharp thorns. Invocations skill increases the briars' health. %%%% -Sunlight ability +Grow Ballistomycete ability -Calls sunlight down over a small area of the dungeon. Monsters illuminated by -the light will be easier to hit. Water affected by the sunlight will evaporate. -In particular, deep water will become shallow water, and shallow water will dry -up completely, revealing the dungeon floor. -%%%% -Growth ability - -Uses food to grow a ring of plants around yourself. If a complete ring cannot -be formed (because you do not have enough food), plants will grow on squares -adjacent to you that are close to monsters. The plants you create gain bonus -health proportional to your Invocations skill. -%%%% -Rain ability +Grow a short-lived ballistomycete at the location you choose. A Ballistomycete +occasionally release spores that quickly seek out foes and violently explode, +damaging and confusing all living things caught in the blast. -Causes rain to fall around you, turning the dungeon floor into shallow water -and shallow water into deep water. This may also allow plants and mushrooms to -grow in areas that are not directly drenched. Invocations skill increases the -number of rain-clouds created. +Invocations skill increases the health and duration of the ballistomycete as +well as the damage of its exploding spores. %%%% -Reproduction ability +Overgrow ability -Creates ballistomycete spores from corpses in your line of sight. The spores will -explode, causing damage to you or your enemies. +Permanently destroy a wall-like structure, growing a short-lived plant ally in +its place. Even a tree will give its life to support the will of Fedhas, and +only truly indestructible walls are immune to this power. %%%% -Evolution ability +Grow Oklob ability -Turns plants and fungi into stronger species. Upgraded plants gain a bonus to attack -accuracy proportional to the user's Invocations skill, and oklob plants are -more likely to spit acid at higher Invocations. +Grow a short-lived oklob plant at the location you choose. These dangerous +plants spray corrosive vitriol at your foes. -Upgrading fungi requires piety, while upgrading plants requires food. +Invocations skill increases the health and duration of the oklob as well as the +frequency with which it sprays its acid. %%%% # Cheibriados Bend Time ability @@ -590,7 +587,7 @@ Call Merchant ability Sends funds to a merchant to help them set up shop at your location. The cost of funding a shop increases with the number of shops funded.{{ - if you.race() == "Mummy" then + if you.race() == "Mummy" or you.race() == "Vampire" then return end @@ -598,8 +595,6 @@ funding a shop increases with the number of shops funded.{{ if you.race() == "Ghoul" then desc = desc .. "carrion" - elseif you.race() == "Vampire" then - desc = desc .. "blood" else desc = desc .. "food" end diff --git a/crawl-ref/source/dat/descript/backgrounds.txt b/crawl-ref/source/dat/descript/backgrounds.txt index e798d8d42a4e..fa57e9a0c7f7 100644 --- a/crawl-ref/source/dat/descript/backgrounds.txt +++ b/crawl-ref/source/dat/descript/backgrounds.txt @@ -21,8 +21,7 @@ tools. %%%% Assassin -Assassins carry a blowgun with poisoned needles, as well as a small number of -nasty curare needles. +Assassins carry poisoned darts and a small number of nasty curare darts. %%%% Berserker diff --git a/crawl-ref/source/dat/descript/features.txt b/crawl-ref/source/dat/descript/features.txt index 0c7d22dcb558..6da2175fbdd8 100644 --- a/crawl-ref/source/dat/descript/features.txt +++ b/crawl-ref/source/dat/descript/features.txt @@ -86,11 +86,6 @@ An iron altar of Okawaru An altar of iron and steel dedicated to the Warmaster Okawaru. Its edges are surprisingly sharp. %%%% -An oddly glowing altar of Pakellas - -An altar to Pakellas the Inventive, adorned with strange half-completed devices -and tools, and glowing with an odd, persistent light. -%%%% A stormy altar of Qazlal An altar dedicated to Qazlal Stormbringer, surrounded by fierce winds, @@ -101,10 +96,11 @@ A sacrificial altar of Ru An altar to Ru the Awakened in the shape of a pair of scales, with a heart in one scale and a pictogram representing power and enlightenment in the other. %%%% -A deep blue altar of Sif Muna +A shimmering blue altar of Sif Muna -An altar of the deepest blue, covered in intricate patterns and writings in -long forgotten scripts, devoted to Sif Muna the Loreminder. +An altar to Sif Muna the Loreminder, covered in intricate patterns and writings +in long forgotten scripts. Atop a deep blue pedestal hovers a shimmering +condensation of pure magic. %%%% A bloodstained altar of Trog @@ -246,15 +242,21 @@ A tree A large tree. While in most places the dim light of the dungeon is not bright enough to sustain large plants, there are spots where, with the grace of -Fedhas, trees as big as those on the surface can grow underground. It is -susceptible to bolts of lightning and to sufficiently intense sources of fire. +Fedhas, trees as big as those on the surface can grow underground. %%%% An awoken tree A large tree that has been enchanted to grant it some limited motility, allowing it to lash out out at any adjacent enemies of the creature who awoke -it. It is susceptible to bolts of lightning and to sufficiently intense sources -of fire. +it. +%%%% +An awoken autumnal tree + + +%%%% +An awoken dead tree + + %%%% An abandoned shop @@ -708,7 +710,8 @@ A passage leading to a sewer. It is gradually rusting away. %%%% A portal to a secret trove of treasure -A portal leading to a treasure trove. +A portal leading to a treasure trove. This portal is unstable and will collapse +after a single use. %%%% A dark tunnel diff --git a/crawl-ref/source/dat/descript/gods.txt b/crawl-ref/source/dat/descript/gods.txt index 1c6d93923a87..5fde8b176b2e 100644 --- a/crawl-ref/source/dat/descript/gods.txt +++ b/crawl-ref/source/dat/descript/gods.txt @@ -44,11 +44,9 @@ followers can convert to the Shining One or Zin while keeping some piety. %%%% Fedhas -The god of plant and fungal life, Fedhas Madash demands that followers -encourage the decomposition of corpses. Fedhas also forbids followers from -harming any species under Fedhas' protection, and from using any necromantic -effects that interfere with corpses. In return Fedhas grants a number of -abilities that promote the growth of plants and fungi. These abilities may +Fedhas is the god of plant and fungal life. Followers are forbidden from +harming any species under Fedhas' protection. In return Fedhas grants a number +of abilities that promote the growth of plants and fungi. These abilities may incidentally prove useful to adventurers. %%%% Gozag @@ -153,10 +151,10 @@ foes. Sif Muna Sif Muna the Loreminder is a contemplative but powerful deity, served by those -who seek magical knowledge. The Loreminder appreciates followers who triumph -over their foes and especially the training of spellcasting skills in order to -do so. The faithful can expect to receive a supply of spellbooks taken directly -from Sif Muna's legendary library. +who seek magical knowledge. Followers who triumph over their foes can call upon +the Loreminder for magical power and the ability to cast spells beyond their +magical training. The devout are rewarded with a supply of spellbooks taken +directly from Sif Muna's legendary library. %%%% the Shining One @@ -267,13 +265,13 @@ upon Elyvilon for protection. Fedhas powers Plants and fungi will not attack Fedhas' worshippers without provocation. -Corpses on different levels will rot away; followers can also induce this decay -directly to create fungi. Fedhas' worshippers may walk through plants or fungi, -and even fire missiles or spells through them without causing harm. Fedhas -grants the ability to promote plants and fungi into stronger species. Followers -can eventually grow a wall of plants and create explosive spores from corpses. -They are also granted some control over the weather, being able to call -sunlight and eventually rainstorms. +Worshippers may walk through plants or fungi, and even fire missiles or spells +through them without causing harm. Fedhas grants powers to temporarily grow a +menagerie of plant allies whose strength and duration increase with Invocations +skill. Worshipers may grow a protective wall of briar patches as well as +ballistomycetes that fire damaging spores whose explosions confuse living +creatures. Devout followers can overgrow a stretch of solid terrain with a +cluster of plant allies or even summon a mighty oklob plant. %%%% Gozag powers @@ -309,11 +307,10 @@ and armour; and mutate followers to better reflect Jiyva's image. %%%% Wu Jian powers -Disciples of the Council learn to perform three martial techniques, shown -below. They will also be taught to move at unfathomable speeds for short -distances, and become able to summon a storm of golden clouds to increase their -precision and damage for as long as they keep using martial attacks. - +Disciples of the Council learn to perform three martial techniques while moving +around in combat. They will also be taught to move at unfathomable speeds for +short distances, and become able to summon a storm of golden clouds to increase +their precision and damage for as long as they keep using martial attacks. %%%% Kikubaaqudgha powers @@ -372,13 +369,14 @@ its normal limits. Qazlal powers Followers of Qazlal are protected from all clouds. As a follower of Qazlal -gains divine favour, they are slowly surrounded by a loud storm, causing -elemental clouds to appear near them, blocking attacks, and (for the -particularly devout) deflecting incoming projectiles. Qazlal allows followers -to incite nature against their foes, causing a localised natural disturbance -or, for particularly devout followers, a more widespread disaster. -Followers of Qazlal eventually gain temporary resistances after taking damage, -and can give life to clouds, turning them into allied elementals. +gains divine favour, they are slowly surrounded by a loud storm. In addition to +generating noise, the storm causes elemental clouds to appear near followers, +blocks attacks, and (for the particularly devout) deflects incoming +projectiles. Qazlal allows followers to incite nature against their foes, +causing a localised natural disturbance or, for particularly devout followers, +a more widespread disaster. Followers of Qazlal eventually gain temporary +resistances after taking damage, and can give life to clouds, turning them into +allied elementals. %%%% Ru powers @@ -393,11 +391,10 @@ Ru's sacrificants increases in strength with more piety from more sacrifices. %%%% Sif Muna powers -Sif Muna will grant followers divine energy, allowing them to occasionally cast -spells even with insufficient reserves of magic. Eventually worshippers will -gain the ability to rapidly restore their magical energy, and to forget spells -at will, so as to learn new ones. Sif Muna protects spellcasters against the -negative effects of miscasting spells. Over time, followers will receive +Sif Muna grants followers the ability to rapidly restore their magical energy, +and to forget spells at will, so as to learn new ones. Worshipers can call upon +the Loreminder to help cast any spell they've discovered, even if it's well +beyond their current magical training. Over time, devout followers will receive spellbooks containing a vast range of spells. %%%% the Shining One powers diff --git a/crawl-ref/source/dat/descript/items.txt b/crawl-ref/source/dat/descript/items.txt index 936b6ddaadac..593b59c8bff0 100644 --- a/crawl-ref/source/dat/descript/items.txt +++ b/crawl-ref/source/dat/descript/items.txt @@ -90,11 +90,6 @@ battleaxe A large war axe with a formidable double-sided head. %%%% -blowgun - -A long, light tube, open at both ends, through which various types of needles -are propelled by puffs of breath at enemies. It makes very little noise. -%%%% bolt A metal projectile, shorter than an arrow, intended to be shot from a crossbow. @@ -348,6 +343,13 @@ dagger A double-edged fighting knife with a sharp point. %%%% +dart + +A thin piece of metal, typically coated in some harmful substance. When +thrown with skill it can deliver its toxins into the bloodstream of a +living or demonic being. In addition to throwing skill, stealth skill improves +the chance for a dart to affect its target. +%%%% decaying skeleton A decaying skeleton. @@ -524,7 +526,9 @@ inedible chunk of flesh %%%% javelin -A lightweight spear, designed for throwing. +A lightweight spear, designed for throwing. It will pass through the targets it +hits, potentially hitting all targets in its path until it reaches maximum +range. {{ if you.race() == "Halfling" or you.race() == "Kobold" @@ -607,12 +611,6 @@ An extremely rare book, powerful and sinister. Many foolhardy magicians have tried to study this tome, only to find themselves entangled within necromantic forces they could not hope to control. %%%% -needle - -A thin piece of metal, typically coated in some harmful substance. When -launched from a blowgun it can deliver its toxins into the bloodstream of a -living or demonic being. -%%%% orb of zot An invaluable artefact. Once you have escaped to the surface with it, your @@ -666,13 +664,14 @@ potion of berserk rage A potion which can send one into an incoherent rage. %%%% +# TAG_MAJOR_VERSION == 34 potion of blood A potion containing the essence of life. %%%% potion of brilliance -A potion which greatly increases the intelligence and magical power of one who +A potion which greatly increases the intelligence and spell power of one who drinks it. %%%% potion of cancellation @@ -927,8 +926,8 @@ contaminated with magical energy. scroll of brand weapon A scroll that imbues a weapon with a random brand, such as flame, freezing, or -venom. Any existing brand will be replaced. Blowguns and artefacts cannot be -branded in this way. +venom. Any existing brand will be replaced. Artefacts cannot be branded in this +way. %%%% scroll of enchant armour @@ -1140,9 +1139,9 @@ A mesh of ropes knotted together with weights around the edge, used to entangle and entrap targets. Struggling victims can eventually destroy the net and break free, if they live long enough. %%%% -tomahawk +boomerang -A small axe, designed for throwing. +A curved, flat, throwing baton designed to return to its user. %%%% trident @@ -1201,8 +1200,8 @@ A magical device which throws little puffs of flame. %%%% wand of iceblast -A magical device which throws exploding shards of ice, partially harming even -creatures resistant to cold. +A magical device which creates a loud explosion of ice shards, partially +harming even creatures resistant to cold. %%%% wand of paralysis diff --git a/crawl-ref/source/dat/descript/monsters.txt b/crawl-ref/source/dat/descript/monsters.txt index d94dc3823de6..7d4f572a5321 100644 --- a/crawl-ref/source/dat/descript/monsters.txt +++ b/crawl-ref/source/dat/descript/monsters.txt @@ -87,16 +87,16 @@ on to his ears with hooks? Dispater Greatest of the Lords of Hell, Dispater was born when first an iron sword drank -mortal blood. It despises mortal vanities, the pursuit of ‘reason’ and ‘beauty’ -- the only art it enjoys is the art of war. +mortal blood. They despise mortal vanities, the pursuit of ‘reason’ and +‘beauty’ - the only art they enjoy is the art of war. %%%% Dissolution The Pits of Slime used to be a thriving civilization, and Dissolution was the prophet of their god. When the city was overtaken by its current residents, -only Dissolution survived, for he alone turned faithfully to the Slime God. -Dissolution became a tremendous mass of acidic ooze, yet retained his -intelligence. He is filled with hatred for anything that can hold a form. +only Dissolution survived, for they alone turned faithfully to the Slime God. +Dissolution became a tremendous mass of acidic ooze, yet retained their +intelligence. They are filled with hatred for anything that can hold a form. %%%% Donald @@ -162,9 +162,9 @@ honour. %%%% Fannar -A cold-hearted elven sorcerer, draped in robes of high office. He whispers -grimly to himself as he stalks the dungeon, trailing ice crystals through the -air behind him. +A cold-hearted elven sorcerer, draped in robes of high office. They whisper +grimly to themself as they stalk the dungeon, trailing ice crystals through the +air behind them. %%%% Frances @@ -276,11 +276,11 @@ for prey to slake its bottomless appetite. %%%% Lom Lobon -Stoic and inscrutable, Lom Lobon is an ancient demon infamous for its mastery +Stoic and inscrutable, Lom Lobon is an ancient demon infamous for their mastery of magic. Rumoured to be the most learned being in the halls of Pandemonium, -it is at times sought out by mortal wizards who offer their souls in exchange -for the secrets of the arcane. Lacking the terrible wrath present in most -demons, Lom Lobon's single eye instead hides a cold, impartial cruelty. +they are at times sought out by mortal wizards who offer their souls in +exchange for the secrets of the arcane. Lacking the terrible wrath present in +most demons, Lom Lobon's single eye instead hides a cold, impartial cruelty. %%%% Louise @@ -359,8 +359,8 @@ fear her; the dead obey her every command. She has a soft spot for the latter. %%%% Nessos -A centaur warrior, skilled at warfare and deceit. He slew a mighty hydra, and -now its deadly blood drips, still-fuming, from his arrows. +A centaur warrior, skilled at warfare and deceit. They slew a mighty hydra, and +now its deadly blood drips, still-fuming, from their arrows. %%%% Nikola @@ -412,7 +412,7 @@ A human whose savage roars bring terror to all those who hear them. %%%% Saint Roka -A mighty warlord. Some orcs even say he is the Chosen One of Beogh. +A mighty warlord. Some orcs even say they are the Chosen One of Beogh. %%%% Sigmund @@ -527,7 +527,7 @@ ancestor A humanoid construct of insubstantial memory, floating through the air. Though perhaps somewhat changed in shape and bearing few discernable features, still -it seems somehow to resemble a warrior from the distantly remembered past. +they seem somehow to resemble a warrior from the distantly remembered past. %%%% ancient champion @@ -575,13 +575,14 @@ A small snake that can augment its bite by constricting small creatures. ballistomycete A lumpy reddish fungus covered in knobbly rhizome growths, growing well in the -dank underground dungeon. When active it will occasionally produce giant -spores, which in turn will give rise to more ballistomycetes. +dank underground dungeon. When it senses a threat, the ballistomycete may +release a deadly floating spore that explodes on its victims. %%%% ballistomycete spore -A deceptively fast, floating ball filled with gas and spores, prone to burst at -the slightest disturbance. +A fast, gas-filled floating spore released by a ballistomycete when it senses a +threat. After seeking out its foe, the spore will explode, damaging anything +caught in the blast and further confusing living creatures. %%%% balrug @@ -1606,7 +1607,7 @@ prey. %%%% meliai -A nymphal hybrid, with a winged torso of a human ontop of the stinging abdomen +A nymphal hybrid, with a winged torso of a human atop the stinging abdomen of a bee. The meliai serve under many roles: priestesses of nature, nurses to the divine, and warrior-champions for their hives. %%%% diff --git a/crawl-ref/source/dat/descript/quotes.txt b/crawl-ref/source/dat/descript/quotes.txt index 136f8bc29833..a168571b9924 100644 --- a/crawl-ref/source/dat/descript/quotes.txt +++ b/crawl-ref/source/dat/descript/quotes.txt @@ -121,11 +121,24 @@ A broken pillar A faded altar of an unknown god “Then Paul stood in the midst of Mars' hill, and said, Ye men of Athens, I -perceive that in all things ye are too superstitious. For as I passed by, and -beheld your devotions, I found an altar with this inscription, TO THE UNKNOWN -GOD.” + perceive that in all things ye are too superstitious. For as I passed by, and + beheld your devotions, I found an altar with this inscription, TO THE UNKNOWN + GOD.” -KJV Bible, Acts 17:23-24. %%%% +A flagged portal + +“We made an expedition; + We met a host and quelled it; + We forced a strong position, + And killed the men who held it. + ... +“Fierce warriors rushed to meet us; + We met them, and o’erthrew them: + They struggled hard to beat us; + But we conquered them, and slew them.” + -Thomas Love Peacock, “The War Song of Dinas Vawr”. 1829. +%%%% A gateway to Hell “I am the way into the city of woe. @@ -225,7 +238,8 @@ A staircase to the Shoals %%%% A staircase to the Tomb - +“A tomb now suffices him for whom the world was not enough.” + -Alexander the Great's epitaph %%%% A stormy altar of Qazlal @@ -812,7 +826,16 @@ amulet of magic regeneration %%%% amulet of rage - +“We there, in strife bewild’ring, + Split blood enough to swim in: + We orphaned many children, + And widowed many women. + The eagles and the ravens + We glutted with our foemen; + The heroes and the cravens, + The spearmen and the bowmen.” + -Thomas Love Peacock, “The War Song of Dinas Vawr”. 1829. + %%%% amulet of regeneration @@ -1490,7 +1513,9 @@ hand axe %%%% hand crossbow - +“Energy may be likened to the bending of a crossbow; + decision, to the releasing of a trigger.” + -Sun Tzu %%%% harpy @@ -1515,7 +1540,7 @@ hell hound If aught disturbed their noise, into her womb, And kennel there; yet there still barked and howled Within unseen.” - -John Milton, _Paradise Lost_, Book II, l. 653-659. 1667. + -John Milton, _Paradise Lost_, Book II, 1667. %%%% hell knight @@ -1605,7 +1630,7 @@ insubstantial wisp Misleads th' amaz'd Night-wanderer from his way To Boggs and Mires, and oft through Pond or Poole, There swallow'd up and lost, from succour farr.” - -John Milton, _Paradise Lost_ + -John Milton, _Paradise Lost_, Book IX, 1667. %%%% Iskenderun's Battlesphere spell @@ -1784,7 +1809,8 @@ hee most desireth.” %%%% manual - +“Manuals have their uses... but they are not to be confused with living.” + -Robert Fulghum %%%% merfolk @@ -2028,7 +2054,7 @@ pandemonium lord A solemn Councel forthwith to be held At Pandaemonium, the high Capital Of Satan and his Peers...” - -John Milton, _Paradise Lost_, Book I, l. 752-7. 1667. + -John Milton, _Paradise Lost_, Book I, 1667. %%%% Passwall spell @@ -2085,6 +2111,7 @@ potion Being the time the potion's force should cease.” -William Shakespeare, _Romeo and Juliet_ %%%% +# TAG_MAJOR_VERSION == 34 potion of blood “Only be sure that thou eat not the blood: for the blood _is_ the life; @@ -2315,15 +2342,20 @@ see rightly; what is essential is invisible to the eye.” %%%% ring of slaying - +“Life is too short to occupy oneself with the slaying of the slain more than + once.” + -Thomas Huxley %%%% ring of strength - +“That which does not kill us makes us stronger.” + -Friedrich Nietzsche %%%% ring of stealth - +“No one ever approaches perfection except by stealth, and unknown to + themselves.” + -William Hazlitt %%%% ring of teleportation @@ -2391,7 +2423,8 @@ scroll of acquirement %%%% scroll of amnesia - +“But revenge is hollow. I'd prefer amnesia.” + -Tera Lynn Childs %%%% scroll of blinking @@ -2411,7 +2444,9 @@ scroll of enchant weapon %%%% scroll of fear - +“We can easily forgive a child who is afraid of the dark; +the real tragedy of life is when men are afraid of the light.” + -Plato %%%% scroll of fog @@ -2419,7 +2454,9 @@ scroll of fog %%%% scroll of holy word - +“However many holy words you read, however many you speak, +what good will they do you if you do not act on upon them?” + -Gautama Buddha %%%% scroll of identify @@ -2460,7 +2497,9 @@ scroll of silence %%%% scroll of summoning - +“When thou attended gloriously from heaven, Shalt in the sky appear, and from + thee send Thy summoning archangels to proclaim Thy dread tribunal.” + -John Milton, _Paradise Lost_, Book III, 1667. %%%% scroll of teleportation @@ -2556,7 +2595,8 @@ shapeshifter %%%% shield - +“...for the shield may be as important for victory, as the sword or spear.” + -Charles Darwin, “The Origin of Species”. 1859. %%%% shortbow @@ -2970,11 +3010,13 @@ against—whom?” -Jorge Luis Borges, “Toenails”. 1960. trans. Andrew Hurley, 1998. %%%% -tomahawk +boomerang -“Let them draw the sharpest knife, and whirl the swiftest tomahawk, for their -bitterest enemy is in their hands.” - -James Fenimore Cooper, “The Last of the Mohicans”, 1826. +“The weapon, thrown at 20 or 30 yards distance, twirled round in the air with +astonishing velocity, and alighting on the right arm of one of his opponents, +actually rebounded to a distance not less than 70 or 80 yards, leaving a +horrible contusion behind, and exciting universal admiration.” + -The Sydney Gazette and New South Wales Advertiser, 23 December 1804 %%%% tormentor diff --git a/crawl-ref/source/dat/descript/skills.txt b/crawl-ref/source/dat/descript/skills.txt index c44d23b41df9..9c155e595f64 100644 --- a/crawl-ref/source/dat/descript/skills.txt +++ b/crawl-ref/source/dat/descript/skills.txt @@ -84,10 +84,9 @@ appropriate launcher and ammunition. %%%% Throwing -Training Throwing will make thrown weapons (such as javelins) and blowguns more -effective. In particular, it makes weapons of returning more likely to return -to their thrower, and makes certain magical needles more likely to have an -effect when fired from a blowgun. +Training Throwing will make thrown weapons (such as javelins)more effective. In +particular, it makes weapons of returning more likely to return to their +thrower, and makes certain magical darts more likely to have an effect. Being skilled in Throwing increases your effectiveness with Slings, and vice versa. diff --git a/crawl-ref/source/dat/descript/species.txt b/crawl-ref/source/dat/descript/species.txt index 892c26481583..5bd9eab50077 100644 --- a/crawl-ref/source/dat/descript/species.txt +++ b/crawl-ref/source/dat/descript/species.txt @@ -128,6 +128,6 @@ stabbers and casters. %%%% Vine Stalker -Frail symbionts, Vine Stalkers cannot regain health from devices or potions. +Frail symbionts, Vine Stalkers cannot regain health from potions. Their magical reserves absorb damage, and they pack a magic-restoring bite. %%%% diff --git a/crawl-ref/source/dat/descript/spells.txt b/crawl-ref/source/dat/descript/spells.txt index 93c46fb45550..304ed12a4015 100644 --- a/crawl-ref/source/dat/descript/spells.txt +++ b/crawl-ref/source/dat/descript/spells.txt @@ -55,9 +55,7 @@ This reduces their remaining time before being sent back where they came from. Aura of Brilliance spell Empowers the magic of nearby wizard allies of the caster, allowing them to -cast their magic more powerfully and more often, but at the price of fixing -the caster in place until they no longer have any nearby allies they can -empower. +cast their magic more powerfully and more often. %%%% Avatar Song spell @@ -96,7 +94,8 @@ Beastly Appendage spell if you.race() == "Octopode" then return "Causes a vicious spike to grow from one of the caster's " .. "tentacles, increasing the damage of their extra tentacle " .. - "attacks." + "attacks. It is not powerful enough to affect tentacles " .. + "which are already mutated." else return "Causes monstrous horns or talons to grow from the caster's " .. "body, granting them a chance of making an extra attack in " .. @@ -176,21 +175,25 @@ the bolt's destination. %%%% Bolt of Cold spell -Fires a penetrating bolt of frost. +Fires a penetrating bolt of frost. The range of the bolt is decreased by one for +every creature it strikes. %%%% Bolt of Draining spell Fires a penetrating bolt of negative energy, which drains any living creature -it strikes. +it strikes. The range of the bolt is decreased by one for every creature it +hits. %%%% Bolt of Fire spell -Fires a penetrating bolt of flames. +Fires a penetrating bolt of flames. The range of the bolt is decreased by one +for every creature it strikes. %%%% Bolt of Magma spell Fires a penetrating bolt of molten rock. A portion of its damage bypasses -fire resistance. +fire resistance. The range of the bolt is decreased by one for every creature +it strikes. %%%% Borgnjor's Revivification spell @@ -270,7 +273,8 @@ Exhales a large cloud of fumes filled with the very essence of chaos. %%%% Chilling Breath spell -Exhales a blast of frost that may knock back airborne targets. +Exhales a blast of frost that may knock back airborne targets. The range of the +blast is decreased by one for every creature it strikes. %%%% Cleansing Flame spell @@ -287,6 +291,7 @@ become available. Cold Breath spell Exhales a focused blast of icy-cold air. It may knock back airborne targets. +The range of the blast is decreased by one for every creature it strikes. %%%% Confuse spell @@ -338,7 +343,8 @@ force of any creature it envelops. %%%% Corrosive Bolt spell -Fires a penetrating bolt of acid. +Fires a penetrating bolt of acid. The range of the bolt is decreased by one for +every creature it strikes. %%%% Corrupting Pulse spell @@ -350,7 +356,8 @@ Crystal Bolt spell Projects a penetrating projectile with peculiar magical properties. It is randomly imbued with either fire or ice, and additionally will reflect from -walls of any material due to the crystal embedded in the shot. +walls of any material due to the crystal embedded in the shot. The range of the +bolt is decreased by one for every creature it strikes. %%%% Darkness spell @@ -482,7 +489,10 @@ reserves. After the call ends, it cannot be issued again for a short time. %%%% Drain Life spell - +# Note: this description duplicates part of the Drain Life ability description. + +Drains the life force of any nearby creatures, healing the user depending on +the damage dealt. %%%% Drain Magic spell @@ -492,7 +502,8 @@ antimagic weapon. Draining Gaze spell Causes the target's magic to leak into the air, as though they were hit by an -antimagic weapon, with no direct line of fire required. +antimagic weapon, and heals the caster for the amount of magic drained, +with no direct line of fire required. %%%% Dream Dust spell @@ -509,7 +520,8 @@ Throws a bolt of electricity at the target, with high accuracy. %%%% Energy Bolt spell -Fires a bolt of highly destructive energy. +Fires a bolt of highly destructive energy. The range of the bolt is decreased +by one for every creature it strikes. %%%% Enslavement spell @@ -541,7 +553,8 @@ ineffective against unnatural targets. %%%% Fire Breath spell -Breathes a blast of fire at a targeted creature. +Breathes a blast of fire at a targeted creature. The range of the blast is +decreased by one for every creature it strikes. %%%% Fire Storm spell @@ -882,7 +895,8 @@ shorter time. Metal Splinters spell Fires a directed stream of sharp metal splinters. Its damage is strongly -reduced by armour. +reduced by armour. The range of the stream is decreased by one for every +creature it strikes. %%%% Miasma Breath spell @@ -1258,6 +1272,12 @@ Spit Poison spell %%%% +Sporulate spell + +Releases a fast, gas-filled floating spore. After seeking out its foe, the +spore will explode, damaging anything caught in the blast and further confusing +living creatures. +%%%% Sprint spell Causes the caster to move across terrain more rapidly for a short duration. @@ -1280,7 +1300,8 @@ While transformed, any equipped body armour, gloves and boots are melded. %%%% Steam Ball spell -Throws a ball of hot steam towards a targeted creature. +Throws a ball of hot steam towards a targeted creature. The range of the ball +is decreased by one for every creature it strikes. %%%% Sticks to Snakes spell @@ -1557,7 +1578,8 @@ Life will not be drained in excess of what the caster can capably absorb. %%%% Venom Bolt spell -Fires a penetrating bolt of poison. +Fires a penetrating bolt of poison. The range of the bolt is decreased by one +for every creature it strikes. %%%% Virulence spell diff --git a/crawl-ref/source/dat/descript/status.txt b/crawl-ref/source/dat/descript/status.txt index 09604e473068..3bf61b8c7003 100644 --- a/crawl-ref/source/dat/descript/status.txt +++ b/crawl-ref/source/dat/descript/status.txt @@ -36,8 +36,8 @@ teleportation. Fly status You fly above the ground, and are able to cross water and lava or fight above -shallow water without penalties. Most types of flight will time out – be sure -you're not above deadly liquids when that happens! +shallow water without penalties. Most types of flight will time out – be +careful what you are above when that happens! %%%% Agi status diff --git a/crawl-ref/source/dat/descript/tutorial.txt b/crawl-ref/source/dat/descript/tutorial.txt index 9718a7832fe2..7b81166a705e 100644 --- a/crawl-ref/source/dat/descript/tutorial.txt +++ b/crawl-ref/source/dat/descript/tutorial.txt @@ -226,15 +226,15 @@ tutorial2 newlevel Remember: You can reread old messages with $cmd[CMD_REPLAY_MESSAGES]. %%%% -tutorial2 tomahawks +tutorial2 boomerangs -Now, for ranged combat! Pick up these tomahawks with +Now, for ranged combat! Pick up these boomerangs with $cmd[CMD_PICKUP] or g, or by mouseclick, and continue. %%%% tutorial2 throwing -You can fire your tomahawks at a monster with $cmd[CMD_FIRE] +You can fire your boomerangs at a monster with $cmd[CMD_FIRE] or by clicking on them in the inventory panel. To confirm the auto-targeted monster, press $cmd[CMD_TARGET_SELECT] or Enter. @@ -247,7 +247,7 @@ Pick up this shortbow and wield it with $cmd[CMD_WIELD_WEAPON] tutorial2 firing # FIXME: Lengthy because of the hint for choosing target. -Firing arrows from your wielded shortbow works exactly the same as throwing tomahawks: +Firing arrows from your wielded shortbow works exactly the same as throwing boomerangs: via $cmd[CMD_FIRE] or mouseclick . You can change the targeted monster by pressing $cmd[CMD_TARGET_CYCLE_FORWARD] while in target mode. The worm is diff --git a/crawl-ref/source/dat/descript/unrand.txt b/crawl-ref/source/dat/descript/unrand.txt index fe94c0fb5f97..a2c2e5880b5f 100644 --- a/crawl-ref/source/dat/descript/unrand.txt +++ b/crawl-ref/source/dat/descript/unrand.txt @@ -66,7 +66,7 @@ enemies. %%%% sceptre of Torment -An accursed golden sceptre, created by some unknown hellish power. When used in +An accursed bone sceptre, created by some unknown hellish power. When used in combat, it causes agonising pain in all those unfortunate enough to gaze upon it, save for the wielder. %%%% @@ -281,12 +281,8 @@ relieve opponents of their weapons, dealing extra damage in the process. %%%% storm bow -A longbow with the colour of dark rain clouds. -{{ - if you.can_smell() then - return "It smells like a storm just past, or one about to begin." - end -}} +A longbow with the colour of dark rain clouds that fires arrows like +lightning. %%%% large shield of Ignorance @@ -597,6 +593,10 @@ lajatang of Order A silver-bladed lajatang, once bestowed upon a favoured worshipper of Zin. It became the most prized weapon of the many priests of Zin. After their order was destroyed, it fell into disuse and vanished from history. + +It deals increased damage compared to a normal lajatang and substantially +increased damage to chaotic and magically transformed beings. It also inflicts +extra damage against mutated beings, according to how mutated they are. %%%% great mace "Firestarter" @@ -687,3 +687,12 @@ A small wrinkle in the very fabric of reality, magically bound to a shaft by some mad acolyte of Lugonu. It possesses an unusually large reach that teleports its attack directly to its victim. %%%% +staff of Battle + +A tubular staff encased in vividly coloured scales, the handiwork of a +long-forgotten mage. It was stolen by the wizard Iskenderun who used it for a +time before unlocking its secrets. The staff increases the power of its +wielder's conjuration magic and, when its wielder is in danger, the staff will +conjure a battlesphere to provide support. The strength of the battlesphere +depends on the wielder's skill with Charms and Conjurations. +%%%% diff --git a/crawl-ref/source/dat/dlua/explorer.lua b/crawl-ref/source/dat/dlua/explorer.lua new file mode 100644 index 000000000000..61482f31322d --- /dev/null +++ b/crawl-ref/source/dat/dlua/explorer.lua @@ -0,0 +1,577 @@ +-- tools for seeing what's in a seed. For example use cases, see both +-- crawl-ref/source/scripts/seed_explorer.lua, and +-- crawl-ref/source/tests/seed_explorer.lua. +-- TODO: LDoc this + +util.namespace('explorer') + +-- matches pregeneration order +explorer.generation_order = { + "D:1", + "D:2", "D:3", "D:4", "D:5", "D:6", "D:7", "D:8", "D:9", + "D:10", "D:11", "D:12", "D:13", "D:14", "D:15", + "Temple", + "Lair:1", "Lair:2", "Lair:3", "Lair:4", "Lair:5", "Lair:6", + "Orc:1", "Orc:2", + "Spider:1", "Spider:2", "Spider:3", "Spider:4", + "Snake:1", "Snake:2", "Snake:3", "Snake:4", + "Shoals:1", "Shoals:2", "Shoals:3", "Shoals:4", + "Swamp:1", "Swamp:2", "Swamp:3", "Swamp:4", + "Vaults:1", "Vaults:2", "Vaults:3", "Vaults:4", "Vaults:5", + "Crypt:1", "Crypt:2", "Crypt:3", + "Depths:1", "Depths:2", "Depths:3", "Depths:4", "Depths:5", + "Hell", + "Elf:1", "Elf:2", "Elf:3", + "Zot:1", "Zot:2", "Zot:3", "Zot:4", "Zot:5", + "Slime:1", "Slime:2", "Slime:3", "Slime:4", "Slime:5", + "Tomb:1", "Tomb:2", "Tomb:3", + "Tar:1", "Tar:2", "Tar:3", "Tar:4", "Tar:5", "Tar:6", "Tar:7", + "Coc:1", "Coc:2", "Coc:3", "Coc:4", "Coc:5", "Coc:6", "Coc:7", + "Dis:1", "Dis:2", "Dis:3", "Dis:4", "Dis:5", "Dis:6", "Dis:7", + "Geh:1", "Geh:2", "Geh:3", "Geh:4", "Geh:5", "Geh:6", "Geh:7", + } +-- generation order continues: pan, zig. However, these are really only in the +-- official generation order so that entering them forces the rest of the +-- dungeon to generate first, so we ignore them here. + +explorer.portal_order = { + "Sewer", + "Ossuary", + "IceCv", + "Volcano", + "Bailey", + "Gauntlet", + "Bazaar", + -- Trove is not pregenerated, so should be ignored here + "WizLab", + "Desolation" +} + + +function explorer.level_to_gendepth(lvl) + -- TODO could parse l, handle things like Hell:1 + for i, l in ipairs(explorer.generation_order) do + if lvl:lower() == l:lower() then + return i + end + end + for i, l in ipairs(explorer.portal_order) do + if lvl:lower() == l:lower() then + return -i -- fairly hacky + end + end + return nil +end + +function explorer.branch_to_gendepth(b) + if dgn.br_exists(b) then + local depth = dgn.br_depth(b) + if depth == 1 then + return explorer.level_to_gendepth(b) + else + return explorer.level_to_gendepth(b .. ":" .. depth) + end + end + return nil +end + +function explorer.to_gendepth(depth) + if type(depth) == "number" then return depth end + if depth == "all" then return #explorer.generation_order end + local num = string.match(depth, "^%d+$") + if num ~= nil then return tonumber(depth) end + local result = explorer.level_to_gendepth(depth) + if result == nil then + result = explorer.branch_to_gendepth(depth) + end + return result +end + +-- a useful depth preset +explorer.zot_depth = explorer.to_gendepth("Zot") +assert(explorer.zot_depth ~= 0) + +-- TODO: generalize, allow changing? +local out = function(s) if not explorer.quiet then crawl.stderr(s) end end + +------------------------------ +-- some generic list/string manipulation code + +function explorer.collapse_dups(l) + local result = {} + util.sort(l) + local cur = "" + local count = 0 + for i, name in ipairs(l) do + if name == cur then + count = count + 1 + else + if (cur ~= "") then + result[#result + 1] = { cur, count } + end + cur = name + count = 1 + end + end + if (cur ~= "") then + result[#result + 1] = { cur, count } + end + for i, name in ipairs(result) do + if result[i][2] > 1 then + result[i] = result[i][1] .. " x" .. result[i][2] + else + result[i] = result[i][1] + end + end + return result +end + +function explorer.fancy_join(l, indent, width, sep, initial_indent) + -- TODO: reflow around spaces? + if #l == 0 then + return "" + end + local lines = {} + local cur_line = string.rep(" ", indent) + for i, s in ipairs(l) do + local full_s = s + if (i ~= #l) then + full_s = full_s .. sep + end + if #cur_line + #full_s > width and i ~= 1 then + lines[#lines + 1] = cur_line + cur_line = string.rep(" ", indent) + end + cur_line = cur_line .. full_s + end + lines[#lines + 1] = cur_line + if not initial_indent and #lines > 0 and indent > 0 then + lines[1] = string.sub(lines[1], indent + 1, #lines[1]) + end + return table.concat(lines, "\n") +end + +------------------------------ +-- code for looking at items + +function explorer.arts_only(item) + return item.artefact +end + +function explorer.item_ignore_boring(item) + -- TODO: + -- show gold totals? + -- missiles - early on? + -- unenchanted weapons/armour - early on? + if item.is_useless then + return false + elseif item.base_type == "gold" + or item.base_type == "missile" + or item.base_type == "food" then + return false + elseif (item.base_type == "weapon" or item.base_type == "armour") + and item.pluses() <= 0 and not item.branded then + return false + end + return true +end + +function explorer.catalog_items_setup() + wiz.identify_all_items() +end + +function explorer.catalog_items(pos, notable) + local stack = dgn.items_at(pos.x, pos.y) + if #stack > 0 then + for i, item in ipairs(stack) do + if explorer.item_notable(item) then + notable[#notable + 1] = item.name() + end + end + end + stack = dgn.shop_inventory_at(pos.x, pos.y) + if stack ~= nil and #stack > 0 then + for i, item in ipairs(stack) do + if explorer.item_notable(item[1]) then + notable[#notable + 1] = item[1].name() .. " (cost: " .. item[2] ..")" + end + end + end +end + +------------------------------ +-- code for looking at features + +explorer.hell_branches = util.set{ "Geh", "Coc", "Dis", "Tar" } + +function explorer.in_hell() + return explorer.hell_branches[you.branch()] +end + +function explorer.feat_interesting(feat_name) + -- most features are pretty boring... + if string.find(feat_name, "altar_") == 1 then + return true + elseif string.find(feat_name, "enter_") == 1 then -- could be more selective + if explorer.in_hell() then + return feat_name ~= "enter_hell" + else + return true + end + elseif feat_name == "transporter" or string.find(feat_name, "runed_") then + return true + end + return false +end + +function explorer.catalog_features_setup() + you.enter_wizard_mode() -- necessary so that magic mapping behaves + -- correctly. this is implied by you.save = false, + -- so it shouldn't affect other tests in actual + -- test mode... + wiz.map_level() -- abyss will break this call... +end + +function explorer.catalog_features(pos, notable) + local feat = dgn.grid(pos.x, pos.y) + if explorer.feat_notable(dgn.feature_name(feat)) then + notable[#notable + 1] = dgn.feature_desc_at(pos.x, pos.y, "A") + end +end + +------------------------------ +-- code for looking at monsters + +function explorer.mons_ignore_boring(mons) + return (mons.unique + or explorer.always_interesting_mons[mons.name] + or mons.type_name == "player ghost" + or mons.type_name == "pandemonium lord") +end + +function explorer.mons_always_native(m, mi) + return (mi:is_unique() + or m.type_name == "player ghost" + or mi:is_firewood()) +end + +function explorer.rare_ood(mi) + local depth = mi:avg_local_depth() + -- TODO: really, probability can't be interpreted without some sense of what + -- the overall distribution for the branch is, so this is fairly heuristic + local prob = mi:avg_local_prob() + local ood_threshold = math.max(2, dgn.br_depth() / 3) + return depth > you.depth() + ood_threshold and prob < 2 +end + +function explorer.feat_in_set(s) + return function (f) if s[f] then return f else return nil end end +end + +function explorer.describe_mons(mons) + local mi = mons.get_info() + -- TODO: weird distribution of labor between mons and moninfo, can this be + -- cleaned up? + -- TODO: does it make sense to use the same item notability function here? + local feats = util.map(function (i) return "item:" .. i.name() end, + util.filter(explorer.item_notable, mons.get_inventory())) + local force_notable = false + if explorer.mons_feat_filter then + feats = util.map(explorer.mons_feat_filter, feats) -- TODO don't brute force this + end + if mons.type_name == "dancing weapon" and #feats > 0 then + -- don't repeat the dancing weapon's item, but if the item_notable check + -- has deemed the item notable, make sure to show it. + feats = { } + force_notable = true + end + + -- may or may not ever be useful -- builder OOD is an indicator of builder + -- choices, not intrinsic monster quality, and it is possible for monsters + -- to be chosen as OOD that are within range, e.g. you can sometimes get + -- the same monster generating as OOD and not OOD at the same depth. + -- if mons.has_prop("mons_is_ood") then + -- feats[#feats + 1] = "builder OOD" + -- end + + if explorer.rare_ood(mi) then + feats[#feats + 1] = "OOD" + end + if not mons.in_local_population and not explorer.mons_always_native(mons, mi) + and not util.set(explorer.portal_order)[you.branch()] then + -- this is a different sense of "native" than used internally to crawl, + -- in that it includes anything that would normally generate in a + -- branch. The internal sense is just supposed to be about which + -- monsters "live" in a particular branch, which is covered as well. + -- + -- portals are ignored because their monsters are mostly determined by + -- vaults, so too many things show up as "non-native". + feats[#feats + 1] = "non-native" + end + if explorer.mons_feat_filter then + feats = util.map(explorer.mons_feat_filter, feats) + end + -- the `item:` prefix is really only there for filtering purposes, remove it + feats = util.map(function (s) + return s:find("item:") == 1 and s:sub(6) or s + end, feats) + local feat_string = "" + if #feats > 0 then + feat_string = " (" .. table.concat(feats, ", ") .. ")" + end + + local name_string = "bug" + + if mons.type_name == "player ghost" then + name_string = mons.type_name + else + name_string = mons.name + end + if (force_notable or explorer.mons_notable(mons) or #feats > 0) then + return name_string .. feat_string + else + return nil + end +end + +function explorer.catalog_monsters_setup() + wiz.identify_all_items() +end + +function explorer.catalog_monsters(pos, notable) + local mons = dgn.mons_at(pos.x, pos.y) + if mons then + notable[#notable + 1] = explorer.describe_mons(mons) + end +end + +------------------------------ +-- code for looking at vaults + +function explorer.catalog_vaults() + -- this list has already been joined into a string, split it + -- TODO: just pass the list directly to lua... + local vaults = debug.vault_names() + first_split = crawl.split(vaults, " and ") + if #first_split < 2 then + return first_split + end + second_split = crawl.split(first_split[1], ", ") + second_split[#second_split + 1] = first_split[2] + return second_split +end + +function explorer.catalog_vaults_raw() + return { debug.vault_names() } +end + +------------------------------ +-- general code for building highlight descriptions + +-- could do this by having each category register itself... +explorer.catalog_funs = {vaults = explorer.catalog_vaults, + vaults_raw= explorer.catalog_vaults_raw, + items = explorer.catalog_items_setup, + monsters = explorer.catalog_monsters_setup, + features = explorer.catalog_features_setup} + +explorer.catalog_pos_funs = {items = explorer.catalog_items, + monsters = explorer.catalog_monsters, + features = explorer.catalog_features} + +explorer.catalog_names = {vaults = " Vaults: ", + vaults_raw= " Vaults: ", + items = " Items: ", + monsters = " Monsters: ", + features = " Features: "} + +-- fairly subjective, some of these should probably be relative to depth +explorer.dangerous_monsters = { + "ancient lich", + "orb of fire", + "greater mummy", + "Hell Sentinel", + "Ice Fiend", + "Brimstone Fiend", + "Tzitzimitl", + "shard shrike", + "caustic shrike", + "iron giant", + "juggernaut", + "Killer Klown", + "Orb Guardian", + "curse toe", + } + +function explorer.reset_to_defaults() + --explorer.mons_feat_filter = explorer.feat_in_set(util.set({ "OOD" })) + explorer.mons_feat_filter = nil + explorer.mons_notable = explorer.mons_ignore_boring + + explorer.always_interesting_mons = util.set(explorer.dangerous_monsters) + explorer.item_notable = explorer.item_ignore_boring + explorer.feat_notable = explorer.feat_interesting +end + +function explorer.make_highlight(h, key, hide_empty) + if not h[key] then + return "" + end + local name = explorer.catalog_names[key] + local l = h[key] + if key ~= "vaults" then + l = explorer.collapse_dups(l) + end + s = explorer.fancy_join(l, #name, 80, ", ") + if (#s == 0 and hide_empty) then + return "" + end + return name .. s +end + +function explorer.catalog_all_positions(cats, highlights) + funs = { } + for _, c in ipairs(cats) do + if explorer.catalog_pos_funs[c] ~= nil then + if highlights[c] == nil then highlights[c] = { } end + funs[#funs + 1] = explorer.catalog_pos_funs[c] + end + end + -- don't bother if there are no positional funs to run + if #funs == 0 then return end + + local gxm, gym = dgn.max_bounds() + for p in iter.rect_iterator(dgn.point(1,1), dgn.point(gxm-2, gym-2)) do + for _,c in ipairs(cats) do + if explorer.catalog_pos_funs[c] ~= nil then + explorer.catalog_pos_funs[c](p, highlights[c]) + end + end + end + for _, c in ipairs(cats) do + if #highlights[c] == 0 then highlights[c] = nil end + end +end + +function explorer.catalog_current_place(lvl, to_show, hide_empty) + highlights = {} + -- setup functions and anything that doesn't require looking at positions + for _, cat in ipairs(to_show) do + if explorer.catalog_funs[cat] ~= nil then + highlights[cat] = explorer.catalog_funs[cat]() + end + end + -- explorer categories that collect information from map positions + explorer.catalog_all_positions(to_show, highlights) + + -- output + if not explorer.quiet then + h_l = { } + for _, cat in ipairs(to_show) do + h_l[#h_l+1] = explorer.make_highlight(highlights, cat, hide_empty) + end + h_l = util.filter(function (a) return #a > 0 end, h_l) + if #h_l > 0 or not hide_empty then + out(lvl .. " highlights:") + for _, h in ipairs(h_l) do + out(h) + end + end + end + return highlights +end + +function explorer.catalog_place(i, lvl, cats_to_show, show_level_fun) + local result = nil + if (dgn.br_exists(string.match(lvl, "[^:]+"))) then + debug.goto_place(lvl) + debug.generate_level() + local old_quiet = explorer.quiet + if show_level_fun ~= nil and not show_level_fun(i) then + explorer.quiet = true + end + result = explorer.catalog_current_place(lvl, cats_to_show, true) + explorer.quiet = old_quiet + end + return result +end + +function explorer.catalog_portals(i, lvl, cats_to_show, show_level_fun) + local result = { } + for j,port in ipairs(explorer.portal_order) do + if you.where() == dgn.level_name(dgn.br_entrance(port)) then + result[port] = explorer.catalog_place(-j, port, cats_to_show, show_level_fun) + end + end + return result +end + +-- a bit redundant with mapstat? +function explorer.catalog_dungeon(max_depth, cats_to_show, show_level_fun) + local result = {} + dgn.reset_level() + debug.flush_map_memory() + debug.dungeon_setup() + for i,lvl in ipairs(explorer.generation_order) do + if i > max_depth then break end + result[lvl] = explorer.catalog_place(i, lvl, cats_to_show, show_level_fun) + local portals = explorer.catalog_portals(i, lvl, cats_to_show, show_level_fun) + for port, cat in pairs(portals) do + result[port] = cat + end + -- if (dgn.br_exists(string.match(lvl, "[^:]+"))) then + -- debug.goto_place(lvl) + -- debug.generate_level() + -- local old_quiet = explorer.quiet + -- if show_level_fun ~= nil and not show_level_fun(i) then + -- explorer.quiet = true + -- end + -- result[lvl] = explorer.catalog_current_place(lvl, cats_to_show, true) + -- explorer.quiet = old_quiet + -- end + end + return result +end + +function explorer.catalog_seed(seed, depth, cats, show_level_fun, describe_cat) + seed_used = debug.reset_rng(seed) + if describe_cat then + out(describe_cat(seed)) + else + out("Catalog for seed " .. seed .. + " (" .. table.concat(cats, ", ") .. "):") + end + return explorer.catalog_dungeon(depth, cats, show_level_fun) +end + +explorer.internal_categories = { "vaults_raw" } +explorer.available_categories = { "vaults", "items", "features", "monsters" } +function explorer.is_category(c) + return util.set(explorer.available_categories)[c] or + util.set(explorer.internal_categories)[c] +end + +-- `seeds` is an array of seeds. If you pass numbers in with this, keep in mind +-- that the max int limit is rather complicated and less than 64bits, because +-- these are doubles behind the scenes. This code will accept strings of digits +-- instead, which is always safe. +function explorer.catalog_seeds(seeds, depth, cats, show_level_fun, describe_cat) + if depth == nil then depth = #explorer.generation_order end + if cats == nil then + cats = util.set(explorer.available_cats) + end + for _, i in ipairs(seeds) do + if _ > 1 then out("") end -- generates a newline for stderr output + explorer.catalog_seed(i, depth, cats, show_level_fun, describe_cat) + end +end + +-- example custom catalog function +function explorer.catalog_arts(seeds, depth) + explorer.item_notable = explorer.arts_only + local run1 = explorer.catalog_dungeon(seeds, depth, { "items" }, + function(seed) out("Artefact catalog for seed " .. seed .. ":") end) + explorer.item_notable = explorer.ignore_boring +end + +explorer.reset_to_defaults() diff --git a/crawl-ref/source/dat/dlua/ghost.lua b/crawl-ref/source/dat/dlua/ghost.lua index a60ba555771f..d17fd09f942d 100644 --- a/crawl-ref/source/dat/dlua/ghost.lua +++ b/crawl-ref/source/dat/dlua/ghost.lua @@ -265,9 +265,12 @@ end -- Set up the chaos dancing weapon for ebering_ghost_xom and -- ebering_vaults_ghost_xom. function setup_xom_dancing_weapon(e) - quality = "" - great_weight = 1 - great_weapons = {} + local quality = "" + local great_weight = 1 + local base_weapons = {} + local good_weapons = {} + local great_weapons = {} + local variability = nil -- Progress the base type of weapons so that it's more reasonable as reward -- as depth increases. The very rare weapons only show up at all in later @@ -309,12 +312,14 @@ function setup_xom_dancing_weapon(e) quality = crawl.coinflip() and "good_item" or crawl.coinflip() and "randart" or "" - variability = not you.unrands("mace of Variability") - and crawl.one_chance_in(100) and "mace of Variability" + + -- if variability has already generated, this will end up with a + -- chaos branded great mace as a backup. + variability = crawl.one_chance_in(100) and "mace of Variability" end -- Make one weapons table with each weapon getting weight by class. - weapons = {} + local weapons = {} for _, wname in ipairs(base_weapons) do weapons[wname] = 10 end diff --git a/crawl-ref/source/dat/dlua/layout/zonify.lua b/crawl-ref/source/dat/dlua/layout/zonify.lua index 1cb53b0e1305..b537cd47b663 100644 --- a/crawl-ref/source/dat/dlua/layout/zonify.lua +++ b/crawl-ref/source/dat/dlua/layout/zonify.lua @@ -84,7 +84,7 @@ function zonify.fill_smallest_zones(zonemap, num_to_keep, fgroup, ffill, min_zon local sorted_zones = {} for name,group in pairs(zonemap) do table.insert(sorted_zones, name) end - table.sort(sorted_zones) + util.sort(sorted_zones) for i, name in ipairs(sorted_zones) do group = zonemap[name] if type(fgroup) == "function" and fgroup(group) or name==fgroup then @@ -232,6 +232,11 @@ end -- is needed to fill in deep water zones and stop flyers/swimmers getting trapped. -- TODO: Need to simplify into a single pass that understands connectivity issues -- between different zone groups and can handle everything more elegantly. +-- TODO: This does not address zones separated by deep water, that in swamp +-- may be further filled by c++ code in _build_vault_impl, creating tele +-- closets of exactly the kind that this code prevents. Because of that, there's +-- yet *another* pass to fill these in there. This is all extremely redundant +-- and should be cleaned up one day. function zonify.grid_fill_water_zones(num_to_keep, feature, min_zone_size) if num_to_keep == nil then num_to_keep = 1 end if feature == nil then feature = 'rock_wall' end diff --git a/crawl-ref/source/dat/dlua/lm_fog.lua b/crawl-ref/source/dat/dlua/lm_fog.lua index cccc0f435f31..4a912b2c145b 100644 --- a/crawl-ref/source/dat/dlua/lm_fog.lua +++ b/crawl-ref/source/dat/dlua/lm_fog.lua @@ -114,6 +114,7 @@ function FogMachine:new(pars) m.buildup_turns = 0 local tick_pars = {} + -- this allows params <= 0, but that value here is min'd to 1 in lm_trig.lua tick_pars.delay_min = pars.delay_min or pars.delay or 1 tick_pars.delay_max = pars.delay_max or pars.delay tick_pars.type = "turn" diff --git a/crawl-ref/source/dat/dlua/lm_trig.lua b/crawl-ref/source/dat/dlua/lm_trig.lua index 7d688c8cb0a9..0d4ff4f6acdb 100644 --- a/crawl-ref/source/dat/dlua/lm_trig.lua +++ b/crawl-ref/source/dat/dlua/lm_trig.lua @@ -240,7 +240,6 @@ function Triggerable:event(marker, ev) error("Triggerable type " .. class .. " at (" ..x .. ", " .. y .. ") " .. "has no triggerers for dgn_event " .. e_type ) end - for _, trig_idx in ipairs(trig_list) do self.triggerers[trig_idx]:event(self, marker, ev) @@ -627,8 +626,9 @@ function DgnTriggerer:new(pars) tr:setup() if tr.type == "turn" and (tr.delay or (tr.delay_min and tr.delay_max)) then - tr.delay_min = tr.delay_min or tr.delay or 1 - tr.delay_max = tr.delay_max or tr.delay + -- lua gotcha reminder: 0 does not evaluate to false. + tr.delay_min = math.max(tr.delay_min or tr.delay, 1) + tr.delay_max = math.max(tr.delay_max or tr.delay, tr.delay_min) tr.buildup_turns = 0 tr.countdown = 0 diff --git a/crawl-ref/source/dat/dlua/lm_trove.lua b/crawl-ref/source/dat/dlua/lm_trove.lua index 9b06550bbb0d..b85ad3624b33 100644 --- a/crawl-ref/source/dat/dlua/lm_trove.lua +++ b/crawl-ref/source/dat/dlua/lm_trove.lua @@ -342,8 +342,7 @@ function TroveMarker:item_name(do_grammar) s = s .. " " .. item.base_type .. " of" end elseif item.base_type == "book" then - books = {"Necronomicon", "tome of Destruction", - "Young Poisoner's Handbook", "Grand Grimoire"} + books = {"Necronomicon", "Young Poisoner's Handbook", "Grand Grimoire"} if util.contains(books, item.sub_type) then if do_grammar == false then return item.sub_type diff --git a/crawl-ref/source/dat/dlua/test.lua b/crawl-ref/source/dat/dlua/test.lua index 2242e84a61f0..c2ef1835984f 100644 --- a/crawl-ref/source/dat/dlua/test.lua +++ b/crawl-ref/source/dat/dlua/test.lua @@ -44,7 +44,7 @@ function test.regenerate_level(place, use_random_maps) if place then debug.goto_place(place) end - debug.flush_map_memory() + debug.flush_map_memory() -- TODO: this is overkill for a single level dgn.reset_level() debug.generate_level(use_random_maps) end diff --git a/crawl-ref/source/dat/dlua/util.lua b/crawl-ref/source/dat/dlua/util.lua index 2ac71198d36f..3eea18b234d6 100644 --- a/crawl-ref/source/dat/dlua/util.lua +++ b/crawl-ref/source/dat/dlua/util.lua @@ -118,6 +118,27 @@ function util.pairs(map) return mappairs end +--- a locale-insensitive less-than operation. +function util.stable_lessthan(x1, x2) + if type(x1) == "string" then + return crawl.string_compare(x1, x2) < 0 + else + return x1 < x2 + end +end + +--- A wrapper on `table.sort` that uses locale-insensitive comparison for +--- strings by default. Like `table.sort`, this sorts an array in-place. +--- @tparam t an array to sort. +--- @tparam f an optional less-than function to use for determining sort order. +function util.sort(t, f) + if f == nil then + return table.sort(t, util.stable_lessthan) + else + return table.sort(t, f) + end +end + --- Creates a string of the elements in list joined by separator. function util.join(sep, list) return table.concat(list, sep) @@ -313,9 +334,9 @@ function util.random_weighted_keys(weightfn, list, order) keys[#keys+1] = k end if order then - table.sort(keys, order) + util.sort(keys, order) else - table.sort(keys) + util.sort(keys) end for i,k in ipairs(keys) do v = list[k] @@ -334,7 +355,7 @@ function util.sorted_weight_table(t, sort) local keys = { } for k, v in pairs(t) do table.insert(keys, k) end if sort == nil then - table.sort(keys) + util.sort(keys) else table.sort(keys, sort) end diff --git a/crawl-ref/source/dat/dlua/v_rooms.lua b/crawl-ref/source/dat/dlua/v_rooms.lua index d8452170b3ba..f56c040dc89d 100644 --- a/crawl-ref/source/dat/dlua/v_rooms.lua +++ b/crawl-ref/source/dat/dlua/v_rooms.lua @@ -88,15 +88,23 @@ local function make_tagged_room(options,chosen) -- Temporarily prevent map getting mirrored / rotated during resolution because it'll seriously -- screw with our attempts to understand and position the vault later; and hardwire transparency because lack of it can fail a whole layout -- TODO: Store these tags on the room first so we can actually support them down the line ... - dgn.tags(mapdef, "no_vmirror no_hmirror no_rotate transparent"); + local old_tags = dgn.tags(mapdef) + dgn.tags(mapdef, "no_vmirror no_hmirror no_rotate transparent") -- Resolve the map so we can find its width / height local map, vplace = dgn.resolve_map(mapdef,false) local room,room_width,room_height -- If we can't find a map then we're probably not going to find one - if map == nil then return nil end + if map == nil then + dgn.tags(mapdef, nil) + dgn.tags(mapdef, old_tags) + return nil + end -- Allow map to be flipped and rotated again, otherwise we'll struggle later when we want to rotate it into the correct orientation dgn.tags_remove(map, "no_vmirror no_hmirror no_rotate") + -- restore the original tags to mapdef, since this state is persistent + dgn.tags(mapdef, nil) + dgn.tags(mapdef, old_tags .. " transparent") local room_width,room_height = dgn.mapsize(map) local veto = false diff --git a/crawl-ref/source/dat/dlua/ziggurat.lua b/crawl-ref/source/dat/dlua/ziggurat.lua index 17f84d98dea9..41f28fc7a14a 100644 --- a/crawl-ref/source/dat/dlua/ziggurat.lua +++ b/crawl-ref/source/dat/dlua/ziggurat.lua @@ -587,8 +587,7 @@ local function ziggurat_create_loot_at(c) dgn.good_scrolls) local super_loot = dgn.item_spec("| no_pickup w:7000 /" .. "potion of experience no_pickup w:190 q:1 /" .. - "potion of mutation no_pickup w:190 /" .. - "potion of mutation no_pickup w:40 q:1 /" .. + "potion of mutation no_pickup w:220 /" .. "ration no_pickup w:80 /" .. "potion of heal wounds q:5 no_pickup / " .. "potion of haste q:5 no_pickup / " .. diff --git a/crawl-ref/source/dat/species/merfolk.yaml b/crawl-ref/source/dat/species/merfolk.yaml index 76825a6a53a1..fc3b859dff56 100644 --- a/crawl-ref/source/dat/species/merfolk.yaml +++ b/crawl-ref/source/dat/species/merfolk.yaml @@ -42,7 +42,7 @@ int: 7 dex: 9 levelup_stat_frequency: 5 fake_mutations: - - long: You revert to your normal form in water + - long: You revert to your normal form in water. short: change form in water - long: You are very nimble and swift while swimming. short: swift swim diff --git a/crawl-ref/source/dat/species/vampire.yaml b/crawl-ref/source/dat/species/vampire.yaml index 54e8d84bb631..c0e73ec603ab 100644 --- a/crawl-ref/source/dat/species/vampire.yaml +++ b/crawl-ref/source/dat/species/vampire.yaml @@ -49,6 +49,9 @@ mutations: MUT_FANGS: 3 MUT_ACUTE_VISION: 1 MUT_UNBREATHING: 1 +fake_mutations: + - long: You do not eat. + short: no food recommended_jobs: - JOB_GLADIATOR - JOB_ASSASSIN diff --git a/crawl-ref/source/dat/tiles/logo.png b/crawl-ref/source/dat/tiles/logo.png index a6d1e2b89422..f8d463fe87cb 100644 Binary files a/crawl-ref/source/dat/tiles/logo.png and b/crawl-ref/source/dat/tiles/logo.png differ diff --git a/crawl-ref/source/dat/tiles/logosmall.png b/crawl-ref/source/dat/tiles/logosmall.png index 2177329c501f..292e7a06fe3a 100644 Binary files a/crawl-ref/source/dat/tiles/logosmall.png and b/crawl-ref/source/dat/tiles/logosmall.png differ diff --git a/crawl-ref/source/dat/tiles/stone_soup_icon-32x32.png b/crawl-ref/source/dat/tiles/stone_soup_icon-32x32.png index 726f4078dea0..31fe81985956 100644 Binary files a/crawl-ref/source/dat/tiles/stone_soup_icon-32x32.png and b/crawl-ref/source/dat/tiles/stone_soup_icon-32x32.png differ diff --git a/crawl-ref/source/dat/tiles/stone_soup_icon-512x512.png b/crawl-ref/source/dat/tiles/stone_soup_icon-512x512.png index c07378acc580..0577d0c33084 100644 Binary files a/crawl-ref/source/dat/tiles/stone_soup_icon-512x512.png and b/crawl-ref/source/dat/tiles/stone_soup_icon-512x512.png differ diff --git a/crawl-ref/source/dat/tiles/stone_soup_icon-win32.png b/crawl-ref/source/dat/tiles/stone_soup_icon-win32.png index f5a618e48c95..fff8fa6b7bad 100644 Binary files a/crawl-ref/source/dat/tiles/stone_soup_icon-win32.png and b/crawl-ref/source/dat/tiles/stone_soup_icon-win32.png differ diff --git a/crawl-ref/source/dat/tiles/title_Cws_Minotauros.png b/crawl-ref/source/dat/tiles/title_Cws_Minotauros.png index 7957a7f78717..ac6b53295362 100644 Binary files a/crawl-ref/source/dat/tiles/title_Cws_Minotauros.png and b/crawl-ref/source/dat/tiles/title_Cws_Minotauros.png differ diff --git a/crawl-ref/source/dat/tiles/title_baconkid_duvessa_dowan.png b/crawl-ref/source/dat/tiles/title_baconkid_duvessa_dowan.png index cadc9c77f01d..9f74df3f3f0a 100644 Binary files a/crawl-ref/source/dat/tiles/title_baconkid_duvessa_dowan.png and b/crawl-ref/source/dat/tiles/title_baconkid_duvessa_dowan.png differ diff --git a/crawl-ref/source/dat/tiles/title_baconkid_gastronok.png b/crawl-ref/source/dat/tiles/title_baconkid_gastronok.png index 5f635ccc9acb..fe3fa8ee0f0c 100644 Binary files a/crawl-ref/source/dat/tiles/title_baconkid_gastronok.png and b/crawl-ref/source/dat/tiles/title_baconkid_gastronok.png differ diff --git a/crawl-ref/source/dat/tiles/title_baconkid_mnoleg.png b/crawl-ref/source/dat/tiles/title_baconkid_mnoleg.png index 662e129d8ca4..71be971fc058 100644 Binary files a/crawl-ref/source/dat/tiles/title_baconkid_mnoleg.png and b/crawl-ref/source/dat/tiles/title_baconkid_mnoleg.png differ diff --git a/crawl-ref/source/dat/tiles/title_denzi_dragon.png b/crawl-ref/source/dat/tiles/title_denzi_dragon.png index 740c841b17b9..2ddff51eda9b 100644 Binary files a/crawl-ref/source/dat/tiles/title_denzi_dragon.png and b/crawl-ref/source/dat/tiles/title_denzi_dragon.png differ diff --git a/crawl-ref/source/dat/tiles/title_denzi_evil_mage.png b/crawl-ref/source/dat/tiles/title_denzi_evil_mage.png index b454152922f7..cb531d8bd04f 100644 Binary files a/crawl-ref/source/dat/tiles/title_denzi_evil_mage.png and b/crawl-ref/source/dat/tiles/title_denzi_evil_mage.png differ diff --git a/crawl-ref/source/dat/tiles/title_denzi_invasion.png b/crawl-ref/source/dat/tiles/title_denzi_invasion.png index 372f8fb66c90..833dd19b223d 100644 Binary files a/crawl-ref/source/dat/tiles/title_denzi_invasion.png and b/crawl-ref/source/dat/tiles/title_denzi_invasion.png differ diff --git a/crawl-ref/source/dat/tiles/title_denzi_kitchen_duty.png b/crawl-ref/source/dat/tiles/title_denzi_kitchen_duty.png index 890d0751f7da..001cd899b6b2 100644 Binary files a/crawl-ref/source/dat/tiles/title_denzi_kitchen_duty.png and b/crawl-ref/source/dat/tiles/title_denzi_kitchen_duty.png differ diff --git a/crawl-ref/source/dat/tiles/title_denzi_summoner.png b/crawl-ref/source/dat/tiles/title_denzi_summoner.png index 4a9128fe7c97..8bcfc74a386f 100644 Binary files a/crawl-ref/source/dat/tiles/title_denzi_summoner.png and b/crawl-ref/source/dat/tiles/title_denzi_summoner.png differ diff --git a/crawl-ref/source/dat/tiles/title_denzi_undead_warrior.png b/crawl-ref/source/dat/tiles/title_denzi_undead_warrior.png index 9b2fa9e5bd8c..96a182acc4d0 100644 Binary files a/crawl-ref/source/dat/tiles/title_denzi_undead_warrior.png and b/crawl-ref/source/dat/tiles/title_denzi_undead_warrior.png differ diff --git a/crawl-ref/source/dat/tiles/title_firemage.png b/crawl-ref/source/dat/tiles/title_firemage.png index b4e16719a13c..2a996abfe17e 100644 Binary files a/crawl-ref/source/dat/tiles/title_firemage.png and b/crawl-ref/source/dat/tiles/title_firemage.png differ diff --git a/crawl-ref/source/dat/tiles/title_froggy_goodgod_tengu_gold.png b/crawl-ref/source/dat/tiles/title_froggy_goodgod_tengu_gold.png index 64879eadf931..ab1de72f1380 100644 Binary files a/crawl-ref/source/dat/tiles/title_froggy_goodgod_tengu_gold.png and b/crawl-ref/source/dat/tiles/title_froggy_goodgod_tengu_gold.png differ diff --git a/crawl-ref/source/dat/tiles/title_froggy_jiyva_felid.png b/crawl-ref/source/dat/tiles/title_froggy_jiyva_felid.png index fdc3a72b08fc..f9cfa8f7fb07 100644 Binary files a/crawl-ref/source/dat/tiles/title_froggy_jiyva_felid.png and b/crawl-ref/source/dat/tiles/title_froggy_jiyva_felid.png differ diff --git a/crawl-ref/source/dat/tiles/title_froggy_natasha_and_boris.png b/crawl-ref/source/dat/tiles/title_froggy_natasha_and_boris.png index 3cd13c7d3ed1..c6d9cd3a6c41 100644 Binary files a/crawl-ref/source/dat/tiles/title_froggy_natasha_and_boris.png and b/crawl-ref/source/dat/tiles/title_froggy_natasha_and_boris.png differ diff --git a/crawl-ref/source/dat/tiles/title_froggy_rune_and_run_failed_on_dis.png b/crawl-ref/source/dat/tiles/title_froggy_rune_and_run_failed_on_dis.png index 99cfd4f75b84..53e5581bc9df 100644 Binary files a/crawl-ref/source/dat/tiles/title_froggy_rune_and_run_failed_on_dis.png and b/crawl-ref/source/dat/tiles/title_froggy_rune_and_run_failed_on_dis.png differ diff --git a/crawl-ref/source/dat/tiles/title_froggy_thunder_fist_nikola.png b/crawl-ref/source/dat/tiles/title_froggy_thunder_fist_nikola.png index 13a7ca6668c2..86c392e1d89a 100644 Binary files a/crawl-ref/source/dat/tiles/title_froggy_thunder_fist_nikola.png and b/crawl-ref/source/dat/tiles/title_froggy_thunder_fist_nikola.png differ diff --git a/crawl-ref/source/dat/tiles/title_omndra_zot_demon.png b/crawl-ref/source/dat/tiles/title_omndra_zot_demon.png index c427641e9c0a..3b06d9af1326 100644 Binary files a/crawl-ref/source/dat/tiles/title_omndra_zot_demon.png and b/crawl-ref/source/dat/tiles/title_omndra_zot_demon.png differ diff --git a/crawl-ref/source/dat/tiles/title_peileppe_bloax_eye.png b/crawl-ref/source/dat/tiles/title_peileppe_bloax_eye.png index be107fc1ee6f..1339f0989cfc 100644 Binary files a/crawl-ref/source/dat/tiles/title_peileppe_bloax_eye.png and b/crawl-ref/source/dat/tiles/title_peileppe_bloax_eye.png differ diff --git a/crawl-ref/source/dat/tiles/title_ploomutoo_ijyb.png b/crawl-ref/source/dat/tiles/title_ploomutoo_ijyb.png index 0f0d12095e15..8c61edb5741c 100644 Binary files a/crawl-ref/source/dat/tiles/title_ploomutoo_ijyb.png and b/crawl-ref/source/dat/tiles/title_ploomutoo_ijyb.png differ diff --git a/crawl-ref/source/dat/tiles/title_pooryurik_knight.png b/crawl-ref/source/dat/tiles/title_pooryurik_knight.png index b3b7a214fb9f..51b6e996cb25 100644 Binary files a/crawl-ref/source/dat/tiles/title_pooryurik_knight.png and b/crawl-ref/source/dat/tiles/title_pooryurik_knight.png differ diff --git a/crawl-ref/source/dat/tiles/title_psiweapon_kiku.png b/crawl-ref/source/dat/tiles/title_psiweapon_kiku.png index 24be7c2f4119..1d1745417e04 100644 Binary files a/crawl-ref/source/dat/tiles/title_psiweapon_kiku.png and b/crawl-ref/source/dat/tiles/title_psiweapon_kiku.png differ diff --git a/crawl-ref/source/dat/tiles/title_psiweapon_roxanne.png b/crawl-ref/source/dat/tiles/title_psiweapon_roxanne.png index 3f41157e5794..852706c483c7 100644 Binary files a/crawl-ref/source/dat/tiles/title_psiweapon_roxanne.png and b/crawl-ref/source/dat/tiles/title_psiweapon_roxanne.png differ diff --git a/crawl-ref/source/dat/tiles/title_shadyamish_octm.png b/crawl-ref/source/dat/tiles/title_shadyamish_octm.png index dacc365b542e..0f0b57edf8a5 100644 Binary files a/crawl-ref/source/dat/tiles/title_shadyamish_octm.png and b/crawl-ref/source/dat/tiles/title_shadyamish_octm.png differ diff --git a/crawl-ref/source/dat/tiles/title_white_noise_entering_the_dungeon.png b/crawl-ref/source/dat/tiles/title_white_noise_entering_the_dungeon.png index 93efdad0f249..d606d6ec07d8 100644 Binary files a/crawl-ref/source/dat/tiles/title_white_noise_entering_the_dungeon.png and b/crawl-ref/source/dat/tiles/title_white_noise_entering_the_dungeon.png differ diff --git a/crawl-ref/source/dat/tiles/title_white_noise_grabbing_the_orb.png b/crawl-ref/source/dat/tiles/title_white_noise_grabbing_the_orb.png index ffd86da2645c..9a37333c130f 100644 Binary files a/crawl-ref/source/dat/tiles/title_white_noise_grabbing_the_orb.png and b/crawl-ref/source/dat/tiles/title_white_noise_grabbing_the_orb.png differ diff --git a/crawl-ref/source/dbg-asrt.cc b/crawl-ref/source/dbg-asrt.cc index 96607deebb6c..9a9d8a012000 100644 --- a/crawl-ref/source/dbg-asrt.cc +++ b/crawl-ref/source/dbg-asrt.cc @@ -197,7 +197,8 @@ static void _dump_player(FILE *file) } fprintf(file, "Skills (mode: %s)\n", you.auto_training ? "auto" : "manual"); - fprintf(file, "Name | can_train | train | training | level | points | progress\n"); + fprintf(file, "Name | can_currently_train | train | training |" + " level | points | progress\n"); for (size_t i = 0; i < NUM_SKILLS; ++i) { const skill_type sk = skill_type(i); @@ -210,9 +211,9 @@ static void _dump_player(FILE *file) if (sk >= 0 && you.skills[sk] < 27) needed_max = skill_exp_needed(you.skills[sk] + 1, sk); - fprintf(file, "%-16s| %c | %u | %3u | %2d | %6d | %d/%d\n", + fprintf(file, "%-16s| %c | %u | %3u | %2d | %6d | %d/%d\n", skill_name(sk), - you.can_train[sk] ? 'X' : ' ', + you.can_currently_train[sk] ? 'X' : ' ', you.train[sk], you.training[sk], you.skills[sk], @@ -563,6 +564,13 @@ static void _dump_ver_stuff(FILE* file) #else fprintf(file, "Tiles: no\n\n"); #endif + if (you.fully_seeded) + { + fprintf(file, "Seed: %" PRIu64 ", deterministic pregen: %d\n", + crawl_state.seed, (int) you.deterministic_levelgen); + } + if (Version::history_size() > 1) + fprintf(file, "Version history:\n%s\n\n", Version::history().c_str()); } static void _dump_command_line(FILE *file) diff --git a/crawl-ref/source/dbg-maps.cc b/crawl-ref/source/dbg-maps.cc index 42c8668b47ba..541eae5a77be 100644 --- a/crawl-ref/source/dbg-maps.cc +++ b/crawl-ref/source/dbg-maps.cc @@ -63,7 +63,7 @@ static bool _is_disconnected_level() { // Don't care about non-Dungeon levels. if (!player_in_connected_branch() - || (branches[you.where_are_you].branch_flags & BFLAG_ISLANDED)) + || (branches[you.where_are_you].branch_flags & brflag::islanded)) { return false; } @@ -263,6 +263,8 @@ bool mapstat_build_levels() dlua.callfn("dgn_clear_data", ""); you.uniq_map_tags.clear(); you.uniq_map_names.clear(); + you.uniq_map_tags_abyss.clear(); + you.uniq_map_names_abyss.clear(); you.unique_creatures.reset(); initialise_branch_depths(); init_level_connectivity(); @@ -303,6 +305,8 @@ static void _report_available_random_vaults(FILE *outf) { you.uniq_map_tags.clear(); you.uniq_map_names.clear(); + you.uniq_map_tags_abyss.clear(); + you.uniq_map_names_abyss.clear(); fprintf(outf, "\n\nRandom vaults available by dungeon level:\n"); for (auto lvl : generated_levels) diff --git a/crawl-ref/source/dbg-objstat.cc b/crawl-ref/source/dbg-objstat.cc index 81a5b1ea15a2..248108be9847 100644 --- a/crawl-ref/source/dbg-objstat.cc +++ b/crawl-ref/source/dbg-objstat.cc @@ -1103,7 +1103,6 @@ static void _write_branch_item_stats(branch_type br, const item_type &item) { unsigned int level_count = 0; const vector &fields = item_fields[item.base_type]; - vector::const_iterator li; const string name = _item_name(item); const char *num_field = _item_has_antiquity(item.base_type) ? "AllNum" : "Num"; diff --git a/crawl-ref/source/dbg-util.cc b/crawl-ref/source/dbg-util.cc index dcfaaa119692..b1cc31279e99 100644 --- a/crawl-ref/source/dbg-util.cc +++ b/crawl-ref/source/dbg-util.cc @@ -10,12 +10,14 @@ #include "artefact.h" #include "directn.h" #include "dungeon.h" +#include "format.h" #include "item-name.h" #include "libutil.h" #include "macro.h" #include "message.h" #include "options.h" #include "religion.h" +#include "scroller.h" #include "shopping.h" #include "skills.h" #include "spl-util.h" @@ -117,6 +119,29 @@ void debug_dump_levgen() mpr(""); } +void debug_show_builder_logs() +{ + if (!you.props.exists("debug_builder_logs")) + { + mprf("This save was not generated on a build that stores logs."); + return; + } + const string cur_level = level_id::current().describe(); + CrawlHashTable &log_table = you.props["debug_builder_logs"].get_table(); + if (!log_table.exists(cur_level) + || log_table[cur_level].get_string().size() == 0) + { + mprf("No builder logs are saved for %s.", cur_level.c_str()); + return; + } + string props_text = log_table[cur_level].get_string(); + auto log = formatted_string::parse_string(trim_string_right(props_text)); + formatted_scroller log_scroller; + log_scroller.set_more(); + log_scroller.add_formatted_string(log, false); + log_scroller.show(); +} + string debug_coord_str(const coord_def &pos) { return make_stringf("(%d, %d)%s", pos.x, pos.y, @@ -472,7 +497,7 @@ void debug_list_vacant_keys() { check(base, string(1, base)); - char lower = tolower(base); + char lower = tolower_safe(base); check(lower, string(1, lower)); char ctrl = CONTROL(base); diff --git a/crawl-ref/source/dbg-util.h b/crawl-ref/source/dbg-util.h index 3b8b5042aaef..b7175fdb706b 100644 --- a/crawl-ref/source/dbg-util.h +++ b/crawl-ref/source/dbg-util.h @@ -12,6 +12,7 @@ skill_type skill_from_name(const char *name); int debug_cap_stat(int stat); void debug_dump_levgen(); +void debug_show_builder_logs(); struct item_def; string debug_art_val_str(const item_def& item); diff --git a/crawl-ref/source/debian/changelog b/crawl-ref/source/debian/changelog index 5bccdd76022b..73b0327a75b3 100644 --- a/crawl-ref/source/debian/changelog +++ b/crawl-ref/source/debian/changelog @@ -1,3 +1,8 @@ +crawl (2:0.24.0-1) UNRELEASED; urgency=low + + * New upstream release. + -- gammafunk Thu, 24 Oct 2019 14:16:07 -0400 + crawl (2:0.23.2-1) UNRELEASED; urgency=low * New upstream release. diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc index cb72651dde4a..c6c2319496cf 100644 --- a/crawl-ref/source/decks.cc +++ b/crawl-ref/source/decks.cc @@ -409,16 +409,15 @@ static void _describe_cards(CrawlVector& cards) title_hbox->add_child(move(icon)); #endif auto title = make_shared(formatted_string(name, WHITE)); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 10}); + title->set_margin_for_sdl(0, 0, 0, 10); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->set_margin_for_crt({first ? 0 : 1, 0, 1, 0}); - title_hbox->set_margin_for_sdl({first ? 0 : 20, 0, 20, 0}); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_crt(first ? 0 : 1, 0); + title_hbox->set_margin_for_sdl(first ? 0 : 20, 0); vbox->add_child(move(title_hbox)); auto text = make_shared(desc); - text->wrap_text = true; + text->set_wrap_text(true); vbox->add_child(move(text)); #ifdef USE_TILE_WEB @@ -431,7 +430,7 @@ static void _describe_cards(CrawlVector& cards) } #ifdef USE_TILE_LOCAL - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif scroller->set_child(move(vbox)); @@ -523,7 +522,7 @@ static char _deck_hotkey(deck_type deck) static deck_type _choose_deck(const string title = "Draw") { - ToggleableMenu deck_menu(MF_SINGLESELECT | MF_ANYPRINTABLE + ToggleableMenu deck_menu(MF_SINGLESELECT | MF_NO_WRAP_ROWS | MF_TOGGLE_ACTION | MF_ALWAYS_SHOW_MORE); { ToggleableMenuEntry* me = @@ -532,9 +531,6 @@ static deck_type _choose_deck(const string title = "Draw") "Describe which deck? " "Cards available", MEL_TITLE); -#ifdef USE_TILE_LOCAL - me->colour = BLUE; -#endif deck_menu.set_title(me, true, true); } deck_menu.set_tag("deck"); @@ -718,9 +714,6 @@ static void _draw_stack(int to_stack) "Describe which deck? " "Cards available", MEL_TITLE); -#ifdef USE_TILE_LOCAL - me->colour = BLUE; -#endif deck_menu.set_title(me, true, true); } deck_menu.set_tag("deck"); diff --git a/crawl-ref/source/defines.h b/crawl-ref/source/defines.h index 8bffc629930c..72bf32cacc61 100644 --- a/crawl-ref/source/defines.h +++ b/crawl-ref/source/defines.h @@ -7,6 +7,14 @@ #pragma once +// In this case, an x86 CPU will use x87 math for floating point calculations, +// which uses 80 bit intermediate results, andleads to difference from the +// (much more common, in 2019) SSE-based calculations. +// probably far from the only case where seeding isn't reliable... +#if defined(TARGET_CPU_X86) && !defined(__SSE__) +#define SEEDING_UNRELIABLE +#endif + // Minimum terminal size allowed. #define MIN_COLS 79 #define MIN_LINES 24 diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc index 67d19ac93a13..d7c642a494a3 100644 --- a/crawl-ref/source/delay.cc +++ b/crawl-ref/source/delay.cc @@ -23,10 +23,12 @@ #include "describe.h" #include "directn.h" #include "dungeon.h" +#include "english.h" #include "enum.h" #include "env.h" #include "exclude.h" #include "exercise.h" +#include "fineff.h" #include "food.h" #include "fprop.h" #include "god-abil.h" @@ -49,6 +51,7 @@ #include "mon-gear.h" #include "mon-tentacle.h" #include "mon-util.h" +#include "mutation.h" #include "nearby-danger.h" #include "notes.h" #include "options.h" @@ -77,21 +80,10 @@ #include "view.h" #include "xom.h" -class interrupt_block -{ -public: - interrupt_block() { ++interrupts_blocked; } - ~interrupt_block() { --interrupts_blocked; } - - static bool blocked() { return interrupts_blocked > 0; } -private: - static int interrupts_blocked; -}; - int interrupt_block::interrupts_blocked = 0; static void _xom_check_corpse_waste(); -static const char *_activity_interrupt_name(activity_interrupt_type ai); +static const char *_activity_interrupt_name(activity_interrupt ai); void push_delay(shared_ptr delay) { @@ -146,12 +138,6 @@ static void _interrupt_butchering(const char* action) mprf("You stop %s the corpse%s.", action, multiple_corpses ? "s" : ""); } -bool BottleBloodDelay::try_interrupt() -{ - _interrupt_butchering("bottling blood from"); - return true; -} - bool ButcherDelay::try_interrupt() { _interrupt_butchering("butchering"); @@ -265,6 +251,38 @@ bool ShaftSelfDelay::try_interrupt() return true; } +bool ExsanguinateDelay::try_interrupt() +{ + if (duration > 1 && !was_prompted) + { + if (!crawl_state.disables[DIS_CONFIRMATIONS] + && !yesno("Keep bloodletting?", false, 0, false)) + { + mpr("You stop emptying yourself of blood."); + return true; + } + else + was_prompted = true; + } + return false; +} + +bool RevivifyDelay::try_interrupt() +{ + if (duration > 1 && !was_prompted) + { + if (!crawl_state.disables[DIS_CONFIRMATIONS] + && !yesno("Continue your ritual?", false, 0, false)) + { + mpr("You stop revivifying."); + return true; + } + else + was_prompted = true; + } + return false; +} + void stop_delay(bool stop_stair_travel) { if (you.delay_queue.empty()) @@ -483,6 +501,16 @@ void BlurryScrollDelay::start() mprf(MSGCH_MULTITURN_ACTION, "You begin reading the scroll."); } +void ExsanguinateDelay::start() +{ + mprf(MSGCH_MULTITURN_ACTION, "You begin bloodletting."); +} + +void RevivifyDelay::start() +{ + mprf(MSGCH_MULTITURN_ACTION, "You begin the revivification ritual."); +} + command_type RunDelay::move_cmd() const { return _get_running_command(); @@ -601,11 +629,6 @@ bool ButcherDelay::invalidated() return _check_corpse_gone(corpse, "butcher it"); } -bool BottleBloodDelay::invalidated() -{ - return _check_corpse_gone(corpse, "bottle its blood"); -} - bool MultidropDelay::invalidated() { // Throw away invalid items. XXX: what are they? @@ -733,7 +756,7 @@ void JewelleryOnDelay::finish() #ifdef USE_SOUND parse_sound(WEAR_JEWELLERY_SOUND); #endif - puton_ring(jewellery.link, false, false); + puton_ring(jewellery, false, false); } void ArmourOnDelay::finish() @@ -863,17 +886,27 @@ void BlurryScrollDelay::finish() { // Make sure the scroll still exists, the player isn't confused, etc if (_can_read_scroll(scroll)) + { read_scroll(scroll); + // we are now probably out of sync with regular world_reacts timing, so + // trigger any fineffs that might have been caused by reading this + // scroll, e.g. torment vs. TRJ. Otherwise they'd have to wait until + // the next world_reacts. + // TODO: is there a more general condition that this can be triggered + // under? it might impact other obscure cases, e.g. passwalling with + // spiny. + fire_final_effects(); + } } -static void _finish_butcher_delay(item_def& corpse, bool bottling) +static void _finish_butcher_delay(item_def& corpse) { // We know the item is valid and a real corpse, because invalidated() // checked for that. - finish_butchering(corpse, bottling); + finish_butchering(corpse); // Don't waste time picking up chunks if you're already // starving. (jpeg) - if ((you.hunger_state > HS_STARVING || you.species == SP_VAMPIRE) + if (you.hunger_state > HS_STARVING // Only pick up chunks if this is the last delay... && (you.delay_queue.size() == 1 // ...Or, equivalently, if it's the last butcher one. @@ -886,12 +919,7 @@ static void _finish_butcher_delay(item_def& corpse, bool bottling) void ButcherDelay::finish() { - _finish_butcher_delay(corpse, false); -} - -void BottleBloodDelay::finish() -{ - _finish_butcher_delay(corpse, true); + _finish_butcher_delay(corpse); } void DropItemDelay::finish() @@ -921,6 +949,25 @@ void DescendingStairsDelay::finish() down_stairs(); } +void ExsanguinateDelay::finish() +{ + blood_spray(you.pos(), MONS_PLAYER, 10); + you.vampire_alive = false; + you.redraw_status_lights = true; + calc_hp(true); + mpr("Now bloodless."); + vampire_update_transformations(); +} + +void RevivifyDelay::finish() +{ + you.vampire_alive = true; + you.redraw_status_lights = true; + mpr("Now alive."); + temp_mutate(MUT_FRAIL, "vampire revification"); + vampire_update_transformations(); +} + void run_macro(const char *macroname) { #ifdef CLUA_BINDINGS @@ -962,12 +1009,12 @@ void run_macro(const char *macroname) // Returns TRUE if the delay should be interrupted, MAYBE if the user function // had no opinion on the matter, FALSE if the delay should not be interrupted. static maybe_bool _userdef_interrupt_activity(Delay* delay, - activity_interrupt_type ai, + activity_interrupt ai, const activity_interrupt_data &at) { #ifdef CLUA_BINDINGS lua_State *ls = clua.state(); - if (!ls || ai == AI_FORCE_INTERRUPT) + if (!ls || ai == activity_interrupt::force) return MB_TRUE; const char *interrupt_name = _activity_interrupt_name(ai); @@ -1002,7 +1049,7 @@ static maybe_bool _userdef_interrupt_activity(Delay* delay, // Returns true if the activity should be interrupted, false otherwise. static bool _should_stop_activity(Delay* delay, - activity_interrupt_type ai, + activity_interrupt ai, const activity_interrupt_data &at) { switch (_userdef_interrupt_activity(delay, ai, at)) @@ -1024,15 +1071,20 @@ static bool _should_stop_activity(Delay* delay, // No monster will attack you inside a sanctuary, // so presence of monsters won't matter. - if (ai == AI_SEE_MONSTER && is_sanctuary(you.pos())) + if (ai == activity_interrupt::see_monster && is_sanctuary(you.pos())) return false; auto curr = current_delay(); // Not necessarily what we were passed. - if ((ai == AI_SEE_MONSTER || ai == AI_MIMIC) && player_stair_delay()) + if ((ai == activity_interrupt::see_monster + || ai == activity_interrupt::mimic) + && player_stair_delay()) + { return false; + } - if (ai == AI_FULL_HP || ai == AI_FULL_MP || ai == AI_ANCESTOR_HP) + if (ai == activity_interrupt::full_hp || ai == activity_interrupt::full_mp + || ai == activity_interrupt::ancestor_hp) { if ((Options.rest_wait_both && curr->is_resting() && !you.is_sufficiently_rested()) @@ -1044,14 +1096,14 @@ static bool _should_stop_activity(Delay* delay, } // Don't interrupt feeding or butchering for monsters already in view. - if (curr->is_butcher() && ai == AI_SEE_MONSTER + if (curr->is_butcher() && ai == activity_interrupt::see_monster && testbits(at.mons_data->flags, MF_WAS_IN_VIEW)) { return false; } - return ai == AI_FORCE_INTERRUPT - || Options.activity_interrupts[delay->name()][ai]; + return ai == activity_interrupt::force + || Options.activity_interrupts[delay->name()][static_cast(ai)]; } static string _abyss_monster_creation_message(const monster* mon) @@ -1088,24 +1140,24 @@ static string _abyss_monster_creation_message(const monster* mon) return *random_choose_weighted(messages); } -static inline bool _monster_warning(activity_interrupt_type ai, +static inline bool _monster_warning(activity_interrupt ai, const activity_interrupt_data &at, shared_ptr delay, vector* msgs_buf = nullptr) { - if (ai == AI_SENSE_MONSTER) + if (ai == activity_interrupt::sense_monster) { mprf(MSGCH_WARN, "You sense a monster nearby."); return true; } - if (ai != AI_SEE_MONSTER) + if (ai != activity_interrupt::see_monster) return false; if (delay && !delay->is_run() && !delay->is_butcher()) return false; if (at.context != SC_NEWLY_SEEN && !delay) return false; - ASSERT(at.apt == AIP_MONSTER); + ASSERT(at.apt == ai_payload::monster); monster* mon = at.mons_data; ASSERT(mon); if (!you.can_see(*mon)) @@ -1142,7 +1194,6 @@ static inline bool _monster_warning(activity_interrupt_type ai, text += make_stringf(" (%s)", short_ghost_description(mon).c_str()); } - set_auto_exclude(mon); if (at.context == SC_DOOR) text += " opens the door."; @@ -1191,7 +1242,9 @@ static inline bool _monster_warning(activity_interrupt_type ai, god_warning = uppercase_first(god_name(you.religion)) + " warns you: " + uppercase_first(mon->pronoun(PRONOUN_SUBJECTIVE)) - + " is a foul "; + + " " + + conjugate_verb("are", mon->pronoun_plurality()) + + " a foul "; if (mon->has_ench(ENCH_GLOWING_SHAPESHIFTER)) god_warning += "glowing "; god_warning += "shapeshifter."; @@ -1204,7 +1257,8 @@ static inline bool _monster_warning(activity_interrupt_type ai, if (!mweap.empty()) { - text += " " + uppercase_first(mon->pronoun(PRONOUN_SUBJECTIVE)) + " is" + text += " " + uppercase_first(mon->pronoun(PRONOUN_SUBJECTIVE)) + + " " + conjugate_verb("are", mi.pronoun_plurality()) + (mweap[0] != ' ' ? " " : "") + mweap + "."; } @@ -1279,7 +1333,7 @@ void autotoggle_autopickup(bool off) } // Returns true if any activity was stopped. Not reentrant. -bool interrupt_activity(activity_interrupt_type ai, +bool interrupt_activity(activity_interrupt ai, const activity_interrupt_data &at, vector* msgs_buf) { @@ -1287,7 +1341,8 @@ bool interrupt_activity(activity_interrupt_type ai, return false; const interrupt_block block_recursive_interrupts; - if (ai == AI_HIT_MONSTER || ai == AI_MONSTER_ATTACKS) + if (ai == activity_interrupt::hit_monster + || ai == activity_interrupt::monster_attacks) { const monster* mon = at.mons_data; if (mon && !mon->visible_to(&you) && !mon->submerged()) @@ -1301,7 +1356,7 @@ bool interrupt_activity(activity_interrupt_type ai, { // Printing "[foo] comes into view." messages even when not // auto-exploring/travelling. - if (ai == AI_SEE_MONSTER) + if (ai == activity_interrupt::see_monster) return _monster_warning(ai, at, nullptr, msgs_buf); else return false; @@ -1310,7 +1365,7 @@ bool interrupt_activity(activity_interrupt_type ai, const auto delay = current_delay(); // If we get hungry while traveling, let's try to auto-eat a chunk. - if (ai == AI_HUNGRY && delay->want_autoeat() && _auto_eat() + if (ai == activity_interrupt::hungry && delay->want_autoeat() && _auto_eat() && prompt_eat_chunks(true) == 1) { return false; @@ -1319,17 +1374,17 @@ bool interrupt_activity(activity_interrupt_type ai, dprf("Activity interrupt: %s", _activity_interrupt_name(ai)); // First try to stop the current delay. - if (ai == AI_FULL_HP && !you.running.notified_hp_full) + if (ai == activity_interrupt::full_hp && !you.running.notified_hp_full) { you.running.notified_hp_full = true; mpr("HP restored."); } - else if (ai == AI_FULL_MP && !you.running.notified_mp_full) + else if (ai == activity_interrupt::full_mp && !you.running.notified_mp_full) { you.running.notified_mp_full = true; mpr("Magic restored."); } - else if (ai == AI_ANCESTOR_HP + else if (ai == activity_interrupt::ancestor_hp && !you.running.notified_ancestor_hp_full) { // This interrupt only triggers when the ancestor is in LOS, @@ -1342,7 +1397,7 @@ bool interrupt_activity(activity_interrupt_type ai, { _monster_warning(ai, at, delay, msgs_buf); // Teleport stops stair delays. - stop_delay(ai == AI_TELEPORT); + stop_delay(ai == activity_interrupt::teleport); return true; } @@ -1377,7 +1432,7 @@ bool interrupt_activity(activity_interrupt_type ai, return false; } -// Must match the order of activity_interrupt_type.h! +// Must match the order of activity_interrupt.h! static const char *activity_interrupt_names[] = { "force", "keypress", "full_hp", "full_mp", "ancestor_hp", "hungry", "message", @@ -1385,23 +1440,23 @@ static const char *activity_interrupt_names[] = "sense_monster", "mimic" }; -static const char *_activity_interrupt_name(activity_interrupt_type ai) +static const char *_activity_interrupt_name(activity_interrupt ai) { - COMPILE_CHECK(ARRAYSZ(activity_interrupt_names) == NUM_AINTERRUPTS); + COMPILE_CHECK(ARRAYSZ(activity_interrupt_names) == NUM_ACTIVITY_INTERRUPTS); - if (ai == NUM_AINTERRUPTS) + if (ai == activity_interrupt::COUNT) return ""; - return activity_interrupt_names[ai]; + return activity_interrupt_names[static_cast(ai)]; } -activity_interrupt_type get_activity_interrupt(const string &name) +activity_interrupt get_activity_interrupt(const string &name) { - COMPILE_CHECK(ARRAYSZ(activity_interrupt_names) == NUM_AINTERRUPTS); + COMPILE_CHECK(ARRAYSZ(activity_interrupt_names) == NUM_ACTIVITY_INTERRUPTS); - for (int i = 0; i < NUM_AINTERRUPTS; ++i) + for (int i = 0; i < NUM_ACTIVITY_INTERRUPTS; ++i) if (name == activity_interrupt_names[i]) - return activity_interrupt_type(i); + return activity_interrupt(i); - return NUM_AINTERRUPTS; + return activity_interrupt::COUNT; } diff --git a/crawl-ref/source/delay.h b/crawl-ref/source/delay.h index 9a80ca0993a0..0b9e33d56e97 100644 --- a/crawl-ref/source/delay.h +++ b/crawl-ref/source/delay.h @@ -13,21 +13,40 @@ #include "operation-types.h" #include "seen-context-type.h" +class interrupt_block +{ +public: + interrupt_block(bool block = true) { + m_block = block; + if (block) + ++interrupts_blocked; + } + ~interrupt_block() { + if (m_block) + --interrupts_blocked; + } + + static bool blocked() { return interrupts_blocked > 0; } +private: + bool m_block; + static int interrupts_blocked; +}; + class monster; struct ait_hp_loss; -enum activity_interrupt_payload_type +enum class ai_payload // activity interrupt payloads { - AIP_NONE, - AIP_INT, - AIP_STRING, - AIP_MONSTER, - AIP_HP_LOSS, + none, + int_payload, + string_payload, + monster, + hp_loss, }; struct activity_interrupt_data { - activity_interrupt_payload_type apt; + ai_payload apt; union { const char* string_data; @@ -40,27 +59,28 @@ struct activity_interrupt_data seen_context_type context; activity_interrupt_data() - : apt(AIP_NONE), no_data(nullptr), context(SC_NONE) + : apt(ai_payload::none), no_data(nullptr), context(SC_NONE) { } activity_interrupt_data(const int *i) - : apt(AIP_INT), int_data(i), context(SC_NONE) + : apt(ai_payload::int_payload), int_data(i), context(SC_NONE) { } activity_interrupt_data(const char *s) - : apt(AIP_STRING), string_data(s), context(SC_NONE) + : apt(ai_payload::string_payload), string_data(s), context(SC_NONE) { } activity_interrupt_data(const string &s) - : apt(AIP_STRING), string_data(s.c_str()), context(SC_NONE) + : apt(ai_payload::string_payload), string_data(s.c_str()), + context(SC_NONE) { } activity_interrupt_data(monster* m, seen_context_type ctx = SC_NONE) - : apt(AIP_MONSTER), mons_data(m), context(ctx) + : apt(ai_payload::monster), mons_data(m), context(ctx) { } activity_interrupt_data(const ait_hp_loss *ahl) - : apt(AIP_HP_LOSS), ait_hp_loss_data(ahl), context(SC_NONE) + : apt(ai_payload::hp_loss), ait_hp_loss_data(ahl), context(SC_NONE) { } }; @@ -270,13 +290,13 @@ class ArmourOffDelay : public Delay class JewelleryOnDelay : public Delay { - item_def& jewellery; + const item_def& jewellery; void tick() override; void finish() override; public: - JewelleryOnDelay(int dur, item_def& item) : + JewelleryOnDelay(int dur, const item_def& item) : Delay(dur), jewellery(item) { } @@ -341,36 +361,6 @@ class ButcherDelay : public Delay } }; -class BottleBloodDelay : public Delay -{ - item_def& corpse; - - bool invalidated() override; - - void finish() override; -public: - BottleBloodDelay(int dur, item_def& item) : - Delay(dur), corpse(item) - { } - - bool try_interrupt() override; - - bool is_butcher() const override - { - return true; - } - - bool is_being_used(const item_def* item, operation_types oper) const override - { - return oper == OPER_BUTCHER && (!item || &corpse == item); - } - - const char* name() const override - { - return "bottle_blood"; - } -}; - class PasswallDelay : public Delay { coord_def dest; @@ -693,6 +683,54 @@ class BlurryScrollDelay : public Delay } }; +class ExsanguinateDelay : public Delay +{ + bool was_prompted = false; + + void start() override; + + void tick() override + { + mprf(MSGCH_MULTITURN_ACTION, "You continue bloodletting."); + } + + void finish() override; +public: + ExsanguinateDelay(int dur) : Delay(dur) + { } + + bool try_interrupt() override; + + const char* name() const override + { + return "exsanguinate"; + } +}; + +class RevivifyDelay : public Delay +{ + bool was_prompted = false; + + void start() override; + + void tick() override + { + mprf(MSGCH_MULTITURN_ACTION, "You continue your ritual."); + } + + void finish() override; +public: + RevivifyDelay(int dur) : Delay(dur) + { } + + bool try_interrupt() override; + + const char* name() const override + { + return "revivify"; + } +}; + void push_delay(shared_ptr delay); template @@ -716,12 +754,12 @@ bool already_learning_spell(int spell = -1); void clear_macro_process_key_delay(); -activity_interrupt_type get_activity_interrupt(const string &); +activity_interrupt get_activity_interrupt(const string &); void run_macro(const char *macroname = nullptr); void autotoggle_autopickup(bool off); -bool interrupt_activity(activity_interrupt_type ai, +bool interrupt_activity(activity_interrupt ai, const activity_interrupt_data &a = activity_interrupt_data(), vector* msgs_buf = nullptr); diff --git a/crawl-ref/source/describe-god.cc b/crawl-ref/source/describe-god.cc index 237a1bccf4e0..2e22b21844ad 100644 --- a/crawl-ref/source/describe-god.cc +++ b/crawl-ref/source/describe-god.cc @@ -225,9 +225,11 @@ static const char *divine_title[][8] = {"Sleeper", "Questioner", "Initiate", "Seeker of Truth", "@Walker@ of the Path","Lifter of the Veil", "Transcendent", "Drop of Water"}, +#if TAG_MAJOR_VERSION == 34 // Pakellas -- inventor theme {"Reactionary", "Apprentice", "Inquisitive", "Experimenter", "Inventor", "Pioneer", "Brilliant", "Grand Gadgeteer"}, +#endif // Uskayaw -- reveler theme {"Prude", "Wallflower", "Party-goer", "Dancer", @@ -966,6 +968,7 @@ static formatted_string _describe_god_powers(god_type which_god) desc.cprintf("Your life essence is reduced. (-10%% HP)\n"); break; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: { have_any = true; @@ -986,6 +989,7 @@ static formatted_string _describe_god_powers(god_type which_god) } break; } +#endif case GOD_LUGONU: have_any = true; @@ -1086,6 +1090,7 @@ static void build_partial_god_ui(god_type which_god, shared_ptr& popu topline += formatted_string(uppercase_first(god_name(which_god, true))); auto vbox = make_shared(Widget::VERT); + vbox->align_cross = Widget::STRETCH; auto title_hbox = make_shared(Widget::HORZ); #ifdef USE_TILE @@ -1096,12 +1101,11 @@ static void build_partial_god_ui(god_type which_god, shared_ptr& popu #endif auto title = make_shared(topline.trim()); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 16}); + title->set_margin_for_sdl(0, 0, 0, 16); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->align_self = Widget::CENTER; + title_hbox->align_main = Widget::CENTER; + title_hbox->align_cross = Widget::CENTER; vbox->add_child(move(title_hbox)); desc_sw = make_shared(); @@ -1147,7 +1151,7 @@ static void build_partial_god_ui(god_type which_god, shared_ptr& popu auto scroller = make_shared(); auto text = make_shared(desc.trim()); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(text); desc_sw->add_child(move(scroller)); @@ -1155,11 +1159,11 @@ static void build_partial_god_ui(god_type which_god, shared_ptr& popu formatted_string::parse_string(mores[mores_index][i]))); } - desc_sw->set_margin_for_sdl({20, 0, 20, 0}); - desc_sw->set_margin_for_crt({1, 0, 1, 0}); + desc_sw->set_margin_for_sdl(20, 0); + desc_sw->set_margin_for_crt(1, 0); desc_sw->expand_h = false; #ifdef USE_TILE_LOCAL - desc_sw->max_size()[0] = tiles.get_crt_font()->char_width()*80; + desc_sw->max_size().width = tiles.get_crt_font()->char_width()*80; #endif vbox->add_child(desc_sw); @@ -1235,7 +1239,7 @@ void describe_god(god_type which_god) #endif return true; } - return done = !popup->get_child()->on_event(ev); + return done = !desc_sw->current_widget()->on_event(ev); }); #ifdef USE_TILE_WEB @@ -1347,7 +1351,7 @@ bool describe_god_with_join(god_type which_god) // Next, allow child widgets to handle scrolling keys if (keyin != ' ' && keyin != CK_ENTER) - if (popup->get_child()->on_event(ev)) + if (desc_sw->current_widget()->on_event(ev)) return true; if (step == SHOW) @@ -1356,14 +1360,14 @@ bool describe_god_with_join(god_type which_god) goto update_ui; } - if (keyin != 'Y' && toupper(keyin) != 'N') + if (keyin != 'Y' && toupper_safe(keyin) != 'N') { yesno_only = true; goto update_ui; } yesno_only = false; - if (toupper(keyin) == 'N') + if (toupper_safe(keyin) == 'N') { canned_msg(MSG_OK); return done = true; diff --git a/crawl-ref/source/describe-spells.cc b/crawl-ref/source/describe-spells.cc index dad8222f126a..2b2702b7204c 100644 --- a/crawl-ref/source/describe-spells.cc +++ b/crawl-ref/source/describe-spells.cc @@ -10,6 +10,7 @@ #include "cio.h" #include "delay.h" #include "describe.h" +#include "english.h" #include "externs.h" #include "invent.h" #include "libutil.h" @@ -124,16 +125,18 @@ static string _describe_spell_filtering(mon_spell_slot_flag type, const char* pr * "possesses the following natural abilities:" */ static string _booktype_header(mon_spell_slot_flag type, size_t num_books, - bool has_silencable, bool has_filtered, const char* pronoun) + bool has_silencable, bool has_filtered, + const char* pronoun, bool pronoun_plural) { const string vulnerabilities = _ability_type_vulnerabilities(type, has_silencable); - const string spell_filter_desc = has_filtered ? _describe_spell_filtering(type, pronoun) - : ""; + const string spell_filter_desc = + has_filtered ? _describe_spell_filtering(type, pronoun) : ""; if (type == MON_SPELL_WIZARD) { - return make_stringf("has mastered %s%s%s:", + return make_stringf("%s mastered %s%s%s:", + conjugate_verb("have", pronoun_plural).c_str(), num_books > 1 ? "one of the following spellbooks" : "the following spells", spell_filter_desc.c_str(), @@ -142,7 +145,8 @@ static string _booktype_header(mon_spell_slot_flag type, size_t num_books, const string descriptor = _ability_type_descriptor(type); - return make_stringf("possesses the following %s abilities%s%s:", + return make_stringf("%s the following %s abilities%s%s:", + conjugate_verb("possess", pronoun_plural).c_str(), descriptor.c_str(), spell_filter_desc.c_str(), vulnerabilities.c_str()); @@ -261,7 +265,8 @@ static void _monster_spellbooks(const monster_info &mi, uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE)) + " " + _booktype_header(type, valid_books.size(), has_silencable, - filtered_books, mi.pronoun(PRONOUN_OBJECTIVE)); + filtered_books, mi.pronoun(PRONOUN_OBJECTIVE), + mi.pronoun_plurality()); } else { @@ -464,7 +469,7 @@ vector> map_chars_to_spells(const spellset &spells, static string _range_string(const spell_type &spell, const monster_info *mon_owner, int hd) { auto flags = get_spell_flags(spell); - int pow = mons_power_for_hd(spell, hd, false); + int pow = mons_power_for_hd(spell, hd); int range = spell_range(spell, pow, false); const bool has_range = mon_owner && range > 0 @@ -505,7 +510,7 @@ static void _describe_book(const spellbook_contents &book, if (source_item) { description.cprintf( - "\n Spells Type Level"); + "\n Spells Type Level Known"); } description.cprintf("\n"); @@ -570,11 +575,21 @@ static void _describe_book(const spellbook_contents &book, } string schools = +#if TAG_MAJOR_VERSION == 34 source_item->base_type == OBJ_RODS ? "Evocations" - : _spell_schools(spell); - description.cprintf("%s%d\n", + : +#endif + _spell_schools(spell); + + string known = ""; + if (!mon_owner) { + known = you.spell_library[spell] ? " yes" : " no"; + } + + description.cprintf("%s%d%s\n", chop_string(schools, 30).c_str(), - spell_difficulty(spell)); + spell_difficulty(spell), + known.c_str()); } // are we halfway through a column? diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index df14179b4669..e61b234c857a 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -118,7 +118,7 @@ int show_description(const describe_info &inf, const tile_def *tile) { auto icon = make_shared(); icon->set_tile(*tile); - icon->set_margin_for_sdl({0, 10, 0, 0}); + icon->set_margin_for_sdl(0, 10, 0, 0); title_hbox->add_child(move(icon)); } #endif @@ -126,9 +126,9 @@ int show_description(const describe_info &inf, const tile_def *tile) auto title = make_shared(inf.title); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->set_margin_for_sdl({0, 0, 20, 0}); - title_hbox->set_margin_for_crt({0, 0, 1, 0}); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_sdl(0, 0, 20, 0); + title_hbox->set_margin_for_crt(0, 0, 1, 0); vbox->add_child(move(title_hbox)); } @@ -145,7 +145,7 @@ int show_description(const describe_info &inf, const tile_def *tile) auto scroller = make_shared(); auto fs = formatted_string::parse_string(trimmed_string(desc)); auto text = make_shared(fs); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(text); switcher->add_child(move(scroller)); } @@ -153,15 +153,15 @@ int show_description(const describe_info &inf, const tile_def *tile) switcher->current() = 0; switcher->expand_h = false; #ifdef USE_TILE_LOCAL - switcher->max_size()[0] = tiles.get_crt_font()->char_width()*80; + switcher->max_size().width = tiles.get_crt_font()->char_width()*80; #endif vbox->add_child(switcher); if (!inf.quote.empty()) { auto footer = make_shared(_toggle_message); - footer->set_margin_for_sdl({20, 0, 0, 0}); - footer->set_margin_for_crt({1, 0, 0, 0}); + footer->set_margin_for_sdl(20, 0, 0, 0); + footer->set_margin_for_crt(1, 0, 0, 0); vbox->add_child(move(footer)); } @@ -176,7 +176,7 @@ int show_description(const describe_info &inf, const tile_def *tile) if (!inf.quote.empty() && (lastch == '!' || lastch == CK_MOUSE_CMD || lastch == '^')) switcher->current() = 1 - switcher->current(); else - done = !vbox->on_event(ev); + done = !switcher->current_widget()->on_event(ev); return true; }); @@ -235,7 +235,6 @@ const char* jewellery_base_ability_string(int subtype) case RING_FIRE: return "Fire"; case RING_ICE: return "Ice"; case RING_TELEPORTATION: return "*Tele"; - case RING_RESIST_CORROSION: return "rCorr"; #if TAG_MAJOR_VERSION == 34 case RING_TELEPORT_CONTROL: return "+cTele"; #endif @@ -632,9 +631,7 @@ static string _randart_descrip(const item_def &item) static const char *trap_names[] = { -#if TAG_MAJOR_VERSION == 34 "dart", -#endif "arrow", "spear", #if TAG_MAJOR_VERSION > 34 "dispersal", @@ -642,7 +639,10 @@ static const char *trap_names[] = #endif "permanent teleport", "alarm", "blade", - "bolt", "net", "Zot", "needle", + "bolt", "net", "Zot", +#if TAG_MAJOR_VERSION == 34 + "needle", +#endif "shaft", "passage", "pressure plate", "web", #if TAG_MAJOR_VERSION == 34 "gas", "teleport", @@ -937,7 +937,7 @@ static int _item_training_target(const item_def &item) return weapon_min_delay_skill(item) * 10; else if (is_shield(item)) return round(you.get_shield_skill_to_offset_penalty(item) * 10); - else if (item.base_type == OBJ_MISSILES && throw_dam) + else if (item.base_type == OBJ_MISSILES && is_throwable(&you, item, false)) return (((10 + throw_dam / 2) - FASTEST_PLAYER_THROWING_SPEED) * 2) * 10; else return 0; @@ -950,14 +950,13 @@ static int _item_training_target(const item_def &item) */ static skill_type _item_training_skill(const item_def &item) { - const int throw_dam = property(item, PWPN_DAMAGE); if (item.base_type == OBJ_WEAPONS || item.base_type == OBJ_STAVES) return item_attack_skill(item); else if (is_shield(item)) return SK_SHIELDS; // shields are armour, so do shields before armour else if (item.base_type == OBJ_ARMOUR) return SK_ARMOUR; - else if (item.base_type == OBJ_MISSILES && throw_dam) + else if (item.base_type == OBJ_MISSILES && is_throwable(&you, item, false)) return SK_THROWING; else if (item_is_evokable(item)) // not very accurate return SK_EVOCATIONS; @@ -982,14 +981,14 @@ static bool _could_set_training_target(const item_def &item, bool ignore_current const int target = min(_item_training_target(item), 270); - return target && you.can_train[skill] + return target && !is_useless_skill(skill) && you.skill(skill, 10, false, false, false) < target && (ignore_current || you.get_training_target(skill) < target); } /** * Produce the "Your skill:" line for item descriptions where specific skill targets - * are releveant (weapons, missiles, shields) + * are relevant (weapons, missiles, shields) * * @param skill the skill to look at. * @param show_target_button whether to show the button for setting a skill target. @@ -1271,8 +1270,8 @@ static string _describe_weapon(const item_def &item, bool verbose) description += "It poisons the flesh of those it strikes."; break; case SPWPN_PROTECTION: - description += "It protects the one who uses it against " - "injury (+AC on strike)."; + description += "It grants its wielder temporary protection when " + "it strikes (+7 to AC)."; break; case SPWPN_DRAINING: description += "A truly terrible weapon, it drains the " @@ -1343,6 +1342,20 @@ static string _describe_weapon(const item_def &item, bool verbose) } } + if (is_unrandom_artefact(item, UNRAND_STORM_BOW)) + { + description += "\n\nAmmo fired by it will pass through the " + "targets it hits, potentially hitting all targets in " + "its path until it reaches maximum range."; + } + else if (is_unrandom_artefact(item, UNRAND_THERMIC_ENGINE)) + { + description += "\n\nIt has been specially enchanted to freeze " + "those struck by it, causing extra injury to most foes " + "and up to half again as much damage against particularly " + "susceptible opponents."; + } + if (you.duration[DUR_EXCRUCIATING_WOUNDS] && &item == you.weapon()) { description += "\nIt is temporarily rebranded; it is actually a"; @@ -1472,51 +1485,25 @@ static string _describe_ammo(const item_def &item) "asphyxiation, dealing direct damage as well as " "poisoning and slowing those it strikes.\n" "It is twice as likely to be destroyed on impact as " - "other needles."; - break; - case SPMSL_PARALYSIS: - description += "It is tipped with a paralysing substance."; - break; - case SPMSL_SLEEP: - description += "It is coated with a fast-acting tranquilizer."; - break; - case SPMSL_CONFUSION: - description += "It is tipped with a substance that causes confusion."; + "other darts."; break; -#if TAG_MAJOR_VERSION == 34 - case SPMSL_SICKNESS: - description += "It has been contaminated by something likely to cause disease."; - break; -#endif case SPMSL_FRENZY: description += "It is tipped with a substance that sends those it " "hits into a mindless rage, attacking friend and " "foe alike."; break; - case SPMSL_RETURNING: - description += "A skilled user can throw it in such a way that it " - "will return to its owner."; - break; - case SPMSL_PENETRATION: - description += "It will pass through any targets it hits, " - "potentially hitting all targets in its path until " - "it reaches its maximum range."; + case SPMSL_BLINDING: + description += "It is tipped with a substance that causes " + "blindness and brief confusion."; break; case SPMSL_DISPERSAL: description += "It will cause any target it hits to blink, with a " "tendency towards blinking further away from the " "one who " + threw_or_fired + " it."; break; - case SPMSL_EXPLODING: - description += "It will explode into fragments upon hitting a " - "target, hitting an obstruction, or reaching its " - "maximum range."; - break; - case SPMSL_STEEL: - description += "It deals increased damage compared to normal ammo."; - break; case SPMSL_SILVER: - description += "It deals substantially increased damage to chaotic " + description += "It deals increased damage compared to normal ammo " + "and substantially increased damage to chaotic " "and magically transformed beings. It also inflicts " "extra damage against mutated beings, according to " "how mutated they are."; @@ -1525,7 +1512,8 @@ static string _describe_ammo(const item_def &item) } const int dam = property(item, PWPN_DAMAGE); - if (dam) + const bool player_throwable = is_throwable(&you, item, false); + if (player_throwable) { const int throw_delay = (10 + dam / 2); const int target_skill = _item_training_target(item); @@ -1994,8 +1982,7 @@ string get_item_description(const item_def &item, bool verbose, break; case OBJ_BOOKS: - if (!verbose - && (Options.dump_book_spells || is_random_artefact(item))) + if (!verbose && is_random_artefact(item)) { desc += describe_item_spells(item); if (desc.empty()) @@ -2211,6 +2198,7 @@ void get_feature_desc(const coord_def &pos, describe_info &inf, bool include_ext string desc = feature_description_at(pos, false, DESC_A, false); string db_name = feat == DNGN_ENTER_SHOP ? "a shop" : desc; + strip_suffix(db_name, " (summoned)"); string long_desc = getLongDescription(db_name); inf.title = uppercase_first(desc); @@ -2249,6 +2237,15 @@ void get_feature_desc(const coord_def &pos, describe_info &inf, bool include_ext command_to_string(CMD_GO_DOWNSTAIRS).c_str()); } + // mention that permanent trees are usually flammable + // (expect for autumnal trees in Wucad Mu's Monastery) + if (feat_is_tree(feat) && !is_temp_terrain(pos) + && env.markers.property_at(pos, MAT_ANY, "veto_fire") != "veto") + { + long_desc += "\nIt is susceptible to bolts of lightning"; + long_desc += " and to sufficiently intense sources of fire."; + } + inf.body << long_desc; if (include_extra) @@ -2264,7 +2261,7 @@ void get_feature_desc(const coord_def &pos, describe_info &inf, bool include_ext void describe_feature_wide(const coord_def& pos) { typedef struct { - string title, body; + string title, body, quote; tile_def tile; } feat_info; @@ -2273,7 +2270,7 @@ void describe_feature_wide(const coord_def& pos) { describe_info inf; get_feature_desc(pos, inf, false); - feat_info f = { "", "", tile_def(TILEG_TODO, TEX_GUI)}; + feat_info f = { "", "", "", tile_def(TILEG_TODO, TEX_GUI)}; f.title = inf.title; f.body = trimmed_string(inf.body.str()); #ifdef USE_TILE @@ -2281,12 +2278,13 @@ void describe_feature_wide(const coord_def& pos) apply_variations(env.tile_flv(pos), &tile, pos); f.tile = tile_def(tile, get_dngn_tex(tile)); #endif + f.quote = trimmed_string(inf.quote); feats.emplace_back(f); } auto extra_descs = _get_feature_extra_descs(pos); for (const auto &desc : extra_descs) { - feat_info f = { "", "", tile_def(TILEG_TODO, TEX_GUI)}; + feat_info f = { "", "", "", tile_def(TILEG_TODO, TEX_GUI)}; f.title = desc.first; f.body = trimmed_string(desc.second); #ifdef USE_TILE @@ -2306,7 +2304,7 @@ void describe_feature_wide(const coord_def& pos) string hint_text = trimmed_string(hints_describe_pos(pos.x, pos.y)); if (!hint_text.empty()) { - feat_info f = { "", "", tile_def(TILEG_TODO, TEX_GUI)}; + feat_info f = { "", "", "", tile_def(TILEG_TODO, TEX_GUI)}; f.title = "Hints."; f.body = hint_text; f.tile = tile_def(TILEG_STARTUP_HINTS, TEX_GUI); @@ -2326,30 +2324,39 @@ void describe_feature_wide(const coord_def& pos) title_hbox->add_child(move(icon)); #endif auto title = make_shared(feat.title); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 10}); + title->set_margin_for_sdl(0, 0, 0, 10); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; + title_hbox->align_cross = Widget::CENTER; + + const bool has_desc = feat.body != feat.title && feat.body != ""; - bool has_desc = feat.body != feat.title && feat.body != ""; if (has_desc || &feat != &feats.back()) { - title_hbox->set_margin_for_crt({0, 0, 1, 0}); - title_hbox->set_margin_for_sdl({0, 0, 20, 0}); + title_hbox->set_margin_for_crt(0, 0, 1, 0); + title_hbox->set_margin_for_sdl(0, 0, 20, 0); } vbox->add_child(move(title_hbox)); if (has_desc) { - auto text = make_shared(formatted_string::parse_string(feat.body)); + formatted_string desc_text = formatted_string::parse_string(feat.body); + if (!feat.quote.empty()) + { + desc_text.cprintf("\n\n"); + desc_text += formatted_string::parse_string(feat.quote); + } + auto text = make_shared(desc_text); if (&feat != &feats.back()) - text->set_margin_for_sdl({0, 0, 20, 0}); - text->wrap_text = true; + { + text->set_margin_for_sdl(0, 0, 20, 0); + text->set_margin_for_crt(0, 0, 1, 0); + } + text->set_wrap_text(true); vbox->add_child(text); } } #ifdef USE_TILE_LOCAL - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif scroller->set_child(move(vbox)); @@ -2357,10 +2364,9 @@ void describe_feature_wide(const coord_def& pos) bool done = false; popup->on(Widget::slots.event, [&](wm_event ev) { - if (ev.type != WME_KEYDOWN) - return false; - done = !scroller->on_event(ev); - return true; + if (scroller->on_event(ev)) + return true; + return done = ev.type == WME_KEYDOWN; }); #ifdef USE_TILE_WEB @@ -2370,7 +2376,8 @@ void describe_feature_wide(const coord_def& pos) { tiles.json_open_object(); tiles.json_write_string("title", feat.title); - tiles.json_write_string("body", feat.body); + tiles.json_write_string("body", trimmed_string(feat.body)); + tiles.json_write_string("quote", trimmed_string(feat.quote)); tiles.json_open_object("tile"); tiles.json_write_int("t", feat.tile.tile); tiles.json_write_int("tex", feat.tile.tex); @@ -2545,7 +2552,7 @@ static command_type _get_action(int key, vector actions) { CMD_SET_SKILL_TARGET, 's' }, }; - key = tolower(key); + key = tolower_safe(key); for (auto cmd : actions) if (key == act_key.at(cmd)) @@ -2691,18 +2698,17 @@ bool describe_item(item_def &item, function fixup_desc) #endif auto title = make_shared(name); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 10}); + title->set_margin_for_sdl(0, 0, 0, 10); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->set_margin_for_crt({0, 0, 1, 0}); - title_hbox->set_margin_for_sdl({0, 0, 20, 0}); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_crt(0, 0, 1, 0); + title_hbox->set_margin_for_sdl(0, 0, 20, 0); vbox->add_child(move(title_hbox)); auto scroller = make_shared(); auto text = make_shared(fs_desc.trim()); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(text); vbox->add_child(scroller); @@ -2714,13 +2720,13 @@ bool describe_item(item_def &item, function fixup_desc) footer_text += formatted_string(_actions_desc(actions, item)); auto footer = make_shared(); footer->set_text(footer_text); - footer->set_margin_for_crt({1, 0, 0, 0}); - footer->set_margin_for_sdl({20, 0, 0, 0}); + footer->set_margin_for_crt(1, 0, 0, 0); + footer->set_margin_for_sdl(20, 0, 0, 0); vbox->add_child(move(footer)); } #ifdef USE_TILE_LOCAL - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif auto popup = make_shared(move(vbox)); @@ -2845,7 +2851,12 @@ static string _player_spell_stats(const spell_type spell) return description; // all other info is player-dependent } - const string failure = failure_rate_to_string(raw_spell_fail(spell)); + + string failure; + if (you.divine_exegesis) + failure = "0%"; + else + failure = failure_rate_to_string(raw_spell_fail(spell)); description += make_stringf(" Fail: %s", failure.c_str()); description += "\n\nPower : "; @@ -2909,7 +2920,7 @@ string get_skill_description(skill_type skill, bool need_title) static int _hex_pow(const spell_type spell, const int hd) { const int cap = 200; - const int pow = mons_power_for_hd(spell, hd, false) / ENCH_POW_FACTOR; + const int pow = mons_power_for_hd(spell, hd) / ENCH_POW_FACTOR; return min(cap, pow); } @@ -3160,7 +3171,7 @@ void describe_spell(spell_type spell, const monster_info *mon_owner, auto vbox = make_shared(Widget::VERT); #ifdef USE_TILE_LOCAL - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif auto title_hbox = make_shared(Widget::HORZ); @@ -3175,19 +3186,18 @@ void describe_spell(spell_type spell, const monster_info *mon_owner, auto title = make_shared(); title->set_text(formatted_string(spl_title)); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 10}); + title->set_margin_for_sdl(0, 0, 0, 10); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->set_margin_for_crt({0, 0, 1, 0}); - title_hbox->set_margin_for_sdl({0, 0, 20, 0}); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_crt(0, 0, 1, 0); + title_hbox->set_margin_for_sdl(0, 0, 20, 0); vbox->add_child(move(title_hbox)); auto scroller = make_shared(); auto text = make_shared(); text->set_text(formatted_string::parse_string(desc)); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(move(text)); vbox->add_child(scroller); @@ -3195,8 +3205,8 @@ void describe_spell(spell_type spell, const monster_info *mon_owner, { auto more = make_shared(); more->set_text(formatted_string("(M)emorise this spell.", CYAN)); - more->set_margin_for_crt({1, 0, 0, 0}); - more->set_margin_for_sdl({20, 0, 0, 0}); + more->set_margin_for_crt(1, 0, 0, 0); + more->set_margin_for_sdl(20, 0, 0, 0); vbox->add_child(move(more)); } @@ -3208,7 +3218,7 @@ void describe_spell(spell_type spell, const monster_info *mon_owner, if (ev.type != WME_KEYDOWN) return false; lastch = ev.key.keysym.sym; - done = (toupper(lastch) == 'M' && can_mem || lastch == CK_ESCAPE + done = (toupper_safe(lastch) == 'M' && can_mem || lastch == CK_ESCAPE || lastch == CK_ENTER || lastch == ' '); return done; }); @@ -3234,7 +3244,7 @@ void describe_spell(spell_type spell, const monster_info *mon_owner, tiles.pop_ui_layout(); #endif - if (toupper(lastch) == 'M' && can_mem) + if (toupper_safe(lastch) == 'M' && can_mem) { redraw_screen(); // necessary to ensure stats is redrawn (!?) if (!learn_spell(spell) || !you.turn_is_over) @@ -3487,7 +3497,9 @@ static string _flavour_base_desc(attack_flavour flavour) { AF_POISON_STRONG, "cause strong poisoning" }, { AF_ROT, "cause rotting" }, { AF_VAMPIRIC, "drain health from the living" }, +#if TAG_MAJOR_VERSION == 34 { AF_KLOWN, "cause random powerful effects" }, +#endif { AF_DISTORT, "cause wild translocation effects" }, { AF_RAGE, "cause berserking" }, { AF_STICKY_FLAME, "apply sticky flame" }, @@ -3851,6 +3863,7 @@ COMPILE_CHECK(ARRAYSZ(size_adj) == NUM_SIZE_LEVELS); // This is used in monster description and on '%' screen for player size const char* get_size_adj(const size_type size, bool ignore_medium) { + ASSERT_RANGE(static_cast(size), 0, ARRAYSZ(size_adj)); if (ignore_medium && size == SIZE_MEDIUM) return nullptr; // don't mention medium size return size_adj[size]; @@ -3941,16 +3954,19 @@ static string _monster_stat_description(const monster_info& mi) } const char* pronoun = mi.pronoun(PRONOUN_SUBJECTIVE); + const bool plural = mi.pronoun_plurality(); if (mi.threat != MTHRT_UNDEF) { - result << uppercase_first(pronoun) << " looks " + result << uppercase_first(pronoun) << " " + << conjugate_verb("look", plural) << " " << _get_threat_desc(mi.threat) << ".\n"; } if (!resist_descriptions.empty()) { - result << uppercase_first(pronoun) << " is " + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) << " " << comma_separated_line(resist_descriptions.begin(), resist_descriptions.end(), "; and ", "; ") @@ -3960,15 +3976,17 @@ static string _monster_stat_description(const monster_info& mi) // Is monster susceptible to anything? (On a new line.) if (!suscept.empty()) { - result << uppercase_first(pronoun) << " is susceptible to " + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) << " susceptible to " << comma_separated_line(suscept.begin(), suscept.end()) << ".\n"; } if (mi.is(MB_CHAOTIC)) { - result << uppercase_first(pronoun) << " is vulnerable to silver and" - " hated by Zin.\n"; + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) + << " vulnerable to silver and hated by Zin.\n"; } if (mons_class_flag(mi.type, M_STATIONARY) @@ -3980,8 +3998,9 @@ static string _monster_stat_description(const monster_info& mi) if (mons_class_flag(mi.type, M_COLD_BLOOD) && get_resist(resist, MR_RES_COLD) <= 0) { - result << uppercase_first(pronoun) << " is cold-blooded and may be " - "slowed by cold attacks.\n"; + result << uppercase_first(pronoun) + << " " << conjugate_verb("are", plural) + << " cold-blooded and may be slowed by cold attacks.\n"; } // Seeing invisible. @@ -3990,7 +4009,11 @@ static string _monster_stat_description(const monster_info& mi) // Echolocation, wolf noses, jellies, etc if (!mons_can_be_blinded(mi.type)) - result << uppercase_first(pronoun) << " is immune to blinding.\n"; + { + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) + << " immune to blinding.\n"; + } // XXX: could mention "immune to dazzling" here, but that's spammy, since // it's true of such a huge number of monsters. (undead, statues, plants). // Might be better to have some place where players can see holiness & @@ -3999,12 +4022,14 @@ static string _monster_stat_description(const monster_info& mi) if (mi.intel() <= I_BRAINLESS) { // Matters for Ely. - result << uppercase_first(pronoun) << " is mindless.\n"; + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) << " mindless.\n"; } else if (mi.intel() >= I_HUMAN) { // Matters for Yred, Gozag, Zin, TSO, Alistair.... - result << uppercase_first(pronoun) << " is intelligent.\n"; + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) << " intelligent.\n"; } // Unusual monster speed. @@ -4013,7 +4038,9 @@ static string _monster_stat_description(const monster_info& mi) if (speed != 10 && speed != 0) { did_speed = true; - result << uppercase_first(pronoun) << " is " << mi.speed_description(); + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) << " " + << mi.speed_description(); } const mon_energy_usage def = DEFAULT_ENERGY; if (!(mi.menergy == def)) @@ -4022,22 +4049,40 @@ static string _monster_stat_description(const monster_info& mi) vector fast, slow; if (!did_speed) result << uppercase_first(pronoun) << " "; - _add_energy_to_string(speed, me.move, "covers ground", fast, slow); + _add_energy_to_string(speed, me.move, + conjugate_verb("cover", plural) + " ground", + fast, slow); // since MOVE_ENERGY also sets me.swim if (me.swim != me.move) - _add_energy_to_string(speed, me.swim, "swims", fast, slow); - _add_energy_to_string(speed, me.attack, "attacks", fast, slow); + { + _add_energy_to_string(speed, me.swim, + conjugate_verb("swim", plural), fast, slow); + } + _add_energy_to_string(speed, me.attack, + conjugate_verb("attack", plural), fast, slow); if (mons_class_itemuse(mi.type) >= MONUSE_STARTING_EQUIPMENT) - _add_energy_to_string(speed, me.missile, "shoots", fast, slow); + { + _add_energy_to_string(speed, me.missile, + conjugate_verb("shoot", plural), fast, slow); + } _add_energy_to_string( speed, me.spell, - mi.is_actual_spellcaster() ? "casts spells" : - mi.is_priest() ? "uses invocations" - : "uses natural abilities", fast, slow); - _add_energy_to_string(speed, me.special, "uses special abilities", + mi.is_actual_spellcaster() ? conjugate_verb("cast", plural) + + " spells" : + mi.is_priest() ? conjugate_verb("use", plural) + + " invocations" + : conjugate_verb("use", plural) + + " natural abilities", fast, slow); + _add_energy_to_string(speed, me.special, + conjugate_verb("use", plural) + + " special abilities", fast, slow); if (mons_class_itemuse(mi.type) >= MONUSE_STARTING_EQUIPMENT) - _add_energy_to_string(speed, me.item, "uses items", fast, slow); + { + _add_energy_to_string(speed, me.item, + conjugate_verb("use", plural) + " items", + fast, slow); + } if (speed >= 10) { @@ -4081,8 +4126,9 @@ static string _monster_stat_description(const monster_info& mi) if (mi.type == MONS_SHADOW) { // Cf. monster::action_energy() in monster.cc. - result << uppercase_first(pronoun) << " covers ground more" - << " quickly when invisible.\n"; + result << uppercase_first(pronoun) << " " + << conjugate_verb("cover", plural) + << " ground more quickly when invisible.\n"; } if (mi.airborne()) @@ -4092,11 +4138,17 @@ static string _monster_stat_description(const monster_info& mi) if (!mi.can_regenerate()) result << uppercase_first(pronoun) << " cannot regenerate.\n"; else if (mons_class_fast_regen(mi.type)) - result << uppercase_first(pronoun) << " regenerates quickly.\n"; + result << uppercase_first(pronoun) << " " + << conjugate_verb("regenerate", plural) + << " quickly.\n"; const char* mon_size = get_size_adj(mi.body_size(), true); if (mon_size) - result << uppercase_first(pronoun) << " is " << mon_size << ".\n"; + { + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) << " " + << mon_size << ".\n"; + } if (in_good_standing(GOD_ZIN, 0) && !mi.pos.origin() && monster_at(mi.pos)) { @@ -4110,8 +4162,9 @@ static string _monster_stat_description(const monster_info& mi) } else if (eligibility == RE_TOO_STRONG) { - result << uppercase_first(pronoun) << - " is too strong to be affected by reciting Zin's laws."; + result << uppercase_first(pronoun) << " " + << conjugate_verb("are", plural) + << " too strong to be affected by reciting Zin's laws."; } else // RE_ELIGIBLE || RE_RECITE_TIMER { @@ -4216,6 +4269,7 @@ void get_monster_db_desc(const monster_info& mi, describe_info &inf, const string it = mi.pronoun(PRONOUN_SUBJECTIVE); const string it_o = mi.pronoun(PRONOUN_OBJECTIVE); const string It = uppercase_first(it); + const string is = conjugate_verb("are", mi.pronoun_plurality()); switch (mi.type) { @@ -4326,7 +4380,7 @@ void get_monster_db_desc(const monster_info& mi, describe_info &inf, bool stair_use = false; if (!mons_class_can_use_stairs(mi.type)) { - inf.body << It << " is incapable of using stairs.\n"; + inf.body << It << " " << is << " incapable of using stairs.\n"; stair_use = true; } @@ -4336,14 +4390,17 @@ void get_monster_db_desc(const monster_info& mi, describe_info &inf, "temporary. Killing " << it_o << " yields no experience, " "nutrition or items"; if (!stair_use) - inf.body << ", and " << it << " is incapable of using stairs"; + { + inf.body << ", and " << it << " " << is + << " incapable of using stairs"; + } inf.body << ".\n"; } else if (mi.is(MB_PERM_SUMMON)) { inf.body << "\nThis monster has been summoned in a durable way. " "Killing " << it_o << " yields no experience, nutrition " - "or items, but " << it_o << " cannot be abjured.\n"; + "or items, but " << it << " cannot be abjured.\n"; } else if (mi.is(MB_NO_REWARD)) { @@ -4352,8 +4409,9 @@ void get_monster_db_desc(const monster_info& mi, describe_info &inf, } else if (mons_class_leaves_hide(mi.type)) { - inf.body << "\nIf " << it << " is slain, it may be possible to " - "recover " << mi.pronoun(PRONOUN_POSSESSIVE) + inf.body << "\nIf " << it << " " << is << + " slain, it may be possible to recover " + << mi.pronoun(PRONOUN_POSSESSIVE) << " hide, which can be used as armour.\n"; } @@ -4494,13 +4552,12 @@ int describe_monsters(const monster_info &mi, bool force_seen, auto title = make_shared(); title->set_text(formatted_string(inf.title)); - title->set_margin_for_crt({0, 0, 0, 0}); - title->set_margin_for_sdl({0, 0, 0, 10}); + title->set_margin_for_sdl(0, 0, 0, 10); title_hbox->add_child(move(title)); - title_hbox->align_items = Widget::CENTER; - title_hbox->set_margin_for_crt({0, 0, 1, 0}); - title_hbox->set_margin_for_sdl({0, 0, 20, 0}); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_crt(0, 0, 1, 0); + title_hbox->set_margin_for_sdl(0, 0, 20, 0); vbox->add_child(move(title_hbox)); desc += formatted_string(inf.body.str()); @@ -4532,7 +4589,7 @@ int describe_monsters(const monster_info &mi, bool force_seen, const formatted_string *content[2] = { &desc, "e }; auto scroller = make_shared(); auto text = make_shared(content[i]->trim()); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(text); desc_sw->add_child(move(scroller)); @@ -4540,15 +4597,15 @@ int describe_monsters(const monster_info &mi, bool force_seen, formatted_string::parse_string(mores[i]))); } - more_sw->set_margin_for_sdl({20, 0, 0, 0}); - more_sw->set_margin_for_crt({1, 0, 0, 0}); + more_sw->set_margin_for_sdl(20, 0, 0, 0); + more_sw->set_margin_for_crt(1, 0, 0, 0); desc_sw->expand_h = false; vbox->add_child(desc_sw); if (!inf.quote.empty()) vbox->add_child(more_sw); #ifdef USE_TILE_LOCAL - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif auto popup = make_shared(move(vbox)); diff --git a/crawl-ref/source/dgn-height.cc b/crawl-ref/source/dgn-height.cc index 3da07f4f1cd1..99768fa21bba 100644 --- a/crawl-ref/source/dgn-height.cc +++ b/crawl-ref/source/dgn-height.cc @@ -146,8 +146,10 @@ coord_def dgn_island_plan::pick_island_spot() void dgn_island_plan::build_island() { const coord_def c = pick_island_spot(); - dgn_island_centred_at(c, resolve_range(n_island_centre_delta_points), - resolve_range(island_centre_radius_range), + const int npoints = resolve_range(n_island_centre_delta_points); + const int radius = resolve_range(island_centre_radius_range); + dgn_island_centred_at(c, npoints, + radius, island_centre_point_height_increment, level_border_depth, x_chance_in_y(atoll_roll, 100)); @@ -161,9 +163,11 @@ void dgn_island_plan::build_island() dgn_random_point_from(c, addition_offset, level_border_depth); if (!offsetC.origin()) { + const int extra_npoints = resolve_range(n_island_aux_delta_points); + const int extra_radius = resolve_range(island_aux_radius_range); dgn_island_centred_at( - offsetC, resolve_range(n_island_aux_delta_points), - resolve_range(island_aux_radius_range), + offsetC, extra_npoints, + extra_radius, island_aux_point_height_increment, level_border_depth, x_chance_in_y(atoll_roll, 100)); diff --git a/crawl-ref/source/dgn-overview.cc b/crawl-ref/source/dgn-overview.cc index 678afa4bedc0..1bc053addc91 100644 --- a/crawl-ref/source/dgn-overview.cc +++ b/crawl-ref/source/dgn-overview.cc @@ -18,6 +18,7 @@ #include "feature.h" #include "files.h" #include "libutil.h" +#include "macro.h" #include "menu.h" #include "message.h" #include "mon-poly.h" @@ -55,12 +56,13 @@ static void _seen_altar(god_type god, const coord_def& pos); static void _seen_staircase(const coord_def& pos); static void _seen_shop(const coord_def& pos); static void _seen_portal(dungeon_feature_type feat, const coord_def& pos); +static void _process_command(const char keypress); static string _get_branches(bool display); static string _get_altars(bool display); static string _get_shops(bool display); static string _get_portals(); -static string _get_notes(); +static string _get_notes(bool display); static string _print_altars_for_gods(const vector& gods, bool print_unseen, bool display); static const string _get_coloured_level_annotation(level_id li); @@ -219,7 +221,7 @@ string overview_description_string(bool display) disp += _get_altars(display); disp += _get_shops(display); disp += _get_portals(); - disp += _get_notes(); + disp += _get_notes(display); return disp.substr(0, disp.find_last_not_of('\n')+1); } @@ -237,7 +239,7 @@ static string _get_seen_branches(bool display) disp += "\nBranches:"; if (display) { - disp += " (use G to reach them and " + disp += " (press G to reach them and " "?/B for more information)"; } disp += "\n"; @@ -252,6 +254,8 @@ static string _get_seen_branches(bool display) if (branch == root_branch || stair_level.count(branch)) { + // having an entry for branch that is an empty set means a branch + // that no longer has any stairs. level_id lid(branch, 0); lid = find_deepest_explored(lid); @@ -264,10 +268,21 @@ static string _get_seen_branches(bool display) ? it->shortname : it->abbrevname); - snprintf(buffer, sizeof buffer, - "%*s (%d/%d)%s", - branch == root_branch ? -7 : 7, - brname, lid.depth, brdepth[branch], entry_desc.c_str()); + if (entry_desc.size() == 0 && branch != BRANCH_DUNGEON + && you.where_are_you != branch) + { + // previously visited portal branches + snprintf(buffer, sizeof buffer, + "%7s (visited)", + brname); + } + else + { + snprintf(buffer, sizeof buffer, + "%*s (%d/%d)%s", + branch == root_branch ? -7 : 7, + brname, lid.depth, brdepth[branch], entry_desc.c_str()); + } disp += buffer; num_printed_branches++; @@ -365,7 +380,7 @@ static string _get_altars(bool display) disp += "\nAltars:"; if (display) { - disp += " (use Ctrl-F \"altar\" to reach them and " + disp += " (press _ to reach them and " "?/G for information about gods)"; } disp += "\n"; @@ -468,7 +483,7 @@ static string _get_shops(bool display) { disp +="\nShops:"; if (display) - disp += " (use Ctrl-F \"shop\" to reach them - yellow denotes antique shop)"; + disp += " (press $ to reach them - yellow denotes antique shop)"; disp += "\n"; } last_id.depth = 10000; @@ -531,7 +546,7 @@ static string _get_portals() } // Loop through each branch, printing stored notes. -static string _get_notes() +static string _get_notes(bool display) { string disp; @@ -545,6 +560,9 @@ static string _get_notes() if (disp.empty()) return disp; + + if (display) + return "\nAnnotations: (press ! to annotate current level)\n" + disp; return "\nAnnotations:\n" + disp; } @@ -607,12 +625,59 @@ bool unnotice_feature(const level_pos &pos) || _unnotice_stair(pos); } +class dgn_overview : public formatted_scroller +{ +public: + dgn_overview(const string& text = "") : formatted_scroller(FS_PREWRAPPED_TEXT, text) {}; + +private: + bool process_key(int ch) override + { + // We handle these after exiting dungeon overview window + // to prevent menus from stacking on top of each other. + if (ch == 'G' || ch == '_' || ch == '$' || ch =='!') + return false; + else + return formatted_scroller::process_key(ch); + } +}; + void display_overview() { string disp = overview_description_string(true); linebreak_string(disp, 80); - int flags = FS_PREWRAPPED_TEXT; // TODO: add ANYPRINTABLE - formatted_scroller(flags, disp).show(); + dgn_overview overview(disp); + _process_command(overview.show()); +} + +static void _process_command(const char keypress) +{ + switch (keypress) + { + case 'G': + do_interlevel_travel(); + return; + case '_': + if (!altars_present.empty()) + { + macro_sendkeys_end_add_expanded('_'); + do_interlevel_travel(); + } + else + mpr("Sorry, you haven't seen any altar yet."); + return; + case '$': + if (!shops_present.empty()) + StashTrack.search_stashes("shop"); + else + mpr("Sorry, you haven't seen any shop yet."); + return; + case '!': + annotate_level(); + return; + default: + return; + } } static void _seen_staircase(const coord_def& pos) @@ -758,6 +823,8 @@ void explored_tracked_feature(dungeon_feature_type feat) void enter_branch(branch_type branch, level_id from) { + // this will ensure that branch is in stair_level either way + // TODO: track stair levels for portal branches somehow? if (stair_level[branch].size() > 1) { stair_level[branch].clear(); diff --git a/crawl-ref/source/dgn-shoals.cc b/crawl-ref/source/dgn-shoals.cc index 15b496e0304e..8540511a13cf 100644 --- a/crawl-ref/source/dgn-shoals.cc +++ b/crawl-ref/source/dgn-shoals.cc @@ -64,13 +64,13 @@ enum shoals_height_thresholds SHT_SHALLOW_WATER = -30, }; -enum tide_direction +enum class tide_dir { - TIDE_RISING, - TIDE_FALLING, + rising, + falling, }; -static tide_direction _shoals_tide_direction; +static tide_dir _shoals_tide_direction; static monster* tide_caller = nullptr; static coord_def tide_caller_pos; static int tide_called_turns = 0; @@ -858,7 +858,7 @@ static void _shoals_apply_tide_feature_at( // Determines if the tide is rising or falling based on before and // after features at the same square. -static tide_direction _shoals_feature_tide_height_change( +static tide_dir _shoals_feature_tide_height_change( dungeon_feature_type oldfeat, dungeon_feature_type newfeat) { @@ -866,7 +866,7 @@ static tide_direction _shoals_feature_tide_height_change( _shoals_feature_height(newfeat) - _shoals_feature_height(oldfeat); // If the apparent height of the new feature is greater (floor vs water), // the tide is receding. - return height_delta < 0 ? TIDE_RISING : TIDE_FALLING; + return height_delta < 0 ? tide_dir::rising : tide_dir::falling; } static void _shoals_apply_tide_at(coord_def c, int tide, bool incremental_tide) @@ -1018,6 +1018,13 @@ void shoals_apply_tides(int turns_elapsed, bool force, bool incremental_tide) return; } + // isolate from main levelgen rng if called from there; the behavior of + // this function is dependent on global state (tide direction, etc) that + // may impact the number of rolls depending on when the player changes + // shoals levels, so doing this with the levelgen rng has downstream + // effects. + rng::generator tide_rng(rng::GAMEPLAY); + CrawlHashTable &props(you.props); _shoals_init_tide(); @@ -1064,7 +1071,7 @@ void shoals_apply_tides(int turns_elapsed, bool force, bool incremental_tide) || old_tide / TIDE_MULTIPLIER != tide / TIDE_MULTIPLIER) { _shoals_tide_direction = - tide > old_tide ? TIDE_RISING : TIDE_FALLING; + tide > old_tide ? tide_dir::rising : tide_dir::falling; _shoals_apply_tide(tide / TIDE_MULTIPLIER, incremental_tide); } } @@ -1103,7 +1110,7 @@ static void _shoals_force_tide(CrawlHashTable &props, int increment) tide += increment * TIDE_MULTIPLIER; tide = min(HIGH_TIDE, max(LOW_TIDE, tide)); props[PROPS_SHOALS_TIDE_KEY] = short(tide); - _shoals_tide_direction = increment > 0 ? TIDE_RISING : TIDE_FALLING; + _shoals_tide_direction = increment > 0 ? tide_dir::rising : tide_dir::falling; _shoals_apply_tide(tide / TIDE_MULTIPLIER, false); } diff --git a/crawl-ref/source/dgn-swamp.cc b/crawl-ref/source/dgn-swamp.cc index 3dfc3fec790d..2eb985a437d0 100644 --- a/crawl-ref/source/dgn-swamp.cc +++ b/crawl-ref/source/dgn-swamp.cc @@ -54,7 +54,7 @@ static void _swamp_apply_features(int margin) grd(c) = DNGN_TREE; } else - grd(c) = _swamp_feature_for_height(dgn_height_at(c)); + grd(c) = _swamp_feature_for_height(dgn_height_at(c)); } } } diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 3adb4c748cb1..761eceea8b40 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -25,6 +25,7 @@ #include "describe.h" #include "dungeon.h" #include "english.h" +#include "externs.h" // INVALID_COORD #include "fight.h" // melee_confuse_chance #include "food.h" #include "god-abil.h" @@ -531,7 +532,7 @@ direction_chooser::direction_chooser(dist& moves_, needs_path = true; show_beam = !just_looking && needs_path; - need_beam_redraw = show_beam; + need_viewport_redraw = show_beam; have_beam = false; need_text_redraw = true; @@ -556,7 +557,7 @@ class view_desc_proc void nextline() { cgotoxy(1, wherey() + 1); } }; -// Lists all the monsters and items currently in view by the player. +// Lists monsters, items, and some interesting features in the player's view. // TODO: Allow sorting of items lists. void full_describe_view() { @@ -569,7 +570,7 @@ void full_describe_view() you.xray_vision ? LOS_NONE : LOS_DEFAULT); ri; ++ri) { if (feat_stair_direction(grd(*ri)) != CMD_NO_CMD - || feat_is_altar(grd(*ri))) + || (feat_is_trap(grd(*ri)))) { list_features.push_back(*ri); } @@ -627,7 +628,8 @@ void full_describe_view() desc_menu.set_title(new MenuEntry(title1, MEL_TITLE)); desc_menu.set_tag("pickup"); - desc_menu.set_type(MT_PICKUP); // necessary for sorting of the item submenu + // necessary for sorting of the item submenu + desc_menu.set_type(menu_type::pickup); desc_menu.action_cycle = Menu::CYCLE_TOGGLE; desc_menu.menu_action = InvMenu::ACT_EXECUTE; @@ -898,21 +900,23 @@ bool direction_chooser::move_is_ok() const { if (!targets_objects() && targets_enemies()) { - if (self == CONFIRM_CANCEL - || self == CONFIRM_PROMPT - && Options.allow_self_target == CONFIRM_CANCEL) + if (self == confirm_prompt_type::cancel + || self == confirm_prompt_type::prompt + && Options.allow_self_target + == confirm_prompt_type::cancel) { mprf(MSGCH_EXAMINE_FILTER, "That would be overly suicidal."); return false; } - else if (self != CONFIRM_NONE - && Options.allow_self_target != CONFIRM_NONE) + else if (self != confirm_prompt_type::none + && Options.allow_self_target + != confirm_prompt_type::none) { return yesno("Really target yourself?", false, 'n'); } } - if (self == CONFIRM_CANCEL) + if (self == confirm_prompt_type::cancel) { mprf(MSGCH_EXAMINE_FILTER, "Sorry, you can't target yourself."); return false; @@ -1074,7 +1078,7 @@ coord_def direction_chooser::find_default_target() const LS_FLIPVH); } else if ((mode != TARG_ANY && mode != TARG_FRIEND) - || self == CONFIRM_CANCEL) + || self == confirm_prompt_type::cancel) { success = find_default_monster_target(result); } @@ -1122,17 +1126,12 @@ static void _draw_ray_cell(coord_def p, coord_def target, aff_type aff) #endif } -void direction_chooser::draw_beam_if_needed() +void direction_chooser::draw_beam() { - if (!need_beam_redraw) - return; - - need_beam_redraw = false; - if (!show_beam) { viewwindow( -#ifndef USE_TILE_LOCAL +#ifndef USE_TILE false #endif ); @@ -1593,7 +1592,7 @@ void direction_chooser::toggle_beam() } show_beam = !show_beam; - need_beam_redraw = true; + need_viewport_redraw = true; if (show_beam) { @@ -1673,7 +1672,7 @@ void direction_chooser::handle_wizard_command(command_type key_command, show_beam = true; have_beam = find_ray(you.pos(), target(), beam, opc_solid_see, you.current_vision, show_beam); - need_beam_redraw = true; + need_viewport_redraw = true; return; case CMD_TARGET_WIZARD_DEBUG_PORTAL: @@ -1700,7 +1699,7 @@ void direction_chooser::handle_wizard_command(command_type key_command, if (target() != you.pos()) { wizard_create_feature(target()); - need_beam_redraw = true; + need_viewport_redraw = true; } return; @@ -1782,13 +1781,21 @@ void direction_chooser::do_redraws() if (need_all_redraw) { - need_beam_redraw = true; + need_viewport_redraw = true; need_text_redraw = true; need_cursor_redraw = true; need_all_redraw = false; } - draw_beam_if_needed(); + if (need_viewport_redraw) + { + draw_beam(); + // draw_beam calls viewwindow(true) at least one in tiles mode, and + // viewwindow(false) at least once in console, so the old highlight and + // the old beam are both gone here. + highlight_summoner(); + need_viewport_redraw = false; + } if (need_text_redraw) { @@ -1811,6 +1818,48 @@ void direction_chooser::do_redraws() } } +coord_def direction_chooser::find_summoner() +{ + const monster* mon = monster_at(target()); + + // Don't leak information about rakshasa mirrored illusions. + if (mon && mon->is_summoned() && !mon->has_ench(ENCH_PHANTOM_MIRROR)) + if (const monster *summ = monster_by_mid(mon->summoner)) + return summ->pos(); + return INVALID_COORD; +} + +void direction_chooser::highlight_summoner() +{ + const coord_def summ_loc = find_summoner(); + + if (summ_loc == INVALID_COORD || !you.see_cell(summ_loc)) + return; + +#ifdef USE_TILE + monster_info* summ_info = env.map_knowledge(summ_loc).monsterinfo(); + + if (!summ_info) // Can happen, e. g. if the summoner is invisible + return; + + summ_info->mb.set(MB_HIGHLIGHTED_SUMMONER); + + // The first argument must be false, because otherwise viewwindow would + // wipe any beams we might have drawn, and also reset the monster_info we + // just altered, before it draws anything. + viewwindow(false, true); +#else + char32_t glych = get_cell_glyph(summ_loc).ch; + int col = CYAN; + col |= COLFLAG_REVERSE; + + const coord_def vp = grid2view(summ_loc); + cgotoxy(vp.x, vp.y, GOTO_DNGN); + textcolour(real_colour(col)); + putwch(glych); +#endif +} + bool direction_chooser::tiles_update_target() { #ifdef USE_TILE @@ -1888,8 +1937,7 @@ bool direction_chooser::do_main_loop() { const bool was_excluded = is_exclude_root(target()); cycle_exclude_radius(target()); - // XXX: abusing need_beam_redraw to force viewwindow call. - need_beam_redraw = true; + need_viewport_redraw = true; const bool is_excluded = is_exclude_root(target()); if (!was_excluded && is_excluded) mpr("Placed new exclusion."); @@ -1987,7 +2035,7 @@ bool direction_chooser::do_main_loop() have_beam = show_beam && find_ray(you.pos(), target(), beam, opc_solid_see, you.current_vision); need_text_redraw = true; - need_beam_redraw = true; + need_viewport_redraw = true; need_cursor_redraw = true; } do_redraws(); @@ -2040,10 +2088,10 @@ bool direction_chooser::choose_direction() { have_beam = find_ray(you.pos(), target(), beam, opc_solid_see, you.current_vision); - need_beam_redraw = have_beam; + need_viewport_redraw = have_beam; } if (hitfunc) - need_beam_redraw = true; + need_viewport_redraw = true; clear_messages(); msgwin_set_temporary(true); @@ -2223,11 +2271,8 @@ static bool _mons_is_valid_target(const monster* mon, targ_mode_type mode, int range) { // Monsters that are no threat to you don't count as monsters. - if (mode != TARG_EVOLVABLE_PLANTS - && !mons_is_threatening(*mon)) - { + if (!mons_is_threatening(*mon)) return false; - } // Don't target submerged monsters. if (mode != TARG_HOSTILE_SUBMERGED && mon->submerged()) @@ -2265,8 +2310,6 @@ static bool _want_target_monster(const monster *mon, targ_mode_type mode, return true; return !mon->wont_attack() && !mon->neutral() && unpacifiable_reason(*mon).empty(); - case TARG_EVOLVABLE_PLANTS: - return mons_is_evolvable(mon); case TARG_BEOGH_GIFTABLE: return beogh_can_gift_items_to(mon); case TARG_MOVABLE_OBJECT: @@ -2835,15 +2878,8 @@ string feature_description_at(const coord_def& where, bool covering, string covering_description = ""; - if (covering && you.see_cell(where)) - { - if (is_bloodcovered(where)) - covering_description = ", spattered with blood"; - else if (glowing_mold(where)) - covering_description = ", covered with glowing mould"; - else if (is_moldy(where)) - covering_description = ", covered with mould"; - } + if (covering && you.see_cell(where) && is_bloodcovered(where)) + covering_description = ", spattered with blood"; // FIXME: remove desc markers completely; only Zin walls are left. // They suffer, among other problems, from an information leak. @@ -2943,6 +2979,8 @@ string feature_description_at(const coord_def& where, bool covering, desc += "awoken "; desc += grid == grd(where) ? raw_feature_description(where) : _base_feature_desc(grid, trap); + if (is_temp_terrain(where)) + desc += " (summoned)"; desc += covering_description; return thing_do_grammar(dtype, add_stop, false, desc); } @@ -3038,7 +3076,9 @@ static string _mon_enchantments_string(const monster_info& mi) if (!enchant_descriptors.empty()) { return uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE)) - + " is " + + " " + + conjugate_verb("are", mi.pronoun_plurality()) + + " " + comma_separated_line(enchant_descriptors.begin(), enchant_descriptors.end()) + "."; @@ -3135,59 +3175,91 @@ static string _get_monster_desc(const monster_info& mi) string pronoun = uppercase_first(mi.pronoun(PRONOUN_SUBJECTIVE)); if (mi.is(MB_CLINGING)) - text += pronoun + " is clinging to the wall.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " clinging to the wall.\n"; + } if (mi.is(MB_MESMERIZING)) { text += string("You are mesmerised by ") - + mi.pronoun(PRONOUN_POSSESSIVE) + " song.\n"; + + mi.pronoun(PRONOUN_POSSESSIVE) + " song.\n"; } if (mi.is(MB_SLEEPING) || mi.is(MB_DORMANT)) { - text += pronoun + " appears to be " - + (mi.is(MB_CONFUSED) ? "sleepwalking" - : "resting") - + ".\n"; + text += pronoun + " " + + conjugate_verb("appear", mi.pronoun_plurality()) + " to be " + + (mi.is(MB_CONFUSED) ? "sleepwalking" : "resting") + ".\n"; } // Applies to both friendlies and hostiles else if (mi.is(MB_FLEEING)) - text += pronoun + " is fleeing.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " fleeing.\n"; + } // hostile with target != you - else if (mi.attitude == ATT_HOSTILE && (mi.is(MB_UNAWARE) || mi.is(MB_WANDERING))) - text += pronoun + " doesn't appear to have noticed you.\n"; + else if (mi.attitude == ATT_HOSTILE + && (mi.is(MB_UNAWARE) || mi.is(MB_WANDERING))) + { + text += pronoun + " " + conjugate_verb("have", mi.pronoun_plurality()) + + " not noticed you.\n"; + } if (mi.attitude == ATT_FRIENDLY) - text += pronoun + " is friendly.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " friendly.\n"; + } else if (mi.attitude == ATT_GOOD_NEUTRAL) - text += pronoun + " seems to be peaceful towards you.\n"; + { + text += pronoun + " " + conjugate_verb("seem", mi.pronoun_plurality()) + + " to be peaceful towards you.\n"; + } else if (mi.attitude != ATT_HOSTILE && !mi.is(MB_INSANE)) { // don't differentiate between permanent or not - text += pronoun + " is indifferent to you.\n"; + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " indifferent to you.\n"; } if (mi.is(MB_SUMMONED) || mi.is(MB_PERM_SUMMON)) { - text += pronoun + " has been summoned"; + text += pronoun + " " + conjugate_verb("have", mi.pronoun_plurality()) + + " been summoned"; if (mi.is(MB_SUMMONED_CAPPED)) - text += ", and is expiring"; + { + text += ", and " + conjugate_verb("are", mi.pronoun_plurality()) + + " expiring"; + } else if (mi.is(MB_PERM_SUMMON)) text += " but will not time out"; text += ".\n"; } if (mi.is(MB_HALOED)) - text += pronoun + " is illuminated by a divine halo.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " illuminated by a divine halo.\n"; + } if (mi.is(MB_UMBRAED)) - text += pronoun + " is wreathed by an umbra.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " wreathed by an umbra.\n"; + } if (mi.intel() <= I_BRAINLESS) - text += pronoun + " is mindless.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " mindless.\n"; + } if (mi.is(MB_CHAOTIC)) - text += pronoun + " is chaotic.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " chaotic.\n"; + } if (mi.is(MB_POSSESSABLE)) { @@ -3195,21 +3267,30 @@ static string _get_monster_desc(const monster_info& mi) + " soul is ripe for the taking.\n"; } else if (mi.is(MB_ENSLAVED)) - text += pronoun + " is a disembodied soul.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " a disembodied soul.\n"; + } if (mi.is(MB_MIRROR_DAMAGE)) - text += pronoun + " is reflecting injuries back at attackers.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " reflecting injuries back at attackers.\n"; + } if (mi.is(MB_INNER_FLAME)) - text += pronoun + " is filled with an inner flame.\n"; + { + text += pronoun + " " + conjugate_verb("are", mi.pronoun_plurality()) + + " filled with an inner flame.\n"; + } if (mi.fire_blocker) { text += string("Your line of fire to ") + mi.pronoun(PRONOUN_OBJECTIVE) - + " is blocked by " // FIXME: renamed features - + feature_description(mi.fire_blocker, NUM_TRAPS, "", - DESC_A) - + "\n"; + + " is blocked by " // FIXME: renamed features + + feature_description(mi.fire_blocker, NUM_TRAPS, "", + DESC_A) + + "\n"; } text += _mon_enchantments_string(mi); @@ -3532,8 +3613,9 @@ static void _describe_cell(const coord_def& where, bool in_range) if (!in_range) { - mprf(MSGCH_EXAMINE_FILTER, "%s is out of range.", - mon->pronoun(PRONOUN_SUBJECTIVE).c_str()); + mprf(MSGCH_EXAMINE_FILTER, "%s %s out of range.", + mon->pronoun(PRONOUN_SUBJECTIVE).c_str(), + conjugate_verb("are", mi.pronoun_plurality()).c_str()); } #ifndef DEBUG_DIAGNOSTICS monster_described = true; diff --git a/crawl-ref/source/directn.h b/crawl-ref/source/directn.h index 6b31eb61f62d..b595a38c5f09 100644 --- a/crawl-ref/source/directn.h +++ b/crawl-ref/source/directn.h @@ -106,7 +106,7 @@ struct direction_chooser_args just_looking(false), needs_path(true), unrestricted(false), - self(CONFIRM_PROMPT), + self(confirm_prompt_type::prompt), target_prefix(nullptr), behaviour(nullptr), show_floor_desc(false), @@ -227,9 +227,12 @@ class direction_chooser void toggle_beam(); void finalize_moves(); - void draw_beam_if_needed(); void do_redraws(); + void draw_beam(); + void highlight_summoner(); + coord_def find_summoner(); + // Whether the current target is you. bool looking_at_you() const; @@ -265,7 +268,7 @@ class direction_chooser // position? // What we need to redraw. - bool need_beam_redraw; + bool need_viewport_redraw; bool need_cursor_redraw; bool need_text_redraw; bool need_all_redraw; // All of the above. @@ -276,7 +279,6 @@ class direction_chooser static targeting_behaviour stock_behaviour; bool unrestricted; - public: // TODO: fix the weird behavior that led to this hack bool needs_path; // Determine a ray while we're at it? diff --git a/crawl-ref/source/domino.h b/crawl-ref/source/domino.h index e2deb554f931..4e3d94cfed43 100644 --- a/crawl-ref/source/domino.h +++ b/crawl-ref/source/domino.h @@ -441,7 +441,7 @@ class DominoSet tiling[pt] = choices[0]; } else - tiling[pt] = rng() % adjacencies_.size(); + tiling[pt] = rng(adjacencies_.size()); has_conflicts |= Conflicts(pt, tiling); } @@ -475,7 +475,7 @@ class DominoSet tiling[pt] = choices[0]; } else - tiling[pt] = rng() % adjacencies_.size(); + tiling[pt] = rng(adjacencies_.size()); int after = Conflicts(pt, tiling); if (after >= conflicts) @@ -574,13 +574,13 @@ class DominoSet for (int y = -sz; y <= sz; ++y) { Point nb = {pt.x + x, pt.y + y}; - if (tiling.find(nb) != tiling.end() && rng() % 2) + if (tiling.find(nb) != tiling.end() && rng(2)) shuffle.insert(nb); } } } for (auto itr : shuffle) - tiling[itr] = rng() % adjacencies_.size(); + tiling[itr] = rng(adjacencies_.size()); } int Conflicts(Point pt, const std::map& tiling) const diff --git a/crawl-ref/source/dungeon-feature-type.h b/crawl-ref/source/dungeon-feature-type.h index f4b9fcffe0ee..912c7e5e67a4 100644 --- a/crawl-ref/source/dungeon-feature-type.h +++ b/crawl-ref/source/dungeon-feature-type.h @@ -197,6 +197,7 @@ enum dungeon_feature_type DNGN_ALTAR_PAKELLAS, DNGN_ALTAR_USKAYAW, DNGN_ALTAR_HEPLIAKLQANA, + DNGN_ALTAR_WU_JIAN, DNGN_ALTAR_ECUMENICAL, #endif diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index c354a33d780c..4ddfc1df72dc 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -27,6 +27,7 @@ #include "chardump.h" #include "cloud.h" #include "coordit.h" +#include "describe.h" #include "directn.h" #include "dbg-maps.h" #include "dbg-scan.h" @@ -49,6 +50,7 @@ #include "libutil.h" #include "mapmark.h" #include "maps.h" +#include "message.h" #include "mon-death.h" #include "mon-pick.h" #include "mon-place.h" @@ -80,9 +82,8 @@ #endif // DUNGEON BUILDERS -static bool _build_level_vetoable(bool enable_random_maps, - dungeon_feature_type dest_stairs_type); -static void _build_dungeon_level(dungeon_feature_type dest_stairs_type); +static bool _build_level_vetoable(bool enable_random_maps); +static void _build_dungeon_level(); static bool _valid_dungeon_level(); static bool _builder_by_type(); @@ -230,18 +231,34 @@ static unique_ptr dgn_colour_grid; static string branch_epilogues[NUM_BRANCHES]; +set &get_uniq_map_tags() +{ + if (you.where_are_you == BRANCH_ABYSS) + return you.uniq_map_tags_abyss; + else + return you.uniq_map_tags; +} + +set &get_uniq_map_names() +{ + if (you.where_are_you == BRANCH_ABYSS) + return you.uniq_map_names_abyss; + else + return you.uniq_map_names; +} + /********************************************************************** * builder() - kickoff for the dungeon generator. *********************************************************************/ -bool builder(bool enable_random_maps, dungeon_feature_type dest_stairs_type) +bool builder(bool enable_random_maps) { // Re-check whether we're in a valid place, it leads to obscure errors // otherwise. ASSERT_RANGE(you.where_are_you, 0, NUM_BRANCHES); ASSERT_RANGE(you.depth, 0 + 1, brdepth[you.where_are_you] + 1); - const set uniq_tags = you.uniq_map_tags; - const set uniq_names = you.uniq_map_names; + const set uniq_tags = get_uniq_map_tags(); + const set uniq_names = get_uniq_map_names(); // For normal cases, this should already be taken care of by enter_level. // However, we want to be really sure that the builder isn't ever run with @@ -258,7 +275,15 @@ bool builder(bool enable_random_maps, dungeon_feature_type dest_stairs_type) temp_unique_items = you.unique_items; unwind_bool levelgen(crawl_state.generating_level, true); - rng_generator levelgen_rng(you.where_are_you); + rng::generator levelgen_rng(you.where_are_you); + +#ifdef DEBUG_DIAGNOSTICS // no point in enabling unless dprf works + CrawlHashTable &debug_logs = you.props["debug_builder_logs"].get_table(); + string &cur_level_log = debug_logs[level_id::current().describe()].get_string(); + message_tee debug_messages(cur_level_log); + debug_messages.append_line(make_stringf("Builder log for %s:", + level_id::current().describe().c_str())); +#endif // N tries to build the level, after which we bail with a capital B. int tries = 50; @@ -271,7 +296,7 @@ bool builder(bool enable_random_maps, dungeon_feature_type dest_stairs_type) try { - if (_build_level_vetoable(enable_random_maps, dest_stairs_type)) + if (_build_level_vetoable(enable_random_maps)) return true; } catch (map_load_exception &mload) @@ -281,8 +306,8 @@ bool builder(bool enable_random_maps, dungeon_feature_type dest_stairs_type) reread_maps(); } - you.uniq_map_tags = uniq_tags; - you.uniq_map_names = uniq_names; + get_uniq_map_tags() = uniq_tags; + get_uniq_map_names() = uniq_names; } if (!crawl_state.map_stat_gen && !crawl_state.obj_stat_gen) @@ -305,8 +330,7 @@ bool builder(bool enable_random_maps, dungeon_feature_type dest_stairs_type) return false; } -static bool _build_level_vetoable(bool enable_random_maps, - dungeon_feature_type dest_stairs_type) +static bool _build_level_vetoable(bool enable_random_maps) { #ifdef DEBUG_STATISTICS mapstat_report_map_build_start(); @@ -319,7 +343,7 @@ static bool _build_level_vetoable(bool enable_random_maps, try { - _build_dungeon_level(dest_stairs_type); + _build_dungeon_level(); } catch (dgn_veto_exception& e) { @@ -597,6 +621,8 @@ void dgn_flush_map_memory() // vaults and map stuff you.uniq_map_tags.clear(); you.uniq_map_names.clear(); + you.uniq_map_tags_abyss.clear(); + you.uniq_map_names_abyss.clear(); you.vault_list.clear(); you.branches_left.reset(); you.branch_stairs.init(0); @@ -604,6 +630,7 @@ void dgn_flush_map_memory() you.zig_max = 0; you.exploration = 0; you.seen_portals = 0; // should be just cosmetic + reset_portal_entrances(); // would it be safe to just clear you.props? you.props.erase(TEMPLE_SIZE_KEY); you.props.erase(TEMPLE_MAP_KEY); @@ -685,18 +712,21 @@ static void _set_grd(const coord_def &c, dungeon_feature_type feat) grd(c) = feat; } -static void _dgn_register_vault(const string &name, const set &tags) +static void _dgn_register_vault(const string &name, const unordered_set &tags) { if (!tags.count("allow_dup")) - you.uniq_map_names.insert(name); + get_uniq_map_names().insert(name); if (tags.count("luniq")) env.level_uniq_maps.insert(name); - for (const string &tag : tags) + vector sorted_tags(tags.begin(), tags.end()); + sort(sorted_tags.begin(), sorted_tags.end()); + + for (const string &tag : sorted_tags) { if (starts_with(tag, "uniq_")) - you.uniq_map_tags.insert(tag); + get_uniq_map_tags().insert(tag); else if (starts_with(tag, "luniq_")) env.level_uniq_map_tags.insert(tag); } @@ -704,7 +734,7 @@ static void _dgn_register_vault(const string &name, const set &tags) static void _dgn_register_vault(const map_def &map) { - _dgn_register_vault(map.name, map.get_tags()); + _dgn_register_vault(map.name, map.get_tags_unsorted()); } static void _dgn_register_vault(const string &name, string &spaced_tags) @@ -714,13 +744,13 @@ static void _dgn_register_vault(const string &name, string &spaced_tags) static void _dgn_unregister_vault(const map_def &map) { - you.uniq_map_names.erase(map.name); + get_uniq_map_names().erase(map.name); env.level_uniq_maps.erase(map.name); - for (const string &tag : map.get_tags()) + for (const string &tag : map.get_tags_unsorted()) { if (starts_with(tag, "uniq_")) - you.uniq_map_tags.erase(tag); + get_uniq_map_tags().erase(tag); else if (starts_with(tag, "luniq_")) env.level_uniq_map_tags.erase(tag); } @@ -753,6 +783,17 @@ static bool _dgn_square_is_passable(const coord_def &c) return !(env.level_map_mask(c) & MMT_OPAQUE) && dgn_square_travel_ok(c); } +static bool _dgn_square_is_ever_passable(const coord_def &c) +{ + if (!(env.level_map_mask(c) & MMT_OPAQUE)) + { + const dungeon_feature_type feat = grd(c); + if (feat == DNGN_DEEP_WATER || feat == DNGN_LAVA) + return true; + } + return _dgn_square_is_passable(c); +} + static inline void _dgn_point_record_stub(const coord_def &) { } template @@ -907,8 +948,9 @@ static bool _is_exit_stair(const coord_def &c) // If fill is non-zero, it fills any disconnected regions with fill. // static int _process_disconnected_zones(int x1, int y1, int x2, int y2, - bool choose_stairless, - dungeon_feature_type fill) + bool choose_stairless, + dungeon_feature_type fill, + bool (*passable)(const coord_def &) = _dgn_square_is_passable) { memset(travel_point_distance, 0, sizeof(travel_distance_grid_t)); int nzones = 0; @@ -919,7 +961,7 @@ static int _process_disconnected_zones(int x1, int y1, int x2, int y2, { if (!map_bounds(x, y) || travel_point_distance[x][y] - || !_dgn_square_is_passable(coord_def(x, y))) + || !passable(coord_def(x, y))) { continue; } @@ -927,7 +969,7 @@ static int _process_disconnected_zones(int x1, int y1, int x2, int y2, const bool found_exit_stair = _dgn_fill_zone(coord_def(x, y), ++nzones, _dgn_point_record_stub, - _dgn_square_is_passable, + passable, choose_stairless ? (at_branch_bottom() ? _is_upwards_exit_stair : _is_exit_stair) : nullptr); @@ -1054,9 +1096,7 @@ dgn_register_place(const vault_placement &place, bool register_vault) } // Find tags matching properties. - set tags = place.map.get_tags(); - - for (const auto &tag : tags) + for (const auto &tag : place.map.get_tags_unsorted()) { const feature_property_type prop = str_to_fprop(tag); if (prop == FPROP_NONE) @@ -1127,7 +1167,7 @@ dgn_register_place(const vault_placement &place, bool register_vault) vault_placement *new_vault_place = new vault_placement(place); env.level_vaults.emplace_back(new_vault_place); if (register_vault) - _remember_vault_placement(place, place.map.has_tag("extra")); + _remember_vault_placement(place, place.map.is_extra_vault()); return new_vault_place; } @@ -1144,7 +1184,7 @@ static bool _dgn_ensure_vault_placed(bool vault_success, static bool _ensure_vault_placed_ex(bool vault_success, const map_def *vault) { return _dgn_ensure_vault_placed(vault_success, - (!vault->has_tag("extra") + (!vault->is_extra_vault() && vault->orient == MAP_ENCOMPASS)); } @@ -1299,7 +1339,6 @@ void dgn_reset_level(bool enable_random_maps) env.spawn_random_rate = 0; env.density = 0; env.forest_awoken_until = 0; - env.sunlight.clear(); env.floor_colour = BLACK; env.rock_colour = BLACK; @@ -1311,11 +1350,13 @@ void dgn_reset_level(bool enable_random_maps) tile_init_default_flavour(); tile_clear_flavour(); env.tile_names.clear(); + + update_portal_entrances(); } static int _num_items_wanted(int absdepth0) { - if (branches[you.where_are_you].branch_flags & BFLAG_NO_ITEMS) + if (branches[you.where_are_you].branch_flags & brflag::no_items) return 0; else if (absdepth0 > 5 && one_chance_in(500 - 5 * absdepth0)) return 10 + random2avg(90, 2); // rich level! @@ -1905,7 +1946,7 @@ static bool _add_connecting_escape_hatches() // For any regions without a down stone stair case, add an // escape hatch. This will always allow (downward) progress. - if (branches[you.where_are_you].branch_flags & BFLAG_ISLANDED) + if (branches[you.where_are_you].branch_flags & brflag::islanded) return true; // Veto D:1 or Pan if there are disconnected areas. @@ -1988,7 +2029,7 @@ static void _dgn_verify_connectivity(unsigned nvaults) // Also check for isolated regions that have no stairs. if (player_in_connected_branch() - && !(branches[you.where_are_you].branch_flags & BFLAG_ISLANDED) + && !(branches[you.where_are_you].branch_flags & brflag::islanded) && dgn_count_disconnected_zones(true) > 0) { throw dgn_veto_exception("Isolated areas with no stairs."); @@ -2094,8 +2135,8 @@ static void _build_overflow_temples() } if (num_gods == 1 - && you.uniq_map_tags.find("uniq_altar_" + name) - != you.uniq_map_tags.end()) + && get_uniq_map_tags().find("uniq_altar_" + name) + != get_uniq_map_tags().end()) { // We've already placed a specialized temple for this // god, so do nothing. @@ -2318,7 +2359,7 @@ static bool _mimic_at_level() && !crawl_state.game_is_tutorial(); } -static void _place_feature_mimics(dungeon_feature_type dest_stairs_type) +static void _place_feature_mimics() { for (rectangle_iterator ri(1); ri; ++ri) { @@ -2343,8 +2384,8 @@ static void _place_feature_mimics(dungeon_feature_type dest_stairs_type) // another one to spawn. const char* dst = branches[stair_destination(pos).branch].abbrevname; const string tag = "uniq_" + lowercase_string(dst); - if (you.uniq_map_tags.count(tag)) - you.uniq_map_tags.erase(tag); + if (get_uniq_map_tags().count(tag)) + get_uniq_map_tags().erase(tag); } } } @@ -2366,7 +2407,7 @@ static void _post_vault_build() } } -static void _build_dungeon_level(dungeon_feature_type dest_stairs_type) +static void _build_dungeon_level() { bool place_vaults = _builder_by_type(); @@ -2420,7 +2461,7 @@ static void _build_dungeon_level(dungeon_feature_type dest_stairs_type) _place_uniques(); if (_mimic_at_level()) - _place_feature_mimics(dest_stairs_type); + _place_feature_mimics(); _place_traps(); @@ -2556,9 +2597,9 @@ static bool _vault_can_use_layout(const map_def *vault, const map_def *layout) ASSERT(layout->has_tag_prefix("layout_type_")); - const set tags = layout->get_tags(); - - for (auto &tag : tags) + // in principle, tag order can matter here, even though that is probably + // a vault designer error + for (const auto &tag : layout->get_tags()) { if (starts_with(tag, "layout_type_")) { @@ -2624,7 +2665,7 @@ static bool _pan_level() for (int i = 0; i < 4; i++) { - if (!you.uniq_map_tags.count(string("uniq_") + pandemon_level_names[i])) + if (!get_uniq_map_tags().count(string("uniq_") + pandemon_level_names[i])) { all_demons_generated = false; break; @@ -2641,7 +2682,7 @@ static bool _pan_level() { which_demon = random2(4); } - while (you.uniq_map_tags.count(string("uniq_") + while (get_uniq_map_tags().count(string("uniq_") + pandemon_level_names[which_demon])); } @@ -3149,7 +3190,7 @@ static void _place_minivaults() if (vault) _build_secondary_vault(vault); } // if ALL maps eligible are "extra" but fail to place, we'd be screwed - while (vault && vault->has_tag("extra") && tries++ < 10000); + while (vault && vault->is_extra_vault() && tries++ < 10000); } } @@ -3209,9 +3250,12 @@ static void _place_traps() { ts.pos.x = random2(GXM); ts.pos.y = random2(GYM); + // Don't place random traps under vault monsters; if a vault + // wants this they have to request it specifically. if (in_bounds(ts.pos) && grd(ts.pos) == DNGN_FLOOR - && !map_masked(ts.pos, MMT_NO_TRAP)) + && !map_masked(ts.pos, MMT_NO_TRAP) + && mgrd(ts.pos) == NON_MONSTER) { break; } @@ -3234,7 +3278,7 @@ static void _place_traps() grd(ts.pos) = ts.category(); ts.prepare_ammo(); env.trap[ts.pos] = ts; - dprf("placed a trap"); + dprf("placed a %s trap", trap_name(type).c_str()); } if (player_in_branch(BRANCH_SPIDER)) @@ -3526,7 +3570,7 @@ static void _place_extra_vaults() if (_build_secondary_vault(vault)) { const map_def &map(*vault); - if (map.has_tag("extra")) + if (map.is_extra_vault()) continue; use_random_maps = false; } @@ -3804,13 +3848,14 @@ static void _randomly_place_item(int item) } if (!found) { + dprf(DIAG_DNGN, "Builder failed to place %s", + mitm[item].name(DESC_PLAIN, false, true).c_str()); // Couldn't find a single good spot! destroy_item(item); } else { - dprf(DIAG_DNGN, "builder placing %s (%s) at %d,%d", - mitm[item].name(DESC_PLAIN).c_str(), + dprf(DIAG_DNGN, "Builder placing %s at %d,%d", mitm[item].name(DESC_PLAIN, false, true).c_str(), itempos.x, itempos.y); move_item_to_grid(&item, itempos); @@ -4212,12 +4257,19 @@ static const vault_placement *_build_vault_impl(const map_def *vault, && player_in_branch(BRANCH_SWAMP)) { _process_disconnected_zones(0, 0, GXM-1, GYM-1, true, DNGN_TREE); + // do a second pass to remove tele closets consisting of deep water + // created by the first pass -- which will not fill in deep water + // because it is treated as impassable. + // TODO: get zonify to prevent these? + // TODO: does this come up anywhere outside of swamp? + _process_disconnected_zones(0, 0, GXM-1, GYM-1, true, DNGN_TREE, + _dgn_square_is_ever_passable); } if (!make_no_exits) { const bool spotty = - branches[you.where_are_you].branch_flags & BFLAG_SPOTTY; + testbits(branches[you.where_are_you].branch_flags, brflag::spotty); if (place.connect(spotty) == 0 && place.exits.size() > 0 && !player_in_branch(BRANCH_ABYSS)) { @@ -4545,10 +4597,10 @@ int dgn_place_item(const item_spec &spec, if (_apply_item_props(item, spec, (useless_tries >= 10), false)) { - dprf(DIAG_DNGN, "vault spec: placing %s (%s) at %d,%d", - mitm[item_made].name(DESC_PLAIN).c_str(), + dprf(DIAG_DNGN, "vault spec: placing %s at %d,%d", mitm[item_made].name(DESC_PLAIN, false, true).c_str(), where.x, where.y); + env.level_map_mask(where) |= MMT_NO_TRAP; return item_made; } else @@ -4705,6 +4757,8 @@ monster* dgn_place_monster(mons_spec &mspec, coord_def where, if (!mspec.place.is_valid()) mspec.place = level_id::current(); + bool chose_ood = false; + const int starting_depth = mspec.place.depth; if (type == RANDOM_SUPER_OOD || type == RANDOM_MODERATE_OOD) { @@ -4742,7 +4796,12 @@ monster* dgn_place_monster(mons_spec &mspec, coord_def where, if (mons_class_is_zombified(mspec.monbase)) type = pick_local_zombifiable_monster(mspec.place, mspec.monbase, coord_def()); else - type = pick_random_monster(mspec.place, mspec.monbase); + { + level_id place = mspec.place; + type = pick_random_monster(mspec.place, mspec.monbase, &place); + if (place.depth > starting_depth + 5) + chose_ood = true; + } if (!type) type = RANDOM_MONSTER; } @@ -4830,6 +4889,12 @@ monster* dgn_place_monster(mons_spec &mspec, coord_def where, mons->props[CUSTOM_SPELLS_KEY] = true; } + // this prop is mainly for the seed explorer. + // not a great or complete measure of monster danger: the builder can roll + // on the low side of the ood range, and vaults don't get this set. + if (chose_ood) + mons->props[MON_OOD_KEY].get_bool() = true; + if (!mspec.items.empty()) _dgn_give_mon_spec_items(mspec, mons, type); @@ -5062,8 +5127,8 @@ static void _vault_grid_glyph(vault_placement &place, const coord_def& where, if (item_made != NON_ITEM) { mitm[item_made].pos = where; - dprf(DIAG_DNGN, "vault grid: placing %s (%s) at %d,%d", - mitm[item_made].name(DESC_PLAIN).c_str(), + env.level_map_mask(where) |= MMT_NO_TRAP; + dprf(DIAG_DNGN, "vault grid: placing %s at %d,%d", mitm[item_made].name(DESC_PLAIN, false, true).c_str(), mitm[item_made].pos.x, mitm[item_made].pos.y); } @@ -5623,12 +5688,6 @@ static void _stock_shop_item(int j, shop_type shop_type_, object_class_type basetype = item_in_shop(shop_type_); int subtype = OBJ_RANDOM; - if (spec.gozag && shop_type_ == SHOP_FOOD && you.species == SP_VAMPIRE) - { - basetype = OBJ_POTIONS; - subtype = POT_BLOOD; - } - if (!spec.items.empty() && !spec.use_all) { // shop spec lists a random set of items; choose one @@ -5684,12 +5743,6 @@ static void _stock_shop_item(int j, shop_type shop_type_, if (shop_type_ == SHOP_BOOK && !is_artefact(item)) stocked[item.sub_type]++; - if (spec.gozag && shop_type_ == SHOP_FOOD && you.species == SP_VAMPIRE) - { - ASSERT(is_blood_potion(item)); - item.quantity += random2(3); // blood for the vampire friends :) - } - // Identify the item, unless we don't do that. if (!_shop_sells_antiques(shop_type_)) set_ident_flags(item, ISFLAG_IDENT_MASK); @@ -5699,6 +5752,8 @@ static void _stock_shop_item(int j, shop_type shop_type_, item.pos = shop.pos; item.link = ITEM_IN_SHOP; shop.stock.push_back(item); + dprf(DIAG_DNGN, "shop spec: placing %s", + item.name(DESC_PLAIN, false, true).c_str()); } /** @@ -5712,6 +5767,7 @@ static void _stock_shop_item(int j, shop_type shop_type_, */ void place_spec_shop(const coord_def& where, shop_spec &spec, int shop_level) { + rng::subgenerator shop_rng; // isolate shop rolls from levelgen no_notes nx; shop_struct& shop = env.shop[where]; @@ -5908,7 +5964,7 @@ static void _place_specific_trap(const coord_def& where, trap_spec* spec, } while (!is_regular_trap(spec_type) #if TAG_MAJOR_VERSION == 34 - || spec_type == TRAP_DART || spec_type == TRAP_GAS + || spec_type == TRAP_NEEDLE || spec_type == TRAP_GAS || spec_type == TRAP_SHADOW || spec_type == TRAP_SHADOW_DORMANT #endif || !is_valid_shaft_level() && spec_type == TRAP_SHAFT); @@ -5920,6 +5976,7 @@ static void _place_specific_trap(const coord_def& where, trap_spec* spec, grd(where) = trap_category(spec_type); t.prepare_ammo(charges); env.trap[where] = t; + dprf("placed a %s trap", trap_name(spec_type).c_str()); } /** @@ -6431,7 +6488,7 @@ static bool _fixup_interlevel_connectivity() if (!player_in_connected_branch() || brdepth[you.where_are_you] == -1) return true; - if (branches[you.where_are_you].branch_flags & BFLAG_ISLANDED) + if (branches[you.where_are_you].branch_flags & brflag::islanded) return true; StairConnectivity prev_con; @@ -6939,10 +6996,15 @@ string dump_vault_maps() // printing a morgue, this check isn't reliable. Ignore it. if (!is_existing_level(lid) && you.save) { - out += "[-gen,-vis] " + lid.describe() + "\n"; - continue; + out += "[-gen] "; + if (!you.vault_list.count(lid)) + { + out += lid.describe() + "\n"; + continue; + } } - out += you.level_visited(lid) ? "[+gen,+vis] " : "[+gen,-vis] "; + else + out += you.level_visited(lid) ? "[+gen,+vis] " : "[+gen,-vis] "; } out += lid.describe(); vector &maps(you.vault_list[lid]); diff --git a/crawl-ref/source/dungeon.h b/crawl-ref/source/dungeon.h index 72c9148db0d5..550cb436bf35 100644 --- a/crawl-ref/source/dungeon.h +++ b/crawl-ref/source/dungeon.h @@ -190,12 +190,14 @@ extern vector Temp_Vaults; extern const map_bitmask *Vault_Placement_Mask; +set &get_uniq_map_tags(); +set &get_uniq_map_names(); + void init_level_connectivity(); void read_level_connectivity(reader &th); void write_level_connectivity(writer &th); -bool builder(bool enable_random_maps = true, - dungeon_feature_type dest_stairs_type = NUM_FEATURES); +bool builder(bool enable_random_maps = true); void dgn_clear_vault_placements(); void dgn_erase_unused_vault_placements(); diff --git a/crawl-ref/source/duration-data.h b/crawl-ref/source/duration-data.h index 9ffa0e51acfd..ebb7f666191e 100644 --- a/crawl-ref/source/duration-data.h +++ b/crawl-ref/source/duration-data.h @@ -273,7 +273,6 @@ static const duration_def duration_data[] = "You are standing in death's doorway.", D_EXPIRES, {{ "Your life is in your own hands again!", []() { you.duration[DUR_DEATHS_DOOR_COOLDOWN] = random_range(10, 30); - calc_hp(); }}, { "Your time is quickly running out!", 5 }}, 10}, { DUR_DEATHS_DOOR_COOLDOWN, YELLOW, "-DDoor", @@ -596,7 +595,6 @@ static const duration_def duration_data[] = { DUR_VEHUMET_GIFT, 0, "", "", "vehumet gift", "", D_NO_FLAGS, {{""}}}, { DUR_SICKENING, 0, "", "", "sickening", "", D_DISPELLABLE, {{""}}}, { DUR_WATER_HOLD, 0, "", "", "drowning", "", D_NO_FLAGS}, - { DUR_WATER_HOLD_IMMUNITY, 0, "", "", "drowning immunity", "", D_NO_FLAGS, {{""}}}, { DUR_SLEEP_IMMUNITY, 0, "", "", "sleep immunity", "", D_NO_FLAGS, {{""}}}, { DUR_ELIXIR_HEALTH, 0, "", "", "elixir health", "", D_NO_FLAGS}, { DUR_ELIXIR_MAGIC, 0, "", "", "elixir magic", "", D_NO_FLAGS}, @@ -609,8 +607,6 @@ static const duration_def duration_data[] = { DUR_BRAINLESS, 0, "", "", "brainless", "", D_NO_FLAGS }, { DUR_CLUMSY, 0, "", "", "clumsy", "", D_NO_FLAGS }, { DUR_ANCESTOR_DELAY, 0, "", "", "ancestor delay", "", D_NO_FLAGS, {{""}}}, - { DUR_NO_CAST, 0, "", "", "no cast", "", D_NO_FLAGS, - {{ "You regain access to your magic." }, {}, true }}, { DUR_HEAVENLY_STORM, 0, "", "", "", "", D_NO_FLAGS, {{ "", wu_jian_heaven_tick }}}, { DUR_GRASPING_ROOTS, 0, "", "grasped by roots", "grasping roots", @@ -652,6 +648,7 @@ static const duration_def duration_data[] = { DUR_MAGIC_ARMOUR, 0, "", "", "old magic armour", "", D_NO_FLAGS}, { DUR_MAGIC_SHIELD, 0, "", "", "old magic shield", "", D_NO_FLAGS}, { DUR_FORTITUDE, 0, "", "", "old fortitude", "", D_NO_FLAGS}, - + { DUR_WATER_HOLD_IMMUNITY, 0, "", "", "old drowning immunity", "", D_NO_FLAGS, {{""}}}, + { DUR_NO_CAST, 0, "", "", "old no cast", "", D_NO_FLAGS}, #endif }; diff --git a/crawl-ref/source/duration-type.h b/crawl-ref/source/duration-type.h index 5f40b56135b2..ab8b01d4dbd7 100644 --- a/crawl-ref/source/duration-type.h +++ b/crawl-ref/source/duration-type.h @@ -120,7 +120,9 @@ enum duration_type DUR_SENTINEL_MARK, DUR_SICKENING, DUR_WATER_HOLD, +#if TAG_MAJOR_VERSION == 34 DUR_WATER_HOLD_IMMUNITY, +#endif DUR_FLAYED, #if TAG_MAJOR_VERSION == 34 DUR_RETCHING, @@ -187,7 +189,9 @@ enum duration_type DUR_VERTIGO, DUR_ANCESTOR_DELAY, DUR_SANGUINE_ARMOUR, +#if TAG_MAJOR_VERSION == 34 DUR_NO_CAST, +#endif DUR_CHANNEL_ENERGY, DUR_SPWPN_PROTECTION, DUR_NO_HOP, diff --git a/crawl-ref/source/easy-confirm-type.h b/crawl-ref/source/easy-confirm-type.h new file mode 100644 index 000000000000..ac8a07ec3311 --- /dev/null +++ b/crawl-ref/source/easy-confirm-type.h @@ -0,0 +1,8 @@ +#pragma once + +enum class easy_confirm_type +{ + none, + safe, + all, +}; diff --git a/crawl-ref/source/enchant-type.h b/crawl-ref/source/enchant-type.h index cea1f80deb6b..04572d75d7be 100644 --- a/crawl-ref/source/enchant-type.h +++ b/crawl-ref/source/enchant-type.h @@ -49,8 +49,8 @@ enum enchant_type ENCH_EAT_ITEMS, #endif ENCH_AQUATIC_LAND, // Water monsters lose hp while on land. - ENCH_SPORE_PRODUCTION, #if TAG_MAJOR_VERSION == 34 + ENCH_SPORE_PRODUCTION, ENCH_SLOUCH, #endif ENCH_SWIFT, @@ -161,8 +161,8 @@ enum enchant_type #endif ENCH_RESISTANCE, ENCH_HEXED, - ENCH_BONE_ARMOUR, #if TAG_MAJOR_VERSION == 34 + ENCH_BONE_ARMOUR, ENCH_CHANT_FIRE_STORM, // chanting the fire storm spell ENCH_CHANT_WORD_OF_ENTROPY, // chanting word of entropy #endif diff --git a/crawl-ref/source/end.cc b/crawl-ref/source/end.cc index 4dcd878cb5f0..e298f3e22c5e 100644 --- a/crawl-ref/source/end.cc +++ b/crawl-ref/source/end.cc @@ -35,6 +35,7 @@ #include "view.h" #include "xom.h" #include "ui.h" +#include "tiledef-feat.h" #ifdef __ANDROID__ #include @@ -290,6 +291,19 @@ static string _exit_type_to_string(game_exit e) return "BUGGY EXIT TYPE"; } +class HiscoreScroller : public Scroller +{ +public: + virtual void _allocate_region(); + int scroll_target = 0; +}; + +void HiscoreScroller::_allocate_region() +{ + m_scroll = scroll_target - m_region.height/2; + Scroller::_allocate_region(); +} + NORETURN void end_game(scorefile_entry &se) { //Update states @@ -387,12 +401,14 @@ NORETURN void end_game(scorefile_entry &se) } break; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: { const string result = getSpeakString("Pakellas death"); god_speaks(GOD_PAKELLAS, result.c_str()); break; } +#endif default: if (will_have_passive(passive_t::goldify_corpses) @@ -441,7 +457,6 @@ NORETURN void end_game(scorefile_entry &se) if (!crawl_state.disables[DIS_CONFIRMATIONS]) display_inventory(); - textcolour(LIGHTGREY); clua.save_persist(); @@ -449,44 +464,91 @@ NORETURN void end_game(scorefile_entry &se) if (crawl_state.unsaved_macros && yesno("Save macros?", true, 'n')) macro_save(); -#ifdef USE_TILE_WEB - tiles_crt_popup show_as_popup; + auto title_hbox = make_shared(Widget::HORZ); +#ifdef USE_TILE + tile_def death_tile(TILEG_ERROR, TEX_GUI); + if (death_type == KILLED_BY_LEAVING || death_type == KILLED_BY_WINNING) + death_tile = tile_def(TILE_DNGN_EXIT_DUNGEON, TEX_FEAT); + else + death_tile = tile_def(TILE_DNGN_GRAVESTONE+1, TEX_FEAT); + + auto tile = make_shared(death_tile); + tile->set_margin_for_sdl(0, 10, 0, 0); + title_hbox->add_child(move(tile)); #endif + string goodbye_title = make_stringf("Goodbye, %s.", you.your_name.c_str()); + title_hbox->add_child(make_shared(goodbye_title)); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_sdl(0, 0, 20, 0); + title_hbox->set_margin_for_crt(0, 0, 1, 0); + + auto vbox = make_shared(Box::VERT); + vbox->add_child(move(title_hbox)); string goodbye_msg; - goodbye_msg += make_stringf("Goodbye, %s.", you.your_name.c_str()); - goodbye_msg += "\n\n "; // Space padding where # would go in list format + goodbye_msg += " "; // Space padding where # would go in list format string hiscore = hiscores_format_single_long(se, true); - const int desc_lines = count_occurrences(hiscore, "\n") + 1; - goodbye_msg += hiscore; goodbye_msg += make_stringf("\nBest Crawlers - %s\n", crawl_state.game_type_name().c_str()); - // "- 5" gives us an extra line in case the description wraps on a line. - goodbye_msg += hiscores_print_list(get_number_of_lines() - desc_lines - 5, - SCORE_TERSE, hiscore_index); +#ifdef USE_TILE_LOCAL + const int line_height = tiles.get_crt_font()->char_height(); +#else + const int line_height = 1; +#endif + + int start; + int num_lines = 100; + string hiscores = hiscores_print_list(num_lines, SCORE_TERSE, hiscore_index, start); + auto scroller = make_shared(); + auto hiscores_txt = make_shared(formatted_string::parse_string(hiscores)); + scroller->set_child(hiscores_txt); + scroller->set_scrollbar_visible(false); + scroller->scroll_target = (hiscore_index - start)*line_height + (line_height/2); + + mouse_control mc(MOUSE_MODE_MORE); + + auto goodbye_txt = make_shared(formatted_string::parse_string(goodbye_msg)); + vbox->add_child(goodbye_txt); + vbox->add_child(scroller); #ifndef DGAMELAUNCH - goodbye_msg += make_stringf("\nYou can find your morgue file in the '%s' directory.", + string morgue_dir = make_stringf("\nYou can find your morgue file in the '%s' directory.", morgue_directory().c_str()); + vbox->add_child(make_shared(formatted_string::parse_string(morgue_dir))); #endif - auto prompt_ui = make_shared(formatted_string::parse_string(goodbye_msg)); + auto popup = make_shared(move(vbox)); bool done = false; - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { + popup->on(Widget::slots.event, [&](wm_event ev) { return done = ev.type == WME_KEYDOWN; }); - mouse_control mc(MOUSE_MODE_MORE); - auto popup = make_shared(prompt_ui); - if (!crawl_state.seen_hups && !crawl_state.disables[DIS_CONFIRMATIONS]) + { +#ifdef USE_TILE_WEB + tiles.json_open_object(); + tiles.json_open_object("tile"); + tiles.json_write_int("t", death_tile.tile); + tiles.json_write_int("tex", death_tile.tex); + tiles.json_close_object(); + tiles.json_write_string("title", goodbye_title); + tiles.json_write_string("body", goodbye_msg + + hiscores_print_list(11, SCORE_TERSE, hiscore_index, start)); + tiles.push_ui_layout("game-over", 0); +#endif + ui::run_layout(move(popup), done); +#ifdef USE_TILE_WEB + tiles.pop_ui_layout(); +#endif + } + #ifdef USE_TILE_WEB tiles.send_exit_reason(reason, hiscore); #endif @@ -516,6 +578,10 @@ NORETURN void game_ended(game_exit exit, const string &message) else end(retval); } +#ifdef USE_TILE_WEB + else if (exit == game_exit::abort) + tiles.send_exit_reason("cancel", message); +#endif throw game_ended_condition(exit, message); } diff --git a/crawl-ref/source/english.cc b/crawl-ref/source/english.cc index 4c48686b216d..0aae62ff1bae 100644 --- a/crawl-ref/source/english.cc +++ b/crawl-ref/source/english.cc @@ -202,7 +202,7 @@ string apostrophise(const string &name) if (name == "herself") return "her own"; - if (name == "themselves") + if (name == "themselves" || name == "themself") return "their own"; if (name == "yourself") @@ -269,6 +269,7 @@ static const char * const _pronoun_declension[][NUM_PRONOUN_CASES] = { "he", "his", "himself", "him" }, // masculine { "she", "her", "herself", "her" }, // feminine { "you", "your", "yourself", "you" }, // 2nd person + { "they", "their", "themself", "them" }, // neutral }; const char *decline_pronoun(gender_type gender, pronoun_type variant) diff --git a/crawl-ref/source/env.h b/crawl-ref/source/env.h index 140e6678c67b..d3604384113d 100644 --- a/crawl-ref/source/env.h +++ b/crawl-ref/source/env.h @@ -107,7 +107,6 @@ struct crawl_environment int forest_awoken_until; int density; int absdepth0; - vector > sunlight; // Remaining fields not marshalled: diff --git a/crawl-ref/source/evoke.cc b/crawl-ref/source/evoke.cc index 8fd56f67e143..17d8db5414bb 100644 --- a/crawl-ref/source/evoke.cc +++ b/crawl-ref/source/evoke.cc @@ -98,7 +98,7 @@ static bool _reaching_weapon_attack(const item_def& wpn) args.mode = TARG_HOSTILE; args.range = reach_range; args.top_prompt = "Attack whom?"; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; unique_ptr hitfunc; if (reach_range == REACH_TWO) @@ -240,8 +240,12 @@ static bool _evoke_horn_of_geryon() return false; } +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif mprf(MSGCH_SOUND, "You produce a hideous howling noise!"); did_god_conduct(DID_EVIL, 3); int num = 1; @@ -346,8 +350,12 @@ static bool _lightning_rod() return false; } +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif const int power = player_adjust_evoc_power(5 + you.skill(SK_EVOCATIONS, 3), surge); @@ -411,12 +419,14 @@ void zap_wand(int slot) return; } +#if TAG_MAJOR_VERSION == 34 if (player_under_penance(GOD_PAKELLAS)) { simple_god_message("'s wrath prevents you from evoking devices!", GOD_PAKELLAS); return; } +#endif const int mp_cost = wand_mp_cost(); @@ -426,7 +436,7 @@ void zap_wand(int slot) else { item_slot = prompt_invent_item("Zap which item?", - MT_INVLIST, + menu_type::invlist, OBJ_WANDS, OPER_ZAP); } @@ -606,8 +616,12 @@ static const pop_entry pop_spiders[] = static bool _box_of_beasts(item_def &box) { - const int surge = pakellas_surge_devices() + you.spec_evoke(); - surge_power(surge); +#if TAG_MAJOR_VERSION == 34 + const int surge = pakellas_surge_devices(); + surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif mpr("You open the lid..."); // two rolls to reduce std deviation - +-6 so can get < max even at 27 sk @@ -659,8 +673,12 @@ static bool _sack_of_spiders_veto_mon(monster_type mon) static bool _sack_of_spiders(item_def &sack) { - const int surge = pakellas_surge_devices() + you.spec_evoke(); - surge_power(surge); +#if TAG_MAJOR_VERSION == 34 + const int surge = pakellas_surge_devices(); + surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif mpr("You reach into the bag..."); const int evo_skill = you.skill(SK_EVOCATIONS); @@ -775,8 +793,12 @@ static bool _ball_of_energy() bool ret = false; mpr("You gaze into the crystal ball."); +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif int use = player_adjust_evoc_power(random2(you.skill(SK_EVOCATIONS, 6)), surge); @@ -1011,14 +1033,18 @@ static bool _lamp_of_fire() args.restricts = DIR_TARGET; args.mode = TARG_HOSTILE; args.top_prompt = "Aim the lamp in which direction?"; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; if (spell_direction(target, base_beam, &args)) { if (you.confused()) target.confusion_fuzz(); +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif mpr("The flames dance!"); @@ -1320,8 +1346,12 @@ static bool _phial_of_floods() beam.set_target(target); } +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif const int power = player_adjust_evoc_power(base_pow, surge); // use real power to recalc hit/dam zappy(ZAP_PRIMAL_WAVE, power, false, beam); @@ -1389,7 +1419,7 @@ static spret _phantom_mirror() direction_chooser_args args; args.restricts = DIR_TARGET; args.needs_path = false; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; args.top_prompt = "Aiming: Phantom Mirror"; args.hitfunc = &tgt; if (!spell_direction(spd, beam, &args)) @@ -1413,17 +1443,15 @@ static spret _phantom_mirror() return spret::abort; } - if (player_will_anger_monster(*victim)) - { - if (you.get_mutation_level(MUT_NO_LOVE)) - mpr("The reflection would only feel hate for you!"); - else - simple_god_message(" forbids your reflecting this monster."); + if (player_angers_monster(victim, false)) return spret::abort; - } +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif monster* mon = clone_mons(victim, true, nullptr, ATT_FRIENDLY); if (!mon) @@ -1480,7 +1508,7 @@ bool evoke_item(int slot, bool check_range) if (slot == -1) { slot = prompt_invent_item("Evoke which item? (* to show all)", - MT_INVLIST, + menu_type::invlist, OSEL_EVOKABLE, OPER_EVOKE); if (prompt_failed(slot)) @@ -1588,18 +1616,21 @@ bool evoke_item(int slot, bool check_range) case OBJ_MISCELLANY: did_work = true; // easier to do it this way for misc items - if ((you.get_mutation_level(MUT_NO_ARTIFICE) - || player_under_penance(GOD_PAKELLAS)) - && item.sub_type != MISC_ZIGGURAT) + if (item.sub_type != MISC_ZIGGURAT) { if (you.get_mutation_level(MUT_NO_ARTIFICE)) + { mpr("You cannot evoke magical items."); - else + return false; + } +#if TAG_MAJOR_VERSION == 34 + if (player_under_penance(GOD_PAKELLAS)) { simple_god_message("'s wrath prevents you from evoking " "devices!", GOD_PAKELLAS); + return false; } - return false; +#endif } switch (item.sub_type) @@ -1618,8 +1649,12 @@ bool evoke_item(int slot, bool check_range) return false; } +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); surge_power(you.spec_evoke() + surge); +#else + const int surge = 0; +#endif wind_blast(&you, player_adjust_evoc_power(you.skill(SK_EVOCATIONS, 15), surge), diff --git a/crawl-ref/source/exclude.cc b/crawl-ref/source/exclude.cc index d5e7509b3ac3..94cc6d5a8311 100644 --- a/crawl-ref/source/exclude.cc +++ b/crawl-ref/source/exclude.cc @@ -81,48 +81,40 @@ static int _get_full_exclusion_radius() + (you.species == SP_BARACHI ? 1 : 0); } -// If the monster is in the auto_exclude list, automatically set an -// exclusion. -void set_auto_exclude(const monster* mon) +/** + * Adds auto-exclusions for any monsters in LOS that need them. + */ +void add_auto_excludes() { - if (!is_map_persistent()) - return; - - // Something of a speed hack, but some vaults have a TON of plants. - if (mon->type == MONS_PLANT) + if (!is_map_persistent() || !map_bounds(you.pos())) return; - if (_need_auto_exclude(mon) && !is_exclude_root(mon->pos())) + vector mons; + for (radius_iterator ri(you.pos(), LOS_DEFAULT); ri; ++ri) { - int rad = _get_full_exclusion_radius(); - if (mon->type == MONS_HYPERACTIVE_BALLISTOMYCETE) - rad = 2; - set_exclude(mon->pos(), rad, true); - // FIXME: If this happens for several monsters in the same turn - // (as is possible for some vaults), this could be really - // annoying. (jpeg) - mprf(MSGCH_WARN, - "Marking area around %s as unsafe for travelling.", - mon->name(DESC_THE).c_str()); + monster *mon = monster_at(*ri); + if (!mon) + continue; + // Something of a speed hack, but some vaults have a TON of plants. + if (mon->type == MONS_PLANT) + continue; + if (_need_auto_exclude(mon) && !is_exclude_root(*ri)) + { + int radius = _get_full_exclusion_radius(); + if (mon->type == MONS_HYPERACTIVE_BALLISTOMYCETE) + radius = 2; -#ifdef USE_TILE - viewwindow(); -#endif - learned_something_new(HINT_AUTO_EXCLUSION, mon->pos()); + set_exclude(*ri, radius, true); + mons.emplace_back(mon); + } } -} -// Clear auto exclusion if the monster is killed or wakes up with the -// player in sight. If sleepy is true, stationary monsters are ignored. -void remove_auto_exclude(const monster* mon, bool sleepy) -{ - if (_need_auto_exclude(mon, sleepy)) - { - del_exclude(mon->pos()); -#ifdef USE_TILE - viewwindow(); -#endif - } + if (mons.empty()) + return; + + mprf(MSGCH_WARN, "Marking area around %s as unsafe for travelling.", + describe_monsters_condensed(mons).c_str()); + learned_something_new(HINT_AUTO_EXCLUSION); } travel_exclude::travel_exclude(const coord_def &p, int r, @@ -556,10 +548,8 @@ void maybe_remove_autoexclusion(const coord_def &p) string desc = exc->desc; bool cloudy_exc = ends_with(desc, "cloud"); if ((!m || !you.can_see(*m) - || m->attitude != ATT_HOSTILE - && m->type != MONS_HYPERACTIVE_BALLISTOMYCETE - || strcmp(mons_type_name(m->type, DESC_PLAIN).c_str(), - exc->desc.c_str()) != 0) + || !_need_auto_exclude(m) + || mons_type_name(m->type, DESC_PLAIN) != desc) && !cloudy_exc) { del_exclude(p); diff --git a/crawl-ref/source/exclude.h b/crawl-ref/source/exclude.h index f292d63fa812..915351af06b2 100644 --- a/crawl-ref/source/exclude.h +++ b/crawl-ref/source/exclude.h @@ -2,8 +2,7 @@ #include "los-def.h" -void set_auto_exclude(const monster* mon); -void remove_auto_exclude(const monster* mon, bool sleepy = false); +void add_auto_excludes(); void init_exclusion_los(); void update_exclusion_los(vector changed); diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index 24cd1c8f57ba..d439c0e03cc1 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -758,10 +758,11 @@ enum mon_spell_slot_flag MON_SPELL_SHORT_RANGE = 1 << 10, // only use at short distances MON_SPELL_LONG_RANGE = 1 << 11, // only use at long distances + MON_SPELL_EVOKE = 1 << 12, // a spell from an evoked item - MON_SPELL_LAST_FLAG = MON_SPELL_LONG_RANGE, + MON_SPELL_LAST_FLAG = MON_SPELL_EVOKE, }; -DEF_BITFIELD(mon_spell_slot_flags, mon_spell_slot_flag, 11); +DEF_BITFIELD(mon_spell_slot_flags, mon_spell_slot_flag, 12); const int MON_SPELL_LAST_EXPONENT = mon_spell_slot_flags::last_exponent; COMPILE_CHECK(mon_spell_slot_flags::exponent(MON_SPELL_LAST_EXPONENT) == MON_SPELL_LAST_FLAG); @@ -874,7 +875,7 @@ struct menu_sort_condition item_sort_comparators cmp; public: - menu_sort_condition(menu_type mt = MT_INVLIST, int sort = 0); + menu_sort_condition(menu_type mt = menu_type::invlist, int sort = 0); menu_sort_condition(const string &s); bool matches(menu_type mt) const; diff --git a/crawl-ref/source/feature-data.h b/crawl-ref/source/feature-data.h index 9f6101ac2866..9e6b183acf45 100644 --- a/crawl-ref/source/feature-data.h +++ b/crawl-ref/source/feature-data.h @@ -257,7 +257,7 @@ static feature_def feat_defs[] = } TRAP(DNGN_TRAP_MECHANICAL, "mechanical trap", "trap_mechanical", LIGHTCYAN), -TRAP(DNGN_TRAP_DISPERSAL, "disperal trap", "trap_dispersal", MAGENTA), +TRAP(DNGN_TRAP_DISPERSAL, "dispersal trap", "trap_dispersal", MAGENTA), TRAP(DNGN_TRAP_TELEPORT, "teleport trap", "trap_teleport", LIGHTBLUE), #if TAG_MAJOR_VERSION == 34 TRAP(DNGN_TRAP_SHADOW, "shadow trap", "trap_shadow", BLUE), @@ -520,7 +520,7 @@ ALTAR(DNGN_ALTAR_XOM, "shimmering altar of Xom", "altar_xom", ETC_RANDOM), ALTAR(DNGN_ALTAR_VEHUMET, "radiant altar of Vehumet", "altar_vehumet", ETC_VEHUMET), ALTAR(DNGN_ALTAR_OKAWARU, "iron altar of Okawaru", "altar_okawaru", CYAN), ALTAR(DNGN_ALTAR_MAKHLEB, "burning altar of Makhleb", "altar_makhleb", ETC_FIRE), -ALTAR(DNGN_ALTAR_SIF_MUNA, "deep blue altar of Sif Muna", "altar_sif_muna", BLUE), +ALTAR(DNGN_ALTAR_SIF_MUNA, "shimmering blue altar of Sif Muna", "altar_sif_muna", ETC_SHIMMER_BLUE), ALTAR(DNGN_ALTAR_TROG, "bloodstained altar of Trog", "altar_trog", RED), ALTAR(DNGN_ALTAR_NEMELEX_XOBEH, "sparkling altar of Nemelex Xobeh", "altar_nemelex_xobeh", LIGHTMAGENTA), ALTAR(DNGN_ALTAR_ELYVILON, "white marble altar of Elyvilon", "altar_elyvilon", WHITE), @@ -535,7 +535,9 @@ ALTAR(DNGN_ALTAR_GOZAG, "opulent altar of Gozag", "altar_gozag", ETC_GOLD), // f ALTAR(DNGN_ALTAR_QAZLAL, "stormy altar of Qazlal", "altar_qazlal", ETC_ELEMENTAL), ALTAR(DNGN_ALTAR_RU, "sacrificial altar of Ru", "altar_ru", BROWN), ALTAR(DNGN_ALTAR_ECUMENICAL, "faded altar of an unknown god", "altar_ecumenical", ETC_DARK), +#if TAG_MAJOR_VERSION == 34 ALTAR(DNGN_ALTAR_PAKELLAS, "oddly glowing altar of Pakellas", "altar_pakellas", ETC_PAKELLAS), +#endif ALTAR(DNGN_ALTAR_USKAYAW, "hide-covered altar of Uskayaw", "altar_uskayaw", ETC_INCARNADINE), ALTAR(DNGN_ALTAR_HEPLIAKLQANA, "hazy altar of Hepliaklqana", "altar_hepliaklqana", LIGHTGREEN), ALTAR(DNGN_ALTAR_WU_JIAN, "ornate altar of the Wu Jian Council", "altar_wu_jian", ETC_WU_JIAN), diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index 85a9bc23ee0e..0881e23fd03d 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -158,7 +158,8 @@ bool fight_melee(actor *attacker, actor *defender, bool *did_hit, bool simu) attk.simu = true; // We're trying to hit a monster, break out of travel/explore now. - interrupt_activity(AI_HIT_MONSTER, defender->as_monster()); + interrupt_activity(activity_interrupt::hit_monster, + defender->as_monster()); // Check if the player is fighting with something unsuitable, // or someone unsuitable. @@ -598,7 +599,7 @@ int apply_chunked_AC(int dam, int ac) int hurt = 0; for (int i = 0; i < dam; i++) - if (get_uint32() < cr) + if (rng::get_uint32() < cr) hurt++; return hurt; @@ -618,10 +619,11 @@ bool wielded_weapon_check(item_def *weapon) return true; } - // Don't pester the player if they're using UC or if they don't have any - // melee weapons yet. + // Don't pester the player if they're using UC, in treeform, + // or if they don't have any melee weapons yet. if (!weapon && (you.skill(SK_UNARMED_COMBAT) > 0 + || you.form == transformation::tree || !any_of(you.inv.begin(), you.inv.end(), [](item_def &it) { return is_melee_weapon(it) && can_wield(&it); }))) @@ -633,7 +635,7 @@ bool wielded_weapon_check(item_def *weapon) if (weapon) prompt = "Really attack while wielding " + weapon->name(DESC_YOUR) + "?"; else - prompt = "Really attack barehanded?"; + prompt = "Really attack unarmed?"; if (penance) prompt += " This could place you under penance!"; @@ -872,8 +874,6 @@ int mons_usable_missile(monster* mons, item_def **launcher) } } - - bool bad_attack(const monster *mon, string& adj, string& suffix, bool& would_cause_penance, coord_def attack_pos) { @@ -1078,3 +1078,36 @@ bool stop_attack_prompt(targeter &hitfunc, const char* verb, return true; } } + +/** + * Does the player have Olgreb's Toxic Radiance up that would/could cause + * a hostile summon to be created? If so, prompt the player as to whether they + * want to continue to create their summon. Note that this prompt is never a + * penance prompt, because we don't cause penance when monsters enter line of + * sight when OTR is active, regardless of how they entered LOS. + * + * @param verb The verb to be used in the prompt. Defaults to "summon". + * @return True if the player wants to abort. + */ +bool otr_stop_summoning_prompt(string verb) +{ + if (!you.duration[DUR_TOXIC_RADIANCE]) + return false; + + if (crawl_state.disables[DIS_CONFIRMATIONS]) + return false; + + if (crawl_state.which_god_acting() == GOD_XOM) + return false; + + string prompt = make_stringf("Really %s while emitting a toxic aura?", + verb.c_str()); + + if (yesno(prompt.c_str(), false, 'n')) + return false; + else + { + canned_msg(MSG_OK); + return true; + } +} diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 81fd5c016252..4d739e603322 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -73,3 +73,5 @@ bool stop_attack_prompt(targeter &hitfunc, const char* verb, function affects = nullptr, bool *prompted = nullptr, const monster *mons = nullptr); + +bool otr_stop_summoning_prompt(string verb = "summon"); diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc index 4e9b625a4099..57bc60ad7620 100644 --- a/crawl-ref/source/files.cc +++ b/crawl-ref/source/files.cc @@ -29,7 +29,6 @@ #include "act-iter.h" #include "areas.h" #include "branch.h" -#include "butcher.h" // for fedhas_rot_all_corpses #include "chardump.h" #include "cloud.h" #include "coordit.h" @@ -45,12 +44,12 @@ #include "food.h" //for HUNGER_MAXIMUM #include "ghost.h" #include "god-abil.h" -#include "god-conduct.h" // for fedhas_rot_all_corpses #include "god-companions.h" #include "god-passive.h" #include "hints.h" #include "initfile.h" #include "item-name.h" +#include "item-status-flag-type.h" #include "items.h" #include "jobs.h" #include "kills.h" @@ -68,7 +67,7 @@ #include "prompt.h" #include "species.h" #include "spl-summoning.h" -#include "stash.h" // for fedhas_rot_all_corpses +#include "stairs.h" #include "state.h" #include "stringutil.h" #include "syscalls.h" @@ -82,6 +81,7 @@ #include "tileview.h" #include "tiles-build-specific.h" #include "timed-effects.h" +#include "ui.h" #include "unwind.h" #include "version.h" #include "view.h" @@ -1127,45 +1127,6 @@ static void _do_lost_items() item_was_lost(item); } -/// Rot all corpses remaining on the level, giving Fedhas piety for doing so. -static void _fedhas_rot_all_corpses(const level_id& old_level) -{ - bool messaged = false; - for (size_t mitm_index = 0; mitm_index < mitm.size(); ++mitm_index) - { - item_def &item = mitm[mitm_index]; - if (!item.defined() - || !item.is_type(OBJ_CORPSES, CORPSE_BODY) - || item.props.exists(CORPSE_NEVER_DECAYS)) - { - continue; - } - - if (mons_skeleton(item.mon_type)) - ASSERT(turn_corpse_into_skeleton(item)); - else - { - item_was_destroyed(item); - destroy_item(mitm_index); - } - - if (!messaged) - { - simple_god_message("'s fungi set to work."); - messaged = true; - } - - const int piety = x_chance_in_y(2, 5) ? 2 : 1; // match fungal_bloom() - // XXX: deduplicate above ^ - did_god_conduct(DID_ROT_CARRION, piety); - } - - // assumption: CORPSE_NEVER_DECAYS is never set for seen corpses - LevelStashes *ls = StashTrack.find_level(old_level); - if (ls) // assert? - ls->rot_all_corpses(); -} - /** * Perform cleanup when leaving a level. * @@ -1184,9 +1145,6 @@ static bool _leave_level(dungeon_feature_type stair_taken, { bool popped = false; - if (you.religion == GOD_FEDHAS) - _fedhas_rot_all_corpses(old_level); - if (!you.level_stack.empty() && you.level_stack.back().id == level_id::current()) { @@ -1232,42 +1190,6 @@ static bool _leave_level(dungeon_feature_type stair_taken, return popped; } - -/** - * Generate a new level. - * - * Cleanup the environment, build the level, and possibly place a ghost or - * handle initial AK entrance. - * - * @param stair_taken The means used to leave the last level. - * @param old_level The ID of the previous level. - */ -static void _make_level(dungeon_feature_type stair_taken, - const level_id& old_level) -{ - - env.turns_on_level = -1; - - tile_init_default_flavour(); - tile_clear_flavour(); - env.tile_names.clear(); - - // XXX: This is ugly. - bool dummy; - dungeon_feature_type stair_type = static_cast( - _get_dest_stair_type(old_level.branch, - static_cast(stair_taken), - dummy)); - - _clear_env_map(); - builder(true, stair_type); - - env.turns_on_level = 0; - // sanctuary - env.sanctuary_pos = coord_def(-1, -1); - env.sanctuary_time = 0; -} - /** * Move the player to the appropriate entrance location in a level. * @@ -1340,53 +1262,6 @@ static string _get_hatch_name() return ""; } - -static void _count_gold() -{ - vector gold_piles; - vector gold_places; - int gold = 0; - for (rectangle_iterator ri(0); ri; ++ri) - { - for (stack_iterator j(*ri); j; ++j) - { - if (j->base_type == OBJ_GOLD) - { - gold += j->quantity; - gold_piles.push_back(&(*j)); - gold_places.push_back(*ri); - } - } - } - - if (!player_in_branch(BRANCH_ABYSS)) - you.attribute[ATTR_GOLD_GENERATED] += gold; - - // TODO: this probably should fire when you join gozag, too? - if (have_passive(passive_t::detect_gold)) - { - for (unsigned int i = 0; i < gold_places.size(); i++) - { - bool detected = false; - int dummy = gold_piles[i]->index(); - coord_def &pos = gold_places[i]; - unlink_item(dummy); - move_item_to_grid(&dummy, pos, true); - if (!env.map_knowledge(pos).item() - || env.map_knowledge(pos).item()->base_type != OBJ_GOLD) - { - detected = true; - } - update_item_at(pos, true); - if (detected) - { - ASSERT(env.map_knowledge(pos).item()); - env.map_knowledge(pos).flags |= MAP_DETECTED_ITEM; - } - } - } -} - static const string VISITED_LEVELS_KEY = "visited_levels"; #if TAG_MAJOR_VERSION == 34 @@ -1421,11 +1296,16 @@ void player::set_level_visited(const level_id &level) visited[level.describe()] = true; } +/** + * Has the player visited the level currently stored in the save under the id + * `level`, if there is one? Returns false if there isn't one. This stores + * *token level* visited state, not type-level -- it does not answer questions + * like, e.g. has the player ever visited a trove? For that, see place_info. + * This distinction matters mainly for portal branches, especially ones that can + * be revisited, e.g. Pan levels and zigs. + */ bool player::level_visited(const level_id &level) { - // this will mean that portal maps that the player is not currently on - // return false, since the map gets deleted. A continuation of legacy - // behavior... // `is_existing_level` is not reliable after the game end, because the // save no longer exists, so we ignore it for printing morgues if (!is_existing_level(level) && you.save) @@ -1434,6 +1314,349 @@ bool player::level_visited(const level_id &level) return visited.exists(level.describe()); } +static void _generic_level_reset() +{ + // TODO: can more be pulled into here? + + you.prev_targ = MHITNOT; + you.prev_grd_targ.reset(); + + // Lose all listeners. + dungeon_events.clear(); + clear_travel_trail(); +} + + +// used to resolve generation order for cases where a single level has multiple +// portals. +static const vector portal_generation_order = +{ + BRANCH_SEWER, + BRANCH_OSSUARY, + BRANCH_ICE_CAVE, + BRANCH_VOLCANO, + BRANCH_BAILEY, + BRANCH_GAUNTLET, +#if TAG_MAJOR_VERSION == 34 + BRANCH_LABYRINTH, +#endif + // do not pregenerate bazaar (TODO: this is non-ideal) + // do not pregenerate trove + BRANCH_WIZLAB, + BRANCH_DESOLATION, +}; + +void update_portal_entrances() +{ + unordered_set> seen_portals; + auto const cur_level = level_id::current(); + // add any portals not currently registered + for (rectangle_iterator ri(0); ri; ++ri) + { + dungeon_feature_type feat = env.grid(*ri); + // excludes pan, hell, abyss. + if (feat_is_portal_entrance(feat) && !feature_mimic_at(*ri)) + { + level_id whither = stair_destination(feat, "", false); + if (whither.branch == BRANCH_ZIGGURAT // not (quite) pregenerated + || whither.branch == BRANCH_TROVE // not pregenerated + || whither.branch == BRANCH_BAZAAR) // multiple bazaars possible + { + continue; // handle these differently + } + dprf("Setting up entry for %s.", whither.describe().c_str()); + ASSERT(count(portal_generation_order.begin(), + portal_generation_order.end(), + whither.branch) == 1); + if (brentry[whither.branch] != level_id()) + { + mprf(MSGCH_ERROR, "Second portal entrance for %s!", + whither.describe().c_str()); + } + brentry[whither.branch] = cur_level; + seen_portals.insert(whither.branch); + } + } + // clean up any portals that aren't actually here -- comes up for wizmode + // and test mode cases. + for (auto b : portal_generation_order) + if (!seen_portals.count(b) && brentry[b] == cur_level) + brentry[b] = level_id(); +} + +void reset_portal_entrances() +{ + for (auto b : portal_generation_order) + if (brentry[b].is_valid()) + brentry[b] = level_id(); +} + +static bool _generate_portal_levels() +{ + // find any portals that branch off of the current level. + level_id here = level_id::current(); + vector to_build; + for (auto b : portal_generation_order) + if (brentry[b] == here) + for (int i = 1; i <= branches[b].numlevels; i++) + to_build.push_back(level_id(b, i)); + + bool generated = false; + for (auto lid : to_build) + generated = generate_level(lid) || generated; + return generated; +} + +/** + * Ensure that the level given by `l` is generated. This does not do much in + * the way of cleanup, and the caller must ensure the player ends up somewhere + * sensible afterwards (this will not place the player, and will wipe out their + * current location state if a level is built). Does not do anything if the + * save already contains the relevant level. + * + * @param l the level to try to build. + * @return whether a level was built. + */ +bool generate_level(const level_id &l) +{ + const string level_name = l.describe(); + if (you.save->has_chunk(level_name)) + return false; + + unwind_var depth(you.depth, l.depth); + unwind_var branch(you.where_are_you, l.branch); + unwind_var saved_position(you.position); + you.position.reset(); + + // simulate a reasonable stair to enter the level with + const dungeon_feature_type stair_taken = + you.depth == 1 + ? (you.where_are_you == BRANCH_DUNGEON + ? DNGN_UNSEEN + : branches[you.where_are_you].entry_stairs) + : DNGN_STONE_STAIRS_DOWN_I; + + unwind_var stair(you.transit_stair, stair_taken); + // TODO how necessary is this? + unwind_bool ylev(you.entering_level, true); + // n.b. crawl_state.generating_level is handled in builder + + _generic_level_reset(); + delete_all_clouds(); + los_changed(); // invalidate the los cache, which impacts monster placement + + // initialize env for builder + env.turns_on_level = -1; + tile_init_default_flavour(); + tile_clear_flavour(); + env.tile_names.clear(); + _clear_env_map(); + + // finally -- everything is set up, call the builder. + dprf("Generating new level for '%s'.", level_name.c_str()); + builder(true); + + auto &vault_list = you.vault_list[level_id::current()]; +#ifdef DEBUG + // places where a level can generate multiple times. + // could add portals to this list for debugging purposes? + if ( you.where_are_you == BRANCH_ABYSS + || you.where_are_you == BRANCH_PANDEMONIUM + || you.where_are_you == BRANCH_BAZAAR + || you.where_are_you == BRANCH_ZIGGURAT) + { + vault_list.push_back("[gen]"); + } +#endif + const auto &level_vaults = level_vault_names(); + vault_list.insert(vault_list.end(), + level_vaults.begin(), level_vaults.end()); + + // initialize env for a new level + env.turns_on_level = 0; + env.sanctuary_pos = coord_def(-1, -1); + env.sanctuary_time = 0; + env.markers.init_all(); // init first, activation happens when entering + show_update_emphasis(); // Clear map knowledge stair emphasis in env. + update_portal_entrances(); + + // save the level and associated env state + _save_level(level_id::current()); + + const string save_name = level_id::current().describe(); // should be same as level_name... + + // generate levels for all portals that branch off from here + if (_generate_portal_levels()) + { + // if portals were generated, we're currently elsewhere. + ASSERT(you.save->has_chunk(save_name)); + dprf("Reloading new level '%s'.", save_name.c_str()); + _restore_tagged_chunk(you.save, save_name, TAG_LEVEL, + "Level file is invalid."); + } + return true; +} + +// bel's original proposal generated D to lair depth, then lair, then D +// to orc depth, then orc, then the rest of D. I have simplified this to +// just generate whole branches at a time -- I am not sure how much real +// impact this has. One idea might be to shuffle this slightly based on +// the seed. +// TODO: probably need to do portal vaults too? +// Should this use something like logical_branch_order? +static const vector branch_generation_order = +{ + BRANCH_DUNGEON, + BRANCH_TEMPLE, + BRANCH_LAIR, + BRANCH_ORC, + BRANCH_SPIDER, + BRANCH_SNAKE, + BRANCH_SHOALS, + BRANCH_SWAMP, + BRANCH_VAULTS, + BRANCH_CRYPT, + BRANCH_DEPTHS, + BRANCH_VESTIBULE, + BRANCH_ELF, + BRANCH_ZOT, + BRANCH_SLIME, + BRANCH_TOMB, + BRANCH_TARTARUS, + BRANCH_COCYTUS, + BRANCH_DIS, + BRANCH_GEHENNA, + BRANCH_PANDEMONIUM, + BRANCH_ZIGGURAT, + NUM_BRANCHES, +}; + +static bool _branch_pregenerates(branch_type b) +{ + if (!you.deterministic_levelgen) + return false; + if (b == NUM_BRANCHES || !brentry[b].is_valid() && is_random_subbranch(b)) + return false; + return count(branch_generation_order.begin(), + branch_generation_order.end(), b) > 0; +} + +/** +* Generate dungeon branches in a stable order until the level `stopping_point` +* is found; `stopping_point` will be generated if it doesn't already exist. If +* it does exist, the function is a noop. +* +* If `stopping_point` is not in the generation order, it will be generated on +* its own. +* +* To generate all generatable levels, pass a level_id with NUM_BRANCHES as the +* branch. +*/ +bool pregen_dungeon(const level_id &stopping_point) +{ + // TODO: the is_valid() check here doesn't look quite right to me, but so + // far I can't get it to break anything... + if (stopping_point.is_valid() + || stopping_point.branch != NUM_BRANCHES && + is_random_subbranch(stopping_point.branch) && you.wizard) + { + if (you.save->has_chunk(stopping_point.describe())) + return false; + + if (!_branch_pregenerates(stopping_point.branch)) + return generate_level(stopping_point); + } + + vector to_generate; + bool at_end = false; + for (auto br : branch_generation_order) + { + if (br == BRANCH_ZIGGURAT && + stopping_point.branch == BRANCH_ZIGGURAT) + { + // zigs delete levels as they go, so don't catchup when we're + // already in one. Zigs are only handled this way so that everything + // else generates first. + to_generate.push_back(stopping_point); + continue; + } + // TODO: why is dungeon invalid? it's not set up properly in + // `initialise_branch_depths` for some reason. The vestibule is invalid + // because its depth isn't set until the player actually enters a + // portal, similarly for other portal branches. + if (br < NUM_BRANCHES && + (brentry[br].is_valid() + || br == BRANCH_DUNGEON || br == BRANCH_VESTIBULE + || !is_connected_branch(br))) + { + for (int i = 1; i <= branches[br].numlevels; i++) + { + level_id new_level = level_id(br, i); + if (you.save->has_chunk(new_level.describe())) + continue; + to_generate.push_back(new_level); + + if (br == stopping_point.branch + && (i == stopping_point.depth + || i == branches[br].numlevels)) + { + at_end = true; + break; + } + } + } + if (at_end) + break; + } + + if (to_generate.size() == 0) + { + dprf("levelgen: No valid levels to generate."); + return false; + } + else if (to_generate.size() == 1) + return generate_level(to_generate[0]); // no popup for this case + else + { + // be sure that AK start doesn't interfere with the builder + unwind_var chapter(you.chapter, CHAPTER_ORB_HUNTING); + + ui::progress_popup progress("Generating dungeon...\n\n", 35); + progress.advance_progress(); + + // in normal usage if we get to here, something will generate. But it + // is possible to call this in a way that doesn't lead to generation. + bool generated = false; + + for (const level_id &new_level : to_generate) + { + string status = "\nbuilding "; + + switch (new_level.branch) + { + case BRANCH_SPIDER: + case BRANCH_SNAKE: + status += "a lair branch"; + break; + case BRANCH_SHOALS: + case BRANCH_SWAMP: + status += "another lair branch"; + break; + default: + status += branches[new_level.branch].longname; + break; + } + progress.set_status_text(status); + dprf("Pregenerating %s:%d", + branches[new_level.branch].abbrevname, new_level.depth); + progress.advance_progress(); + generated = generate_level(new_level) || generated; + } + + return generated; + } +} + /** * Load the current level. * @@ -1445,6 +1668,9 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, const level_id& old_level) { const string level_name = level_id::current().describe(); + if (!you.save->has_chunk(level_name) && load_mode == LOAD_VISITOR) + return false; + const bool make_changes = (load_mode == LOAD_START_GAME || load_mode == LOAD_ENTER_LEVEL); @@ -1463,7 +1689,7 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, if (feat_is_escape_hatch(stair_taken)) hatch_name = _get_hatch_name(); - if (load_mode != LOAD_VISITOR && load_mode != LOAD_GENERATE) + if (load_mode != LOAD_VISITOR) popped = _leave_level(stair_taken, old_level, &return_pos); unwind_var stair( @@ -1478,17 +1704,13 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, // Save position for hatches to place a marker on the destination level. coord_def dest_pos = you.pos(); - you.prev_targ = MHITNOT; - you.prev_grd_targ.reset(); + _generic_level_reset(); // We clear twice - on save and on load. // Once would be enough... if (make_changes) delete_all_clouds(); - // Lose all listeners. - dungeon_events.clear(); - // This block is to grab followers and save the old level to disk. if (load_mode == LOAD_ENTER_LEVEL) { @@ -1505,19 +1727,14 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, _save_level(old_level); } - // TODO: for staged pregeneration this also needs to happen on - // LOAD_GENERATE. However, it's a bit tricky, because player position - // does need to be saved for the later LOAD_ENTER_LEVEL call. postpone. // The player is now between levels. you.position.reset(); update_companions(); } - clear_travel_trail(); - #ifdef USE_TILE - if (load_mode != LOAD_VISITOR && load_mode != LOAD_GENERATE) + if (load_mode != LOAD_VISITOR) { tiles.clear_minimap(); crawl_view_buffer empty_vbuf; @@ -1525,10 +1742,7 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, } #endif - bool just_created_level = false; - - if (load_mode != LOAD_GENERATE - && load_mode != LOAD_VISITOR + if (load_mode != LOAD_VISITOR && you.chapter == CHAPTER_POCKET_ABYSS && player_in_branch(BRANCH_DUNGEON)) { @@ -1538,26 +1752,16 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, you.chapter = CHAPTER_ORB_HUNTING; } - // GENERATE new level when the file can't be opened: - if (!you.save->has_chunk(level_name)) - { - ASSERT(load_mode != LOAD_VISITOR); - dprf("Generating new level for '%s'.", level_name.c_str()); - _make_level(stair_taken, old_level); - you.vault_list[level_id::current()] = level_vault_names(); - just_created_level = true; - } - else + // GENERATE new level(s) when the file can't be opened: + if (!pregen_dungeon(level_id::current())) { + ASSERT(you.save->has_chunk(level_name)); dprf("Loading old level '%s'.", level_name.c_str()); _restore_tagged_chunk(you.save, level_name, TAG_LEVEL, "Level file is invalid."); - - if (load_mode != LOAD_GENERATE) - _redraw_all(); // TODO why is there a redraw call here? + _redraw_all(); // TODO why is there a redraw call here? } - if (just_created_level) - env.markers.init_all(); // init first, activation happens when entering + const bool just_created_level = !you.level_visited(level_id::current()); // Clear map knowledge stair emphasis. show_update_emphasis(); @@ -1567,16 +1771,6 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, los_changed(); - if (load_mode == LOAD_GENERATE) - { - if (just_created_level) - _save_level(level_id::current()); - return just_created_level; - } - - if (!you.level_visited(level_id::current())) - just_created_level = true; // in case level was pre-generated - if (load_mode != LOAD_VISITOR) you.set_level_visited(level_id::current()); @@ -1728,7 +1922,7 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, if (just_created_level) { you.attribute[ATTR_ABYSS_ENTOURAGE] = 0; - _count_gold(); + gozag_detect_level_gold(true); } @@ -1777,8 +1971,11 @@ bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, // Maybe make a note if we reached a new level. // Don't do so if we are just moving around inside Pan, though. - if (just_created_level && stair_taken != DNGN_TRANSIT_PANDEMONIUM) + if (just_created_level && make_changes + && stair_taken != DNGN_TRANSIT_PANDEMONIUM) + { take_note(Note(NOTE_DUNGEON_LEVEL_CHANGE)); + } // If the player entered the level from a different location than they last // exited it, have monsters lose track of where they are @@ -2313,7 +2510,7 @@ static vector _load_permastore_ghosts(bool backup_on_upgrade=false) */ bool define_ghost_from_bones(monster& mons) { - rng_generator rng(RNG_SYSTEM_SPECIFIC); + rng::generator rng(rng::SYSTEM_SPECIFIC); bool used_permastore = false; @@ -2628,6 +2825,10 @@ void delete_level(const level_id &level) if (you.save) you.save->delete_chunk(level.describe()); + + auto &visited = you.props[VISITED_LEVELS_KEY].get_table(); + visited.erase(level.describe()); + if (level.branch == BRANCH_ABYSS) { save_abyss_uniques(); @@ -2924,7 +3125,7 @@ static size_t _ghost_permastore_size() static vector _update_permastore(const vector &ghosts) { - rng_generator rng(RNG_SYSTEM_SPECIFIC); + rng::generator rng(rng::SYSTEM_SPECIFIC); if (ghosts.empty()) return ghosts; diff --git a/crawl-ref/source/files.h b/crawl-ref/source/files.h index fbe2db6a150e..44909dc1a02d 100644 --- a/crawl-ref/source/files.h +++ b/crawl-ref/source/files.h @@ -19,7 +19,6 @@ enum load_mode_type LOAD_RESTART_GAME, // loaded savefile LOAD_ENTER_LEVEL, // entered a level normally LOAD_VISITOR, // Visitor pattern to see all levels - LOAD_GENERATE, // Generating the level only }; /// Exception indicating that a dangerous path was supplied. @@ -82,6 +81,10 @@ class level_id; void trackers_init_new_level(bool transit); +void update_portal_entrances(); +void reset_portal_entrances(); +bool generate_level(const level_id &l); +bool pregen_dungeon(const level_id &stopping_point); bool load_level(dungeon_feature_type stair_taken, load_mode_type load_mode, const level_id& old_level); void delete_level(const level_id &level); diff --git a/crawl-ref/source/fineff.cc b/crawl-ref/source/fineff.cc index da87cf80a48b..64700be62546 100644 --- a/crawl-ref/source/fineff.cc +++ b/crawl-ref/source/fineff.cc @@ -472,7 +472,7 @@ void shock_serpent_discharge_fineff::fire() int amount = roll_dice(3, 4 + power * 3 / 2); amount = oppressor.apply_ac(oppressor.beam_resists(beam, amount, true), - 0, AC_HALF); + 0, ac_type::half); oppressor.hurt(serpent, amount, beam.flavour, KILLED_BY_BEAM, "a shock serpent", "electric aura"); if (amount) @@ -581,6 +581,17 @@ void make_derived_undead_fineff::fire() } } +const actor *mummy_death_curse_fineff::fixup_attacker(const actor *a) +{ + if (a && a->is_monster() && a->as_monster()->friendly() + && !crawl_state.game_is_arena()) + { + // Mummies are smart enough not to waste curses on summons or allies. + return &you; + } + return a; +} + void mummy_death_curse_fineff::fire() { if (pow <= 0) @@ -615,13 +626,6 @@ void mummy_death_curse_fineff::fire() if (!victim->alive()) return; - // Mummies are smart enough not to waste curses on summons or allies. - if (victim->is_monster() && victim->as_monster()->friendly() - && !crawl_state.game_is_arena()) - { - victim = &you; - } - // Stepped from time? if (!in_bounds(victim->pos())) return; diff --git a/crawl-ref/source/fineff.h b/crawl-ref/source/fineff.h index e369124b5cac..6dd9a935247f 100644 --- a/crawl-ref/source/fineff.h +++ b/crawl-ref/source/fineff.h @@ -374,9 +374,12 @@ class mummy_death_curse_fineff : public final_effect } protected: mummy_death_curse_fineff(const actor * attack, string _name, killer_type _killer, int _pow) - : final_effect(attack, 0, coord_def()), name(_name), killer(_killer), pow(_pow) + : final_effect(fixup_attacker(attack), 0, coord_def()), name(_name), + killer(_killer), pow(_pow) { } + const actor *fixup_attacker(const actor *a); + string name; killer_type killer; int pow; diff --git a/crawl-ref/source/fontwrapper-ft.cc b/crawl-ref/source/fontwrapper-ft.cc index bd3b0d7b0bdb..5539b1da872e 100644 --- a/crawl-ref/source/fontwrapper-ft.cc +++ b/crawl-ref/source/fontwrapper-ft.cc @@ -461,18 +461,11 @@ void FTFontWrapper::draw_m_buf(unsigned int x_pos, unsigned int y_pos, glmanager->reset_transform(); } -static void _draw_box(int x_pos, int y_pos, float width, float height, - float box_width, unsigned char box_colour, - unsigned char box_alpha) +static void _draw_box(int x_pos, int y_pos, int width, int height, VColour colour) { unique_ptr buf(GLShapeBuffer::create(false, true)); - GLWPrim rect(x_pos - box_width, y_pos - box_width, - x_pos + width + box_width, y_pos + height + box_width); + GLWPrim rect(x_pos, y_pos, x_pos + width, y_pos + height); - VColour colour(term_colours[box_colour].r, - term_colours[box_colour].g, - term_colours[box_colour].b, - box_alpha); rect.set_col(colour); buf->add(rect); @@ -644,94 +637,32 @@ formatted_string FTFontWrapper::split(const formatted_string &str, return ret; } -void FTFontWrapper::render_string(unsigned int px, unsigned int py, - const char *text, +/** + * Render a tooltip around the given position. + * + * @param px the x coordinate + * @param py the y coordinate + * @param text the string to render + * @param min_pos the top-left boundary of the screen + * @param max_pos the bottom-right boundary of the screen + */ +void FTFontWrapper::render_tooltip(unsigned int px, unsigned int py, + const formatted_string &text, const coord_def &min_pos, - const coord_def &max_pos, - unsigned char font_colour, bool drop_shadow, - unsigned char box_alpha, - unsigned char box_colour, - unsigned int outline, - bool tooltip) -{ - ASSERT(text); - - // Determine extent of this text - unsigned int max_rows = 1; - unsigned int cols = 0; - unsigned int max_cols = 0; - char32_t c; - for (const char *tp = text; int s = utf8towc(&c, tp); tp += s) - { - int w = wcwidth(c); - if (w != -1) - cols += w; - max_cols = max(cols, max_cols); - - // NOTE: only newlines should be used for tool tips. Don't use EOL. - ASSERT(c != '\r'); - - if (c == '\n') - { - cols = 0; - max_rows++; - } - } - - // Create the text block - char32_t *chars = (char32_t*)malloc(max_rows * max_cols * sizeof(char32_t)); - uint8_t *colours = (uint8_t*)malloc(max_rows * max_cols); - for (unsigned int i = 0; i < max_rows * max_cols; i++) - chars[i] = ' '; - memset(colours, font_colour, max_rows * max_cols); - - // Fill the text block - cols = 0; - unsigned int rows = 0; - for (const char *tp = text; int s = utf8towc(&c, tp); tp += s) - { - int w = wcwidth(c); - if (w > 0) // FIXME: combining characters are silently ignored - { - chars[cols + rows * max_cols] = c; - cols++; - if (w == 2) - chars[cols + rows * max_cols] = ' ', cols++; - } - - if (c == '\n') - { - cols = 0; - rows++; - } - } - - // Find a suitable location on screen - const int buffer = 5; // additional buffer size from edges + const coord_def &max_pos) +{ + int outline = 7; + const int wx = string_width(text); + const int wy = string_height(text); - int wx = string_width(text); - int wy = max_rows * char_height(); + // text starting location + int tx = px - 15, ty = py + 20; - int sx, sy; // box starting location, uses extra buffer - int tx, ty; // text starting location - - if (tooltip) - { - sy = py + outline; - ty = sy + buffer; - tx = px - 20; - sx = tx - buffer; - } - else - { - ty = py - wy - outline; - sy = ty - buffer; - tx = px - wx / 2; - sx = tx - buffer; - } - // box ending position - int ex = tx + wx + buffer; - int ey = ty + wy + buffer; + // box position, before shifting + const int sx = tx - outline; + const int sy = ty - outline; + const int ex = tx + wx + outline; + const int ey = ty + wy + outline; if (ex > max_pos.x) tx += max_pos.x - ex; @@ -743,13 +674,49 @@ void FTFontWrapper::render_string(unsigned int px, unsigned int py, else if (sy < min_pos.y) ty -= sy - min_pos.y; - if (box_alpha != 0) - _draw_box(tx, ty, wx, wy, outline, box_colour, box_alpha); + const VColour border_colour(125, 98, 60); + const VColour bg_colour(4, 2, 4); + _draw_box(tx-outline, ty-outline, wx+2*outline, wy+2*outline, border_colour); + outline -= 2; + _draw_box(tx-outline, ty-outline, wx+2*outline, wy+2*outline, bg_colour); + + render_string(tx, ty, text); +} - render_textblock(tx, ty, chars, colours, max_cols, max_rows, drop_shadow); +/** + * Render a string at the given position. + * + * @param px the x coordinate + * @param py the y coordinate + * @param text the string to render + * @param font_colour the text colour to use + */ +void FTFontWrapper::render_string(unsigned int px, unsigned int py, + const formatted_string &text) +{ + glmanager->reset_transform(); + FontBuffer m_font_buf(this); + m_font_buf.add(text, px, py); + m_font_buf.draw(); +} + +/** + * Render a string hovering above the given position, centred horizontally. + * + * @param px the x coordinate + * @param py the y coordinate + * @param text the string to render + * @param font_colour the text colour to use + */ +void FTFontWrapper::render_hover_string(unsigned int px, unsigned int py, + const formatted_string &text) +{ + const int wx = string_width(text); + const int wy = string_height(text); + const int ty = py - wy; + const int tx = px - wx / 2; - free(chars); - free(colours); + render_string(tx, ty, text); } /** @@ -828,7 +795,7 @@ void FTFontWrapper::store(FontBuffer &buf, float &x, float &y, { case FSOP_COLOUR: // Only foreground colors for now... - colour = op.x & 0xF; + colour = op.colour & 0xF; break; case FSOP_TEXT: store(buf, x, y, op.text, term_colours[colour], orig_x); diff --git a/crawl-ref/source/fontwrapper-ft.h b/crawl-ref/source/fontwrapper-ft.h index 5c96bd0f5bd1..0360164f4d75 100644 --- a/crawl-ref/source/fontwrapper-ft.h +++ b/crawl-ref/source/fontwrapper-ft.h @@ -37,15 +37,16 @@ class FTFontWrapper : public FontWrapper bool drop_shadow = false) override; // render text + background box + virtual void render_tooltip(unsigned int x, unsigned int y, + const formatted_string &text, + const coord_def &min_pos, + const coord_def &max_pos) override; + virtual void render_string(unsigned int x, unsigned int y, - const char *text, const coord_def &min_pos, - const coord_def &max_pos, - unsigned char font_colour, - bool drop_shadow = false, - unsigned char box_alpha = 0, - unsigned char box_colour = 0, - unsigned int outline = 0, - bool tooltip = false) override; + const formatted_string &text) override; + + virtual void render_hover_string(unsigned int x, unsigned int y, + const formatted_string &text) override; // FontBuffer helper functions virtual void store(FontBuffer &buf, float &x, float &y, diff --git a/crawl-ref/source/food.cc b/crawl-ref/source/food.cc index af2dd16bdbff..5f95fec77390 100644 --- a/crawl-ref/source/food.cc +++ b/crawl-ref/source/food.cc @@ -44,7 +44,6 @@ #include "xom.h" static void _describe_food_change(int hunger_increment); -static bool _vampire_consume_corpse(item_def& corpse); static void _heal_from_food(int hp_amt); void make_hungry(int hunger_amount, bool suppress_msg, @@ -120,21 +119,16 @@ void set_hunger(int new_hunger_level, bool suppress_msg) bool you_foodless(bool temp) { - return you.undead_state(temp) == US_UNDEAD; + return you.undead_state(temp) == US_UNDEAD + || you.undead_state(temp) == US_SEMI_UNDEAD; } bool prompt_eat_item(int slot) { - // There's nothing in inventory that a vampire can 'e', and floor corpses - // are handled by prompt_eat_chunks. - if (you.species == SP_VAMPIRE) - return false; - item_def* item = nullptr; if (slot == -1) { - item = use_an_item(OBJ_FOOD, OPER_EAT, "Eat which item?"); - if (!item) + if (!use_an_item(item, OBJ_FOOD, OPER_EAT, "Eat which item (* to show all)?")) return false; } else @@ -169,8 +163,7 @@ static bool _eat_check(bool check_hunger = true, bool silent = false, { if (!silent) { - mprf("You're too full to %s anything.", - you.species == SP_VAMPIRE ? "drain" : "eat"); + mprf("You're too full to eat anything."); crawl_state.zero_turns_taken(); } return false; @@ -194,9 +187,6 @@ bool eat_food(int slot) return false; } - if (you.species == SP_VAMPIRE) - mpr("There's nothing here to drain!"); - return prompt_eat_item(slot); } @@ -204,11 +194,18 @@ static string _how_hungry() { if (you.hunger_state > HS_SATIATED) return "full"; - else if (you.species == SP_VAMPIRE) - return "thirsty"; return "hungry"; } +hunger_state_t calc_hunger_state() +{ + // Get new hunger state. + hunger_state_t newstate = HS_FAINTING; + while (newstate < HS_ENGORGED && you.hunger > hunger_threshold[newstate]) + newstate = (hunger_state_t)(newstate + 1); + return newstate; +} + // "initial" is true when setting the player's initial hunger state on game // start or load: in that case it's not really a change, so we suppress the // state change message and don't identify rings or stimulate Xom. @@ -220,10 +217,7 @@ bool food_change(bool initial) you.hunger = max(you_min_hunger(), you.hunger); you.hunger = min(you_max_hunger(), you.hunger); - // Get new hunger state. - hunger_state_t newstate = HS_FAINTING; - while (newstate < HS_ENGORGED && you.hunger > hunger_threshold[newstate]) - newstate = (hunger_state_t)(newstate + 1); + hunger_state_t newstate = calc_hunger_state(); if (newstate != you.hunger_state) { @@ -235,29 +229,7 @@ bool food_change(bool initial) you.redraw_status_lights = true; if (newstate < HS_SATIATED) - interrupt_activity(AI_HUNGRY); - - if (you.species == SP_VAMPIRE) - { - const undead_form_reason form_reason = lifeless_prevents_form(); - if (form_reason == UFR_GOOD) - { - if (newstate == HS_ENGORGED && is_vampire_feeding()) // Alive - { - print_stats(); - mpr("You can't stomach any more blood right now."); - } - } - else if (you.duration[DUR_TRANSFORMATION]) - { - print_stats(); - mprf(MSGCH_WARN, - "Your blood-%s body can't sustain your transformation.", - form_reason == UFR_TOO_DEAD ? "deprived" : "filled"); - you.duration[DUR_TRANSFORMATION] = 1; // end at end of turn - // could maybe end immediately, but that makes me nervous - } - } + interrupt_activity(activity_interrupt::hungry); if (!initial) { @@ -270,10 +242,7 @@ bool food_change(bool initial) break; case HS_STARVING: - if (you.species == SP_VAMPIRE) - msg += "feel devoid of blood!"; - else - msg += "are starving!"; + msg += "are starving!"; mprf(MSGCH_FOOD, less_hungry, "%s", msg.c_str()); @@ -282,10 +251,7 @@ bool food_change(bool initial) break; case HS_NEAR_STARVING: - if (you.species == SP_VAMPIRE) - msg += "feel almost devoid of blood!"; - else - msg += "are near starving!"; + msg += "are near starving!"; mprf(MSGCH_FOOD, less_hungry, "%s", msg.c_str()); @@ -411,7 +377,7 @@ int prompt_eat_chunks(bool only_auto) // If we *know* the player can eat chunks, doesn't have the gourmand // effect and isn't hungry, don't prompt for chunks. - if (you.species != SP_VAMPIRE && you.hunger_state > _max_chunk_state()) + if (you.hunger_state > _max_chunk_state()) return 0; bool found_valid = false; @@ -419,15 +385,7 @@ int prompt_eat_chunks(bool only_auto) for (stack_iterator si(you.pos(), true); si; ++si) { - if (you.species == SP_VAMPIRE) - { - if (si->base_type != OBJ_CORPSES || si->sub_type != CORPSE_BODY) - continue; - - if (!mons_has_blood(si->mon_type)) - continue; - } - else if (si->base_type != OBJ_FOOD + if (si->base_type != OBJ_FOOD || si->sub_type != FOOD_CHUNK || is_bad_food(*si)) { @@ -444,10 +402,6 @@ int prompt_eat_chunks(bool only_auto) if (!item.defined()) continue; - // Vampires can't eat anything in their inventory. - if (you.species == SP_VAMPIRE) - continue; - if (item.base_type != OBJ_FOOD || item.sub_type != FOOD_CHUNK) continue; @@ -484,8 +438,7 @@ int prompt_eat_chunks(bool only_auto) return 0; else { - mprf(MSGCH_PROMPT, "%s %s%s? (ye/n/q)", - (you.species == SP_VAMPIRE ? "Drink blood from" : "Eat"), + mprf(MSGCH_PROMPT, "Eat %s%s? (ye/n/q)", ((item->quantity > 1) ? "one of " : ""), item_name.c_str()); } @@ -507,9 +460,7 @@ int prompt_eat_chunks(bool only_auto) { if (autoeat) { - mprf("%s %s%s.", - (you.species == SP_VAMPIRE ? "Drinking blood from" - : "Eating"), + mprf("Eating %s%s.", ((item->quantity > 1) ? "one of " : ""), item_name.c_str()); } @@ -666,19 +617,7 @@ static void _eat_chunk(item_def& food) bool eat_item(item_def &food) { if (food.is_type(OBJ_CORPSES, CORPSE_BODY)) - { - if (you.species != SP_VAMPIRE) - return false; - - if (_vampire_consume_corpse(food)) - { - count_action(CACT_EAT, -1); // subtype Corpse - you.turn_is_over = true; - return true; - } - return false; - } mprf("You eat %s%s.", food.quantity > 1 ? "one of " : "", food.name(DESC_THE).c_str()); @@ -739,19 +678,11 @@ bool is_inedible(const item_def &item, bool temp) if (item.sub_type == CORPSE_SKELETON) return true; - if (you.species == SP_VAMPIRE) - { - if (!mons_has_blood(item.mon_type)) - return true; - } - else - { - item_def chunk = item; - chunk.base_type = OBJ_FOOD; - chunk.sub_type = FOOD_CHUNK; - if (is_inedible(chunk, temp)) - return true; - } + item_def chunk = item; + chunk.base_type = OBJ_FOOD; + chunk.sub_type = FOOD_CHUNK; + if (is_inedible(chunk, temp)) + return true; } return false; @@ -762,15 +693,10 @@ bool is_inedible(const item_def &item, bool temp) // still be edible or even delicious. bool is_preferred_food(const item_def &food) { - // Mummies and liches don't eat. + // Mummies, vampirees, and liches don't eat. if (you_foodless()) return false; - // Vampires don't really have a preferred food type, but they really - // like blood potions. - if (you.species == SP_VAMPIRE) - return is_blood_potion(food); - if (you.species == SP_GHOUL) return food.is_type(OBJ_FOOD, FOOD_CHUNK); @@ -830,14 +756,7 @@ bool can_eat(const item_def &food, bool suppress_msg, bool check_hunger, if (is_noxious(food)) FAIL("It is completely inedible."); - if (you.species == SP_VAMPIRE) - { - if (food.is_type(OBJ_CORPSES, CORPSE_BODY)) - return true; - - FAIL("Blech - you need blood!") - } - else if (food.base_type == OBJ_CORPSES) + if (food.base_type == OBJ_CORPSES) return false; if (food_is_meaty(food)) @@ -889,7 +808,7 @@ corpse_effect_type determine_chunk_effect(corpse_effect_type chunktype) switch (chunktype) { case CE_NOXIOUS: - if (you.species == SP_GHOUL || you.species == SP_VAMPIRE) + if (you.species == SP_GHOUL) chunktype = CE_CLEAN; break; @@ -900,44 +819,6 @@ corpse_effect_type determine_chunk_effect(corpse_effect_type chunktype) return chunktype; } -static bool _vampire_consume_corpse(item_def& corpse) -{ - ASSERT(you.species == SP_VAMPIRE); - ASSERT(corpse.base_type == OBJ_CORPSES); - ASSERT(corpse.sub_type == CORPSE_BODY); - - const monster_type mons_type = corpse.mon_type; - - if (!mons_has_blood(mons_type)) - { - mpr("There is no blood in this body!"); - return false; - } - - mprf("This %sblood tastes delicious!", - mons_class_flag(mons_type, M_WARM_BLOOD) ? "warm " : ""); - - const int food_value = CHUNK_BASE_NUTRITION - * num_blood_potions_from_corpse(mons_type); - lessen_hunger(food_value, false); - - // this will never matter :) - if (mons_genus(mons_type) == MONS_ORC) - did_god_conduct(DID_DESECRATE_ORCISH_REMAINS, 2); - if (mons_class_holiness(mons_type) & MH_HOLY) - did_god_conduct(DID_DESECRATE_HOLY_REMAINS, 2); - - if (mons_skeleton(mons_type) && one_chance_in(3)) - { - turn_corpse_into_skeleton(corpse); - item_check(); - } - else - dec_mitm_item_quantity(corpse.index(), 1); - - return true; -} - static void _heal_from_food(int hp_amt) { if (hp_amt > 0) diff --git a/crawl-ref/source/food.h b/crawl-ref/source/food.h index f918ce588822..cc81aaa43026 100644 --- a/crawl-ref/source/food.h +++ b/crawl-ref/source/food.h @@ -53,6 +53,7 @@ bool eat_item(item_def &food); int prompt_eat_chunks(bool only_auto = false); +hunger_state_t calc_hunger_state(); bool food_change(bool initial = false); bool prompt_eat_item(int slot = -1); diff --git a/crawl-ref/source/format.cc b/crawl-ref/source/format.cc index 375d54f5399b..86f500c05dfe 100644 --- a/crawl-ref/source/format.cc +++ b/crawl-ref/source/format.cc @@ -236,7 +236,7 @@ formatted_string::operator string() const { string s; for (const fs_op &op : ops) - if (op == FSOP_TEXT) + if (op.type == FSOP_TEXT) s += op.text; return s; @@ -273,7 +273,7 @@ string formatted_string::html_dump() const break; case FSOP_COLOUR: s += ""; break; } @@ -286,6 +286,12 @@ bool formatted_string::operator < (const formatted_string &other) const return string(*this) < string(other); } +bool formatted_string::operator == (const formatted_string &other) const +{ + // May produce false negative in some cases, e.g. duplicated colour ops + return ops == other.ops; +} + const formatted_string & formatted_string::operator += (const formatted_string &other) { @@ -298,7 +304,7 @@ int formatted_string::width() const // Just add up the individual string lengths. int len = 0; for (const fs_op &op : ops) - if (op == FSOP_TEXT) + if (op.type == FSOP_TEXT) len += strwidth(op.text); return len; } @@ -319,7 +325,7 @@ char &formatted_string::operator [] (size_t idx) int size = ops.size(); for (int i = 0; i < size; ++i) { - if (ops[i] != FSOP_TEXT) + if (ops[i].type != FSOP_TEXT) continue; size_t len = ops[i].text.length(); @@ -341,7 +347,7 @@ string formatted_string::tostring(int s, int e) const for (int i = s; i <= e && i < size; ++i) { - if (ops[i] == FSOP_TEXT) + if (ops[i].type == FSOP_TEXT) st += ops[i].text; } return st; @@ -353,7 +359,7 @@ string formatted_string::to_colour_string() const const int size = ops.size(); for (int i = 0; i < size; ++i) { - if (ops[i] == FSOP_TEXT) + if (ops[i].type == FSOP_TEXT) { // gotta double up those '<' chars ... size_t start = st.size(); @@ -369,10 +375,10 @@ string formatted_string::to_colour_string() const start = left_angle + 2; } } - else if (ops[i] == FSOP_COLOUR) + else if (ops[i].type == FSOP_COLOUR) { st += "<"; - st += colour_to_str(ops[i].x); + st += colour_to_str(ops[i].colour); st += ">"; } } @@ -399,7 +405,7 @@ int formatted_string::find_last_colour() const { for (int i = ops.size() - 1; i >= 0; --i) if (ops[i].type == FSOP_COLOUR) - return ops[i].x; + return ops[i].colour; } return LIGHTGREY; } @@ -497,7 +503,7 @@ void formatted_string::add_glyph(cglyph_t g) void formatted_string::textcolour(int colour) { - if (!ops.empty() && ops[ ops.size() - 1 ].type == FSOP_COLOUR) + if (!ops.empty() && ops.back().type == FSOP_COLOUR) ops.pop_back(); ops.emplace_back(colour); @@ -532,9 +538,9 @@ void formatted_string::fs_op::display() const { case FSOP_COLOUR: #ifndef USE_TILE_LOCAL - if (x < NUM_TERM_COLOURS) + if (colour < NUM_TERM_COLOURS) #endif - ::textcolour(x); + ::textcolour(colour); break; case FSOP_TEXT: ::cprintf("%s", text.c_str()); @@ -589,54 +595,3 @@ int count_linebreaks(const formatted_string& fs) } return count; } - -/** - * How long are the tags in the string? - * - * This function assumes that every character making up the tag is one char - * long. This shouldn't break as long as nobody uses non-ASCII characters - * in the tag. It also can't handle nested tags, but that shouldn't be an issue. - * - * @param s the string with the tags. - * @return the length of the tags, including the angle brackets. - */ -int tagged_string_tag_length(const string& s) -{ - int len = 0; - bool in_tag = false; - char prev_char = '\0'; - for (char ch : s) - { - if (in_tag) - { - if (ch == '<' && prev_char == '<') // "<<" sequence - { - in_tag = false; - len--; // Correct for len being incremented last time - } - else if (ch == '>') - in_tag = false; - } - else if (ch == '<') - in_tag = true; - - if (in_tag) - len++; - prev_char = ch; - } - - ASSERT(len >= 0); - return len; -} - -/** - * How long will this string be when printed? - * - * @param string str - * @return how much the user will see -- counting combining characters together, - * not including tags, etc. - */ -int printed_width(const string& str) -{ - return strwidth(str) - tagged_string_tag_length(str); -} diff --git a/crawl-ref/source/format.h b/crawl-ref/source/format.h index 3ac43d7023a8..a3d49be44d1a 100644 --- a/crawl-ref/source/format.h +++ b/crawl-ref/source/format.h @@ -44,6 +44,7 @@ class formatted_string string html_dump() const; bool operator < (const formatted_string &other) const; + bool operator == (const formatted_string &other) const; const formatted_string &operator += (const formatted_string &other); char &operator [] (size_t idx); @@ -68,23 +69,20 @@ class formatted_string struct fs_op { fs_op_type type; - int x, y; - bool relative; + int colour; string text; - fs_op(int colour) - : type(FSOP_COLOUR), x(colour), y(-1), relative(false), text() + fs_op(int _colour) : type(FSOP_COLOUR), colour(_colour), text() { } - fs_op(const string &s) - : type(FSOP_TEXT), x(-1), y(-1), relative(false), text(s) + fs_op(const string &s) : type(FSOP_TEXT), colour(-1), text(s) { } - operator fs_op_type () const + bool operator == (const fs_op &other) const { - return type; + return type == other.type && colour == other.colour && text == other.text; } void display() const; }; @@ -95,7 +93,4 @@ class formatted_string int count_linebreaks(const formatted_string& fs); -int tagged_string_tag_length(const string& s); -int printed_width(const string& s); - void display_tagged_block(const string& s); diff --git a/crawl-ref/source/fprop.cc b/crawl-ref/source/fprop.cc index 713b6820912e..e11a3788b239 100644 --- a/crawl-ref/source/fprop.cc +++ b/crawl-ref/source/fprop.cc @@ -32,31 +32,12 @@ bool is_tide_immune(const coord_def &p) return bool(env.pgrid(p) & FPROP_NO_TIDE); } -bool is_moldy(const coord_def & p) -{ - return env.pgrid(p) & FPROP_MOLD - || env.pgrid(p) & FPROP_GLOW_MOLD; -} - -bool glowing_mold(const coord_def & p) -{ - return bool(env.pgrid(p) & FPROP_GLOW_MOLD); -} - -void remove_mold(const coord_def & p) -{ - env.pgrid(p) &= ~FPROP_MOLD; - env.pgrid(p) &= ~FPROP_GLOW_MOLD; -} - feature_property_type str_to_fprop(const string &str) { if (str == "bloody") return FPROP_BLOODY; if (str == "highlight") return FPROP_HIGHLIGHT; - if (str == "mold") - return FPROP_MOLD; if (str == "no_cloud_gen") return FPROP_NO_CLOUD_GEN; if (str == "no_tele_into") diff --git a/crawl-ref/source/fprop.h b/crawl-ref/source/fprop.h index 0c0a50b846b4..156517353272 100644 --- a/crawl-ref/source/fprop.h +++ b/crawl-ref/source/fprop.h @@ -20,8 +20,10 @@ enum feature_property_type // Squares that the tide should not affect. FPROP_NO_TIDE = (1 << 10), FPROP_NO_SUBMERGE = (1 << 11), +#if TAG_MAJOR_VERSION == 34 FPROP_MOLD = (1 << 12), FPROP_GLOW_MOLD = (1 << 13), +#endif // Immune to spawning jellies and off-level eating. FPROP_NO_JIYVA = (1 << 14), // Permanent, unlike map memory. @@ -36,8 +38,5 @@ DEF_BITFIELD(terrain_property_t, feature_property_type); bool is_sanctuary(const coord_def& p); bool is_bloodcovered(const coord_def& p); bool is_tide_immune(const coord_def &p); -bool is_moldy(const coord_def & p); -bool glowing_mold(const coord_def & p); -void remove_mold(const coord_def & p); feature_property_type str_to_fprop(const string &str); char blood_rotation(const coord_def & p); diff --git a/crawl-ref/source/gender-type.h b/crawl-ref/source/gender-type.h index 1395b2af1145..d132db98f84c 100644 --- a/crawl-ref/source/gender-type.h +++ b/crawl-ref/source/gender-type.h @@ -6,5 +6,6 @@ enum gender_type GENDER_MALE, GENDER_FEMALE, GENDER_YOU, // A person, not a gender, but close enough. + GENDER_NEUTRAL, NUM_GENDERS }; diff --git a/crawl-ref/source/ghost.cc b/crawl-ref/source/ghost.cc index ee748c888cf8..503417631d78 100644 --- a/crawl-ref/source/ghost.cc +++ b/crawl-ref/source/ghost.cc @@ -278,7 +278,7 @@ void ghost_demon::init_player_ghost(bool actual_ghost) name = you.your_name; max_hp = min(get_real_hp(false, false), MAX_GHOST_HP); - ev = min(you.evasion(EV_IGNORE_HELPLESS), MAX_GHOST_EVASION); + ev = min(you.evasion(ev_ignore::helpless), MAX_GHOST_EVASION); ac = you.armour_class(); dprf("ghost ac: %d, ev: %d", ac, ev); @@ -780,7 +780,7 @@ bool debug_check_ghost(const ghost_demon &ghost) return false; // Name validation. - if (!validate_player_name(ghost.name, false)) + if (!validate_player_name(ghost.name)) return false; // Many combining characters can come per every letter, but if there's // that much, it's probably a maliciously forged ghost of some kind. diff --git a/crawl-ref/source/god-abil.cc b/crawl-ref/source/god-abil.cc index 305c0b3ecf1c..a64032f20805 100644 --- a/crawl-ref/source/god-abil.cc +++ b/crawl-ref/source/god-abil.cc @@ -9,7 +9,6 @@ #include #include -#include #include #include "act-iter.h" @@ -17,7 +16,6 @@ #include "attitude-change.h" #include "bloodspatter.h" #include "branch.h" -#include "butcher.h" #include "chardump.h" #include "cloud.h" #include "colour.h" @@ -136,7 +134,8 @@ bool bless_weapon(god_type god, brand_type brand, colour_t colour) { ASSERT(can_do_capstone_ability(god)); - int item_slot = prompt_invent_item("Brand which weapon?", MT_INVLIST, + int item_slot = prompt_invent_item("Brand which weapon?", + menu_type::invlist, OSEL_BLESSABLE_WEAPON, OPER_ANY, invprompt_flag::escape_only); @@ -1569,7 +1568,7 @@ bool beogh_gift_item() args.mode = TARG_BEOGH_GIFTABLE; args.range = LOS_RADIUS; args.needs_path = false; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; args.show_floor_desc = true; args.top_prompt = "Select a follower to give a gift to."; @@ -1583,7 +1582,7 @@ bool beogh_gift_item() return false; int item_slot = prompt_invent_item("Give which item?", - MT_INVLIST, OSEL_BEOGH_GIFT); + menu_type::invlist, OSEL_BEOGH_GIFT); if (item_slot == PROMPT_ABORT || item_slot == PROMPT_NOTHING) { @@ -2034,1416 +2033,6 @@ bool fedhas_passthrough(const monster_info* target) || target->attitude != ATT_HOSTILE); } -// Basically we want to break a circle into n_arcs equal sized arcs and find -// out which arc the input point pos falls on. -static int _arc_decomposition(const coord_def & pos, int n_arcs) -{ - float theta = atan2((float)pos.y, (float)pos.x); - - if (pos.x == 0 && pos.y != 0) - theta = pos.y > 0 ? PI / 2 : -PI / 2; - - if (theta < 0) - theta += 2 * PI; - - float arc_angle = 2 * PI / n_arcs; - - theta += arc_angle / 2.0f; - - if (theta >= 2 * PI) - theta -= 2 * PI; - - return static_cast (theta / arc_angle); -} - -int place_ring(vector &ring_points, - const coord_def &origin, - mgen_data prototype, - int n_arcs, - int arc_occupancy, - int &seen_count) -{ - shuffle_array(ring_points); - - int target_amount = ring_points.size(); - int spawned_count = 0; - seen_count = 0; - - vector arc_counts(n_arcs, arc_occupancy); - - for (unsigned i = 0; - spawned_count < target_amount && i < ring_points.size(); - i++) - { - int direction = _arc_decomposition(ring_points.at(i) - - origin, n_arcs); - - if (arc_counts[direction]-- <= 0) - continue; - - prototype.pos = ring_points.at(i); - - if (create_monster(prototype, false)) - { - spawned_count++; - if (you.see_cell(ring_points.at(i))) - seen_count++; - } - } - - return spawned_count; -} - -// Collect lists of points that are within LOS (under the given env map), -// unoccupied, and not solid (walls/statues). -void collect_radius_points(vector > &radius_points, - const coord_def &origin, los_type los) -{ - radius_points.clear(); - radius_points.resize(LOS_RADIUS); - - // Just want to associate a point with a distance here for convenience. - typedef pair coord_dist; - - // Using a priority queue because squares don't make very good circles at - // larger radii. We will visit points in order of increasing euclidean - // distance from the origin (not path distance). We want a min queue - // based on the distance, so we use greater_second as the comparator. - priority_queue, - greater_second > fringe; - - fringe.push(coord_dist(origin, 0)); - - set visited_indices; - - int current_r = 1; - int current_thresh = current_r * (current_r + 1); - - int max_distance = LOS_RADIUS * LOS_RADIUS + 1; - - while (!fringe.empty()) - { - coord_dist current = fringe.top(); - // We're done here once we hit a point that is farther away from the - // origin than our maximum permissible radius. - if (current.second > max_distance) - break; - - fringe.pop(); - - int idx = current.first.x + current.first.y * X_WIDTH; - if (!visited_indices.insert(idx).second) - continue; - - while (current.second > current_thresh) - { - current_r++; - current_thresh = current_r * (current_r + 1); - } - - // We don't include radius 0. This is also a good place to check if - // the squares are already occupied since we want to search past - // occupied squares but don't want to consider them valid targets. - if (current.second && !actor_at(current.first)) - radius_points[current_r - 1].push_back(current.first); - - for (adjacent_iterator i(current.first); i; ++i) - { - coord_dist temp(*i, current.second); - - // If the grid is out of LOS, skip it. - if (!cell_see_cell(origin, temp.first, los)) - continue; - - coord_def local = temp.first - origin; - - temp.second = local.abs(); - - idx = temp.first.x + temp.first.y * X_WIDTH; - - if (!visited_indices.count(idx) - && in_bounds(temp.first) - && !cell_is_solid(temp.first)) - { - fringe.push(temp); - } - } - - } -} - -static int _mushroom_prob(item_def & corpse) -{ - int low_threshold = 5; - int high_threshold = FRESHEST_CORPSE - 5; - - // Expect this many trials over a corpse's lifetime since this function - // is called once for every 10 units of rot_time. - int step_size = 10; - float total_trials = (high_threshold - low_threshold) / step_size; - - // Chance of producing no mushrooms (not really because of weight_factor - // below). - float p_failure = 0.5f; - - float trial_prob_f = 1 - powf(p_failure, 1.0f / total_trials); - - // The chance of producing mushrooms depends on the corpse capacity. - // Take humans as the base factor here. - float weight_factor = (float) max_corpse_chunks(corpse.mon_type) / - (float) max_corpse_chunks(MONS_HUMAN); - - trial_prob_f *= weight_factor; - - int trial_prob = static_cast(100 * trial_prob_f); - - return trial_prob; -} - -static bool _mushroom_spawn_message(int seen_targets, int seen_corpses) -{ - if (seen_targets <= 0) - return false; - - string what = seen_targets > 1 ? "Some toadstools" - : "A toadstool"; - string where = seen_corpses > 1 ? "nearby corpses" : - seen_corpses == 1 ? "a nearby corpse" - : "the ground"; - mprf("%s grow%s from %s.", - what.c_str(), seen_targets > 1 ? "" : "s", where.c_str()); - - return true; -} - -// Place a partial ring of toadstools around the given corpse. Returns -// the number of mushrooms spawned. A return of 0 indicates no -// mushrooms were placed -> some sort of failure mode was reached. -static int _mushroom_ring(item_def &corpse, int & seen_count) -{ - // minimum number of mushrooms spawned on a given ring - unsigned min_spawn = 2; - - seen_count = 0; - - vector > radius_points; - - collect_radius_points(radius_points, corpse.pos, LOS_SOLID); - - // So what we have done so far is collect the set of points at each radius - // reachable from the origin with (somewhat constrained) 8 connectivity, - // now we will choose one of those radii and spawn mushrooms at some - // of the points along it. - int chosen_idx = random2(LOS_RADIUS); - - unsigned max_size = 0; - for (unsigned i = 0; i < LOS_RADIUS; ++i) - { - if (radius_points[i].size() >= max_size) - { - max_size = radius_points[i].size(); - chosen_idx = i; - } - } - - chosen_idx = random2(chosen_idx + 1); - - // Not enough valid points? - if (radius_points[chosen_idx].size() < min_spawn) - return 0; - - mgen_data temp(MONS_TOADSTOOL, BEH_GOOD_NEUTRAL, coord_def(), MHITNOT, - MG_FORCE_PLACE); - temp.set_col(corpse.get_colour()); - - float target_arc_len = 2 * sqrtf(2.0f); - - int n_arcs = static_cast (ceilf(2 * PI * (chosen_idx + 1) - / target_arc_len)); - - int spawned_count = place_ring(radius_points[chosen_idx], corpse.pos, temp, - n_arcs, 1, seen_count); - - return spawned_count; -} - -// Try to spawn 'target_count' mushrooms around the position of -// 'corpse'. Returns the number of mushrooms actually spawned. -// Mushrooms radiate outwards from the corpse following bfs with -// 8-connectivity. Could change the expansion pattern by using a -// priority queue for sequencing (priority = distance from origin under -// some metric). -static int _spawn_corpse_mushrooms(item_def& corpse, - int target_count, - int& seen_targets) - -{ - seen_targets = 0; - if (target_count == 0) - return 0; - - int placed_targets = 0; - - queue fringe; - set visited_indices; - - // Slight chance of spawning a ring of mushrooms around the corpse (and - // skeletonising it) if the corpse square is unoccupied. - if (!actor_at(corpse.pos) && one_chance_in(100)) - { - int ring_seen; - // It's possible no reasonable ring can be found, in that case we'll - // give up and just place a toadstool on top of the corpse (probably). - int res = _mushroom_ring(corpse, ring_seen); - - if (res) - { - corpse.freshness = 0; - - if (you.see_cell(corpse.pos)) - mpr("A ring of toadstools grows before your very eyes."); - else if (ring_seen > 1) - mpr("Some toadstools grow in a peculiar arc."); - else if (ring_seen > 0) - mpr("A toadstool grows."); - - seen_targets = -1; - - return res; - } - } - - visited_indices.insert(X_WIDTH * corpse.pos.y + corpse.pos.x); - fringe.push(corpse.pos); - - while (!fringe.empty()) - { - coord_def current = fringe.front(); - - fringe.pop(); - - monster* mons = monster_at(current); - - bool player_occupant = you.pos() == current; - - // Is this square occupied by a non mushroom? - if (mons && mons->mons_species() != MONS_TOADSTOOL - || player_occupant && !you_worship(GOD_FEDHAS) - || !can_spawn_mushrooms(current)) - { - continue; - } - - if (!mons) - { - monster *mushroom = create_monster( - mgen_data(MONS_TOADSTOOL, - BEH_GOOD_NEUTRAL, - current, - MHITNOT, - MG_FORCE_PLACE).set_col(corpse.get_colour()), - false); - - if (mushroom) - { - // Going to explicitly override the die-off timer in - // this case since, we're creating a lot of toadstools - // at once that should die off quickly. - coord_def offset = corpse.pos - current; - - int dist = static_cast(sqrtf(offset.abs()) + 0.5); - - // Trying a longer base duration... - int time_left = random2(8) + dist * 8 + 8; - - time_left *= 10; - - mon_enchant temp_en(ENCH_SLOWLY_DYING, 1, 0, time_left); - mushroom->update_ench(temp_en); - - placed_targets++; - if (current == you.pos()) - { - mprf("A toadstool grows %s.", - player_has_feet() ? "at your feet" : "before you"); - current = mushroom->pos(); - } - else if (you.see_cell(current)) - seen_targets++; - } - else - continue; - } - - // We're done here if we placed the desired number of mushrooms. - if (placed_targets == target_count) - break; - - for (fair_adjacent_iterator ai(current); ai; ++ai) - { - if (in_bounds(*ai) && mons_class_can_pass(MONS_TOADSTOOL, grd(*ai))) - { - const int index = ai->x + ai->y * X_WIDTH; - if (visited_indices.insert(index).second) - fringe.push(*ai); // Not previously visited. - } - } - } - - return placed_targets; -} - -// Turns corpses in LOS into skeletons and grows toadstools on them. -// Can also turn zombies into skeletons and destroy ghoul-type monsters. -// Returns the number of corpses consumed. -int fedhas_fungal_bloom() -{ - int seen_mushrooms = 0; - int seen_corpses = 0; - int processed_count = 0; - bool kills = false; - - for (radius_iterator i(you.pos(), LOS_NO_TRANS); i; ++i) - { - monster* target = monster_at(*i); - if (!can_spawn_mushrooms(*i)) - continue; - - if (target && target->type != MONS_TOADSTOOL) - { - bool piety = !target->is_summoned(); - switch (mons_genus(target->mons_species())) - { - case MONS_ZOMBIE: - // Maybe turn a zombie into a skeleton. - if (mons_skeleton(mons_zombie_base(*target))) - { - simple_monster_message(*target, "'s flesh rots away."); - - downgrade_zombie_to_skeleton(target); - - behaviour_event(target, ME_ALERT, &you); - - if (piety) - processed_count++; - - continue; - } - // Else fall through and destroy the zombie. - // Ghoul-type monsters are always destroyed. - case MONS_GHOUL: - { - simple_monster_message(*target, " rots away and dies."); - - kills = true; - - const coord_def pos = target->pos(); - const int colour = target->colour; - const item_def* corpse = monster_die(*target, KILL_MISC, - NON_MONSTER, true); - - // If a corpse didn't drop, create a toadstool. - // If one did drop, we will create toadstools from it as usual - // later on. - // Give neither piety nor toadstools for summoned creatures. - // Assumes that summoned creatures do not drop corpses (hence - // will not give piety in the next loop). - if (!corpse && piety) - { - if (create_monster( - mgen_data(MONS_TOADSTOOL, - BEH_GOOD_NEUTRAL, - pos, - MHITNOT, - MG_FORCE_PLACE).set_col(colour) - .set_summoned(&you, 0, 0), - false)) - { - seen_mushrooms++; - } - - processed_count++; - continue; - } - - // Verify that summoned creatures do not drop a corpse. - ASSERT(!corpse || piety); - - break; - } - - default: - continue; - } - } - - for (stack_iterator j(*i); j; ++j) - { - bool corpse_on_pos = false; - - if (j->is_type(OBJ_CORPSES, CORPSE_BODY)) - { - corpse_on_pos = true; - - const int trial_prob = _mushroom_prob(*j); - const int target_count = 1 + binomial(20, trial_prob); - int seen_per; - _spawn_corpse_mushrooms(*j, target_count, seen_per); - - // Either turn this corpse into a skeleton or destroy it. - if (mons_skeleton(j->mon_type)) - turn_corpse_into_skeleton(*j); - else - { - item_was_destroyed(*j); - destroy_item(j->index()); - } - - seen_mushrooms += seen_per; - - processed_count++; - } - - if (corpse_on_pos && you.see_cell(*i)) - seen_corpses++; - } - } - - if (seen_mushrooms > 0) - _mushroom_spawn_message(seen_mushrooms, seen_corpses); - - if (kills) - mpr("That felt like a moral victory."); - - if (processed_count) - { - simple_god_message(" appreciates your contribution to the " - "ecosystem."); - // Doubling the expected value per sacrifice to approximate the - // extra piety gain blood god worshipers get for the initial kill. - // -cao - - int piety_gain = 0; - for (int i = 0; i < processed_count * 2; ++i) - piety_gain += random2(15); // avg 1.4 piety per corpse - gain_piety(piety_gain, 10); - } - else - canned_msg(MSG_NOTHING_HAPPENS); - - return processed_count; -} - -static bool _create_plant(coord_def& target, int hp_adjust = 0) -{ - if (actor_at(target) || !mons_class_can_pass(MONS_PLANT, grd(target))) - return 0; - - if (monster *plant = create_monster(mgen_data(MONS_PLANT, - BEH_FRIENDLY, - target, - MHITNOT, - MG_FORCE_PLACE, - GOD_FEDHAS) - .set_summoned(&you, 0, 0))) - { - plant->flags |= MF_NO_REWARD; - plant->flags |= MF_ATT_CHANGE_ATTEMPT; - - mons_make_god_gift(*plant, GOD_FEDHAS); - - plant->max_hit_points += hp_adjust; - plant->hit_points += hp_adjust; - - if (you.see_cell(target)) - { - if (hp_adjust) - { - mprf("A plant, strengthened by %s, grows up from the ground.", - god_name(GOD_FEDHAS).c_str()); - } - else - mpr("A plant grows up from the ground."); - } - return true; - } - - return false; -} - -#define SUNLIGHT_DURATION 80 - -spret fedhas_sunlight(bool fail) -{ - dist spelld; - - bolt temp_bolt; - temp_bolt.colour = YELLOW; - - targeter_smite tgt(&you, LOS_RADIUS, 0, 1); - direction_chooser_args args; - args.restricts = DIR_TARGET; - args.mode = TARG_HOSTILE_SUBMERGED; - args.range = LOS_RADIUS; - args.needs_path = false; - args.top_prompt = "Select sunlight destination."; - args.hitfunc = &tgt; - direction(spelld, args); - - if (!spelld.isValid) - return spret::abort; - - fail_check(); - - const coord_def base = spelld.target; - - int revealed_count = 0; - - for (adjacent_iterator ai(base, false); ai; ++ai) - { - if (!in_bounds(*ai) || cell_is_solid(*ai)) - continue; - - for (size_t i = 0; i < env.sunlight.size(); ++i) - if (env.sunlight[i].first == *ai) - { - erase_any(env.sunlight, i); - break; - } - const int expiry = you.elapsed_time + SUNLIGHT_DURATION; - env.sunlight.emplace_back(*ai, expiry); - - temp_bolt.explosion_draw_cell(*ai); - - monster *victim = monster_at(*ai); - if (victim && you.see_cell(*ai) && !victim->visible_to(&you)) - { - // Like entering/exiting angel halos, flipping autopickup would - // be probably too much hassle. - revealed_count++; - } - - if (victim) - behaviour_event(victim, ME_ALERT, &you); - } - - { - unwind_var no_time(you.time_taken, 0); - process_sunlights(false); - } - -#ifndef USE_TILE_LOCAL - // Move the cursor out of the way (it looks weird). - coord_def temp = grid2view(base); - cgotoxy(temp.x, temp.y, GOTO_DNGN); -#endif - scaled_delay(200); - - if (revealed_count) - { - mprf("In the bright light, you notice %s.", revealed_count == 1 ? - "an invisible shape" : "some invisible shapes"); - } - - return spret::success; -} - -void process_sunlights(bool future) -{ - int time_cap = future ? INT_MAX - SUNLIGHT_DURATION : you.elapsed_time; - - int evap_count = 0; - - for (int i = env.sunlight.size() - 1; i >= 0; --i) - { - coord_def c = env.sunlight[i].first; - int until = env.sunlight[i].second; - - if (until <= time_cap) - erase_any(env.sunlight, i); - - until = min(until, time_cap); - int from = you.elapsed_time - you.time_taken; - - // Deterministic roll, to guarantee evaporation when shined long enough. - struct { level_id place; coord_def coord; int64_t game_start; } to_hash; - to_hash.place = level_id::current(); - to_hash.coord = c; - to_hash.game_start = you.birth_time; - int h = hash32(&to_hash, sizeof(to_hash)) % SUNLIGHT_DURATION; - - if ((from + h) / SUNLIGHT_DURATION == (until + h) / SUNLIGHT_DURATION) - continue; - - // Anything further on goes only on a successful evaporation roll, at - // most once peer coord per invocation. - - // If this is a water square we will evaporate it. - dungeon_feature_type ftype = grd(c); - dungeon_feature_type orig_type = ftype; - - switch (ftype) - { - case DNGN_SHALLOW_WATER: - ftype = DNGN_FLOOR; - break; - - case DNGN_DEEP_WATER: - ftype = DNGN_SHALLOW_WATER; - break; - - default: - break; - } - - if (orig_type != ftype) - { - dungeon_terrain_changed(c, ftype); - - if (you.see_cell(c)) - evap_count++; - - // This is a little awkward but if we evaporated all the way to - // the dungeon floor that may have given a monster - // ENCH_AQUATIC_LAND, and if that happened the player should get - // credit if the monster dies. The enchantment is inflicted via - // the dungeon_terrain_changed call chain and that doesn't keep - // track of what caused the terrain change. -cao - monster* mons = monster_at(c); - if (mons && ftype == DNGN_FLOOR - && mons->has_ench(ENCH_AQUATIC_LAND)) - { - mon_enchant temp = mons->get_ench(ENCH_AQUATIC_LAND); - temp.who = KC_YOU; - temp.source = MID_PLAYER; - mons->add_ench(temp); - } - } - } - - if (evap_count) - mpr("Some water evaporates in the bright sunlight."); - - invalidate_agrid(true); -} - -template -static bool less_second(const T & left, const T & right) -{ - return left.second < right.second; -} - -typedef pair point_distance; - -// Find the distance from origin to each of the targets, those results -// are stored in distances (which is the same size as targets). Exclusion -// is a set of points which are considered disconnected for the search. -static void _path_distance(const coord_def& origin, - const vector& targets, - set exclusion, - vector& distances) -{ - queue fringe; - fringe.push(point_distance(origin,0)); - distances.clear(); - distances.resize(targets.size(), INT_MAX); - - while (!fringe.empty()) - { - point_distance current = fringe.front(); - fringe.pop(); - - // did we hit a target? - for (unsigned i = 0; i < targets.size(); ++i) - { - if (current.first == targets[i]) - { - distances[i] = current.second; - break; - } - } - - for (adjacent_iterator adj_it(current.first); adj_it; ++adj_it) - { - int idx = adj_it->x + adj_it->y * X_WIDTH; - if (you.see_cell(*adj_it) - && !feat_is_solid(env.grid(*adj_it)) - && *adj_it != you.pos() - && exclusion.insert(idx).second) - { - monster* temp = monster_at(*adj_it); - if (!temp || (temp->attitude == ATT_HOSTILE - && !temp->is_stationary())) - { - fringe.push(point_distance(*adj_it, current.second+1)); - } - } - } - } -} - -// Find the minimum distance from each point of origin to one of the targets -// The distance is stored in 'distances', which is the same size as origins. -static void _point_point_distance(const vector& origins, - const vector& targets, - vector& distances) -{ - distances.clear(); - distances.resize(origins.size(), INT_MAX); - - // Consider all points of origin as blocked (you can search outward - // from one, but you can't form a path across a different one). - set base_exclusions; - for (coord_def c : origins) - { - int idx = c.x + c.y * X_WIDTH; - base_exclusions.insert(idx); - } - - vector current_distances; - for (unsigned i = 0; i < origins.size(); ++i) - { - // Find the distance from the point of origin to each of the targets. - _path_distance(origins[i], targets, base_exclusions, - current_distances); - - // Find the smallest of those distances - int min_dist = current_distances[0]; - for (unsigned j = 1; j < current_distances.size(); ++j) - if (current_distances[j] < min_dist) - min_dist = current_distances[j]; - - distances[i] = min_dist; - } -} - -// So the idea is we want to decide which adjacent tiles are in the most 'danger' -// We claim danger is proportional to the minimum distances from the point to a -// (hostile) monster. This function carries out at most 7 searches to calculate -// the distances in question. -bool prioritise_adjacent(const coord_def &target, vector& candidates) -{ - radius_iterator los_it(target, LOS_NO_TRANS, true); - - vector mons_positions; - // collect hostile monster positions in LOS - for (; los_it; ++los_it) - { - monster* hostile = monster_at(*los_it); - - if (hostile && hostile->attitude == ATT_HOSTILE - && you.can_see(*hostile)) - { - mons_positions.push_back(hostile->pos()); - } - } - - if (mons_positions.empty()) - { - shuffle_array(candidates); - return true; - } - - vector distances; - - _point_point_distance(candidates, mons_positions, distances); - - vector possible_moves(candidates.size()); - - for (unsigned i = 0; i < possible_moves.size(); ++i) - { - possible_moves[i].first = candidates[i]; - possible_moves[i].second = distances[i]; - } - - sort(possible_moves.begin(), possible_moves.end(), - less_second); - - for (unsigned i = 0; i < candidates.size(); ++i) - candidates[i] = possible_moves[i].first; - - return true; -} - -static bool _prompt_amount(int max, int& selected, const string& prompt) -{ - selected = max; - while (true) - { - msg::streams(MSGCH_PROMPT) << prompt << " (" << max << " max) " << endl; - - const int keyin = get_ch(); - - // Cancel - if (key_is_escape(keyin) || keyin == ' ' || keyin == '0') - { - canned_msg(MSG_OK); - return false; - } - - // Default is max - if (keyin == '\n' || keyin == '\r') - { - selected = max; - return true; - } - - // Otherwise they should enter a digit - if (isadigit(keyin)) - { - selected = keyin - '0'; - if (selected > 0 && selected <= max) - return true; - } - // else they entered some garbage? - } - - return false; -} - -static int _collect_rations(vector >& available_rations) -{ - int total = 0; - - for (int i = 0; i < ENDOFPACK; i++) - { - if (you.inv[i].defined() && you.inv[i].is_type(OBJ_FOOD, FOOD_RATION)) - { - total += you.inv[i].quantity; - available_rations.emplace_back(you.inv[i].quantity, i); - } - } - sort(available_rations.begin(), available_rations.end()); - - return total; -} - -static void _decrease_amount(vector >& available, int amount) -{ - const int total_decrease = amount; - for (const auto &avail : available) - { - const int decrease_amount = min(avail.first, amount); - amount -= decrease_amount; - dec_inv_item_quantity(avail.second, decrease_amount); - } - if (total_decrease > 1) - mprf("%d rations are consumed!", total_decrease); - else - mpr("A ration is consumed!"); -} - -// Create a ring or partial ring around the caster. The user is -// prompted to select a stack of rations, and then plants are placed on open -// squares adjacent to the user. Of course, two rations are -// consumed per plant, so a complete ring may not be formed. -bool fedhas_plant_ring_from_rations() -{ - // How many rations is available? - vector > collected_rations; - int total_rations = _collect_rations(collected_rations); - - // How many adjacent open spaces are there? - vector adjacent; - for (adjacent_iterator adj_it(you.pos()); adj_it; ++adj_it) - { - if (monster_habitable_grid(MONS_PLANT, env.grid(*adj_it)) - && !actor_at(*adj_it)) - { - adjacent.push_back(*adj_it); - } - } - - const int max_use = min(total_rations/2, static_cast(adjacent.size())); - - // Don't prompt if we can't do anything (due to having no rations or - // no squares to place plants on). - if (max_use == 0) - { - if (adjacent.empty()) - mpr("No empty adjacent squares."); - else - mpr("Not enough rations available."); - - return false; - } - - prioritise_adjacent(you.pos(), adjacent); - - // Screwing around with display code I don't really understand. -cao - targeter_smite range(&you, 1); - range_view_annotator show_range(&range); - - for (int i = 0; i < max_use; ++i) - { -#ifndef USE_TILE_LOCAL - coord_def temp = grid2view(adjacent[i]); - cgotoxy(temp.x, temp.y, GOTO_DNGN); - put_colour_ch(GREEN, '1' + i); -#endif -#ifdef USE_TILE - tiles.add_overlay(adjacent[i], TILE_INDICATOR + i); -#endif - } - - // And how many plants does the user want to create? - int target_count; - if (!_prompt_amount(max_use, target_count, - "How many plants will you create?")) - { - // User cancelled at the prompt. - return false; - } - - const int hp_adjust = you.skill(SK_INVOCATIONS, 10); - - // The user entered a number, remove all number overlays which - // are higher than that number. -#ifndef USE_TILE_LOCAL - unsigned not_used = adjacent.size() - unsigned(target_count); - for (unsigned i = adjacent.size() - not_used; i < adjacent.size(); i++) - view_update_at(adjacent[i]); -#endif -#ifdef USE_TILE - // For tiles we have to clear all overlays and redraw the ones - // we want. - tiles.clear_overlays(); - for (int i = 0; i < target_count; ++i) - tiles.add_overlay(adjacent[i], TILE_INDICATOR + i); -#endif - - int created_count = 0; - for (int i = 0; i < target_count; ++i) - { - if (_create_plant(adjacent[i], hp_adjust)) - created_count++; - - // Clear the overlay and draw the plant we just placed. - // This is somewhat more complicated in tiles. - view_update_at(adjacent[i]); -#ifdef USE_TILE - tiles.clear_overlays(); - for (int j = i + 1; j < target_count; ++j) - tiles.add_overlay(adjacent[j], TILE_INDICATOR + j); - viewwindow(false); -#endif - scaled_delay(200); - } - - if (created_count) - _decrease_amount(collected_rations, 2 * created_count); - else - canned_msg(MSG_NOTHING_HAPPENS); - - return created_count; -} - -// Create a circle of water around the target, with a radius of -// approximately 2. This turns normal floor tiles into shallow water -// and turns (unoccupied) shallow water into deep water. There is a -// chance of spawning plants or fungus on unoccupied dry floor tiles -// outside of the rainfall area. Return the number of plants/fungi -// created. -int fedhas_rain(const coord_def &target) -{ - int spawned_count = 0; - int processed_count = 0; - - for (radius_iterator rad(target, LOS_NO_TRANS, true); rad; ++rad) - { - int rain_thresh = 2; - coord_def local = *rad - target; - - dungeon_feature_type ftype = grd(*rad); - - if (local.rdist() > rain_thresh) - { - // Maybe spawn a plant on (dry, open) squares that are in - // LOS but outside the rainfall area. In open space, there - // are 225 squares in LOS, and we are going to drop water on - // 25 of those, so if we want x plants to spawn on - // average in open space, the trial probability should be - // x/200. - if (x_chance_in_y(5, 200) - && !actor_at(*rad) - && ftype == DNGN_FLOOR) - { - if (create_monster(mgen_data( - random_choose(MONS_PLANT, MONS_FUNGUS), - BEH_GOOD_NEUTRAL, - *rad, - MHITNOT, - MG_FORCE_PLACE, - GOD_FEDHAS) - .set_summoned(&you, 0, 0))) - { - spawned_count++; - } - - processed_count++; - } - - continue; - } - - // Turn regular floor squares only into shallow water. - if (ftype == DNGN_FLOOR) - { - dungeon_terrain_changed(*rad, DNGN_SHALLOW_WATER); - - processed_count++; - } - // We can also turn shallow water into deep water, but we're - // just going to skip cases where there is something on the - // shallow water. Destroying items will probably be annoying, - // and insta-killing monsters is clearly out of the question. - else if (!actor_at(*rad) - && igrd(*rad) == NON_ITEM - && ftype == DNGN_SHALLOW_WATER) - { - dungeon_terrain_changed(*rad, DNGN_DEEP_WATER); - - processed_count++; - } - - if (!feat_is_solid(ftype)) - { - // Maybe place a raincloud. - // - // The rainfall area is 24 (5*5 - 1 (center)); - // the expected number of clouds generated by a fixed chance - // per tile is 24 * p = expected. Say an Invocations skill - // of 27 gives expected 6 clouds. - int max_expected = 6; - int expected = you.skill_rdiv(SK_INVOCATIONS, max_expected, 27); - - if (x_chance_in_y(expected, 24)) - { - place_cloud(CLOUD_RAIN, *rad, 10, &you); - - processed_count++; - } - } - } - - if (spawned_count > 0) - { - mprf("%s grow%s in the rain.", - (spawned_count > 1 ? "Some plants" : "A plant"), - (spawned_count > 1 ? "" : "s")); - } - - return processed_count; -} - -int count_corpses_in_los(vector *positions) -{ - int count = 0; - - for (radius_iterator rad(you.pos(), LOS_NO_TRANS, true); rad; - ++rad) - { - if (actor_at(*rad)) - continue; - - for (stack_iterator stack_it(*rad); stack_it; ++stack_it) - { - if (stack_it->is_type(OBJ_CORPSES, CORPSE_BODY)) - { - if (positions) - positions->push_back(stack_it); - count++; - break; - } - } - } - - return count; -} - -int fedhas_check_corpse_spores(bool quiet) -{ - vector positions; - int count = count_corpses_in_los(&positions); - - if (quiet || count == 0) - return count; - - viewwindow(false); - for (const stack_iterator &si : positions) - { -#ifndef USE_TILE_LOCAL - coord_def temp = grid2view(si->pos); - cgotoxy(temp.x, temp.y, GOTO_DNGN); - - unsigned colour = GREEN | COLFLAG_FRIENDLY_MONSTER; - colour = real_colour(colour); - - unsigned character = mons_char(MONS_BALLISTOMYCETE_SPORE); - put_colour_ch(colour, character); -#endif -#ifdef USE_TILE - tiles.add_overlay(si->pos, TILE_SPORE_OVERLAY); -#endif - } - - if (yesnoquit("Will you create these spores?", true, 'y') <= 0) - { - viewwindow(false); - return -1; - } - - return count; -} - -// Destroy corpses in the player's LOS (first corpse on a stack only) -// and make 1 ballistomycete spore per corpse. Spores are given the input as -// their starting behavior; the function returns the number of corpses -// processed. -int fedhas_corpse_spores(beh_type attitude) -{ - vector positions; - int count = count_corpses_in_los(&positions); - ASSERT(attitude != BEH_FRIENDLY || count > 0); - - if (count == 0) - return count; - - for (const stack_iterator &si : positions) - { - count++; - - if (monster *plant = create_monster(mgen_data(MONS_BALLISTOMYCETE_SPORE, - attitude, - si->pos, - MHITNOT, - MG_FORCE_PLACE, - GOD_FEDHAS) - .set_summoned(&you, 0, 0))) - { - plant->flags |= MF_NO_REWARD; - - if (attitude == BEH_FRIENDLY) - { - plant->flags |= MF_ATT_CHANGE_ATTEMPT; - - mons_make_god_gift(*plant, GOD_FEDHAS); - - plant->behaviour = BEH_WANDER; - plant->foe = MHITNOT; - } - } - - if (mons_skeleton(si->mon_type)) - turn_corpse_into_skeleton(*si); - else - { - item_was_destroyed(*si); - destroy_item(si->index()); - } - } - - viewwindow(false); - - return count; -} - -struct monster_conversion -{ - monster_type new_type; - int piety_cost; - int ration_cost; -}; - -static const map conversions = -{ - { MONS_PLANT, { MONS_OKLOB_PLANT, 0, 2 } }, - { MONS_BUSH, { MONS_OKLOB_PLANT, 0, 2 } }, - { MONS_BURNING_BUSH, { MONS_OKLOB_PLANT, 0, 2 } }, - { MONS_OKLOB_SAPLING, { MONS_OKLOB_PLANT, 4, 0 } }, - { MONS_FUNGUS, { MONS_WANDERING_MUSHROOM, 3, 0 } }, - { MONS_TOADSTOOL, { MONS_WANDERING_MUSHROOM, 3, 0 } }, - { MONS_BALLISTOMYCETE, { MONS_HYPERACTIVE_BALLISTOMYCETE, 4, 0 } }, -}; - -bool mons_is_evolvable(const monster* mon) -{ - return conversions.count(mon->type) && !mon->has_ench(ENCH_PETRIFIED); -} - -bool fedhas_check_evolve_flora(bool quiet) -{ - for (monster_near_iterator mi(&you, LOS_NO_TRANS); mi; ++mi) - if (mons_is_evolvable(*mi)) - return true; - - if (!quiet) - mpr("No evolvable flora in sight."); - return false; -} - -static vector _evolution_name(const monster_info& mon) -{ - auto conv = map_find(conversions, mon.type); - if (conv && !mon.has_trivial_ench(ENCH_PETRIFIED)) - return { "can evolve into " + mons_type_name(conv->new_type, DESC_A) }; - else - return { "cannot be evolved" }; -} - -spret fedhas_evolve_flora(bool fail) -{ - dist spelld; - - direction_chooser_args args; - args.restricts = DIR_TARGET; - args.mode = TARG_EVOLVABLE_PLANTS; - args.range = LOS_RADIUS; - args.needs_path = false; - args.self = CONFIRM_CANCEL; - args.show_floor_desc = true; - args.top_prompt = "Select plant or fungus to evolve."; - args.get_desc_func = _evolution_name; - - direction(spelld, args); - - if (!spelld.isValid) - { - // Check for user cancel. - canned_msg(MSG_OK); - return spret::abort; - } - - monster* const plant = monster_at(spelld.target); - - if (!plant) - { - if (feat_is_tree(env.grid(spelld.target))) - mpr("The tree has already reached the pinnacle of evolution."); - else - mpr("You must target a plant or fungus."); - return spret::abort; - } - - if (!mons_is_evolvable(plant)) - { - if (plant->type == MONS_BALLISTOMYCETE_SPORE) - mpr("You can evolve only complete plants, not seeds."); - else if (!mons_is_plant(*plant)) - mpr("Only plants or fungi may be evolved."); - else if (plant->has_ench(ENCH_PETRIFIED)) - mpr("Stone cannot grow or evolve."); - else - { - simple_monster_message(*plant, " has already reached the pinnacle" - " of evolution."); - } - - return spret::abort; - } - auto upgrade_ptr = map_find(conversions, plant->type); - ASSERT(upgrade_ptr); - monster_conversion upgrade = *upgrade_ptr; - - vector > collected_rations; - if (upgrade.ration_cost) - { - const int total_rations = _collect_rations(collected_rations); - - if (total_rations < upgrade.ration_cost) - { - mpr("Not enough rations available."); - return spret::abort; - } - } - - if (upgrade.piety_cost && upgrade.piety_cost > you.piety) - { - mpr("Not enough piety available."); - return spret::abort; - } - - fail_check(); - - switch (upgrade.new_type) - { - case MONS_OKLOB_PLANT: - { - if (plant->type == MONS_OKLOB_SAPLING) - simple_monster_message(*plant, " appears stronger."); - else - { - string evolve_desc = " can now spit acid"; - const int skill = you.skill(SK_INVOCATIONS); - if (skill >= 20) - evolve_desc += " continuously"; - else if (skill >= 15) - evolve_desc += " quickly"; - else if (skill >= 10) - evolve_desc += " rather quickly"; - else if (skill >= 5) - evolve_desc += " somewhat quickly"; - evolve_desc += "."; - - simple_monster_message(*plant, evolve_desc.c_str()); - } - break; - } - - case MONS_WANDERING_MUSHROOM: - simple_monster_message(*plant, " can now pick up its mycelia and move."); - break; - - case MONS_HYPERACTIVE_BALLISTOMYCETE: - simple_monster_message(*plant, " appears agitated."); - env.level_state |= LSTATE_GLOW_MOLD; - break; - - default: - break; - } - - plant->upgrade_type(upgrade.new_type, true, true); - - plant->flags |= MF_NO_REWARD; - plant->flags |= MF_ATT_CHANGE_ATTEMPT; - - mons_make_god_gift(*plant, GOD_FEDHAS); - - plant->attitude = ATT_FRIENDLY; - - behaviour_event(plant, ME_ALERT); - mons_att_changed(plant); - - // Try to remove slowly dying in case we are upgrading a - // toadstool, and spore production in case we are upgrading a - // ballistomycete. - plant->del_ench(ENCH_SLOWLY_DYING); - plant->del_ench(ENCH_SPORE_PRODUCTION); - - if (plant->type == MONS_HYPERACTIVE_BALLISTOMYCETE) - plant->add_ench(ENCH_EXPLODING); - else if (plant->type == MONS_OKLOB_PLANT) - { - // frequency will be set by set_hit_dice below - plant->spells = { { SPELL_SPIT_ACID, 0, MON_SPELL_NATURAL } }; - } - - plant->set_hit_dice(plant->get_experience_level() - + you.skill_rdiv(SK_INVOCATIONS)); - - if (upgrade.ration_cost) - _decrease_amount(collected_rations, upgrade.ration_cost); - - if (upgrade.piety_cost) - { - lose_piety(upgrade.piety_cost); - mpr("Your piety has decreased."); - } - - return spret::success; -} - static bool _lugonu_warp_monster(monster& mon, int pow) { if (coinflip()) @@ -3739,7 +2328,8 @@ bool ashenzari_curse_item(int num_rc) "Curse which item? (%d remove curse scroll%s left)" " (Esc to abort)", num_rc, num_rc == 1 ? "" : "s"); - const int item_slot = prompt_invent_item(prompt_msg.c_str(), MT_INVLIST, + const int item_slot = prompt_invent_item(prompt_msg.c_str(), + menu_type::invlist, OSEL_CURSABLE, OPER_ANY, invprompt_flag::escape_only); if (prompt_failed(item_slot)) @@ -4264,9 +2854,7 @@ static string _gozag_special_shop_name(shop_type type) { if (type == SHOP_FOOD) { - if (you.species == SP_VAMPIRE) - return "Blood"; - else if (you.species == SP_GHOUL) + if (you.species == SP_GHOUL) return "Carrion"; // yum! } @@ -4387,7 +2975,7 @@ static void _gozag_place_shop(int index) ASSERT(shop); const gender_type gender = random_choose(GENDER_FEMALE, GENDER_MALE, - GENDER_NEUTER); + GENDER_NEUTRAL); mprf(MSGCH_GOD, "%s invites you to visit %s %s%s%s.", shop->shop_name.c_str(), @@ -4671,7 +3259,7 @@ spret qazlal_upheaval(coord_def target, bool quiet, bool fail) args.mode = TARG_HOSTILE; args.needs_path = false; args.top_prompt = "Aiming: Upheaval"; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; args.hitfunc = &tgt; if (!spell_direction(spd, beam, &args)) return spret::abort; @@ -5660,7 +4248,7 @@ static bool _execute_sacrifice(ability_type sac, const char* message) static void _ru_kill_skill(skill_type skill) { change_skill_points(skill, -you.skill_points[skill], true); - you.can_train.set(skill, false); + you.can_currently_train.set(skill, false); reset_training(); check_selected_skills(); } @@ -6041,7 +4629,7 @@ void ru_reset_sacrifice_timer(bool clear_timer, bool faith_penalty) } } - delay = div_rand_round((delay + added_delay) * (3 + you.faith()), 3); + delay = div_rand_round((delay + added_delay) * (3 - you.faith()), 3); if (crawl_state.game_is_sprint()) delay /= SPRINT_MULTIPLIER; @@ -6167,7 +4755,7 @@ bool ru_power_leap() args.range = 3; args.needs_path = false; args.top_prompt = "Aiming: Power Leap"; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; const int explosion_size = 1; targeter_smite tgt(&you, args.range, explosion_size, explosion_size); tgt.obeys_mesmerise = true; @@ -6379,6 +4967,7 @@ bool ru_apocalypse() return true; } +#if TAG_MAJOR_VERSION == 34 /** * Calculate the effective power of a surged hex wand. * Works by iterating over the possible rolls from random2avg(). @@ -6463,6 +5052,7 @@ int pakellas_surge_devices() } return severity; } +#endif static bool _mons_stompable(const monster &mons) { @@ -6673,7 +5263,7 @@ spret uskayaw_grand_finale(bool fail) args.mode = TARG_HOSTILE; args.needs_path = false; args.top_prompt = "Aiming: Grand Finale"; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; targeter_smite tgt(&you); args.hitfunc = &tgt; direction(beam, args); @@ -6873,7 +5463,7 @@ static coord_def _get_transference_target() args.mode = TARG_MOBILE_MONSTER; args.range = LOS_RADIUS; args.needs_path = false; - args.self = CONFIRM_NONE; + args.self = confirm_prompt_type::none; args.show_floor_desc = true; args.top_prompt = "Select a target."; @@ -7035,15 +5625,22 @@ static void _hepliaklqana_choose_name() static void _hepliaklqana_choose_gender() { - static const string gender_names[] = { "neither", "male", "female" }; - const int current_gender - = you.props[HEPLIAKLQANA_ALLY_GENDER_KEY].get_int(); - ASSERT(size_t(current_gender) < ARRAYSZ(gender_names)); + static const map gender_map = + { + { GENDER_NEUTRAL, "neither" }, + { GENDER_MALE, "male" }, + { GENDER_FEMALE, "female" }, + }; + + const gender_type current_gender = + (gender_type)you.props[HEPLIAKLQANA_ALLY_GENDER_KEY].get_int(); + const string* desc = map_find(gender_map, current_gender); + ASSERT(desc); mprf(MSGCH_PROMPT, "Was %s a) male, b) female, or c) neither? (Currently %s.)", hepliaklqana_ally_name().c_str(), - gender_names[current_gender].c_str()); + desc->c_str()); int keyin = toalower(get_ch()); if (!isaalpha(keyin)) @@ -7052,15 +5649,19 @@ static void _hepliaklqana_choose_gender() return; } + static const gender_type gender_options[] = { GENDER_MALE, + GENDER_FEMALE, + GENDER_NEUTRAL }; + const uint32_t choice = keyin - 'a'; - if (choice > ARRAYSZ(gender_names)) + if (choice >= ARRAYSZ(gender_options)) { canned_msg(MSG_OK); return; } - // fun trick - const int new_gender = (choice + 1) % 3; + const gender_type new_gender = gender_options[choice]; + if (new_gender == current_gender) { canned_msg(MSG_OK); @@ -7070,7 +5671,7 @@ static void _hepliaklqana_choose_gender() you.props[HEPLIAKLQANA_ALLY_GENDER_KEY] = new_gender; mprf("%s was always %s, you're pretty sure.", hepliaklqana_ally_name().c_str(), - gender_names[new_gender].c_str()); + map_find(gender_map, new_gender)->c_str()); upgrade_hepliaklqana_ancestor(true); } @@ -7290,7 +5891,7 @@ spret wu_jian_wall_jump_ability() args.range = 1; args.needs_path = false; // TODO: overridden by hitfunc? args.top_prompt = "Aiming: Wall Jump"; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; targeter_walljump tgt; tgt.obeys_mesmerise = true; args.hitfunc = &tgt; diff --git a/crawl-ref/source/god-abil.h b/crawl-ref/source/god-abil.h index 1f7b559af160..6a0e9e81bcfa 100644 --- a/crawl-ref/source/god-abil.h +++ b/crawl-ref/source/god-abil.h @@ -106,29 +106,7 @@ bool kiku_gift_necronomicon(); bool fedhas_passthrough_class(const monster_type mc); bool fedhas_passthrough(const monster* target); bool fedhas_passthrough(const monster_info* target); -struct mgen_data; -int place_ring(vector& ring_points, - const coord_def& origin, - mgen_data prototype, - int n_arcs, - int arc_occupancy, - int& seen_count); -// Collect lists of points that are within LOS, unoccupied, and not solid -// (walls/statues). -void collect_radius_points(vector > &radius_points, - const coord_def &origin, los_type los); -int fedhas_fungal_bloom(); -spret fedhas_sunlight(bool fail = false); -void process_sunlights(bool future = false); -bool prioritise_adjacent(const coord_def& target, vector& candidates); -bool fedhas_plant_ring_from_rations(); int fedhas_rain(const coord_def &target); -int count_corpses_in_los(vector *positions); -int fedhas_check_corpse_spores(bool quiet = false); -int fedhas_corpse_spores(beh_type attitude = BEH_FRIENDLY); -bool mons_is_evolvable(const monster* mon); -bool fedhas_check_evolve_flora(bool quiet); -spret fedhas_evolve_flora(bool fail); void lugonu_bend_space(); diff --git a/crawl-ref/source/god-blessing.cc b/crawl-ref/source/god-blessing.cc index 0c5414cc8298..711bf1ad2881 100644 --- a/crawl-ref/source/god-blessing.cc +++ b/crawl-ref/source/god-blessing.cc @@ -166,19 +166,13 @@ void gift_ammo_to_orc(monster* orc, bool initial_gift) ammo.base_type = OBJ_MISSILES; if (!launcher) - ammo.sub_type = MI_TOMAHAWK; + ammo.sub_type = MI_BOOMERANG; else ammo.sub_type = fires_ammo_type(*launcher); if (ammo.sub_type == MI_STONE) ammo.sub_type = MI_SLING_BULLET; // ugly special case - // XXX: should beogh be gifting needles? - // if not, we'd need special checks in player gifting, etc... better to - // go along for now. - if (ammo.sub_type == MI_NEEDLE) - ammo.brand = SPMSL_POISONED; - ammo.quantity = 30 + random2(10); if (initial_gift || !launcher) ammo.quantity /= 2; @@ -293,7 +287,7 @@ static string _beogh_bless_ranged_weapon(monster* mon) } } - // If they have a shield but no launcher, give tomahawks. + // If they have a shield but no launcher, give boomerangs. if (mon->shield() != nullptr) { gift_ammo_to_orc(mon); @@ -581,6 +575,11 @@ static void _beogh_reinf_callback(const mgen_data &mg, monster *&mon, int placed // you out. static void _beogh_blessing_reinforcements() { + // Don't gift orcs if Toxic radiance is up, to avoid the player being + // penanced through no fault of their own. + if (you.duration[DUR_TOXIC_RADIANCE]) + return; + // Possible reinforcement. const monster_type followers[] = { diff --git a/crawl-ref/source/god-conduct.cc b/crawl-ref/source/god-conduct.cc index 2edd84477f47..0ab535e90029 100644 --- a/crawl-ref/source/god-conduct.cc +++ b/crawl-ref/source/god-conduct.cc @@ -57,16 +57,14 @@ static const char *conducts[] = { "", "Evil", "Holy", "Attack Holy", "Attack Neutral", "Attack Friend", - "Friend Died", "Kill Living", "Kill Undead", "Kill Demon", - "Kill Natural Evil", "Kill Unclean", "Kill Chaotic", "Kill Wizard", - "Kill Priest", "Kill Holy", "Kill Fast", "Banishment", "Spell Memorise", - "Spell Cast", "Spell Practise", "Cannibalism", "Eat Souled Being", - "Deliberate Mutation", "Cause Glowing", "Use Unclean", "Use Chaos", - "Desecrate Orcish Remains", "Kill Slime", "Kill Plant", "Was Hasty", - "Corpse Violation", "Carrion Rot", "Souled Friend Died", - "Attack In Sanctuary", "Kill Artificial", "Exploration", - "Desecrate Holy Remains", "Seen Monster", "Sacrificed Love", "Channel", - "Hurt Foe", + "Kill Living", "Kill Undead", "Kill Demon", "Kill Natural Evil", + "Kill Unclean", "Kill Chaotic", "Kill Wizard", "Kill Priest", "Kill Holy", + "Kill Fast", "Banishment", "Spell Memorise", "Spell Cast", + "Spell Practise", "Cannibalism", "Eat Souled Being", "Deliberate Mutation", + "Cause Glowing", "Use Unclean", "Use Chaos", "Desecrate Orcish Remains", + "Kill Slime", "Kill Plant", "Was Hasty", "Attack In Sanctuary", + "Kill Artificial", "Exploration", "Desecrate Holy Remains", "Seen Monster", + "Sacrificed Love", "Channel", "Hurt Foe", }; COMPILE_CHECK(ARRAYSZ(conducts) == NUM_CONDUCTS); @@ -258,20 +256,6 @@ static dislike_response _on_attack_friend(const char* desc) }; } -/// Fedhas's response to a friend(ly plant) dying. -static dislike_response _on_fedhas_friend_death(const char* desc) -{ - return - { - desc, false, - 1, 0, nullptr, nullptr, [] (const monster* victim) -> bool { - // ballistomycetes are penalized separately. - return victim && fedhas_protects(*victim) - && victim->mons_species() != MONS_BALLISTOMYCETE; - } - }; -} - typedef map peeve_map; /// a per-god map of conducts to that god's angry reaction to those conducts. @@ -418,18 +402,11 @@ static peeve_map divine_peeves[] = }, // GOD_FEDHAS, { - { DID_CORPSE_VIOLATION, { - "you use necromancy on corpses, chunks or skeletons", true, - 1, 1, " forgives your inadvertent necromancy, just this once." - } }, { DID_KILL_PLANT, { "you destroy plants", false, 1, 0 } }, { DID_ATTACK_FRIEND, _on_attack_friend(nullptr) }, - - { DID_FRIEND_DIED, _on_fedhas_friend_death("allied flora die") }, - { DID_SOULED_FRIEND_DIED, _on_fedhas_friend_death(nullptr) }, }, // GOD_CHEIBRIADOS, { @@ -449,6 +426,7 @@ static peeve_map divine_peeves[] = peeve_map(), // GOD_RU, peeve_map(), +#if TAG_MAJOR_VERSION == 34 // GOD_PAKELLAS { { DID_CHANNEL, { @@ -456,6 +434,7 @@ static peeve_map divine_peeves[] = 1, 1, } }, }, +#endif // GOD_USKAYAW, peeve_map(), // GOD_HEPLIAKLQANA, @@ -771,51 +750,11 @@ static like_map divine_likes[] = }, // GOD_SIF_MUNA, { - { DID_KILL_LIVING, _on_kill("you kill living beings", MH_NATURAL, false, - [](int &piety, int &denom, - const monster* victim) - { - denom *= 3; - } - ) }, - { DID_KILL_UNDEAD, _on_kill("you destroy the undead", MH_UNDEAD, false, - [](int &piety, int &denom, - const monster* victim) - { - denom *= 3; - } - ) }, - { DID_KILL_DEMON, _on_kill("you kill demons", MH_DEMONIC, false, - [](int &piety, int &denom, - const monster* victim) - { - denom *= 3; - } - ) }, - { DID_KILL_HOLY, _on_kill("you kill holy beings", MH_HOLY, false, - [](int &piety, int &denom, - const monster* victim) - { - denom *= 3; - } - ) }, - { DID_KILL_NONLIVING, _on_kill("you destroy nonliving beings", - MH_NONLIVING, false, - [](int &piety, int &denom, - const monster* victim) - { - denom *= 3; - } - ) }, - { DID_SPELL_PRACTISE, { - "you train your various spell casting skills", true, - 0, 0, 0, nullptr, - [] (int &piety, int &denom, const monster* /*victim*/) - { - // piety = denom = level at the start of the function - denom = 6; - } - } }, + { DID_KILL_LIVING, KILL_LIVING_RESPONSE }, + { DID_KILL_UNDEAD, KILL_UNDEAD_RESPONSE }, + { DID_KILL_DEMON, KILL_DEMON_RESPONSE }, + { DID_KILL_HOLY, KILL_HOLY_RESPONSE }, + { DID_KILL_NONLIVING, KILL_NONLIVING_RESPONSE }, }, // GOD_TROG, { @@ -873,9 +812,11 @@ static like_map divine_likes[] = like_map(), // GOD_FEDHAS, { - { DID_ROT_CARRION, { - "corpses rot away", false, 0, 0, 0, " appreciates ongoing decay." - } }, + { DID_KILL_LIVING, KILL_LIVING_RESPONSE }, + { DID_KILL_UNDEAD, KILL_UNDEAD_RESPONSE }, + { DID_KILL_DEMON, KILL_DEMON_RESPONSE }, + { DID_KILL_HOLY, KILL_HOLY_RESPONSE }, + { DID_KILL_NONLIVING, KILL_NONLIVING_RESPONSE }, }, // GOD_CHEIBRIADOS, { @@ -949,6 +890,7 @@ static like_map divine_likes[] = } } }, }, +#if TAG_MAJOR_VERSION == 34 // GOD_PAKELLAS, { { DID_KILL_LIVING, _on_kill("you kill living beings", MH_NATURAL, false, @@ -964,6 +906,7 @@ static like_map divine_likes[] = { DID_KILL_HOLY, KILL_HOLY_RESPONSE }, { DID_KILL_NONLIVING, KILL_NONLIVING_RESPONSE }, }, +#endif // GOD_USKAYAW { { DID_HURT_FOE, { @@ -1232,11 +1175,5 @@ bool god_punishes_spell(spell_type spell, god_type god) if (map_find(divine_peeves[god], DID_HASTY) && spell == SPELL_SWIFTNESS) return true; - if (map_find(divine_peeves[god], DID_CORPSE_VIOLATION) - && is_corpse_violating_spell(spell)) - { - return true; - } - return false; } diff --git a/crawl-ref/source/god-item.cc b/crawl-ref/source/god-item.cc index 84b708d2bdbe..e5c8bc991018 100644 --- a/crawl-ref/source/god-item.cc +++ b/crawl-ref/source/god-item.cc @@ -125,41 +125,6 @@ bool is_potentially_evil_item(const item_def& item, bool calc_unid) return false; } -// This is a subset of is_evil_item(). -bool is_corpse_violating_item(const item_def& item, bool calc_unid) -{ - bool retval = false; - - if (is_unrandom_artefact(item)) - { - const unrandart_entry* entry = get_unrand_entry(item.unrand_idx); - - if (entry->flags & UNRAND_FLAG_CORPSE_VIOLATING) - return true; - } - - if (item.base_type == OBJ_WEAPONS - && (calc_unid || item_brand_known(item)) - && get_weapon_brand(item) == SPWPN_REAPING) - { - return true; - } - - if (!calc_unid && !item_type_known(item)) - return false; - - switch (item.base_type) - { - case OBJ_BOOKS: - retval = _is_book_type(item, is_corpse_violating_spell); - break; - default: - break; - } - - return retval; -} - /** * Do good gods always hate use of this item? * @@ -362,11 +327,12 @@ bool is_channeling_item(const item_def& item, bool calc_unid) && item.sub_type == MISC_CRYSTAL_BALL_OF_ENERGY; } -bool is_corpse_violating_spell(spell_type spell) +bool is_wizardly_item(const item_def& item, bool calc_unid) { - spell_flags flags = get_spell_flags(spell); + if (is_unrandom_artefact(item, UNRAND_BATTLE)) + return true; - return testbits(flags, spflag::corpse_violating); + return false; } /** @@ -433,15 +399,13 @@ vector item_conducts(const item_def &item) if (item_is_spellbook(item)) conducts.push_back(DID_SPELL_MEMORISE); - if (item.sub_type == BOOK_MANUAL && item_type_known(item) + if ((item.sub_type == BOOK_MANUAL && item_type_known(item) && is_magic_skill((skill_type)item.plus)) + || is_wizardly_item(item)) { conducts.push_back(DID_SPELL_PRACTISE); } - if (is_corpse_violating_item(item, false)) - conducts.push_back(DID_CORPSE_VIOLATION); - if (_is_potentially_hasty_item(item) || is_hasty_item(item, false)) conducts.push_back(DID_HASTY); diff --git a/crawl-ref/source/god-item.h b/crawl-ref/source/god-item.h index af3fc81f0131..3c4eee816ff3 100644 --- a/crawl-ref/source/god-item.h +++ b/crawl-ref/source/god-item.h @@ -6,13 +6,12 @@ bool is_holy_item(const item_def& item, bool calc_unid = true); bool is_potentially_evil_item(const item_def& item, bool calc_unid = true); -bool is_corpse_violating_item(const item_def& item, bool calc_unid = true); bool is_evil_item(const item_def& item, bool calc_unid = true); bool is_unclean_item(const item_def& item, bool calc_unid = true); bool is_chaotic_item(const item_def& item, bool calc_unid = true); bool is_hasty_item(const item_def& item, bool calc_unid = true); +bool is_wizardly_item(const item_def& item, bool calc_unid = true); bool is_channeling_item(const item_def& item, bool calc_unid = true); -bool is_corpse_violating_spell(spell_type spell); bool is_evil_spell(spell_type spell); bool is_unclean_spell(spell_type spell); bool is_chaotic_spell(spell_type spell); diff --git a/crawl-ref/source/god-passive.cc b/crawl-ref/source/god-passive.cc index b49903d7eaf2..3337a19d5cdf 100644 --- a/crawl-ref/source/god-passive.cc +++ b/crawl-ref/source/god-passive.cc @@ -220,10 +220,7 @@ static const vector god_passives[] = }, // Sif Muna - { - { 2, passive_t::miscast_protection, - "GOD NOW protects you from miscasts" }, - }, + { }, // Trog { @@ -397,6 +394,7 @@ static const vector god_passives[] = }, }, +#if TAG_MAJOR_VERSION == 34 // Pakellas { { -1, passive_t::no_mp_regen, @@ -407,6 +405,7 @@ static const vector god_passives[] = "GOD NOW collects and distills excess magic from your kills" }, }, +#endif // Uskayaw { }, @@ -1063,6 +1062,57 @@ int gozag_gold_in_los(actor *whom) return gold_count; } +void gozag_detect_level_gold(bool count) +{ + vector gold_piles; + vector gold_places; + int gold = 0; + for (rectangle_iterator ri(0); ri; ++ri) + { + for (stack_iterator j(*ri); j; ++j) + { + if (j->base_type == OBJ_GOLD && !(j->flags & ISFLAG_UNOBTAINABLE)) + { + gold += j->quantity; + gold_piles.push_back(&(*j)); + gold_places.push_back(*ri); + } + } + } + + if (!player_in_branch(BRANCH_ABYSS) && count) + you.attribute[ATTR_GOLD_GENERATED] += gold; + + if (have_passive(passive_t::detect_gold)) + { + for (unsigned int i = 0; i < gold_places.size(); i++) + { + bool detected = false; + int dummy = gold_piles[i]->index(); + coord_def &pos = gold_places[i]; + unlink_item(dummy); + move_item_to_grid(&dummy, pos, true); + if (!env.map_knowledge(pos).item() + || env.map_knowledge(pos).item()->base_type != OBJ_GOLD) + { + detected = true; + update_item_at(pos, true); + } + // the pile can still remain undetected if it is not in + // you.visible_igrd, for example if it is under deep water and the + // player will not be able to see it. + if (detected && env.map_knowledge(pos).item()) + { + env.map_knowledge(pos).flags |= MAP_DETECTED_ITEM; +#ifdef USE_TILE + // force an update for gold generated during Abyss shifts + tiles.update_minimap(pos); +#endif + } + } + } +} + int qazlal_sh_boost(int piety) { if (!have_passive(passive_t::storm_shield)) diff --git a/crawl-ref/source/god-passive.h b/crawl-ref/source/god-passive.h index b11100f1ad7a..c3de3467aeed 100644 --- a/crawl-ref/source/god-passive.h +++ b/crawl-ref/source/god-passive.h @@ -147,9 +147,6 @@ enum class passive_t /// Torment resistance, piety dependent. resist_torment, - /// Protection against miscasts. Piety dependent. - miscast_protection, - /// Protection against necromancy miscasts and mummy death curses. miscast_protection_necromancy, @@ -281,6 +278,7 @@ int ash_skill_boost(skill_type sk, int scale); bool ash_has_skill_boost(skill_type sk); map ash_get_boosted_skills(eq_type type); int gozag_gold_in_los(actor* whom); +void gozag_detect_level_gold(bool count); int qazlal_sh_boost(int piety = you.piety); int tso_sh_boost(); void qazlal_storm_clouds(); diff --git a/crawl-ref/source/god-type.h b/crawl-ref/source/god-type.h index 37fd386566fb..a37024b785a7 100644 --- a/crawl-ref/source/god-type.h +++ b/crawl-ref/source/god-type.h @@ -25,7 +25,9 @@ enum god_type GOD_GOZAG, GOD_QAZLAL, GOD_RU, +#if TAG_MAJOR_VERSION == 34 GOD_PAKELLAS, +#endif GOD_USKAYAW, GOD_HEPLIAKLQANA, GOD_WU_JIAN, diff --git a/crawl-ref/source/god-wrath.cc b/crawl-ref/source/god-wrath.cc index 4842dee62054..4ab2fb812f0a 100644 --- a/crawl-ref/source/god-wrath.cc +++ b/crawl-ref/source/god-wrath.cc @@ -7,11 +7,14 @@ #include "god-wrath.h" +#include +#include #include #include "areas.h" #include "artefact.h" #include "attitude-change.h" +#include "butcher.h" #include "cleansing-flame-source-type.h" #include "coordit.h" #include "database.h" @@ -26,6 +29,7 @@ #include "item-prop.h" #include "item-status-flag-type.h" #include "items.h" +#include "losglobal.h" #include "makeitem.h" #include "message.h" #include "misc.h" @@ -85,7 +89,9 @@ static const char *_god_wrath_adjectives[] = "greed", // Gozag (unused) "adversity", // Qazlal "disappointment", // Ru +#if TAG_MAJOR_VERSION == 34 "progress", // Pakellas +#endif "fury", // Uskayaw "memory", // Hepliaklqana (unused) "rancor", // Wu Jian @@ -609,6 +615,31 @@ static bool _makhleb_retribution() return _makhleb_summon_servants(); } +static int _count_corpses_in_los(vector *positions) +{ + int count = 0; + + for (radius_iterator rad(you.pos(), LOS_NO_TRANS, true); rad; + ++rad) + { + if (actor_at(*rad)) + continue; + + for (stack_iterator stack_it(*rad); stack_it; ++stack_it) + { + if (stack_it->is_type(OBJ_CORPSES, CORPSE_BODY)) + { + if (positions) + positions->push_back(stack_it); + count++; + break; + } + } + } + + return count; +} + static bool _kikubaaqudgha_retribution() { // death/necromancy theme @@ -617,7 +648,7 @@ static bool _kikubaaqudgha_retribution() god_speaks(god, coinflip() ? "You hear Kikubaaqudgha cackling." : "Kikubaaqudgha's malice focuses upon you."); - if (!count_corpses_in_los(nullptr) || random2(you.experience_level) > 4) + if (!_count_corpses_in_los(nullptr) || random2(you.experience_level) > 4) { // Either zombies, or corpse rot + skeletons. kiku_receive_corpses(you.experience_level * 4); @@ -1301,6 +1332,280 @@ static void _fedhas_elemental_miscast() _god_wrath_name(god)); } +// Collect lists of points that are within LOS (under the given env map), +// unoccupied, and not solid (walls/statues). +static void _collect_radius_points(vector > &radius_points, + const coord_def &origin, los_type los) +{ + radius_points.clear(); + radius_points.resize(LOS_RADIUS); + + // Just want to associate a point with a distance here for convenience. + typedef pair coord_dist; + + // Using a priority queue because squares don't make very good circles at + // larger radii. We will visit points in order of increasing euclidean + // distance from the origin (not path distance). We want a min queue + // based on the distance, so we use greater_second as the comparator. + priority_queue, + greater_second > fringe; + + fringe.push(coord_dist(origin, 0)); + + set visited_indices; + + int current_r = 1; + int current_thresh = current_r * (current_r + 1); + + int max_distance = LOS_RADIUS * LOS_RADIUS + 1; + + while (!fringe.empty()) + { + coord_dist current = fringe.top(); + // We're done here once we hit a point that is farther away from the + // origin than our maximum permissible radius. + if (current.second > max_distance) + break; + + fringe.pop(); + + int idx = current.first.x + current.first.y * X_WIDTH; + if (!visited_indices.insert(idx).second) + continue; + + while (current.second > current_thresh) + { + current_r++; + current_thresh = current_r * (current_r + 1); + } + + // We don't include radius 0. This is also a good place to check if + // the squares are already occupied since we want to search past + // occupied squares but don't want to consider them valid targets. + if (current.second && !actor_at(current.first)) + radius_points[current_r - 1].push_back(current.first); + + for (adjacent_iterator i(current.first); i; ++i) + { + coord_dist temp(*i, current.second); + + // If the grid is out of LOS, skip it. + if (!cell_see_cell(origin, temp.first, los)) + continue; + + coord_def local = temp.first - origin; + + temp.second = local.abs(); + + idx = temp.first.x + temp.first.y * X_WIDTH; + + if (!visited_indices.count(idx) + && in_bounds(temp.first) + && !cell_is_solid(temp.first)) + { + fringe.push(temp); + } + } + + } +} + +// Basically we want to break a circle into n_arcs equal sized arcs and find +// out which arc the input point pos falls on. +static int _arc_decomposition(const coord_def & pos, int n_arcs) +{ + float theta = atan2((float)pos.y, (float)pos.x); + + if (pos.x == 0 && pos.y != 0) + theta = pos.y > 0 ? PI / 2 : -PI / 2; + + if (theta < 0) + theta += 2 * PI; + + float arc_angle = 2 * PI / n_arcs; + + theta += arc_angle / 2.0f; + + if (theta >= 2 * PI) + theta -= 2 * PI; + + return static_cast (theta / arc_angle); +} + +static int _place_ring(vector &ring_points, + const coord_def &origin, mgen_data prototype, + int n_arcs, int arc_occupancy, int &seen_count) +{ + shuffle_array(ring_points); + + int target_amount = ring_points.size(); + int spawned_count = 0; + seen_count = 0; + + vector arc_counts(n_arcs, arc_occupancy); + + for (unsigned i = 0; + spawned_count < target_amount && i < ring_points.size(); + i++) + { + int direction = _arc_decomposition(ring_points.at(i) + - origin, n_arcs); + + if (arc_counts[direction]-- <= 0) + continue; + + prototype.pos = ring_points.at(i); + + if (create_monster(prototype, false)) + { + spawned_count++; + if (you.see_cell(ring_points.at(i))) + seen_count++; + } + } + + return spawned_count; +} + +template +static bool less_second(const T & left, const T & right) +{ + return left.second < right.second; +} + +typedef pair point_distance; + +// Find the distance from origin to each of the targets, those results +// are stored in distances (which is the same size as targets). Exclusion +// is a set of points which are considered disconnected for the search. +static void _path_distance(const coord_def& origin, + const vector& targets, + set exclusion, + vector& distances) +{ + queue fringe; + fringe.push(point_distance(origin,0)); + distances.clear(); + distances.resize(targets.size(), INT_MAX); + + while (!fringe.empty()) + { + point_distance current = fringe.front(); + fringe.pop(); + + // did we hit a target? + for (unsigned i = 0; i < targets.size(); ++i) + { + if (current.first == targets[i]) + { + distances[i] = current.second; + break; + } + } + + for (adjacent_iterator adj_it(current.first); adj_it; ++adj_it) + { + int idx = adj_it->x + adj_it->y * X_WIDTH; + if (you.see_cell(*adj_it) + && !feat_is_solid(env.grid(*adj_it)) + && *adj_it != you.pos() + && exclusion.insert(idx).second) + { + monster* temp = monster_at(*adj_it); + if (!temp || (temp->attitude == ATT_HOSTILE + && !temp->is_stationary())) + { + fringe.push(point_distance(*adj_it, current.second+1)); + } + } + } + } +} + +// Find the minimum distance from each point of origin to one of the targets +// The distance is stored in 'distances', which is the same size as origins. +static void _point_point_distance(const vector& origins, + const vector& targets, + vector& distances) +{ + distances.clear(); + distances.resize(origins.size(), INT_MAX); + + // Consider all points of origin as blocked (you can search outward + // from one, but you can't form a path across a different one). + set base_exclusions; + for (coord_def c : origins) + { + int idx = c.x + c.y * X_WIDTH; + base_exclusions.insert(idx); + } + + vector current_distances; + for (unsigned i = 0; i < origins.size(); ++i) + { + // Find the distance from the point of origin to each of the targets. + _path_distance(origins[i], targets, base_exclusions, + current_distances); + + // Find the smallest of those distances + int min_dist = current_distances[0]; + for (unsigned j = 1; j < current_distances.size(); ++j) + if (current_distances[j] < min_dist) + min_dist = current_distances[j]; + + distances[i] = min_dist; + } +} + +// So the idea is we want to decide which adjacent tiles are in the most +// 'danger' We claim danger is proportional to the minimum distances from the +// point to a (hostile) monster. This function carries out at most 7 searches +// to calculate the distances in question. +static bool _prioritise_adjacent(const coord_def &target, + vector& candidates) +{ + radius_iterator los_it(target, LOS_NO_TRANS, true); + + vector mons_positions; + // collect hostile monster positions in LOS + for (; los_it; ++los_it) + { + monster* hostile = monster_at(*los_it); + + if (hostile && hostile->attitude == ATT_HOSTILE + && you.can_see(*hostile)) + { + mons_positions.push_back(hostile->pos()); + } + } + + if (mons_positions.empty()) + { + shuffle_array(candidates); + return true; + } + + vector distances; + + _point_point_distance(candidates, mons_positions, distances); + + vector possible_moves(candidates.size()); + + for (unsigned i = 0; i < possible_moves.size(); ++i) + { + possible_moves[i].first = candidates[i]; + possible_moves[i].second = distances[i]; + } + + sort(possible_moves.begin(), possible_moves.end(), + less_second); + + for (unsigned i = 0; i < candidates.size(); ++i) + candidates[i] = possible_moves[i].first; + + return true; +} + /** * Summon Fedhas's oklobs & mushrooms around the player. * @@ -1314,7 +1619,7 @@ static bool _fedhas_summon_plants() // We are going to spawn some oklobs but first we need to find // out a little about the situation. vector > radius_points; - collect_radius_points(radius_points, you.pos(), LOS_NO_TRANS); + _collect_radius_points(radius_points, you.pos(), LOS_NO_TRANS); int max_idx = 3; unsigned max_points = radius_points[max_idx].size(); @@ -1338,22 +1643,16 @@ static bool _fedhas_summon_plants() temp.cls = MONS_PLANT; - place_ring(radius_points[0], - you.pos(), - temp, - 1, radius_points[0].size(), - seen_count); + _place_ring(radius_points[0], you.pos(), temp, 1, + radius_points[0].size(), seen_count); if (seen_count > 0) success = true; temp.cls = MONS_OKLOB_PLANT; - place_ring(radius_points[max_idx], - you.pos(), - temp, - random_range(3, 8), 1, - seen_count); + _place_ring(radius_points[max_idx], you.pos(), temp, + random_range(3, 8), 1, seen_count); if (seen_count > 0) success = true; @@ -1364,7 +1663,7 @@ static bool _fedhas_summon_plants() { unsigned target_count = random_range(2, 8); if (target_count < radius_points[0].size()) - prioritise_adjacent(you.pos(), radius_points[0]); + _prioritise_adjacent(you.pos(), radius_points[0]); else target_count = radius_points[0].size(); @@ -1389,6 +1688,54 @@ static bool _fedhas_summon_plants() return true; } +static int _fedhas_corpse_spores(beh_type attitude) +{ + vector positions; + int count = _count_corpses_in_los(&positions); + ASSERT(attitude != BEH_FRIENDLY || count > 0); + + if (count == 0) + return count; + + for (const stack_iterator &si : positions) + { + count++; + + if (monster *plant = create_monster(mgen_data(MONS_BALLISTOMYCETE_SPORE, + attitude, + si->pos, + MHITNOT, + MG_FORCE_PLACE, + GOD_FEDHAS) + .set_summoned(&you, 0, 0))) + { + plant->flags |= MF_NO_REWARD; + + if (attitude == BEH_FRIENDLY) + { + plant->flags |= MF_ATT_CHANGE_ATTEMPT; + + mons_make_god_gift(*plant, GOD_FEDHAS); + + plant->behaviour = BEH_WANDER; + plant->foe = MHITNOT; + } + } + + if (mons_skeleton(si->mon_type)) + turn_corpse_into_skeleton(*si); + else + { + item_was_destroyed(*si); + destroy_item(si->index()); + } + } + + viewwindow(false); + + return count; +} + /** * Call down the wrath of Fedhas upon the player! * @@ -1409,7 +1756,7 @@ static bool _fedhas_retribution() case 0: // Try and spawn some hostile ballistomycete spores, if none are created // fall through to the elemental miscast effects. - if (fedhas_corpse_spores(BEH_HOSTILE)) + if (_fedhas_corpse_spores(BEH_HOSTILE)) { simple_god_message(" produces spores.", god); return true; @@ -1755,7 +2102,9 @@ bool divine_retribution(god_type god, bool no_bonus, bool force) case GOD_GOZAG: case GOD_RU: case GOD_HEPLIAKLQANA: +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: +#endif // No reduction with time. return false; @@ -1810,7 +2159,7 @@ static void _tso_blasts_cleansing_flame(const char *message) // damage is 2d(pow), *3/2 for undead and demonspawn cleansing_flame(5 + (you.experience_level * 7) / 12, - CLEANSING_FLAME_TSO, you.pos()); + cleansing_flame_source::tso, you.pos()); } static void _god_smites_you(god_type god, const char *message, diff --git a/crawl-ref/source/hints.cc b/crawl-ref/source/hints.cc index 4621153f4806..88434fe6544d 100644 --- a/crawl-ref/source/hints.cc +++ b/crawl-ref/source/hints.cc @@ -29,6 +29,7 @@ #include "macro.h" #include "message.h" #include "mutation.h" +#include "outer-menu.h" #include "nearby-danger.h" #include "options.h" #include "output.h" @@ -40,6 +41,7 @@ #include "state.h" #include "stringutil.h" #include "terrain.h" +#include "tilepick-p.h" #include "travel.h" #include "viewchar.h" #include "viewgeom.h" @@ -157,59 +159,125 @@ static string _print_hints_menu(hints_types type) break; } - return make_stringf("%c - %s %s %s\n", + return make_stringf("%c - %s %s %s", letter, species_name(_get_hints_species(type)).c_str(), get_job_name(_get_hints_job(type)), desc); } +static void _fill_newgame_choice_for_hints(newgame_def& choice, hints_types type) +{ + choice.species = _get_hints_species(type); + choice.job = _get_hints_job(type); + // easiest choice for fighters + choice.weapon = choice.job == JOB_HUNTER ? WPN_SHORTBOW + : WPN_HAND_AXE; +} + // Hints mode selection screen and choice. void pick_hints(newgame_def& choice) { string prompt = "You must be new here indeed!" "\n\n" - "You can be:" - "\n"; - for (int i = 0; i < HINT_TYPES_NUM; i++) - prompt += _print_hints_menu((hints_types)i); - prompt += "\nEsc - Quit" - "\n* - Random hints mode character" - ""; + "You can be:"; auto prompt_ui = make_shared(formatted_string::parse_string(prompt)); - bool done = false; - int keyn; - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { - if (ev.type != WME_KEYDOWN) - return false; - keyn = ev.key.keysym.sym; + auto vbox = make_shared(Box::VERT); + vbox->align_cross = Widget::Align::STRETCH; + vbox->add_child(prompt_ui); - // Random choice. - if (keyn == '*' || keyn == '+' || keyn == '!' || keyn == '#') - keyn = 'a' + random2(HINT_TYPES_NUM); + auto main_items = make_shared(true, 1, 3); + main_items->set_margin_for_sdl(15, 0); + main_items->set_margin_for_crt(1, 0); + vbox->add_child(main_items); - // Choose character for hints mode game and set starting values. - if (keyn >= 'a' && keyn <= 'a' + HINT_TYPES_NUM - 1) - { - Hints.hints_type = keyn - 'a'; - choice.species = _get_hints_species(Hints.hints_type); - choice.job = _get_hints_job(Hints.hints_type); - // easiest choice for fighters - choice.weapon = choice.job == JOB_HUNTER ? WPN_SHORTBOW - : WPN_HAND_AXE; + for (int i = 0; i < 3; i++) + { + auto label = make_shared(); + label->set_text(_print_hints_menu(static_cast(i))); - return done = true; - } +#ifdef USE_TILE_LOCAL + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + dolls_data doll; + newgame_def tng = choice; + _fill_newgame_choice_for_hints(tng, static_cast(i)); + fill_doll_for_newgame(doll, tng); + auto tile = make_shared(doll); + tile->set_margin_for_sdl(0, 6, 0, 0); + hbox->add_child(move(tile)); + hbox->add_child(label); +#endif + + auto btn = make_shared(); +#ifdef USE_TILE_LOCAL + hbox->set_margin_for_sdl(4,8); + btn->set_child(move(hbox)); +#else + btn->set_child(move(label)); +#endif + btn->id = i; + btn->hotkey = 'a' + i; + + if (i == 0) + main_items->set_initial_focus(btn.get()); + + main_items->add_button(btn, 0, i); + } + + auto sub_items = make_shared(false, 1, 2); + vbox->add_child(sub_items); - switch (keyn) + int keyn; + bool done = false; + auto menu_item_activated = [&](int id) { + if (id == CK_ESCAPE) { - case 'X': CASE_ESCAPE - return done = true; - default: - return true; + keyn = CK_ESCAPE; + done = true; + return; } - }); + else if (id == '*') + id = random2(HINT_TYPES_NUM); + Hints.hints_type = id; + _fill_newgame_choice_for_hints(choice, static_cast(id)); + done = true; + }; - auto popup = make_shared(prompt_ui); + main_items->on_button_activated = menu_item_activated; + sub_items->on_button_activated = menu_item_activated; + main_items->linked_menus[2] = sub_items; + sub_items->linked_menus[0] = main_items; + + { + auto label = make_shared(formatted_string("Esc - Quit", BROWN)); + auto btn = make_shared(); + btn->set_child(move(label)); + btn->hotkey = CK_ESCAPE; + btn->id = CK_ESCAPE; + sub_items->add_button(btn, 0, 0); + } + { + auto label = make_shared(formatted_string(" * - Random hints mode character", BROWN)); + auto btn = make_shared(); + btn->set_child(move(label)); + btn->hotkey = '*'; + btn->id = '*'; + sub_items->add_button(btn, 0, 1); + } + + auto popup = make_shared(vbox); + + popup->on(Widget::slots.event, [&](wm_event ev) { + if (ev.type != WME_KEYDOWN) + return false; + keyn = ev.key.keysym.sym; + if (keyn == 'X') + return done = true; + // Random choice. + if (keyn == '+' || keyn == '!' || keyn == '#') + ev.key.keysym.sym = '*'; + return false; + }); ui::run_layout(move(popup), done); switch (keyn) @@ -220,7 +288,6 @@ void pick_hints(newgame_def& choice) #endif game_ended(game_exit::abort); case 'X': - cprintf("\nGoodbye!"); #ifdef USE_TILE_WEB tiles.send_exit_reason("cancel"); #endif @@ -349,11 +416,11 @@ void hints_starting_screen() trim_string(text); auto prompt_ui = make_shared(formatted_string::parse_string(text)); - prompt_ui->wrap_text = true; + prompt_ui->set_wrap_text(true); #ifdef USE_TILE_LOCAL - prompt_ui->max_size()[0] = 800; + prompt_ui->max_size().width = 800; #else - prompt_ui->max_size()[0] = 80; + prompt_ui->max_size().width = 80; #endif bool done = false; @@ -1204,9 +1271,9 @@ void learned_something_new(hints_event_type seen_what, coord_def gc) "('" << stringize_glyph(get_item_symbol(SHOW_ITEM_MISSILE)) << "') " - "you've picked up. Missiles like tomahawks and throwing nets " + "you've picked up. Missiles like boomerangs and throwing nets " "can be thrown by hand, but other missiles like arrows and " - "needles require a launcher and training in using it to be " + "bolts require a launcher and training in using it to be " "really effective. " #ifdef USE_TILE_LOCAL "Right-clicking on " @@ -2730,7 +2797,6 @@ formatted_string hints_abilities_info() // aptitude information. string hints_skills_info() { - textcolour(channel_to_colour(MSGCH_TUTORIAL)); ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; string broken = "This screen shows the skill set of your character. " @@ -2749,7 +2815,6 @@ string hints_skills_info() string hints_skill_training_info() { - textcolour(channel_to_colour(MSGCH_TUTORIAL)); ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; string broken = "The training percentage (in brown) " @@ -2765,7 +2830,6 @@ string hints_skill_training_info() string hints_skill_costs_info() { - textcolour(channel_to_colour(MSGCH_TUTORIAL)); ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; string broken = "The training cost (in cyan) " @@ -2780,7 +2844,6 @@ string hints_skill_costs_info() string hints_skill_targets_info() { - textcolour(channel_to_colour(MSGCH_TUTORIAL)); ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; string broken = "Press the letter of a skill to set a training target. " @@ -2794,7 +2857,6 @@ string hints_skill_targets_info() string hints_skills_description_info() { - textcolour(channel_to_colour(MSGCH_TUTORIAL)); ostringstream text; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; string broken = "This screen shows the skill set of your character. " @@ -2841,7 +2903,6 @@ string hints_memorise_info() { // TODO: this should probably be in z or I, but adding it to the memorise // menu was easier for the moment. - //textcolour(channel_to_colour(MSGCH_TUTORIAL)); ostringstream text; vector cmd; text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; @@ -2933,7 +2994,7 @@ static string _hints_throw_stuff(const item_def &item) string result; result = "To do this, type % to fire, then "; - if (!item.slot) + if (item.slot) { result += ""; result += item.slot; @@ -3441,38 +3502,6 @@ string hints_describe_item(const item_def &item) return broken; } -void hints_inscription_info(string prompt) -{ - // Don't print anything if there's not enough space. - if (wherey() >= get_number_of_lines() - 1) - return; - - ostringstream text; - text << "<" << colour_to_str(channel_to_colour(MSGCH_TUTORIAL)) << ">"; - - bool longtext = false; - if (wherey() <= get_number_of_lines() - 8) - { - text << "\n" - "Inscriptions are a powerful concept of Dungeon Crawl.\n" - "You can inscribe items to comment on them \n" - "or to set rules for item interaction. If you are new to Crawl, \n" - "you can safely ignore this feature."; - - longtext = true; - } - - text << "\n" - "(In the main screen, press ?6 for more information.)\n"; - text << ""; - - formatted_string::parse_string(text.str()).display(); - - // Ask a second time, if it's been a longish interruption. - if (longtext && !prompt.empty() && wherey() <= get_number_of_lines() - 2) - formatted_string::parse_string(prompt).display(); -} - // FIXME: With the new targeting system, the hints for interesting monsters // and features ("right-click/press v for more information") are no // longer getting displayed. diff --git a/crawl-ref/source/hints.h b/crawl-ref/source/hints.h index 6ad7c21f9754..bd1886a225dc 100644 --- a/crawl-ref/source/hints.h +++ b/crawl-ref/source/hints.h @@ -181,7 +181,6 @@ string hints_memorise_info(); // Additional information for tutorial players. void check_item_hint(const item_def &item, unsigned int num_old_talents); string hints_describe_item(const item_def &item); -void hints_inscription_info(string prompt); bool hints_pos_interesting(int x, int y); string hints_describe_pos(int x, int y); bool hints_monster_interesting(const monster* mons); diff --git a/crawl-ref/source/hiscores.cc b/crawl-ref/source/hiscores.cc index 970c9e786628..fa96b6569d5b 100644 --- a/crawl-ref/source/hiscores.cc +++ b/crawl-ref/source/hiscores.cc @@ -57,6 +57,7 @@ #endif #include "unwind.h" #include "version.h" +#include "outer-menu.h" using namespace ui; @@ -283,7 +284,7 @@ void hiscores_print_all(int display_count, int format) // Displays high scores using curses. For output to the console, use // hiscores_print_all. -string hiscores_print_list(int display_count, int format, int newest_entry) +string hiscores_print_list(int display_count, int format, int newest_entry, int& start_out) { unwind_bool scorefile_display(crawl_state.updating_scores, true); string ret; @@ -325,48 +326,10 @@ string hiscores_print_list(int display_count, int format, int newest_entry) ret += ""; } + start_out = start; return ret; } -static void _add_hiscore_row(MenuScroller* scroller, scorefile_entry& se, int id) -{ - TextItem* tmp = nullptr; - tmp = new TextItem(); - - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(WHITE); - - tmp->set_text(hiscores_format_single(se)); - tmp->set_description_text(hiscores_format_single_long(se, true)); - tmp->set_id(id); - tmp->set_bounds(coord_def(1,1), coord_def(1,2)); - - scroller->attach_item(tmp); - tmp->set_visible(true); -} - -static void _construct_hiscore_table(MenuScroller* scroller) -{ - FILE *scores = _hs_open("r", _score_file_name()); - - if (scores == nullptr) - return; - - int i; - // read highscore file - for (i = 0; i < SCORE_FILE_ENTRIES; i++) - { - hs_list[i].reset(new scorefile_entry); - if (_hs_read(scores, *hs_list[i]) == false) - break; - } - - _hs_close(scores, _score_file_name()); - - for (int j=0; j get_child_at_offset(int x, int y) override { + return static_pointer_cast(m_root); + } virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; virtual bool on_event(const wm_event& event) override; - bool done; + void on_show(); + + bool done = false; + private: - PrecisionMenu menu; + void _construct_hiscore_table(); + void _add_hiscore_row(scorefile_entry& se, int id); + + Widget* initial_focus = nullptr; + bool have_allocated {false}; + + shared_ptr m_root; + shared_ptr m_description; + shared_ptr m_score_entries; }; -void UIHiscoresMenu::_render() -{ -#ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {1, 1, 1}; - glmanager->set_transform(t, s); -#endif - menu.draw_menu(); -#ifdef USE_TILE_LOCAL - glmanager->reset_transform(); -#endif -} +static int nhsr; -SizeReq UIHiscoresMenu::_get_preferred_size(Direction dim, int prosp_width) +UIHiscoresMenu::UIHiscoresMenu() { - SizeReq ret; - if (!dim) - ret = { 80, 100 }; - else - ret = { 10, 10 }; -#ifdef USE_TILE_LOCAL - const FontWrapper* font = tiles.get_crt_font(); - const int f = !dim ? font->char_width() : font->char_height(); - ret.min *= f; - ret.nat *= f; -#endif - return ret; -} + m_root = make_shared(Widget::VERT); + m_root->_set_parent(this); + m_root->align_cross = Widget::STRETCH; -void UIHiscoresMenu::_allocate_region() -{ - menu.clear(); + auto title_hbox = make_shared(Widget::HORZ); + title_hbox->set_margin_for_sdl(0, 0, 20, 0); + title_hbox->set_margin_for_crt(0, 0, 1, 0); -#ifdef USE_TILE_LOCAL - const FontWrapper* font = tiles.get_crt_font(); - const int max_col = m_region[2]/font->char_width(); - const int max_line = m_region[3]/font->char_height(); -#else - const int max_col = m_region[2] - 1, max_line = m_region[3] - 1; +#ifdef USE_TILE + auto tile = make_shared(); + tile->set_tile(tile_def(TILEG_STARTUP_HIGH_SCORES, TEX_GUI)); + title_hbox->add_child(move(tile)); #endif - const int scores_col_start = 1; - const int descriptor_col_start = 1; - const int scores_row_start = 10; - const int scores_col_end = max_col; - const int scores_row_end = max_line+1; - - menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT); - - MenuScroller* score_entries = new MenuScroller(); + auto title = make_shared(formatted_string( + "Dungeon Crawl Stone Soup: High Scores", YELLOW)); + title->set_margin_for_sdl(0, 0, 0, 16); + title_hbox->add_child(move(title)); - score_entries->init(coord_def(scores_col_start, scores_row_start), - coord_def(scores_col_end, scores_row_end), "score entries"); + title_hbox->align_main = Widget::CENTER; + title_hbox->align_cross = Widget::CENTER; - _construct_hiscore_table(score_entries); + m_description = make_shared(string(9, '\n')); - MenuDescriptor* descriptor = new MenuDescriptor(&menu); - descriptor->init(coord_def(descriptor_col_start, 1), - coord_def(max_col+1, scores_row_start - 1), - "descriptor"); + m_score_entries= make_shared(true, 1, 100); + nhsr = 0; + _construct_hiscore_table(); -#ifdef USE_TILE_LOCAL - BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu); -#else - BlackWhiteHighlighter* highlighter = new BlackWhiteHighlighter(&menu); -#endif - highlighter->init(coord_def(-1,-1), coord_def(-1,-1), "highlighter"); + m_root->add_child(move(title_hbox)); + if (initial_focus) + { + m_root->add_child(m_description); + m_root->add_child(m_score_entries); + } + else + { + auto placeholder = formatted_string("No high scores yet...", DARKGRAY); + m_root->add_child(make_shared(placeholder)); + initial_focus = this; + } +} - MenuFreeform* freeform = new MenuFreeform(); - freeform->init(coord_def(1, 1), coord_def(max_col, max_line), "freeform"); - // This freeform will only contain unfocusable texts - freeform->allow_focus(false); - freeform->set_visible(true); +void UIHiscoresMenu::_construct_hiscore_table() +{ + FILE *scores = _hs_open("r", _score_file_name()); - score_entries->set_visible(true); - descriptor->set_visible(true); - highlighter->set_visible(true); + if (scores == nullptr) + return; - menu.attach_object(freeform); - menu.attach_object(score_entries); - menu.attach_object(descriptor); - menu.attach_object(highlighter); + int i; + // read highscore file + for (i = 0; i < SCORE_FILE_ENTRIES; i++) + { + hs_list[i].reset(new scorefile_entry); + if (_hs_read(scores, *hs_list[i]) == false) + break; + } - menu.set_active_object(score_entries); - score_entries->set_active_item((MenuItem*) nullptr); - score_entries->activate_first_item(); + _hs_close(scores, _score_file_name()); - enable_smart_cursor(false); + for (int j=0; j(); - int key = menu.handle_mouse(mouse_ev); - if (key && key != CK_NO_KEY) + tmp->set_text(formatted_string(hiscores_format_single(se))); + auto btn = make_shared(); + tmp->set_margin_for_sdl(2); + btn->set_child(move(tmp)); + btn->on(Widget::slots.event, [this, id, se](wm_event ev) { + if (ev.type == WME_MOUSEBUTTONUP && ev.mouse_event.button == MouseEvent::LEFT + || ev.type == WME_KEYDOWN && ev.key.keysym.sym == CK_ENTER) + { + _show_morgue(*hs_list[id]); + return true; + } + if (ev.type == WME_FOCUSIN) { - wm_event fake_key = {0}; - fake_key.type = WME_KEYDOWN; - fake_key.key.keysym.sym = key; - on_event(fake_key); + formatted_string desc(hiscores_format_single_long(se, true)); + desc.cprintf(string(max(0, 9-count_linebreaks(desc)), '\n')); + m_description->set_text(move(desc)); } + return false; + }); - if (ev.type == WME_MOUSEMOTION) - _expose(); - return true; - } -#endif + if (!initial_focus) + initial_focus = btn.get(); + m_score_entries->add_button(move(btn), 0, nhsr++); +} - if (ev.type != WME_KEYDOWN) - return false; - int keyn = ev.key.keysym.sym; +void UIHiscoresMenu::_render() +{ + m_root->render(); +} - if (key_is_escape(keyn) || keyn == CK_MOUSE_CMD) - return done = true; +void UIHiscoresMenu::on_show() +{ + ui::set_focused_widget(initial_focus); +} + +SizeReq UIHiscoresMenu::_get_preferred_size(Direction dim, int prosp_width) +{ + return m_root->get_preferred_size(dim, prosp_width); +} - if (menu.process_key(keyn)) +void UIHiscoresMenu::_allocate_region() +{ + if (!have_allocated) { - menu.clear_selections(); - _show_morgue(*hs_list[menu.get_active_item()->get_id()]); + have_allocated = true; + on_show(); } - _expose(); + m_root->allocate_region(m_region); +} +bool UIHiscoresMenu::on_event(const wm_event& ev) +{ + if (ev.type != WME_KEYDOWN) + return false; + int key = ev.key.keysym.sym; + if (key_is_escape(key) || key == CK_MOUSE_CMD) + return done = true; return true; } void show_hiscore_table() { unwind_var sprintmap(crawl_state.map, crawl_state.sprint_map); - - auto vbox = make_shared(Widget::VERT); - auto title = make_shared(formatted_string("Dungeon Crawl Stone Soup: High Scores", YELLOW)); - title->align_self = Widget::CENTER; - title->set_margin_for_sdl({0, 0, 20, 0}); auto hiscore_ui = make_shared(); - vbox->add_child(move(title)); - vbox->add_child(hiscore_ui); - auto popup = make_shared(move(vbox)); - - bool smart_cursor_enabled = is_smart_cursor_enabled(); - + auto popup = make_shared(hiscore_ui); ui::run_layout(move(popup), hiscore_ui->done); - - // Go back to the menu and return the smart cursor to its previous state - enable_smart_cursor(smart_cursor_enabled); } // Trying to supply an appropriate verb for the attack type. -- bwr @@ -834,6 +797,7 @@ void scorefile_entry::init_from(const scorefile_entry &se) zigmax = se.zigmax; scrolls_used = se.scrolls_used; potions_used = se.potions_used; + seed = se.seed; fixup_char_name(); // We could just reset raw_line to "" instead. @@ -1137,6 +1101,8 @@ void scorefile_entry::init_with_fields() scrolls_used = fields->int_field("scrollsused"); potions_used = fields->int_field("potionsused"); + seed = fields->str_field("seed"); + fixup_char_name(); } @@ -1286,6 +1252,8 @@ void scorefile_entry::set_score_fields() const if (!killer_map.empty()) fields->add_field("killermap", "%s", killer_map.c_str()); + fields->add_field("seed", "%s", seed.c_str()); + #ifdef DGL_EXTENDED_LOGFILES const string short_msg = short_kill_message(); fields->add_field("tmsg", "%s", short_msg.c_str()); @@ -1313,7 +1281,7 @@ string scorefile_entry::long_kill_message() const { string msg = death_description(DDV_LOGVERBOSE); msg = make_oneline(msg); - msg[0] = tolower(msg[0]); + msg[0] = tolower_safe(msg[0]); trim_string(msg); return msg; } @@ -1322,7 +1290,7 @@ string scorefile_entry::short_kill_message() const { string msg = death_description(DDV_ONELINE); msg = make_oneline(msg); - msg[0] = tolower(msg[0]); + msg[0] = tolower_safe(msg[0]); trim_string(msg); return msg; } @@ -1571,6 +1539,7 @@ void scorefile_entry::reset() zigmax = 0; scrolls_used = 0; potions_used = 0; + seed.clear(); } static int _award_modified_experience() @@ -1810,6 +1779,7 @@ void scorefile_entry::init(time_t dt) wiz_mode = (you.wizard || you.suppress_wizard ? 1 : 0); explore_mode = (you.explore ? 1 : 0); + seed = make_stringf("%" PRIu64, crawl_state.seed); } string scorefile_entry::hiscore_line(death_desc_verbosity verbosity) const @@ -2013,6 +1983,7 @@ scorefile_entry::character_description(death_desc_verbosity verbosity) const ASSERT(birth_time); desc += " on "; desc += _hiscore_date_string(birth_time); + // TODO: show seed here? desc = _append_sentence_delimiter(desc, "."); desc += _hiscore_newline_string(); @@ -2667,7 +2638,7 @@ string scorefile_entry::death_description(death_desc_verbosity verbosity) const } if (oneline && desc.length() > 2) - desc[1] = tolower(desc[1]); + desc[1] = tolower_safe(desc[1]); // TODO: Eventually, get rid of "..." for cases where the text fits. if (terse) diff --git a/crawl-ref/source/hiscores.h b/crawl-ref/source/hiscores.h index f24102cef5de..081c7dcc60c2 100644 --- a/crawl-ref/source/hiscores.h +++ b/crawl-ref/source/hiscores.h @@ -16,8 +16,7 @@ void logfile_new_entry(const scorefile_entry &se); void hiscores_read_to_memory(); -string hiscores_print_list(int display_count = -1, int format = SCORE_TERSE, - int newest_entry = -1); +string hiscores_print_list(int display_count, int format, int newest_entry, int& start_out); void hiscores_print_all(int display_count = -1, int format = SCORE_TERSE); void show_hiscore_table(); @@ -132,6 +131,8 @@ class scorefile_entry int scrolls_used; // Number of scrolls used. int potions_used; // Number of potions used. + string seed; // Game seed: use string here for simplicity + // even though the seed is really uint64_t mutable unique_ptr fields; diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc index 6c58942ed4d5..6d77d2451f28 100644 --- a/crawl-ref/source/initfile.cc +++ b/crawl-ref/source/initfile.cc @@ -110,11 +110,13 @@ static bool _first_greater(const pair &l, const pair &r) const vector game_options::build_options_list() { +#ifndef DEBUG const bool USING_TOUCH = #if defined(TOUCH_UI) true; #else false; +#endif #endif const bool USING_DGL = #if defined(DGAMELAUNCH) @@ -194,7 +196,11 @@ const vector game_options::build_options_list() new BoolGameOption(SIMPLE_NAME(note_chat_messages), false), new BoolGameOption(SIMPLE_NAME(note_dgl_messages), true), new BoolGameOption(SIMPLE_NAME(clear_messages), false), +#ifdef DEBUG + new BoolGameOption(SIMPLE_NAME(show_more), false), +#else new BoolGameOption(SIMPLE_NAME(show_more), !USING_TOUCH), +#endif new BoolGameOption(SIMPLE_NAME(small_more), false), new BoolGameOption(SIMPLE_NAME(pickup_thrown), true), new BoolGameOption(SIMPLE_NAME(show_travel_trail), USING_DGL), @@ -216,7 +222,6 @@ const vector game_options::build_options_list() new BoolGameOption(SIMPLE_NAME(wall_jump_prompt), false), new BoolGameOption(SIMPLE_NAME(wall_jump_move), false), new BoolGameOption(SIMPLE_NAME(darken_beyond_range), true), - new BoolGameOption(SIMPLE_NAME(dump_book_spells), true), new BoolGameOption(SIMPLE_NAME(arena_dump_msgs), false), new BoolGameOption(SIMPLE_NAME(arena_dump_msgs_all), false), new BoolGameOption(SIMPLE_NAME(arena_list_eq), false), @@ -294,9 +299,6 @@ const vector game_options::build_options_list() new ColourThresholdOption(stat_colour, {"stat_colour", "stat_color"}, "3:red", _first_less), new StringGameOption(SIMPLE_NAME(sound_file_path), ""), -#ifndef DGAMELAUNCH - new BoolGameOption(SIMPLE_NAME(pregen_dungeon), false), -#endif #ifdef DGL_SIMPLE_MESSAGING new BoolGameOption(SIMPLE_NAME(messaging), false), @@ -553,7 +555,7 @@ static map _special_weapon_map = { {"thrown", WPN_THROWN}, {"rocks", WPN_THROWN}, - {"tomahawks", WPN_THROWN}, + {"boomerangs", WPN_THROWN}, {"javelins", WPN_THROWN}, {"random", WPN_RANDOM}, @@ -631,12 +633,12 @@ static fire_type _str_to_fire_types(const string &str) return FIRE_ROCK; else if (str == "javelin") return FIRE_JAVELIN; - else if (str == "tomahawk") - return FIRE_TOMAHAWK; + else if (str == "boomerang") + return FIRE_BOOMERANG; + else if (str == "dart") + return FIRE_DART; else if (str == "net") return FIRE_NET; - else if (str == "return" || str == "returning") - return FIRE_RETURNING; else if (str == "inscribed") return FIRE_INSCRIBED; @@ -838,8 +840,8 @@ void game_options::set_default_activity_interrupts() "interrupt_jewellery_on = interrupt_armour_on", "interrupt_memorise = hp_loss, monster_attack, stat", "interrupt_butcher = interrupt_armour_on, teleport, stat", - "interrupt_bottle_blood = interrupt_butcher", - "interrupt_vampire_feed = interrupt_butcher", + "interrupt_exsanguinate = interrupt_butcher", + "interrupt_revivify = interrupt_butcher", "interrupt_multidrop = hp_loss, monster_attack, teleport, stat", "interrupt_macro = interrupt_multidrop", "interrupt_travel = interrupt_butcher, hungry, hit_monster, " @@ -863,7 +865,7 @@ void game_options::set_default_activity_interrupts() } void game_options::set_activity_interrupt( - FixedBitVector &eints, + FixedBitVector &eints, const string &interrupt) { if (starts_with(interrupt, interrupt_prefix)) @@ -873,21 +875,21 @@ void game_options::set_activity_interrupt( if (!activity_interrupts.count(delay_name)) return report_error("Unknown delay: %s\n", delay_name.c_str()); - FixedBitVector &refints = + FixedBitVector &refints = activity_interrupts[delay_name]; eints |= refints; return; } - activity_interrupt_type ai = get_activity_interrupt(interrupt); - if (ai == NUM_AINTERRUPTS) + activity_interrupt ai = get_activity_interrupt(interrupt); + if (ai == activity_interrupt::COUNT) { return report_error("Delay interrupt name \"%s\" not recognised.\n", interrupt.c_str()); } - eints.set(ai); + eints.set(static_cast(ai)); } void game_options::set_activity_interrupt(const string &activity_name, @@ -900,11 +902,11 @@ void game_options::set_activity_interrupt(const string &activity_name, if (remove_interrupts) { - FixedBitVector refints; + FixedBitVector refints; for (const string &interrupt : interrupts) set_activity_interrupt(refints, interrupt); - for (int i = 0; i < NUM_AINTERRUPTS; ++i) + for (int i = 0; i < NUM_ACTIVITY_INTERRUPTS; ++i) if (refints[i]) eints.set(i, false); } @@ -917,7 +919,7 @@ void game_options::set_activity_interrupt(const string &activity_name, set_activity_interrupt(eints, interrupt); } - eints.set(AI_FORCE_INTERRUPT); + eints.set(static_cast(activity_interrupt::force)); } #if defined(DGAMELAUNCH) @@ -1031,6 +1033,9 @@ void game_options::reset_options() char_set = CSET_DEFAULT; + incremental_pregen = true; + pregen_dungeon = false; + // set it to the .crawlrc default autopickups.reset(); autopickups.set(OBJ_GOLD); @@ -1041,10 +1046,10 @@ void game_options::reset_options() autopickups.set(OBJ_WANDS); autopickups.set(OBJ_FOOD); - confirm_butcher = CONFIRM_AUTO; + confirm_butcher = confirm_butcher_type::normal; auto_butcher = HS_VERY_HUNGRY; - easy_confirm = CONFIRM_SAFE_EASY; - allow_self_target = CONFIRM_PROMPT; + easy_confirm = easy_confirm_type::safe; + allow_self_target = confirm_prompt_type::prompt; skill_focus = SKM_FOCUS_ON; user_note_prefix = ""; @@ -1076,8 +1081,8 @@ void game_options::reset_options() fire_items_start = 0; // start at slot 'a' // Clear fire_order and set up the defaults. - set_fire_order("launcher, return, " - "javelin / tomahawk / stone / rock / net, " + set_fire_order("launcher," + "javelin / boomerang / stone / rock / net / dart, " "inscribed", false, false); @@ -1131,6 +1136,8 @@ void game_options::reset_options() tile_weapon_offsets.second = INT_MAX; tile_shield_offsets.first = INT_MAX; tile_shield_offsets.second = INT_MAX; + tile_viewport_scale = 100; + tile_map_scale = 60; #endif #ifdef USE_TILE_WEB @@ -1149,10 +1156,8 @@ void game_options::reset_options() dump_order.clear(); new_dump_fields("header,hiscore,stats,misc,inventory," "skills,spells,overview,mutations,messages," - "screenshot,monlist,kills,notes"); - if (Version::ReleaseType == VER_ALPHA) - new_dump_fields("vaults"); - new_dump_fields("skill_gains,action_counts"); + "screenshot,monlist,kills,notes,vaults", + "skill_gains,action_counts"); // Currently enabled by default for testing in trunk. if (Version::ReleaseType == VER_ALPHA) new_dump_fields("xp_by_level"); @@ -2327,7 +2332,7 @@ void game_options::set_menu_sort(string field) menu_sort_condition cond(field); // Overrides all previous settings. - if (cond.mtype == MT_ANY) + if (cond.mtype == menu_type::any) sort_menus.clear(); // Override existing values, if necessary. @@ -2654,29 +2659,29 @@ void game_options::read_option_line(const string &str, bool runscript) { // decide when to allow both 'Y'/'N' and 'y'/'n' on yesno() prompts if (field == "none") - easy_confirm = CONFIRM_NONE_EASY; + easy_confirm = easy_confirm_type::none; else if (field == "safe") - easy_confirm = CONFIRM_SAFE_EASY; + easy_confirm = easy_confirm_type::safe; else if (field == "all") - easy_confirm = CONFIRM_ALL_EASY; + easy_confirm = easy_confirm_type::all; } else if (key == "allow_self_target") { if (field == "yes") - allow_self_target = CONFIRM_NONE; + allow_self_target = confirm_prompt_type::none; else if (field == "no") - allow_self_target = CONFIRM_CANCEL; + allow_self_target = confirm_prompt_type::cancel; else if (field == "prompt") - allow_self_target = CONFIRM_PROMPT; + allow_self_target = confirm_prompt_type::prompt; } else if (key == "confirm_butcher") { if (field == "always") - confirm_butcher = CONFIRM_ALWAYS; + confirm_butcher = confirm_butcher_type::always; else if (field == "never") - confirm_butcher = CONFIRM_NEVER; + confirm_butcher = confirm_butcher_type::never; else if (field == "auto") - confirm_butcher = CONFIRM_AUTO; + confirm_butcher = confirm_butcher_type::normal; } else if (key == "auto_butcher") { @@ -3434,13 +3439,6 @@ void game_options::read_option_line(const string &str, bool runscript) } else if (key == "game_seed") { -#ifdef DGAMELAUNCH - // try to avoid confusing online players who put this in their rc - // file. N.b. it is still possible to use the -seed CLO. - report_error("Your rc file specifies a game seed, but this build of " - "crawl does not support seed selection. I will " - "choose a seed randomly."); -#else // special handling because of the large type. uint64_t tmp_seed = 0; if (sscanf(field.c_str(), "%" SCNu64, &tmp_seed)) @@ -3450,8 +3448,66 @@ void game_options::read_option_line(const string &str, bool runscript) if (!seed_from_rc) seed_from_rc = tmp_seed; } + } + else if (key == "pregen_dungeon") + { + // TODO: probably convert the underlying values to some kind of enum + // TODO: store these options in a save? + if (field == "true" || field == "full") + { +#ifdef DGAMELAUNCH + report_error( + "Full pregeneration is not allowed on this build of crawl."); +#else + pregen_dungeon = true; + incremental_pregen = true; // still affects loading games not + // started with full pregen #endif + } + else if (field == "incremental") + { + pregen_dungeon = false; + incremental_pregen = true; + } + else if (field == "false" || field == "classic") + pregen_dungeon = incremental_pregen = false; + else + { + report_error("Unknown value '%s' for pregen_dungeon.", + field.c_str()); + } + } +#ifdef USE_TILE + // TODO: generalize these to an option type? + else if (key == "tile_viewport_scale") + { + float tmp_scale; + if (sscanf(field.c_str(), "%f", &tmp_scale)) + { + tile_viewport_scale = min(1600, max(20, + static_cast(tmp_scale * 100))); + } + else + { + report_error("Expected a decimal value for tile_viewport_scale," + " but got '%s'.", field.c_str()); + } } + else if (key == "tile_map_scale") + { + float tmp_scale; + if (sscanf(field.c_str(), "%f", &tmp_scale)) + { + tile_map_scale = min(1600, max(20, + static_cast(tmp_scale * 100))); + } + else + { + report_error("Expected a decimal value for tile_map_scale," + " but got '%s'.", field.c_str()); + } + } +#endif // Catch-all else, copies option into map else if (runscript) @@ -4468,6 +4524,8 @@ void game_options::write_webtiles_options(const string& name) tiles.json_write_string("tile_display_mode", Options.tile_display_mode); tiles.json_write_int("tile_cell_pixels", Options.tile_cell_pixels); + tiles.json_write_int("tile_viewport_scale", Options.tile_viewport_scale); + tiles.json_write_int("tile_map_scale", Options.tile_map_scale); tiles.json_write_bool("tile_filter_scaling", Options.tile_filter_scaling); tiles.json_write_bool("tile_water_anim", Options.tile_water_anim); tiles.json_write_bool("tile_misc_anim", Options.tile_misc_anim); @@ -4797,6 +4855,7 @@ bool parse_args(int argc, char **argv, bool rc_only) case CLO_SCRIPT: crawl_state.test = true; crawl_state.script = true; + crawl_state.script_args.clear(); if (current < argc - 1) { crawl_state.tests_selected = split_string(",", next_arg); @@ -4805,8 +4864,10 @@ bool parse_args(int argc, char **argv, bool rc_only) current = argc; } else + { end(1, false, "-script must specify comma-separated script names"); + } break; case CLO_BUILDDB: @@ -5083,7 +5144,7 @@ menu_sort_condition::menu_sort_condition(menu_type _mt, int _sort) } menu_sort_condition::menu_sort_condition(const string &s) - : mtype(MT_ANY), sort(-1), cmp() + : mtype(menu_type::any), sort(-1), cmp() { string cp = s; set_menu_type(cp); @@ -5093,7 +5154,7 @@ menu_sort_condition::menu_sort_condition(const string &s) bool menu_sort_condition::matches(menu_type mt) const { - return mtype == MT_ANY || mtype == mt; + return mtype == menu_type::any || mtype == mt; } void menu_sort_condition::set_menu_type(string &s) @@ -5104,11 +5165,11 @@ void menu_sort_condition::set_menu_type(string &s) menu_type mtype; } menu_type_map[] = { - { "any:", MT_ANY }, - { "inv:", MT_INVLIST }, - { "drop:", MT_DROP }, - { "pickup:", MT_PICKUP }, - { "know:", MT_KNOW } + { "any:", menu_type::any }, + { "inv:", menu_type::invlist }, + { "drop:", menu_type::drop }, + { "pickup:", menu_type::pickup }, + { "know:", menu_type::know } }; for (const auto &mi : menu_type_map) diff --git a/crawl-ref/source/invent.cc b/crawl-ref/source/invent.cc index 4f7716524fad..1d2cc7f3e1dc 100644 --- a/crawl-ref/source/invent.cc +++ b/crawl-ref/source/invent.cc @@ -18,6 +18,7 @@ #include "colour.h" #include "command.h" #include "describe.h" +#include "english.h" #include "env.h" #include "food.h" #include "god-item.h" @@ -68,7 +69,7 @@ string InvTitle::get_text(const bool) const } InvEntry::InvEntry(const item_def &i) - : MenuEntry("", MEL_ITEM), item(&i) + : MenuEntry("", MEL_ITEM), item(&i), _has_star(false) { data = const_cast(item); @@ -175,6 +176,11 @@ void InvEntry::select(int qty) MenuEntry::select(qty); } +bool InvEntry::has_star() const +{ + return _has_star; +} + string InvEntry::get_filter_text() const { return item_prefix(*item) + " " + get_text(); @@ -189,10 +195,12 @@ string InvEntry::get_text(bool need_cursor) const const bool nosel = hotkeys.empty(); const char key = nosel ? ' ' : static_cast(hotkeys[0]); - tstr << ' ' << key; + tstr << ' '; if (!nosel || tag == "pickup") { + tstr << key; + if (need_cursor) tstr << '['; else @@ -204,6 +212,8 @@ string InvEntry::get_text(bool need_cursor) const tstr << '-'; else if (selected_qty < quantity) tstr << '#'; + else if (_has_star) + tstr << '*'; else tstr << '+'; @@ -216,12 +226,7 @@ string InvEntry::get_text(bool need_cursor) const tstr << "(" << glyph_to_tagstr(get_item_glyph(*item)) << ")" << " "; tstr << text; - const string str = tstr.str(); - - if (printed_width(str) > get_number_of_cols()) - return chop_tagged_string(str, get_number_of_cols() - 2) + ".."; - else - return str; + return tstr.str(); } void get_class_hotkeys(const int type, vector &glyphs) @@ -306,8 +311,8 @@ void InvEntry::set_show_glyph(bool doshow) } InvMenu::InvMenu(int mflags) - : Menu(mflags, "inventory"), type(MT_INVLIST), pre_select(nullptr), - title_annotate(nullptr) + : Menu(mflags, "inventory"), type(menu_type::invlist), pre_select(nullptr), + title_annotate(nullptr), _mode_special_drop(false) { #ifdef USE_TILE_LOCAL if (Options.tile_menu_icons) @@ -317,6 +322,11 @@ InvMenu::InvMenu(int mflags) InvEntry::set_show_cursor(false); } +bool InvMenu::mode_special_drop() const +{ + return _mode_special_drop; +} + void InvMenu::set_type(menu_type t) { type = t; @@ -349,6 +359,60 @@ void InvMenu::set_title(const string &s) title_annotate)); } +int InvMenu::pre_process(int key) +{ + if (type == menu_type::drop && key == '\\') + { + _mode_special_drop = !_mode_special_drop; + key = CK_NO_KEY; + } + else if (key == ';' + && you.last_unequip != -1 + && (type == menu_type::drop || type == menu_type::invlist)) + { + key = index_to_letter(you.last_unequip); + } + else if (key == '-') + _mode_special_drop = false; + return key; +} + +static bool _item_is_permadrop_candidate(const item_def &item) +{ + // Known, non-artefact items of the types you see on the '\' menu proper. + // (No disabling autopickup for "green fizzy potion", "+3 whip", etc.) + if (item_type_unknown(item)) + return false; + return item.base_type == OBJ_MISCELLANY + || is_stackable_item(item) + || item_type_has_ids(item.base_type); +} + +void InvMenu::select_item_index(int idx, int qty, bool draw_cursor) +{ + if (type != menu_type::drop) + return Menu::select_item_index(idx, qty, draw_cursor); + + InvEntry *ie = static_cast(items[idx]); + + bool should_toggle_star = _item_is_permadrop_candidate(ie->item[0]) + && (ie->has_star() || _mode_special_drop); + + if (should_toggle_star) + { + // Toggle starred items back to selected-but-not-starred in this mode + // instead of turning them all the way off. + qty = _mode_special_drop ? -2 : 0; + ie->set_star(!ie->has_star()); + } + Menu::select_item_index(idx, qty, draw_cursor); +} + +void InvEntry::set_star(bool val) +{ + _has_star = val; +} + static bool _has_melded_armour() { for (int e = EQ_CLOAK; e <= EQ_BODY_ARMOUR; e++) @@ -573,7 +637,7 @@ bool InvEntry::get_tiles(vector& tileset) const { return false; } bool InvMenu::is_selectable(int index) const { - if (type == MT_DROP) + if (type == menu_type::drop) { InvEntry *item = dynamic_cast(items[index]); if (item->is_cursed() && item->is_equipped()) @@ -878,20 +942,21 @@ vector InvMenu::get_selitems() const { InvEntry *inv = dynamic_cast(me); selected_items.emplace_back(inv->hotkeys[0], inv->selected_qty, - inv->item); + inv->item, inv->has_star()); } return selected_items; } string InvMenu::help_key() const { - return type == MT_DROP || type == MT_PICKUP ? "pick-up" : ""; + return type == menu_type::drop || type == menu_type::pickup ? "pick-up" + : ""; } int InvMenu::getkey() const { auto mkey = lastch; - if (type == MT_KNOW && (mkey == 0 || mkey == CK_ENTER)) + if (type == menu_type::know && (mkey == 0 || mkey == CK_ENTER)) return mkey; if (!isaalnum(mkey) && mkey != '$' && mkey != '-' && mkey != '?' @@ -973,14 +1038,14 @@ vector select_items(const vector &items, InvMenu menu; menu.set_type(mtype); menu.set_title(title); - if (mtype == MT_PICKUP) + if (mtype == menu_type::pickup) menu.set_tag("pickup"); menu.load_items(items); int new_flags = noselect ? MF_NOSELECT : MF_MULTISELECT | MF_ALLOW_FILTER; - if (mtype == MT_SELONE) + if (mtype == menu_type::sel_one) { new_flags |= MF_SINGLESELECT; new_flags &= ~MF_MULTISELECT; @@ -1138,9 +1203,9 @@ bool any_items_of_type(int selector, int excluded_slot, bool inspect_floor) } // Use title = nullptr for stock Inventory title -// type = MT_DROP allows the multidrop toggle +// type = menu_type::drop allows the multidrop toggle static unsigned char _invent_select(const char *title = nullptr, - menu_type type = MT_INVLIST, + menu_type type = menu_type::invlist, int item_selector = OSEL_ANY, int excluded_slot = -1, int flags = MF_NOSELECT, @@ -1176,7 +1241,7 @@ void display_inventory() { InvMenu menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING); menu.load_inv_items(OSEL_ANY, -1); - menu.set_type(MT_INVLIST); + menu.set_type(menu_type::invlist); menu.on_single_selection = [](const MenuEntry& item) { @@ -1242,6 +1307,30 @@ static string _drop_selitem_text(const vector *s) s->size() > 1? "s" : ""); } +static string _drop_prompt(bool as_menu_title, bool menu_autopickup_mode) +{ + string prompt_base; + + if (as_menu_title && menu_autopickup_mode) + prompt_base = "Drop (and turn off autopickup for) what? "; + else if (as_menu_title) + prompt_base = "Drop what? "; + else + prompt_base = "Drop what? "; + return prompt_base + slot_description() +#ifdef TOUCH_UI + + " ( or tap header to drop)"; +#else + + " (_ for help)"; +#endif +} + +static string _drop_menu_titlefn(const Menu *m, const string &) +{ + const InvMenu *invmenu = static_cast(m); + return _drop_prompt(true, invmenu->mode_special_drop()); +} + /** * Prompt the player to select zero or more items to drop. * TODO: deduplicate/merge with prompt_invent_item(). @@ -1251,14 +1340,6 @@ static string _drop_selitem_text(const vector *s) */ vector prompt_drop_items(const vector &preselected_items) { - const string prompt = "Drop what? " + slot_description() -#ifdef TOUCH_UI - + " ( or tap header to drop)" -#else - + " (_ for help)" -#endif - ; - unsigned char keyin = '?'; int ret = PROMPT_ABORT; @@ -1278,6 +1359,7 @@ vector prompt_drop_items(const vector &preselected_items) if (need_prompt) { + const string prompt = _drop_prompt(false, false); mprf(MSGCH_PROMPT, "%s (? for menu, Esc to quit)", prompt.c_str()); } @@ -1289,17 +1371,18 @@ vector prompt_drop_items(const vector &preselected_items) need_prompt = true; need_getch = true; - // Note: We handle any "special" character first, so that - // it can be used to override the others. - if (keyin == '?' || keyin == '*' || keyin == ',') + if (keyin == '_') + show_specific_help("pick-up"); + else if (keyin == '?' || keyin == '*' || keyin == ',') { // The "view inventory listing" mode. - const int ch = _invent_select(prompt.c_str(), - MT_DROP, + const int ch = _invent_select("", + menu_type::drop, OSEL_ANY, -1, MF_MULTISELECT | MF_ALLOW_FILTER, - nullptr, &items, + _drop_menu_titlefn, + &items, &Options.drop_filter, _drop_selitem_text, &preselected_items); @@ -1356,6 +1439,11 @@ vector prompt_drop_items(const vector &preselected_items) else break; } + else if (keyin == ';') + { + ret = you.last_unequip; + break; + } else if (!isspace(keyin)) { // We've got a character we don't understand... @@ -1369,10 +1457,7 @@ vector prompt_drop_items(const vector &preselected_items) } if (ret != PROMPT_ABORT) - { - items.emplace_back(ret, count, - ret != PROMPT_GOT_SPECIAL ? &you.inv[ret] : nullptr); - } + items.emplace_back(ret, count, &you.inv[ret]); return items; } @@ -1506,8 +1591,7 @@ static string _operation_verb(operation_types oper) case OPER_WIELD: return "wield"; case OPER_QUAFF: return "quaff"; case OPER_DROP: return "drop"; - case OPER_EAT: return you.species == SP_VAMPIRE ? - "drain" : "eat"; + case OPER_EAT: return "eat"; case OPER_TAKEOFF: return "take off"; case OPER_WEAR: return "wear"; case OPER_PUTON: return "put on"; @@ -1590,7 +1674,10 @@ bool needs_handle_warning(const item_def &item, operation_types oper, return true; if (oper == OPER_ATTACK && god_hates_item(item) - && !you_worship(GOD_PAKELLAS)) +#if TAG_MAJOR_VERSION == 34 + && !you_worship(GOD_PAKELLAS) +#endif + ) { penance = true; return true; @@ -1605,15 +1692,6 @@ bool needs_handle_warning(const item_def &item, operation_types oper, return true; } - if (get_weapon_brand(item) == SPWPN_VAMPIRISM - && you.undead_state() == US_ALIVE - && !you_foodless() - // Don't prompt if you aren't wielding it and you can't. - && (you.hunger_state >= HS_FULL || _is_wielded(item))) - { - return true; - } - if (is_artefact(item) && artefact_property(item, ARTP_CONTAM)) { if (_is_wielded(item) && you_worship(GOD_ZIN)) @@ -1806,7 +1884,7 @@ int prompt_invent_item(const char *prompt, if (!any_items_of_type(type_expect) && type_expect == OSEL_THROWABLE && (oper == OPER_FIRE || oper == OPER_QUIVER) - && mtype == MT_INVLIST) + && mtype == menu_type::invlist) { type_expect = OSEL_ANY; } @@ -1871,12 +1949,15 @@ int prompt_invent_item(const char *prompt, // The "view inventory listing" mode. vector< SelItem > items; current_type_expected = keyin == '*' ? OSEL_ANY : type_expect; + int mflags = MF_SINGLESELECT | MF_ANYPRINTABLE | MF_NO_SELECT_QTY; + if (other_valid_char == '-') + mflags |= MF_SPECIAL_MINUS; keyin = _invent_select( prompt, mtype, current_type_expected, -1, - MF_SINGLESELECT | MF_ANYPRINTABLE | MF_NO_SELECT_QTY, + mflags, nullptr, &items); @@ -1939,6 +2020,11 @@ int prompt_invent_item(const char *prompt, else if (!do_warning || check_warning_inscriptions(you.inv[ret], oper)) break; } + else if (keyin == ';') + { + ret = you.last_unequip; + break; + } else if (!isspace(keyin)) { // We've got a character we don't understand... @@ -1974,6 +2060,27 @@ bool item_is_wieldable(const item_def &item) return is_weapon(item) && you.species != SP_FELID; } +/// Does the item only serve to produce summons or allies? +static bool _item_ally_only(const item_def &item) +{ + if (item.base_type == OBJ_WANDS) + return item.sub_type == WAND_ENSLAVEMENT; + else if (item.base_type == OBJ_MISCELLANY) + { + switch (item.sub_type) + { + case MISC_PHANTOM_MIRROR: + case MISC_HORN_OF_GERYON: + case MISC_BOX_OF_BEASTS: + case MISC_SACK_OF_SPIDERS: + return true; + default: + return false; + } + } + return false; +} + /** * Return whether an item can be evoked. * @@ -2029,6 +2136,14 @@ bool item_is_evokable(const item_def &item, bool reach, bool known, return false; } + // TODO: check other summoning constraints here? + if (_item_ally_only(item) && you.has_mutation(MUT_NO_LOVE)) + { + if (msg) + mpr("That item cannot be used by those hated by all!"); + return false; + } + const bool wielded = !equip || you.equip[EQ_WEAPON] == item.link && !item_is_melded(item); diff --git a/crawl-ref/source/invent.h b/crawl-ref/source/invent.h index 52b17c281e43..451abc3c9e26 100644 --- a/crawl-ref/source/invent.h +++ b/crawl-ref/source/invent.h @@ -70,7 +70,6 @@ DEF_BITFIELD(invent_prompt_flags, invprompt_flag); #define PROMPT_ABORT -1 #define PROMPT_GOT_SPECIAL -2 #define PROMPT_NOTHING -3 -#define PROMPT_INAPPROPRIATE -4 #define SLOT_BARE_HANDS PROMPT_GOT_SPECIAL @@ -81,10 +80,10 @@ struct SelItem int slot; int quantity; const item_def *item; - - SelItem() : slot(0), quantity(0), item(nullptr) { } - SelItem(int s, int q, const item_def *it = nullptr) - : slot(s), quantity(q), item(it) + bool has_star; + SelItem() : slot(0), quantity(0), item(nullptr), has_star(false) { } + SelItem(int s, int q, const item_def *it = nullptr, bool do_star = false) + : slot(s), quantity(q), item(it), has_star(do_star) { } }; @@ -141,6 +140,8 @@ class InvEntry : public MenuEntry } virtual void select(int qty = -1) override; + void set_star(bool); + bool has_star() const; virtual string get_filter_text() const override; @@ -149,6 +150,7 @@ class InvEntry : public MenuEntry private: void add_class_hotkeys(const item_def &i); + bool _has_star; }; class InvMenu : public Menu @@ -194,8 +196,13 @@ class InvMenu : public Menu const menu_sort_condition *find_menu_sort_condition() const; void sort_menu(vector &items, const menu_sort_condition *cond); + // Drop menu only: if true, dropped items are removed from autopickup. + bool mode_special_drop() const; + protected: void do_preselect(InvEntry *ie); + void select_item_index(int idx, int qty, bool draw_cursor = true) override; + int pre_process(int key) override; virtual bool is_selectable(int index) const override; virtual string help_key() const override; @@ -205,6 +212,9 @@ class InvMenu : public Menu invtitle_annotator title_annotate; string temp_title; + +private: + bool _mode_special_drop; }; void get_class_hotkeys(const int type, vector &glyphs); @@ -225,7 +235,7 @@ int prompt_invent_item(const char *prompt, vector select_items( const vector &items, const char *title, bool noselect = false, - menu_type mtype = MT_PICKUP, + menu_type mtype = menu_type::pickup, invtitle_annotator titlefn = nullptr); vector prompt_drop_items(const vector &preselected_items); diff --git a/crawl-ref/source/item-name.cc b/crawl-ref/source/item-name.cc index cff226849b3e..827202695a7a 100644 --- a/crawl-ref/source/item-name.cc +++ b/crawl-ref/source/item-name.cc @@ -15,7 +15,6 @@ #include "areas.h" #include "artefact.h" #include "art-enum.h" -#include "pcg.h" // for make_name()'s use #include "branch.h" #include "butcher.h" #include "cio.h" @@ -61,9 +60,9 @@ #include "viewgeom.h" static bool _is_consonant(char let); -static char _random_vowel(int seed); -static char _random_cons(int seed); -static string _random_consonant_set(int seed); +static char _random_vowel(); +static char _random_cons(); +static string _random_consonant_set(size_t seed); static void _maybe_identify_pack_item() { @@ -335,8 +334,12 @@ static bool _missile_brand_is_prefix(special_missile_type brand) { case SPMSL_POISONED: case SPMSL_CURARE: + case SPMSL_BLINDING: + case SPMSL_FRENZY: case SPMSL_EXPLODING: +#if TAG_MAJOR_VERSION == 34 case SPMSL_STEEL: +#endif case SPMSL_SILVER: return true; default: @@ -365,40 +368,38 @@ const char* missile_brand_name(const item_def &item, mbn_type t) return t == MBN_NAME ? "poisoned" : "poison"; case SPMSL_CURARE: return t == MBN_NAME ? "curare-tipped" : "curare"; +#if TAG_MAJOR_VERSION == 34 case SPMSL_EXPLODING: return t == MBN_TERSE ? "explode" : "exploding"; case SPMSL_STEEL: return "steel"; + case SPMSL_RETURNING: + return t == MBN_TERSE ? "return" : "returning"; + case SPMSL_PENETRATION: + return t == MBN_TERSE ? "penet" : "penetration"; +#endif case SPMSL_SILVER: return "silver"; +#if TAG_MAJOR_VERSION == 34 case SPMSL_PARALYSIS: return "paralysis"; -#if TAG_MAJOR_VERSION == 34 case SPMSL_SLOW: return t == MBN_TERSE ? "slow" : "slowing"; -#endif case SPMSL_SLEEP: return t == MBN_TERSE ? "sleep" : "sleeping"; case SPMSL_CONFUSION: return t == MBN_TERSE ? "conf" : "confusion"; -#if TAG_MAJOR_VERSION == 34 case SPMSL_SICKNESS: return t == MBN_TERSE ? "sick" : "sickness"; #endif case SPMSL_FRENZY: - return "frenzy"; - case SPMSL_RETURNING: - return t == MBN_TERSE ? "return" : "returning"; + return t == MBN_NAME ? "datura-tipped" : "datura"; case SPMSL_CHAOS: return "chaos"; - case SPMSL_PENETRATION: - return t == MBN_TERSE ? "penet" : "penetration"; case SPMSL_DISPERSAL: return t == MBN_TERSE ? "disperse" : "dispersal"; -#if TAG_MAJOR_VERSION == 34 case SPMSL_BLINDING: - return t == MBN_TERSE ? "blind" : "blinding"; -#endif + return t == MBN_NAME ? "atropa-tipped" : "atropa"; case SPMSL_NORMAL: return ""; default: @@ -726,8 +727,8 @@ const char* potion_type_name(int potiontype) case POT_CURE_MUTATION: return "cure mutation"; #endif case POT_MUTATION: return "mutation"; - case POT_BLOOD: return "blood"; #if TAG_MAJOR_VERSION == 34 + case POT_BLOOD: return "blood"; case POT_BLOOD_COAGULATED: return "coagulated blood"; #endif case POT_RESISTANCE: return "resistance"; @@ -1627,9 +1628,6 @@ string item_def::name_aux(description_level_type desc, bool terse, bool ident, buff << ammo_name(static_cast(item_typ)); if (msl_brand != SPMSL_NORMAL -#if TAG_MAJOR_VERSION == 34 - && msl_brand != SPMSL_BLINDING -#endif && !basename && !dbname) { if (terse) @@ -2323,12 +2321,7 @@ class KnownEntry : public InvEntry } } else if (item->base_type == OBJ_MISCELLANY) - { - if (item->sub_type == MISC_PHANTOM_MIRROR) - name = pluralise(item->name(DESC_PLAIN)); - else - name = "miscellaneous"; - } + name = pluralise(item->name(DESC_DBNAME)); else if (item->is_type(OBJ_BOOKS, BOOK_MANUAL)) name = "manuals"; else if (item->base_type == OBJ_GOLD) @@ -2388,9 +2381,12 @@ class KnownEntry : public InvEntry else selected_qty = 0; - // Set the force_autopickup values - const int forceval = (selected_qty == 2 ? -1 : selected_qty); - you.force_autopickup[item->base_type][item->sub_type] = forceval; + if (selected_qty == 2) + set_item_autopickup(*item, AP_FORCE_OFF); + else if (selected_qty == 1) + set_item_autopickup(*item, AP_FORCE_ON); + else + set_item_autopickup(*item, AP_FORCE_NONE); } }; @@ -2462,9 +2458,9 @@ static void _add_fake_item(object_class_type base, int sub, items.push_back(ptmp); - if (you.force_autopickup[base][sub] == 1) + if (you.force_autopickup[base][sub] == AP_FORCE_ON) selected.emplace_back(0, 1, ptmp); - else if (you.force_autopickup[base][sub] == -1) + else if (you.force_autopickup[base][sub] == AP_FORCE_OFF) selected.emplace_back(0, 2, ptmp); } @@ -2473,6 +2469,7 @@ void check_item_knowledge(bool unknown_items) vector items; vector items_missile; //List of missiles should go after normal items vector items_food; //List of foods should come next + vector items_misc; vector items_other; //List of other items should go after everything vector selected_items; @@ -2487,7 +2484,7 @@ void check_item_knowledge(bool unknown_items) if (i == OBJ_JEWELLERY && j >= NUM_RINGS && j < AMU_FIRST_AMULET) continue; - if (i == OBJ_BOOKS && j > MAX_FIXED_BOOK) + if (i == OBJ_BOOKS && (j > MAX_FIXED_BOOK || !unknown_items)) continue; if (item_type_removed(i, j)) @@ -2508,7 +2505,7 @@ void check_item_knowledge(bool unknown_items) for (int ii = 0; ii < NUM_OBJECT_CLASSES; ii++) { object_class_type i = (object_class_type)ii; - if (!item_type_has_ids(i)) + if (i == OBJ_BOOKS || !item_type_has_ids(i)) continue; _add_fake_item(i, get_max_subtype(i), selected_items, items); } @@ -2516,7 +2513,7 @@ void check_item_knowledge(bool unknown_items) for (int i = 0; i < NUM_MISSILES; i++) { #if TAG_MAJOR_VERSION == 34 - if (i == MI_DART) + if (i == MI_NEEDLE) continue; #endif _add_fake_item(OBJ_MISSILES, i, selected_items, items_missile); @@ -2531,12 +2528,31 @@ void check_item_knowledge(bool unknown_items) _add_fake_item(OBJ_FOOD, i, selected_items, items_food); } + for (int i = 0; i < NUM_MISCELLANY; i++) + { + if (i == MISC_HORN_OF_GERYON +#if TAG_MAJOR_VERSION == 34 + || is_deck_type(i) + || i == MISC_BUGGY_EBONY_CASKET + || i == MISC_BUGGY_LANTERN_OF_SHADOWS + || i == MISC_BOTTLED_EFREET + || i == MISC_RUNE_OF_ZOT + || i == MISC_STONE_OF_TREMORS + || i == MISC_XOMS_CHESSBOARD +#endif + || (i == MISC_QUAD_DAMAGE && !crawl_state.game_is_sprint())) + { + continue; + } + _add_fake_item(OBJ_MISCELLANY, i, selected_items, items_misc); + } + // Misc. static const pair misc_list[] = { { OBJ_BOOKS, BOOK_MANUAL }, { OBJ_GOLD, 1 }, - { OBJ_MISCELLANY, NUM_MISCELLANY }, + { OBJ_BOOKS, NUM_BOOKS }, { OBJ_RUNES, NUM_RUNE_TYPES }, }; for (auto e : misc_list) @@ -2546,6 +2562,7 @@ void check_item_knowledge(bool unknown_items) sort(items.begin(), items.end(), _identified_item_names); sort(items_missile.begin(), items_missile.end(), _identified_item_names); sort(items_food.begin(), items_food.end(), _identified_item_names); + sort(items_misc.begin(), items_misc.end(), _identified_item_names); KnownMenu menu; string stitle; @@ -2566,13 +2583,14 @@ void check_item_knowledge(bool unknown_items) menu.set_flags( MF_QUIET_SELECT | MF_ALLOW_FORMATTING | MF_USE_TWO_COLUMNS | ((unknown_items) ? MF_NOSELECT : MF_MULTISELECT | MF_ALLOW_FILTER)); - menu.set_type(MT_KNOW); + menu.set_type(menu_type::know); menu_letter ml; ml = menu.load_items(items, unknown_items ? unknown_item_mangle : known_item_mangle, 'a', false); ml = menu.load_items(items_missile, known_item_mangle, ml, false); ml = menu.load_items(items_food, known_item_mangle, ml, false); + ml = menu.load_items(items_misc, known_item_mangle, ml, false); if (!items_other.empty()) { menu.add_entry(new MenuEntry("Other Items", MEL_SUBTITLE)); @@ -2587,6 +2605,7 @@ void check_item_knowledge(bool unknown_items) deleteAll(items); deleteAll(items_missile); deleteAll(items_food); + deleteAll(items_misc); deleteAll(items_other); if (!all_items_known && (last_char == '\\' || last_char == '-')) @@ -2657,7 +2676,7 @@ void display_runes() if (!crawl_state.game_is_sprint()) { // Add the runes in order of challenge (semi-arbitrary). - for (branch_iterator it(BRANCH_ITER_DANGER); it; ++it) + for (branch_iterator it(branch_iterator_type::danger); it; ++it) { const branch_type br = it->id; if (!connected_branch_can_exist(br)) @@ -2726,7 +2745,7 @@ const size_t RCS_END = RCS_EM; * * @param seed The seed to generate the name from. * The same seed will always generate the same name. - * By default a random number from the RNG. + * By default a random number from the current RNG. * @param name_type The type of name to be generated. * If MNAME_SCROLL, increase length by 6 and force to allcaps. * If MNAME_JIYVA, start with J, do not generate spaces, @@ -2738,16 +2757,18 @@ const size_t RCS_END = RCS_EM; */ string make_name(uint32_t seed, makename_type name_type) { - uint64_t sarg[1] = { static_cast(seed) }; - PcgRNG rng = PcgRNG(sarg, ARRAYSZ(sarg)); + // use the seed to select sequence, rather than seed per se. This is + // because it is not important that the sequence be randomly distributed + // in uint64_t. + rng::subgenerator subgen(you.game_seed, static_cast(seed)); string name; bool has_space = false; // Keep track of whether the name contains a space. size_t len = 3; - len += rng.get_uint32() % 5; - len += (rng.get_uint32() % 5 == 0) ? rng.get_uint32() % 6 : 1; + len += random2(5); + len += (random2(5) == 0) ? random2(6) : 1; if (name_type == MNAME_SCROLL) // scrolls have longer names len += 6; @@ -2764,7 +2785,6 @@ string make_name(uint32_t seed, makename_type name_type) : '\0'; const char penult_char = name.length() > 1 ? name[name.length() - 2] : '\0'; - if (name.empty() && name_type == MNAME_JIYVA) { // Start the name with a predefined letter. @@ -2773,11 +2793,11 @@ string make_name(uint32_t seed, makename_type name_type) else if (name.empty() || prev_char == ' ') { // Start the word with any letter. - name += 'a' + (rng.get_uint32() % 26); + name += 'a' + random2(26); } else if (!has_space && name_type != MNAME_JIYVA && name.length() > 5 && name.length() < len - 4 - && rng.get_uint32() % 5 != 0) // 4/5 chance + && random2(5) != 0) // 4/5 chance { // Hand out a space. name += ' '; @@ -2787,10 +2807,10 @@ string make_name(uint32_t seed, makename_type name_type) || (name.length() > 1 && !_is_consonant(prev_char) && _is_consonant(penult_char) - && rng.get_uint32() % 5 <= 1))) // 2/5 + && random2(5) <= 1))) // 2/5 { // Place a vowel. - const char vowel = _random_vowel(rng.get_uint32()); + const char vowel = _random_vowel(); if (vowel == ' ') { @@ -2814,7 +2834,7 @@ string make_name(uint32_t seed, makename_type name_type) else if (name.length() > 1 && vowel == prev_char && (vowel == 'y' || vowel == 'i' - || rng.get_uint32() % 5 <= 1)) + || random2(5) <= 1)) { // Replace the vowel with something else if the previous // letter was the same, and it's a 'y', 'i' or with 2/5 chance. @@ -2831,7 +2851,7 @@ string make_name(uint32_t seed, makename_type name_type) // Use one of number of predefined letter combinations. if ((len > 3 || !name.empty()) - && rng.get_uint32() % 7 <= 1 // 2/7 chance + && random2(7) <= 1 // 2/7 chance && (!beg || !end)) { const int first = (beg ? RCS_BB : (end ? RCS_BE : RCS_BM)); @@ -2839,7 +2859,7 @@ string make_name(uint32_t seed, makename_type name_type) const int range = last - first; - const int cons_seed = rng.get_uint32() % range + first; + const int cons_seed = random2(range) + first; const string consonant_set = _random_consonant_set(cons_seed); @@ -2850,7 +2870,7 @@ string make_name(uint32_t seed, makename_type name_type) else // Place a single letter instead. { // Pick a random consonant. - name += _random_cons(rng.get_uint32()); + name += _random_cons(); } } @@ -2869,10 +2889,10 @@ string make_name(uint32_t seed, makename_type name_type) && !_is_consonant(name[name.length() - 1]) && (name.length() < len // early exit || (len < 8 - && rng.get_uint32() % 3 != 0))) // 2/3 chance for other short names + && random2(3) != 0))) // 2/3 chance for other short names { // Specifically, add a consonant. - name += _random_cons(rng.get_uint32()); + name += _random_cons(); } if (maxlen != SIZE_MAX) @@ -2884,7 +2904,7 @@ string make_name(uint32_t seed, makename_type name_type) { // convolute & recurse if (name_type == MNAME_JIYVA) - return make_name(rng.get_uint32(), MNAME_JIYVA); + return make_name(rng::get_uint32(), MNAME_JIYVA); name = "plog"; } @@ -2896,7 +2916,7 @@ string make_name(uint32_t seed, makename_type name_type) ASSERT(name[i] != ' '); if (name_type == MNAME_SCROLL || i == 0 || name[i - 1] == ' ') - uppercased_name += toupper(name[i]); + uppercased_name += toupper_safe(name[i]); else uppercased_name += name[i]; } @@ -2921,18 +2941,18 @@ static bool _is_consonant(char let) // Returns a random vowel (a, e, i, o, u with equal probability) or space // or 'y' with lower chances. -static char _random_vowel(int seed) +static char _random_vowel() { static const char vowels[] = "aeiouaeiouaeiouy "; - return vowels[ seed % (sizeof(vowels) - 1) ]; + return vowels[random2(sizeof(vowels) - 1)]; } // Returns a random consonant with not quite equal probability. // Does not include 'y'. -static char _random_cons(int seed) +static char _random_cons() { static const char consonants[] = "bcdfghjklmnpqrstvwxzcdfghlmnrstlmnrst"; - return consonants[ seed % (sizeof(consonants) - 1) ]; + return consonants[random2(sizeof(consonants) - 1)]; } /** @@ -2944,7 +2964,7 @@ static char _random_cons(int seed) * @return A random length 2 or 3 consonant set; e.g. "kl", "str", etc. * If the seed is out of bounds, return ""; */ -static string _random_consonant_set(int seed) +static string _random_consonant_set(size_t c) { // Pick a random combination of consonants from the set below. // begin -> [RCS_BB, RCS_EB) = [ 0, 27) @@ -2974,9 +2994,9 @@ static string _random_consonant_set(int seed) }; COMPILE_CHECK(ARRAYSZ(consonant_sets) == RCS_END); - ASSERT_RANGE(seed, 0, (int) ARRAYSZ(consonant_sets)); + ASSERT_RANGE(c, 0, ARRAYSZ(consonant_sets)); - return consonant_sets[seed]; + return consonant_sets[c]; } /** @@ -3016,10 +3036,10 @@ static void _test_jiyva_names(const string& fname) sysfail("can't write test output"); string longest; - seed_rng(27); + rng::seed(27); for (int i = 0; i < 1000000; i++) { - const string name = make_name(get_uint32(), MNAME_JIYVA); + const string name = make_name(rng::get_uint32(), MNAME_JIYVA); ASSERT(name[0] == 'J'); if (name.length() > longest.length()) longest = name; @@ -3041,7 +3061,7 @@ void make_name_tests() _test_jiyva_names("jiyva_names.out"); _test_scroll_names("scroll_names.out"); - seed_rng(27); + rng::seed(27); for (int i = 0; i < 1000000; ++i) make_name(); } @@ -3492,7 +3512,7 @@ bool is_useless_item(const item_def &item, bool temp) case POT_LIGNIFY: return you.undead_state(temp) && (you.species != SP_VAMPIRE - || temp && you.hunger_state < HS_SATIATED); + || temp && !you.vampire_alive); case POT_FLIGHT: return you.permanent_flight() @@ -3503,10 +3523,8 @@ bool is_useless_item(const item_def &item, bool temp) return you.species == SP_VAMPIRE || you.get_mutation_level(MUT_CARNIVOROUS) > 0; case POT_BLOOD_COAGULATED: -#endif case POT_BLOOD: return you.species != SP_VAMPIRE; -#if TAG_MAJOR_VERSION == 34 case POT_DECAY: return you.res_rotting(temp) > 0; case POT_STRONG_POISON: @@ -3540,7 +3558,7 @@ bool is_useless_item(const item_def &item, bool temp) case AMU_RAGE: return you.undead_state(temp) && (you.species != SP_VAMPIRE - || temp && you.hunger_state < HS_SATIATED) + || temp && !you.vampire_alive) || you.species == SP_FORMICID || you.get_mutation_level(MUT_NO_ARTIFICE); @@ -3567,13 +3585,14 @@ bool is_useless_item(const item_def &item, bool temp) case AMU_REGENERATION: return you.get_mutation_level(MUT_NO_REGENERATION) > 0 || (temp - && you.get_mutation_level(MUT_INHIBITED_REGENERATION) > 0 - && regeneration_is_inhibited()) - || (temp && you.species == SP_VAMPIRE - && you.hunger_state <= HS_STARVING); + && (you.get_mutation_level(MUT_INHIBITED_REGENERATION) > 0 + || you.species == SP_VAMPIRE) + && regeneration_is_inhibited()); +#if TAG_MAJOR_VERSION == 34 case AMU_MANA_REGENERATION: return you_worship(GOD_PAKELLAS); +#endif case RING_SEE_INVISIBLE: return you.innate_sinv(); diff --git a/crawl-ref/source/item-name.h b/crawl-ref/source/item-name.h index bef0f62b7553..acd1b65dc9bd 100644 --- a/crawl-ref/source/item-name.h +++ b/crawl-ref/source/item-name.h @@ -121,7 +121,7 @@ bool is_bad_item(const item_def &item, bool temp = false); bool is_dangerous_item(const item_def& item, bool temp = false); bool is_useless_item(const item_def &item, bool temp = false); -string make_name(uint32_t seed = get_uint32(), +string make_name(uint32_t seed = rng::get_uint32(), makename_type name_type = MNAME_DEFAULT); void make_name_tests(); diff --git a/crawl-ref/source/item-prop-enum.h b/crawl-ref/source/item-prop-enum.h index bbc4b860d366..36c6aa976bd5 100644 --- a/crawl-ref/source/item-prop-enum.h +++ b/crawl-ref/source/item-prop-enum.h @@ -100,8 +100,8 @@ enum armour_type ARM_SHADOW_DRAGON_ARMOUR, ARM_QUICKSILVER_DRAGON_HIDE, ARM_QUICKSILVER_DRAGON_ARMOUR, -#endif ARM_SCARF, +#endif NUM_ARMOURS }; @@ -325,10 +325,10 @@ const vector misc_types = enum missile_type { -#if TAG_MAJOR_VERSION == 34 MI_DART, -#endif +#if TAG_MAJOR_VERSION == 34 MI_NEEDLE, +#endif MI_ARROW, MI_BOLT, MI_JAVELIN, @@ -337,7 +337,7 @@ enum missile_type MI_LARGE_ROCK, MI_SLING_BULLET, MI_THROWING_NET, - MI_TOMAHAWK, + MI_BOOMERANG, NUM_MISSILES, MI_NONE // was MI_EGGPLANT... used for launch type detection @@ -460,26 +460,30 @@ enum special_missile_type // to separate from weapons in general {dlb} SPMSL_FROST, SPMSL_POISONED, SPMSL_CURARE, // Needle-only brand +#if TAG_MAJOR_VERSION == 34 SPMSL_RETURNING, +#endif SPMSL_CHAOS, +#if TAG_MAJOR_VERSION == 34 SPMSL_PENETRATION, +#endif SPMSL_DISPERSAL, +#if TAG_MAJOR_VERSION == 34 SPMSL_EXPLODING, SPMSL_STEEL, +#endif SPMSL_SILVER, - SPMSL_PARALYSIS, // needle only from here on #if TAG_MAJOR_VERSION == 34 + SPMSL_PARALYSIS, // dart only from here on SPMSL_SLOW, -#endif SPMSL_SLEEP, SPMSL_CONFUSION, -#if TAG_MAJOR_VERSION == 34 SPMSL_SICKNESS, #endif SPMSL_FRENZY, - NUM_REAL_SPECIAL_MISSILES, SPMSL_BLINDING, - NUM_SPECIAL_MISSILES, + NUM_REAL_SPECIAL_MISSILES, + NUM_SPECIAL_MISSILES = NUM_REAL_SPECIAL_MISSILES, }; enum special_ring_type // jewellery mitm[].special values @@ -566,7 +570,9 @@ enum weapon_type WPN_GLAIVE, WPN_BARDICHE, +#if TAG_MAJOR_VERSION == 34 WPN_BLOWGUN, +#endif #if TAG_MAJOR_VERSION > 34 WPN_HAND_CROSSBOW, @@ -631,7 +637,7 @@ enum weapon_type WPN_RANDOM, WPN_VIABLE, -// thrown weapons (for hunter weapon selection) - rocks, javelins, tomahawks +// thrown weapons (for hunter weapon selection) - rocks, javelins, boomerangs WPN_THROWN, }; diff --git a/crawl-ref/source/item-prop.cc b/crawl-ref/source/item-prop.cc index 8b5bd870b644..e23286a7bbae 100644 --- a/crawl-ref/source/item-prop.cc +++ b/crawl-ref/source/item-prop.cc @@ -610,9 +610,11 @@ static const weapon_def Weapon_prop[] = }}, // Range weapons +#if TAG_MAJOR_VERSION == 34 { WPN_BLOWGUN, "blowgun", 0, 2, 10, SK_THROWING, SIZE_LITTLE, SIZE_LITTLE, MI_NEEDLE, - DAMV_NON_MELEE, 5, 0, 25, {}, }, + DAMV_NON_MELEE, 0, 0, 0, {}, }, +#endif { WPN_HUNTING_SLING, "hunting sling", 5, 2, 12, SK_SLINGS, SIZE_LITTLE, SIZE_LITTLE, MI_STONE, @@ -652,10 +654,10 @@ struct missile_def static int Missile_index[NUM_MISSILES]; static const missile_def Missile_prop[] = { + { MI_DART, "dart", 0, 12, 2, true }, #if TAG_MAJOR_VERSION == 34 - { MI_DART, "dart", 2, 1, 1, true }, -#endif { MI_NEEDLE, "needle", 0, 12, 2, false }, +#endif { MI_STONE, "stone", 2, 8, 1, true }, { MI_ARROW, "arrow", 0, 8, 2, false }, { MI_BOLT, "bolt", 0, 8, 2, false }, @@ -663,7 +665,7 @@ static const missile_def Missile_prop[] = { MI_SLING_BULLET, "sling bullet", 4, 8, 5, false }, { MI_JAVELIN, "javelin", 10, 20, 8, true }, { MI_THROWING_NET, "throwing net", 0, 0, 30, true }, - { MI_TOMAHAWK, "tomahawk", 6, 20, 5, true }, + { MI_BOOMERANG, "boomerang", 6, 20, 5, true }, }; struct food_def @@ -748,6 +750,7 @@ const set > removed_items = { OBJ_POTIONS, POT_WATER }, { OBJ_POTIONS, POT_STRONG_POISON }, { OBJ_POTIONS, POT_BLOOD_COAGULATED }, + { OBJ_POTIONS, POT_BLOOD }, { OBJ_POTIONS, POT_PORRIDGE }, { OBJ_POTIONS, POT_SLOWING }, { OBJ_POTIONS, POT_DECAY }, @@ -830,34 +833,22 @@ static bool _maybe_note_found_unrand(const item_def &item) } /** - * Is the provided item cursable? Note: this function would leak - * information about unidentified holy wrath weapons, which is alright - * because only Ashenzari worshippers can deliberately curse items and - * they see all weapon egos anyway. + * Is the provided item cursable? * * @param item The item under consideration. - * @return Whether the given item is a blessed weapon. + * @return Whether the given item is cursable. */ -bool item_is_cursable(const item_def &item, bool ignore_holy_wrath) +bool item_is_cursable(const item_def &item) { if (!item_type_has_curses(item.base_type)) return false; if (item_known_cursed(item)) return false; - if (!ignore_holy_wrath - && item.base_type == OBJ_WEAPONS - && (get_weapon_brand(item) == SPWPN_HOLY_WRATH - || you.duration[DUR_EXCRUCIATING_WOUNDS] - && item_is_equipped(item) - && you.props[ORIGINAL_BRAND_KEY].get_int() == SPWPN_HOLY_WRATH)) - { - return false; - } return true; } // Curses a random player inventory item. -bool curse_an_item(bool ignore_holy_wrath) +bool curse_an_item() { // allowing these would enable mummy scumming if (have_passive(passive_t::want_curses)) @@ -875,7 +866,7 @@ bool curse_an_item(bool ignore_holy_wrath) if (!item.defined()) continue; - if (!item_is_cursable(item, ignore_holy_wrath)) + if (!item_is_cursable(item)) continue; // Item is valid for cursing, so we'll give it a chance. @@ -912,28 +903,6 @@ void do_curse_item(item_def &item, bool quiet) return; } - // Holy wrath weapons cannot be cursed. - if (item.base_type == OBJ_WEAPONS - && (get_weapon_brand(item) == SPWPN_HOLY_WRATH - || you.duration[DUR_EXCRUCIATING_WOUNDS] - && item_is_equipped(item) - && you.props[ORIGINAL_BRAND_KEY].get_int() == SPWPN_HOLY_WRATH)) - { - if (!quiet) - { - mprf("Your %s glows black briefly, but repels the curse.", - item.name(DESC_PLAIN).c_str()); - if (is_artefact(item)) - artefact_learn_prop(item, ARTP_BRAND); - else - set_ident_flags(item, ISFLAG_KNOW_TYPE); - - if (!item_brand_known(item)) - mprf_nocap("%s", item.name(DESC_INVENTORY_EQUIP).c_str()); - } - return; - } - if (!quiet) { mprf("Your %s glows black for a moment.", @@ -1885,8 +1854,14 @@ bool is_brandable_weapon(const item_def &wpn, bool allow_ranged, bool divine) if (is_artefact(wpn)) return false; - if (!allow_ranged && is_range_weapon(wpn) || wpn.sub_type == WPN_BLOWGUN) + if (!allow_ranged && is_range_weapon(wpn) +#if TAG_MAJOR_VERSION == 34 + || wpn.sub_type == WPN_BLOWGUN +#endif + ) + { return false; + } // Only gods can rebrand blessed weapons, and they revert back to their // old base type in the process. @@ -2066,7 +2041,7 @@ bool has_launcher(const item_def &ammo) && ammo.sub_type != MI_DART #endif && ammo.sub_type != MI_JAVELIN - && ammo.sub_type != MI_TOMAHAWK + && ammo.sub_type != MI_BOOMERANG && ammo.sub_type != MI_THROWING_NET; } @@ -2268,18 +2243,16 @@ bool is_real_food(food_type food) return food < NUM_FOODS && Food_index[food] < Food_index[FOOD_UNUSED]; } -#endif bool is_blood_potion(const item_def &item) { if (item.base_type != OBJ_POTIONS) return false; return item.sub_type == POT_BLOOD -#if TAG_MAJOR_VERSION == 34 || item.sub_type == POT_BLOOD_COAGULATED -#endif ; } +#endif bool food_is_meaty(int food_type) { diff --git a/crawl-ref/source/item-prop.h b/crawl-ref/source/item-prop.h index 8c8d5f82cfe7..3d04f027e48a 100644 --- a/crawl-ref/source/item-prop.h +++ b/crawl-ref/source/item-prop.h @@ -51,8 +51,8 @@ bool item_type_removed(object_class_type base, int subtype); // cursed: bool item_known_cursed(const item_def &item) PURE; -bool item_is_cursable(const item_def &item, bool ignore_holy_wrath = false); -bool curse_an_item(bool ignore_holy_wrath = false); +bool item_is_cursable(const item_def &item); +bool curse_an_item(); void do_curse_item(item_def &item, bool quiet = true); void do_uncurse_item(item_def &item, bool check_bondage = true); inline constexpr bool item_type_has_curses(object_class_type base_type) @@ -195,8 +195,8 @@ bool ring_has_stackable_effect(const item_def &item) PURE; // food functions: #if TAG_MAJOR_VERSION == 34 bool is_real_food(food_type food) PURE; -#endif bool is_blood_potion(const item_def &item) PURE; +#endif bool food_is_meaty(int food_type) PURE; bool food_is_meaty(const item_def &item) PURE; int food_value(const item_def &item) PURE; diff --git a/crawl-ref/source/item-use.cc b/crawl-ref/source/item-use.cc index f10c963c8917..a2602f0c1dd6 100644 --- a/crawl-ref/source/item-use.cc +++ b/crawl-ref/source/item-use.cc @@ -82,13 +82,16 @@ class UseItemMenu : public InvMenu { - bool is_inventory = true; - - void populate_list(int item_type); + void populate_list(); void populate_menu(); bool process_key(int key) override; + void repopulate_menu(); public: + bool display_all; + bool is_inventory; + int item_type_filter; + vector item_inv; vector item_floor; @@ -97,35 +100,55 @@ class UseItemMenu : public InvMenu // Accepts: // OBJ_POTIONS // OBJ_SCROLLS + // OSEL_WIELD + // OBJ_ARMOUR + // OBJ_FOOD UseItemMenu(int selector, const char* prompt); - void toggle(); + void toggle_display_all(); + void toggle_inv_or_floor(); }; UseItemMenu::UseItemMenu(int item_type, const char* prompt) - : InvMenu(MF_SINGLESELECT) + : InvMenu(MF_SINGLESELECT), display_all(false), is_inventory(true), + item_type_filter(item_type) { set_title(prompt); - populate_list(item_type); + populate_list(); populate_menu(); } -void UseItemMenu::populate_list(int item_type) +void UseItemMenu::populate_list() { // Load inv items first for (const auto &item : you.inv) { - // Populate the vector with filter - if (item.defined() && item_is_selected(item, item_type)) + if (item.defined()) item_inv.push_back(&item); } - // Load floor items + // Load floor items... item_floor = item_list_on_square(you.visible_igrd(you.pos())); - // Filter - erase_if(item_floor, [=](const item_def* item) + // ...only stuff that can go into your inventory though + erase_if(item_floor, [=](const item_def* it) { - return !item_is_selected(*item,item_type); + // Did we get them all...? + return !it->defined() || item_is_stationary(*it) || item_is_orb(*it) + || item_is_spellbook(*it) || it->base_type == OBJ_GOLD + || it->base_type == OBJ_RUNES; }); + + // Filter by type + if (!display_all) + { + erase_if(item_inv, [=](const item_def* item) + { + return !item_is_selected(*item, item_type_filter); + }); + erase_if(item_floor, [=](const item_def* item) + { + return !item_is_selected(*item, item_type_filter); + }); + } } void UseItemMenu::populate_menu() @@ -135,6 +158,14 @@ void UseItemMenu::populate_menu() else if (item_floor.empty()) is_inventory = true; + // Entry for unarmed + if (item_type_filter == OSEL_WIELD) + { + string hands_title = " - unarmed"; + MenuEntry *hands = new MenuEntry (hands_title, MEL_ITEM); + add_entry(hands); + } + if (!item_inv.empty()) { // Only clarify that these are inventory items if there are also floor @@ -193,16 +224,31 @@ void UseItemMenu::populate_menu() } } -void UseItemMenu::toggle() +void UseItemMenu::repopulate_menu() { - is_inventory = !is_inventory; deleteAll(items); populate_menu(); } +void UseItemMenu::toggle_display_all() +{ + display_all = !display_all; + item_inv.clear(); + item_floor.clear(); + populate_list(); + repopulate_menu(); +} + +void UseItemMenu::toggle_inv_or_floor() +{ + is_inventory = !is_inventory; + repopulate_menu(); +} + bool UseItemMenu::process_key(int key) { - if (isadigit(key) || key == '*' || key == '\\' || key == ',') + if (isadigit(key) || key == '*' || key == '\\' || key == ',' + || key == '-' && item_type_filter == OSEL_WIELD) { lastch = key; return false; @@ -221,13 +267,17 @@ static string _weird_sound() } /** - * Prompt use of a consumable from either player inventory or the floor. + * Prompt use of an item from either player inventory or the floor. * * This function generates a menu containing type_expect items based on the * object_class_type to be acted on by another function. First it will list - * items in inventory, then items on the floor. If player cancels out of menu, - * nullptr is returned. + * items in inventory, then items on the floor. If the prompt is cancelled, + * false is returned. If something is successfully choosen, then true is + * returned, and at function exit the parameter target points to the object the + * player chose or to nullptr if the player chose to wield bare hands (this is + * only possible if item_type is OSEL_WIELD). * + * @param target A pointer by reference to indicate the object selected. * @param item_type The object_class_type or OSEL_* of items to list. * @param oper The operation being done to the selected item. * @param prompt The prompt on the menu title @@ -235,23 +285,25 @@ static string _weird_sound() * function. If it returns false, continue the prompt rather * than returning null. * - * @return a pointer to the chosen item, or nullptr if none was chosen. + * @return boolean true if something was chosen, false if the process failed and + * no choice was made */ -item_def* use_an_item(int item_type, operation_types oper, const char* prompt, - function allowcancel) +bool use_an_item(item_def *&target, int item_type, operation_types oper, + const char* prompt, function allowcancel) { - item_def* target = nullptr; - - // First handle things that will return nullptr - - // No selectable items in inv or floor - if (!any_items_of_type(item_type, -1, true)) + // First bail if there's nothing appropriate to choose in inv or on floor + // (if choosing weapons, then bare hands are always a possibility) + if (item_type != OSEL_WIELD && !any_items_of_type(item_type, -1, true)) { mprf(MSGCH_PROMPT, "%s", no_selectables_message(item_type).c_str()); - return nullptr; + return false; } + bool choice_made = false; + item_def *tmp_tgt = nullptr; // We'll change target only if the player + // actually chooses + // Init the menu UseItemMenu menu(item_type, prompt); @@ -262,15 +314,30 @@ item_def* use_an_item(int item_type, operation_types oper, const char* prompt, // Handle inscribed item keys if (isadigit(keyin)) - target = digit_inscription_to_item(keyin, oper); - // TODO: handle * key + { + // This allows you to select stuff by inscription that is not on the + // screen, but only if you couldn't by default use it for that operation + // anyway. It's a bit weird, but it does save a '*' keypress for + // bread-swingers. + tmp_tgt = digit_inscription_to_item(keyin, oper); + if (tmp_tgt) + choice_made = true; + } + else if (keyin == '*') + { + menu.toggle_display_all(); + continue; + } else if (keyin == ',') { if (Options.easy_floor_use && menu.item_floor.size() == 1) - target = const_cast(menu.item_floor[0]); + { + choice_made = true; + tmp_tgt = const_cast(menu.item_floor[0]); + } else { - menu.toggle(); + menu.toggle_inv_or_floor(); continue; } } @@ -279,27 +346,45 @@ item_def* use_an_item(int item_type, operation_types oper, const char* prompt, check_item_knowledge(); continue; } + else if (keyin == '-' && menu.item_type_filter == OSEL_WIELD) + { + choice_made = true; + tmp_tgt = nullptr; + } else if (!sel.empty()) { ASSERT(sel.size() == 1); + choice_made = true; auto ie = dynamic_cast(sel[0]); - target = const_cast(ie->item); + tmp_tgt = const_cast(ie->item); } redraw_screen(); - if (target && !check_warning_inscriptions(*target, oper)) - target = nullptr; - if (target) - return target; + // For weapons, armour, and jewellery this is handled in wield_weapon, + // wear_armour, and _puton_item after selection + if (item_type != OSEL_WIELD && item_type != OBJ_ARMOUR + && item_type != OBJ_JEWELLERY && choice_made && tmp_tgt + && !check_warning_inscriptions(*tmp_tgt, oper)) + { + choice_made = false; + } + + if (choice_made) + break; else if (allowcancel()) { prompt_failed(PROMPT_ABORT); - return nullptr; + break; } else continue; } + if (choice_made) + target = tmp_tgt; + + ASSERT(!choice_made || target || item_type == OSEL_WIELD); + return choice_made; } static bool _safe_to_remove_or_wear(const item_def &item, bool remove, @@ -381,21 +466,6 @@ bool can_wield(const item_def *weapon, bool say_reason, else return false; } - else if (!ignore_temporary_disability - && you.hunger_state < HS_FULL - && get_weapon_brand(*weapon) == SPWPN_VAMPIRISM - && you.undead_state() == US_ALIVE - && !you_foodless() - && (item_type_known(*weapon) || !only_known)) - { - if (say_reason) - { - mpr("This weapon is vampiric, and you must be Full or above to equip it."); - id_brand = true; - } - else - return false; - } if (id_brand) { auto wwpn = const_cast(weapon); @@ -423,6 +493,72 @@ bool can_wield(const item_def *weapon, bool say_reason, #undef SAY } +/** + * Helper function for wield_weapon, wear_armour, and puton_ring + * @param item item on floor (where the player is standing) + * @param quiet print message or not + * @return boolean can the player move the item into their inventory, or are + * they out of space? + */ +static bool _can_move_item_from_floor_to_inv(const item_def &item) +{ + if (inv_count() < ENDOFPACK) + return true; + if (!is_stackable_item(item)) + { + mpr("You can't carry that many items."); + return false; + } + for (int i = 0; i < ENDOFPACK; ++i) + { + if (items_stack(you.inv[i], item)) + return true; + } + mpr("You can't carry that many items."); + return false; +} + +/** + * Helper function for wield_weapon, wear_armour, and puton_ring + * @param to_get item on floor (where the player is standing) to move into + inventory + * @return int -1 if failure due to already full inventory; otherwise, index in + * you.inv where the item ended up + */ +static int _move_item_from_floor_to_inv(const item_def &to_get) +{ + map tmp_l_p = you.last_pickup; // if we need to restore + you.last_pickup.clear(); + + if (!move_item_to_inv(to_get.index(), to_get.quantity, true)) + { + mpr("You can't carry that many items."); + you.last_pickup = tmp_l_p; + } + // Get the slot of the last thing picked up + // TODO: this is a bit hacky---perhaps it's worth making a function like + // move_item_to_inv that returns the slot the item moved into + else + { + ASSERT(you.last_pickup.size() == 1); // Sanity check... + return you.last_pickup.begin()->first; + } + return -1; +} + +/** + * Helper function for wield_weapon, wear_armour, and puton_ring + * @param item item on floor (where the player is standing) or in inventory + * @return ret index in you.inv where the item is (either because it was already + * there or just got moved there), or -1 if we tried and failed to + * move the item into inventory + */ +static int _get_item_slot_maybe_with_move(const item_def &item) +{ + int ret = item.pos == ITEM_IN_INVENTORY + ? item.link : _move_item_from_floor_to_inv(item); + return ret; +} /** * @param auto_wield false if this was initiated by the wield weapon command (w) * true otherwise (e.g. switching between ranged and melee with the @@ -437,59 +573,63 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, bool show_unwield_msg, bool show_wield_msg, bool adjust_time_taken) { - if (inv_count() < 1) - { - canned_msg(MSG_NOTHING_CARRIED); - return false; - } - - // Look for conditions like berserking that could prevent wielding + // Abort immediately if there's some condition that could prevent wielding // weapons. if (!can_wield(nullptr, true, false, slot == SLOT_BARE_HANDS)) return false; - int item_slot = 0; // default is 'a' + item_def *to_wield = &you.inv[0]; // default is 'a' + // we'll set this to nullptr to indicate bare hands if (auto_wield) { - if (item_slot == you.equip[EQ_WEAPON] - || you.equip[EQ_WEAPON] == -1 - && !item_is_wieldable(you.inv[item_slot])) + if (to_wield == you.weapon() + || you.equip[EQ_WEAPON] == -1 && !item_is_wieldable(*to_wield)) { - item_slot = 1; // backup is 'b' + to_wield = &you.inv[1]; // backup is 'b' } if (slot != -1) // allow external override - item_slot = slot; + { + if (slot == SLOT_BARE_HANDS) + to_wield = nullptr; + else + to_wield = &you.inv[slot]; + } } - // If the swap slot has a bad (but valid) item in it, - // the swap will be to bare hands. - const bool good_swap = (item_slot == SLOT_BARE_HANDS - || item_is_wieldable(you.inv[item_slot])); - - // Prompt if not using the auto swap command, or if the swap slot - // is empty. - if (item_slot != SLOT_BARE_HANDS - && (!auto_wield || !you.inv[item_slot].defined() || !good_swap)) + if (to_wield) { + // Prompt if not using the auto swap command if (!auto_wield) { - item_slot = prompt_invent_item( - "Wield which item (- for none, * to show all)?", - MT_INVLIST, OSEL_WIELD, - OPER_WIELD, invprompt_flag::no_warning, '-'); + if (!use_an_item(to_wield, OSEL_WIELD, OPER_WIELD, "Wield " + "which item (- for none, * to show all)?")) + { + return false; + } + // We abort if trying to wield from the floor with full inventory. We could + // try something more sophisticated, e.g., drop the currently held weapon, + // but that's surely not always what's wanted, and the problem persists if + // the player's hands are empty. For now, let's stick with simple behaviour + // over trying to guess what the player would like. + if (to_wield && to_wield->pos != ITEM_IN_INVENTORY + && !_can_move_item_from_floor_to_inv(*to_wield)) + { + return false; + } } - else - item_slot = SLOT_BARE_HANDS; + + // If autowielding and the swap slot has a bad or invalid item in it, the + // swap will be to bare hands. + else if (!to_wield->defined() || !item_is_wieldable(*to_wield)) + to_wield = nullptr; } - if (prompt_failed(item_slot)) - return false; - else if (item_slot == you.equip[EQ_WEAPON]) + if (to_wield && to_wield == you.weapon()) { if (Options.equip_unequip) - item_slot = SLOT_BARE_HANDS; + to_wield = nullptr; else { mpr("You are already wielding that!"); @@ -500,7 +640,7 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, // Reset the warning counter. you.received_weapon_warning = false; - if (item_slot == SLOT_BARE_HANDS) + if (!to_wield) { if (const item_def* wpn = you.weapon()) { @@ -549,7 +689,9 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, return true; } - item_def& new_wpn(you.inv[item_slot]); + // By now we're sure we're swapping to a real weapon, not bare hands + + item_def& new_wpn = *to_wield; // Switching to a launcher while berserk is likely a mistake. if (you.berserk() && is_range_weapon(new_wpn)) @@ -586,7 +728,7 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, if (unwield_item(show_weff_messages)) { // Enable skills so they can be re-disabled later - update_can_train(); + update_can_currently_train(); } else return false; @@ -594,6 +736,15 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, const unsigned int old_talents = your_talents(false).size(); + // If it's on the ground, pick it up. Once it's picked up, there should be + // no aborting, lest we introduce a way to instantly pick things up + // NB we already made sure there was space for the item + int item_slot = _get_item_slot_maybe_with_move(new_wpn); + + // At this point new_wpn is potentially not the right thing anymore (the + // thing actually in the player's inventory), that is, in the case where the + // player chose something from the floor. So use item_slot from here on. + // Go ahead and wield the weapon. equip_item(EQ_WEAPON, item_slot, show_weff_messages); @@ -602,10 +753,10 @@ bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, #ifdef USE_SOUND parse_sound(WIELD_WEAPON_SOUND); #endif - mprf_nocap("%s", new_wpn.name(DESC_INVENTORY_EQUIP).c_str()); + mprf_nocap("%s", you.inv[item_slot].name(DESC_INVENTORY_EQUIP).c_str()); } - check_item_hint(new_wpn, old_talents); + check_item_hint(you.inv[item_slot], old_talents); // Time calculations. if (adjust_time_taken) @@ -648,7 +799,8 @@ bool armour_prompt(const string & mesg, int *index, operation_types oper) int selector = OBJ_ARMOUR; if (oper == OPER_TAKEOFF && !Options.equip_unequip) selector = OSEL_WORN_ARMOUR; - int slot = prompt_invent_item(mesg.c_str(), MT_INVLIST, selector, oper); + int slot = prompt_invent_item(mesg.c_str(), menu_type::invlist, + selector, oper); if (!prompt_failed(slot)) { @@ -1025,34 +1177,45 @@ bool wear_armour(int item) return false; } + item_def *to_wear = nullptr; + if (item == -1) { - item = prompt_invent_item("Wear which item?", MT_INVLIST, OBJ_ARMOUR, - OPER_WEAR, invprompt_flag::no_warning); - if (prompt_failed(item)) + if (!use_an_item(to_wear, OBJ_ARMOUR, OPER_WEAR, + "Wear which item (* to show all)?")) + { + return false; + } + // use_an_item on armour should never return true and leave to_wear + // nullptr + if (to_wear->pos != ITEM_IN_INVENTORY + && !_can_move_item_from_floor_to_inv(*to_wear)) + { return false; + } } + else + to_wear = &you.inv[item]; - item_def &invitem = you.inv[item]; // First, let's check for any conditions that would make it impossible to // equip the given item - if (!invitem.defined()) + if (!to_wear->defined()) { mpr("You don't have any such object."); return false; } - if (item == you.equip[EQ_WEAPON]) + if (to_wear == you.weapon()) { mpr("You are wielding that object!"); return false; } - if (item_is_worn(item)) + if (to_wear->pos == ITEM_IN_INVENTORY && item_is_worn(to_wear->link)) { if (Options.equip_unequip) // TODO: huh? Why are we inverting the return value? - return !takeoff_armour(item); + return !takeoff_armour(to_wear->link); else { mpr("You're already wearing that object!"); @@ -1060,20 +1223,20 @@ bool wear_armour(int item) } } - if (!_can_equip_armour(invitem)) + if (!_can_equip_armour(*to_wear)) return false; // At this point, we know it's possible to equip this item. However, there // might be reasons it's not advisable. Warn about any dangerous // inscriptions, giving the player an opportunity to bail out. - if (!check_warning_inscriptions(invitem, OPER_WEAR)) + if (!check_warning_inscriptions(*to_wear, OPER_WEAR)) { canned_msg(MSG_OK); return false; } bool swapping = false; - const equipment_type slot = get_armour_slot(invitem); + const equipment_type slot = get_armour_slot(*to_wear); if ((slot == EQ_CLOAK || slot == EQ_HELMET || slot == EQ_GLOVES @@ -1092,12 +1255,20 @@ bool wear_armour(int item) // TODO: It would be nice if we checked this before taking off the item // currently in the slot. But doing so is not quite trivial. Also applies // to jewellery. - if (!_safe_to_remove_or_wear(invitem, false)) + if (!_safe_to_remove_or_wear(*to_wear, false)) return false; - const int delay = armour_equip_delay(invitem); + // If it's on the ground, pick it up. Once it's picked up, there should be + // no aborting + // NB we already made sure there was space for the item + int item_slot = _get_item_slot_maybe_with_move(*to_wear); + + const int delay = armour_equip_delay(*to_wear); if (delay) - start_delay(delay - (swapping ? 0 : 1), invitem); + { + start_delay(delay - (swapping ? 0 : 1), + you.inv[item_slot]); + } return true; } @@ -1245,7 +1416,7 @@ static const char _ring_slot_key(equipment_type slot) } } -static int _prompt_ring_to_remove(int new_ring) +static int _prompt_ring_to_remove() { const vector ring_types = _current_ring_types(); vector slot_chars; @@ -1452,12 +1623,12 @@ bool safe_to_remove(const item_def &item, bool quiet) } // Assumptions: -// you.inv[ring_slot] is a valid ring. -// EQ_LEFT_RING and EQ_RIGHT_RING are both occupied, and ring_slot is not -// one of the worn rings. +// item is an item in inventory or on the floor where the player is standing +// EQ_LEFT_RING and EQ_RIGHT_RING are both occupied, and item is not +// in one of those slots. // // Does not do amulets. -static bool _swap_rings(int ring_slot) +static bool _swap_rings(const item_def& to_puton) { vector ring_types = _current_ring_types(); const int num_rings = ring_types.size(); @@ -1540,7 +1711,7 @@ static bool _swap_rings(int ring_slot) { // Don't prompt if all the rings are the same. if (!all_same || Options.jewellery_prompt) - unwanted = _prompt_ring_to_remove(ring_slot); + unwanted = _prompt_ring_to_remove(); if (unwanted == EQ_NONE) { @@ -1548,7 +1719,7 @@ static bool _swap_rings(int ring_slot) // message is visible. unwanted = prompt_invent_item( "You're wearing all the rings you can. Remove which one?", - MT_INVLIST, OSEL_UNCURSED_WORN_RINGS, OPER_REMOVE, + menu_type::invlist, OSEL_UNCURSED_WORN_RINGS, OPER_REMOVE, invprompt_flag::no_warning | invprompt_flag::hide_known); } @@ -1564,7 +1735,7 @@ static bool _swap_rings(int ring_slot) } // Put on the new ring. - start_delay(1, you.inv[ring_slot]); + start_delay(1, to_puton); return true; } @@ -1627,14 +1798,12 @@ static equipment_type _choose_ring_slot() // Is it possible to put on the given item in a jewellery slot? // Preconditions: -// - item_slot is a valid index into inventory // - item is not already equipped in a jewellery slot -static bool _can_puton_jewellery(int item_slot) +static bool _can_puton_jewellery(const item_def &item) { // TODO: between this function, _puton_item, _swap_rings, and remove_ring, // there's a bit of duplicated work, and sep. of concerns not clear - item_def& item = you.inv[item_slot]; - if (item_slot == you.equip[EQ_WEAPON]) + if (&item == you.weapon()) { mpr("You are wielding that object."); return false; @@ -1701,19 +1870,20 @@ static bool _can_puton_jewellery(int item_slot) } // Put on a particular ring or amulet -static bool _puton_item(int item_slot, bool prompt_slot, +static bool _puton_item(const item_def &item, bool prompt_slot, bool check_for_inscriptions) { - item_def& item = you.inv[item_slot]; + vector current_jewellery = _current_ring_types(); + current_jewellery.push_back(EQ_AMULET); - for (int eq = EQ_FIRST_JEWELLERY; eq <= EQ_LAST_JEWELLERY; eq++) - if (item_slot == you.equip[eq]) + for (auto eq : current_jewellery) + if (&item == you.slot_item(eq, true)) { // "Putting on" an equipped item means taking it off. if (Options.equip_unequip) // TODO: why invert the return value here? failing to remove // a ring is equivalent to successfully putting one on? - return !remove_ring(item_slot); + return !remove_ring(item.link); else { mpr("You're already wearing that object!"); @@ -1721,7 +1891,7 @@ static bool _puton_item(int item_slot, bool prompt_slot, } } - if (!_can_puton_jewellery(item_slot)) + if (!_can_puton_jewellery(item)) return false; // It looks to be possible to equip this item. Before going any further, @@ -1753,7 +1923,7 @@ static bool _puton_item(int item_slot, bool prompt_slot, // No unused ring slots. Swap out a worn ring for the new one. if (need_swap) - return _swap_rings(item_slot); + return _swap_rings(item); } else if (you.slot_item(EQ_AMULET, true)) { @@ -1817,6 +1987,7 @@ static bool _puton_item(int item_slot, bool prompt_slot, const unsigned int old_talents = your_talents(false).size(); // Actually equip the item. + int item_slot = _get_item_slot_maybe_with_move(item); equip_item(hand_used, item_slot); check_item_hint(you.inv[item_slot], old_talents); @@ -1835,38 +2006,47 @@ static bool _puton_item(int item_slot, bool prompt_slot, return true; } -// Put on a ring or amulet. (If slot is -1, first prompt for which item to put on) -bool puton_ring(int slot, bool allow_prompt, bool check_for_inscriptions) +// Put on a ring or amulet. (Most of the work is in _puton_item.) +bool puton_ring(const item_def &to_puton, bool allow_prompt, + bool check_for_inscriptions) { - int item_slot; - - if (inv_count() < 1) - { - canned_msg(MSG_NOTHING_CARRIED); - return false; - } - if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return false; } - - if (slot != -1) - item_slot = slot; - else + if (!to_puton.defined()) { - item_slot = prompt_invent_item("Put on which piece of jewellery?", - MT_INVLIST, OBJ_JEWELLERY, OPER_PUTON, - invprompt_flag::no_warning); + mpr("You don't have any such object."); + return false; } - - if (prompt_failed(item_slot)) + if (to_puton.pos != ITEM_IN_INVENTORY + && !_can_move_item_from_floor_to_inv(to_puton)) + { return false; + } bool prompt = allow_prompt ? Options.jewellery_prompt : false; + return _puton_item(to_puton, prompt, check_for_inscriptions); +} + +// Wraps version of puton_ring with item_def param. If slot is -1, prompt for +// which item to put on; otherwise, pass on the item in inventory slot +bool puton_ring(int slot, bool allow_prompt, bool check_for_inscriptions) +{ + item_def *to_puton_ptr = nullptr; + if (slot == -1) + { + if (!use_an_item(to_puton_ptr, OBJ_JEWELLERY, OPER_PUTON, + "Put on which piece of jewellery (* to show all)?")) + { + return false; + } + } + else + to_puton_ptr = &you.inv[slot]; - return _puton_item(item_slot, prompt, check_for_inscriptions); + return puton_ring(*to_puton_ptr, allow_prompt, check_for_inscriptions); } // Remove the amulet/ring at given inventory slot (or, if slot is -1, prompt @@ -1919,7 +2099,7 @@ bool remove_ring(int slot, bool announce) { const int equipn = (slot == -1)? prompt_invent_item("Remove which piece of jewellery?", - MT_INVLIST, + menu_type::invlist, OBJ_JEWELLERY, OPER_REMOVE, invprompt_flag::no_warning @@ -2020,7 +2200,7 @@ void prompt_inscribe_item() } int item_slot = prompt_invent_item("Inscribe which item?", - MT_INVLIST, OSEL_ANY); + menu_type::invlist, OSEL_ANY); if (prompt_failed(item_slot)) return; @@ -2028,31 +2208,9 @@ void prompt_inscribe_item() inscribe_item(you.inv[item_slot]); } -static bool _check_blood_corpses_on_ground() -{ - for (stack_iterator si(you.pos(), true); si; ++si) - { - if (si->is_type(OBJ_CORPSES, CORPSE_BODY) - && mons_has_blood(si->mon_type)) - { - return true; - } - } - return false; -} - -static void _vampire_corpse_help() -{ - if (you.species != SP_VAMPIRE) - return; - - if (_check_blood_corpses_on_ground()) - mpr("Use e to drain blood from corpses."); -} - void drink(item_def* potion) { - if (you_foodless()) + if (you_foodless() && you.species != SP_VAMPIRE) { mpr("You can't drink."); return; @@ -2072,13 +2230,8 @@ void drink(item_def* potion) if (!potion) { - potion = use_an_item(OBJ_POTIONS, OPER_QUAFF, "Drink which item?"); - - if (!potion) - { - _vampire_corpse_help(); + if (!use_an_item(potion, OBJ_POTIONS, OPER_QUAFF, "Drink which item (* to show all)?")) return; - } } if (potion->base_type != OBJ_POTIONS) @@ -2095,9 +2248,12 @@ void drink(item_def* potion) return; } - string prompt = make_stringf("Really quaff the %s?", - potion->name(DESC_DBNAME).c_str()); - if (alreadyknown && is_dangerous_item(*potion, true) + bool penance = god_hates_item(*potion); + string prompt = make_stringf("Really quaff the %s?%s", + potion->name(DESC_DBNAME).c_str(), + penance ? " This action would place" + " you under penance!" : ""); + if (alreadyknown && (is_dangerous_item(*potion, true) || penance) && Options.bad_item_prompt && !yesno(prompt.c_str(), false, 'n')) { @@ -2127,11 +2283,6 @@ void drink(item_def* potion) // a dangerous monster nearby... xom_is_stimulated(200); } - if (is_blood_potion(*potion)) - { - // Always drink oldest potion. - remove_oldest_perishable_item(*potion); - } // We'll need this later, after destroying the item. const bool was_exp = potion->sub_type == POT_EXPERIENCE; @@ -2305,7 +2456,10 @@ static void _brand_weapon(item_def &wpn) static item_def* _choose_target_item_for_scroll(bool scroll_known, object_selector selector, const char* prompt) { - return use_an_item(selector, OPER_ANY, prompt, + item_def *target = nullptr; + bool success = false; + + success = use_an_item(target, selector, OPER_ANY, prompt, [=]() { if (scroll_known @@ -2316,6 +2470,7 @@ static item_def* _choose_target_item_for_scroll(bool scroll_known, object_select } return false; }); + return success ? target : nullptr; } static object_selector _enchant_selector(scroll_type scroll) @@ -2778,8 +2933,7 @@ static bool _scroll_will_harm(const scroll_type scr, const actor &m) /** * Check to see if the player can read the item in the given slot, and if so, - * reads it. (Examining books, evoking the tome of destruction, & using - * scrolls.) + * reads it. (Examining books and using scrolls.) * * @param slot The slot of the item in the player's inventory. If -1, the * player is prompted to choose a slot. @@ -2791,8 +2945,7 @@ void read(item_def* scroll) if (!scroll) { - scroll = use_an_item(OBJ_SCROLLS, OPER_READ, "Read which item?"); - if (!scroll) + if (!use_an_item(scroll, OBJ_SCROLLS, OPER_READ, "Read which item (* to show all)?")) return; } @@ -3183,10 +3336,23 @@ void read_scroll(item_def& scroll) } if (you.spell_no == 0) mpr("You feel forgetful for a moment."); - else if (!alreadyknown) - cast_selective_amnesia(); else - cancel_scroll = (cast_selective_amnesia(pre_succ_msg) == -1); + { + bool done; + bool aborted; + do + { + aborted = cast_selective_amnesia() == -1; + done = !aborted + || alreadyknown + || crawl_state.seen_hups + || yesno("Really abort (and waste the scroll)?", + false, 0); + cancel_scroll = aborted && alreadyknown; + } while (!done); + if (aborted) + canned_msg(MSG_OK); + } break; default: @@ -3382,13 +3548,6 @@ void tile_item_use(int idx) wear_armour(idx); return; - case OBJ_CORPSES: - if (you.species != SP_VAMPIRE - || item.sub_type == CORPSE_SKELETON) - { - break; - } - // intentional fall-through for Vampires case OBJ_FOOD: if (check_warning_inscriptions(item, OPER_EAT)) eat_food(idx); diff --git a/crawl-ref/source/item-use.h b/crawl-ref/source/item-use.h index a1dabdf492ca..7afb20f1b181 100644 --- a/crawl-ref/source/item-use.h +++ b/crawl-ref/source/item-use.h @@ -12,8 +12,9 @@ #include "item-prop-enum.h" #include "operation-types.h" -item_def* use_an_item(int item_type, operation_types oper, const char* prompt, - function allowcancel = [](){ return true; }); +bool use_an_item(item_def *&target, int item_type, operation_types oper, + const char* prompt, + function allowcancel = [](){ return true; }); // Change the lambda to always_true<> when g++ 4.7 support is dropped. bool armour_prompt(const string & mesg, int *index, operation_types oper); @@ -29,6 +30,9 @@ bool safe_to_remove(const item_def &item, bool quiet = false); bool puton_ring(int slot = -1, bool allow_prompt = true, bool check_for_inscriptions = true); +bool puton_ring(const item_def &to_puton, bool allow_prompt = true, + bool check_for_inscriptions = true); + void read(item_def* scroll = nullptr); void read_scroll(item_def& scroll); bool player_can_read(); diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index 9c17da770bc7..69635fb172cb 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -36,6 +36,7 @@ #include "dgn-event.h" #include "directn.h" #include "dungeon.h" +#include "english.h" #include "env.h" #include "food.h" #include "god-passive.h" @@ -962,7 +963,7 @@ static bool _id_floor_item(item_def &item) bool should_pickup = item_needs_autopickup(item); set_ident_type(item, true); if (!should_pickup) - you.force_autopickup[item.base_type][item.sub_type] = -1; + set_item_autopickup(item, AP_FORCE_OFF); return true; } } @@ -996,7 +997,7 @@ void pickup_menu(int item_link) if (items.size() == 1 && items[0]->quantity > 1) prompt = "Select pick up quantity by entering a number, then select the item"; vector selected = select_items(items, prompt.c_str(), false, - MT_PICKUP); + menu_type::pickup); if (selected.empty()) canned_msg(MSG_OK); redraw_screen(); @@ -2629,6 +2630,8 @@ bool drop_item(int item_dropped, int quant_drop) you.turn_is_over = true; you.last_pickup.erase(item_dropped); + if (you.last_unequip == item.link) + you.last_unequip = -1; return true; } @@ -2713,6 +2716,38 @@ static bool _drop_item_order(const SelItem &first, const SelItem &second) return first.slot < second.slot; } +void set_item_autopickup(const item_def &item, autopickup_level_type ap) +{ + you.force_autopickup[item.base_type][_autopickup_subtype(item)] = ap; +} + +int item_autopickup_level(const item_def &item) +{ + return you.force_autopickup[item.base_type][_autopickup_subtype(item)]; +} + +static void _disable_autopickup_for_starred_items(vector &items) +{ + int autopickup_remove_count = 0; + const item_def *last_touched_item; + for (SelItem &si : items) + { + if (si.has_star && item_autopickup_level(si.item[0]) != AP_FORCE_OFF) + { + last_touched_item = si.item; + ++autopickup_remove_count; + set_item_autopickup(*last_touched_item, AP_FORCE_OFF); + } + } + if (autopickup_remove_count == 1) + { + mprf("Autopickup disabled for %s.", + pluralise(last_touched_item->name(DESC_DBNAME)).c_str()); + } + else if (autopickup_remove_count > 1) + mprf("Autopickup disabled for %d items.", autopickup_remove_count); +} + /** * Prompts the user for an item to drop. */ @@ -2734,6 +2769,7 @@ void drop() return; } + _disable_autopickup_for_starred_items(tmp_items); _multidrop(tmp_items); } @@ -2884,8 +2920,6 @@ static int _autopickup_subtype(const item_def &item) case OBJ_POTIONS: case OBJ_STAVES: return item_type_known(item) ? item.sub_type : max_type; - case OBJ_MISCELLANY: - return max_type; case OBJ_BOOKS: if (item.sub_type == BOOK_MANUAL || item_type_known(item)) return item.sub_type; @@ -2908,9 +2942,9 @@ static bool _is_option_autopickup(const item_def &item, bool ignore_force) if (item.base_type < NUM_OBJECT_CLASSES) { - const int force = you.force_autopickup[item.base_type][_autopickup_subtype(item)]; - if (!ignore_force && force != 0) - return force == 1; + const int force = item_autopickup_level(item); + if (!ignore_force && force != AP_FORCE_NONE) + return force == AP_FORCE_ON; } else return false; @@ -3045,6 +3079,8 @@ static bool _similar_wands(const item_def& pickup_item, #if TAG_MAJOR_VERSION == 34 // Not similar if wand in inventory is empty. return !is_known_empty_wand(inv_item); +#else + return true; #endif } @@ -3212,10 +3248,8 @@ static void _do_autopickup() if (you_are_delayed() && current_delay()->want_autoeat()) butchery(&mi); else - { o = next; - continue; - } + continue; } // Do this before it's picked up, otherwise the picked up @@ -3499,16 +3533,16 @@ colour_t item_def::missile_colour() const { case MI_STONE: return BROWN; -#if TAG_MAJOR_VERSION == 34 - case MI_DART: -#endif case MI_SLING_BULLET: return CYAN; case MI_LARGE_ROCK: return LIGHTGREY; case MI_ARROW: return BLUE; +#if TAG_MAJOR_VERSION == 34 case MI_NEEDLE: +#endif + case MI_DART: return WHITE; case MI_BOLT: return LIGHTBLUE; @@ -3516,10 +3550,9 @@ colour_t item_def::missile_colour() const return RED; case MI_THROWING_NET: return MAGENTA; - case MI_TOMAHAWK: + case MI_BOOMERANG: return GREEN; case NUM_SPECIAL_MISSILES: - case NUM_REAL_SPECIAL_MISSILES: default: die("invalid missile type"); } @@ -4208,6 +4241,8 @@ static bool _book_from_spell(const char* specs, item_def &item) bool get_item_by_name(item_def *item, const char* specs, object_class_type class_wanted, bool create_for_real) { + // used only for wizmode and item lookup + int type_wanted = -1; int special_wanted = 0; diff --git a/crawl-ref/source/items.h b/crawl-ref/source/items.h index 0bf7c58d6ed6..90ee59183589 100644 --- a/crawl-ref/source/items.h +++ b/crawl-ref/source/items.h @@ -29,6 +29,13 @@ enum item_source_type AQ_WIZMODE = 200, }; +enum autopickup_level_type +{ + AP_FORCE_OFF = -1, + AP_FORCE_NONE = 0, + AP_FORCE_ON = 1, +}; + /// top-priority item override colour #define FORCED_ITEM_COLOUR_KEY "forced_item_colour" @@ -135,6 +142,9 @@ bool can_autopickup(); bool need_to_autopickup(); void autopickup(); +void set_item_autopickup(const item_def &item, autopickup_level_type ap); +int item_autopickup_level(const item_def &item); + int find_free_slot(const item_def &i); bool need_to_autoinscribe(); diff --git a/crawl-ref/source/job-data.h b/crawl-ref/source/job-data.h index 9bfc398d661a..658d6e691d5e 100644 --- a/crawl-ref/source/job-data.h +++ b/crawl-ref/source/job-data.h @@ -72,8 +72,8 @@ static const map job_data = 3, 3, 6, { SP_TROLL, SP_HALFLING, SP_SPRIGGAN, SP_DEMONSPAWN, SP_VAMPIRE, SP_VINE_STALKER, }, - { "dagger plus:2", "blowgun", "robe", "cloak", "needle ego:poisoned q:8", - "needle ego:curare q:2" }, + { "dagger plus:2", "robe", "cloak", "dart ego:poisoned q:8", + "dart ego:curare q:2" }, WCHOICE_NONE, { { SK_FIGHTING, 2 }, { SK_DODGING, 1 }, { SK_STEALTH, 4 }, { SK_THROWING, 2 }, { SK_WEAPON, 2 }, }, @@ -265,7 +265,7 @@ static const map job_data = { SP_FELID, SP_HALFLING, SP_DEEP_DWARF, SP_SPRIGGAN, SP_CENTAUR, SP_BASE_DRACONIAN, }, { "leather armour", "book of Spatial Translocations", "scroll of blinking", - "tomahawk ego:dispersal q:5" }, + "boomerang ego:dispersal q:5" }, WCHOICE_PLAIN, { { SK_FIGHTING, 2 }, { SK_ARMOUR, 1 }, { SK_DODGING, 2 }, { SK_SPELLCASTING, 2 }, { SK_TRANSLOCATIONS, 3 }, { SK_THROWING, 1 }, diff --git a/crawl-ref/source/jobs.cc b/crawl-ref/source/jobs.cc index 96d8668f86dc..209b12b8a703 100644 --- a/crawl-ref/source/jobs.cc +++ b/crawl-ref/source/jobs.cc @@ -68,6 +68,8 @@ job_type get_job_by_name(const char *name) // Must be called after species_stat_init for the wanderer formula to work. void job_stat_init(job_type job) { + rng::subgenerator stat_rng; + you.hp_max_adj_perm = 0; you.base_stats[STAT_STR] += _job_def(job).s; diff --git a/crawl-ref/source/l-crawl.cc b/crawl-ref/source/l-crawl.cc index b18d6b25569c..c11c4321e61d 100644 --- a/crawl-ref/source/l-crawl.cc +++ b/crawl-ref/source/l-crawl.cc @@ -6,6 +6,7 @@ #include "l-libs.h" +#include "branch.h" #include "chardump.h" #include "cluautil.h" #include "command.h" @@ -846,6 +847,23 @@ static int crawl_split(lua_State *ls) return 1; } +/*** Compare two strings in a locale-independent way. + * Lua's built in comparison operations for strings are dependent on locale, + * which isn't always desireable. This is just a wrapper on + * std::basic_string::compare. + * + * @tparam string s1 the first string. + * @tparam string s2 the second sring. + * @treturn -1 if s1 < s2, 1 if s2 < s1, 0 if s1 == s2. + */ +static int crawl_string_compare(lua_State *ls) +{ + const string s1 = luaL_checkstring(ls, 1), + s2 = luaL_checkstring(ls, 2); + lua_pushnumber(ls, s1.compare(s2)); + return 1; +} + /*** Grammatically describe something. * Crawl provides the following description types: * @@ -1390,6 +1408,7 @@ static const struct luaL_reg crawl_clib[] = { "message_filter", crawl_message_filter }, { "trim", crawl_trim }, { "split", crawl_split }, + { "string_compare", crawl_string_compare }, { "grammar", _crawl_grammar }, { "article_a", crawl_article_a }, { "game_started", crawl_game_started }, @@ -1634,6 +1653,50 @@ LUAFN(crawl_hints_type) return 1; } +LUAFN(crawl_rng_wrap) +{ + if (!lua_isstring(ls, 2)) + luaL_error(ls, "rng_wrap missing rng name"); + string rng_name = lua_tostring(ls, 2); + if (!rng_name.size()) + luaL_error(ls, "rng_wrap missing rng name"); + rng::rng_type r = rng::NUM_RNGS; + if (rng_name == "gameplay") + r = rng::GAMEPLAY; + else if (rng_name == "ui") + r = rng::UI; + else if (rng_name == "system_specific") + r = rng::SYSTEM_SPECIFIC; + else if (rng_name == "subgenerator") + r = rng::SUB_GENERATOR; + else + { + branch_type b = NUM_BRANCHES; + if ((b = branch_by_shortname(rng_name)) == NUM_BRANCHES) + if ((b = branch_by_abbrevname(rng_name)) == NUM_BRANCHES) + luaL_error(ls, "Unknown rng name %s", rng_name.c_str()); + r = rng::get_branch_generator(b); + } + + lua_pop(ls, 1); // get rid of the rng name + if (!lua_isfunction(ls, 1)) + luaL_error(ls, "rng_wrap missing function"); + int result; + if (r == rng::SUB_GENERATOR) + { + rng::subgenerator subgen; // TODO: implement seed + seq? + result = lua_pcall(ls, 0, LUA_MULTRET, 0); + } + else + { + rng::generator gen(r); // generator to use + result = lua_pcall(ls, 0, LUA_MULTRET, 0); + } + if (result != 0) + luaL_error(ls, "Failed to run rng-wrapped function (%d)", result); + return lua_gettop(ls); +} + static const struct luaL_reg crawl_dlib[] = { { "args", _crawl_args }, @@ -1651,6 +1714,7 @@ static const struct luaL_reg crawl_dlib[] = { "mark_game_won", _crawl_mark_game_won }, { "hints_type", crawl_hints_type }, { "unavailable_god", _crawl_unavailable_god }, +{ "rng_wrap", crawl_rng_wrap }, { nullptr, nullptr } }; diff --git a/crawl-ref/source/l-debug.cc b/crawl-ref/source/l-debug.cc index 945c955ac2ff..2c499a95263b 100644 --- a/crawl-ref/source/l-debug.cc +++ b/crawl-ref/source/l-debug.cc @@ -105,6 +105,7 @@ LUAFN(debug_generate_level) tile_clear_flavour(); tile_new_level(true); builder(lua_isboolean(ls, 1)? lua_toboolean(ls, 1) : true); + update_portal_entrances(); return 0; } @@ -413,7 +414,7 @@ LUAFN(debug_reset_rng) unsigned int seed = (unsigned int) luaL_safe_checkint(ls, 1); Options.seed = (uint64_t) seed; } - reset_rng(); + rng::reset(); const string ret = make_stringf("%" PRIu64, Options.seed); lua_pushstring(ls, ret.c_str()); return 1; @@ -423,7 +424,7 @@ LUAFN(debug_get_rng_state) { string r = make_stringf("seed: %" PRIu64 ", generator states: ", Options.seed); - vector states = get_rng_states(); + vector states = rng::get_states(); for (auto i : states) r += make_stringf("%" PRIu64 " ", i); lua_pushstring(ls, r.c_str()); diff --git a/crawl-ref/source/l-dgngrd.cc b/crawl-ref/source/l-dgngrd.cc index f4dfd520f0ca..843079d59bc0 100644 --- a/crawl-ref/source/l-dgngrd.cc +++ b/crawl-ref/source/l-dgngrd.cc @@ -57,9 +57,9 @@ static int dgn_feature_desc(lua_State *ls) const dungeon_feature_type feat = static_cast(luaL_safe_checkint(ls, 1)); const description_level_type dtype = - lua_isnumber(ls, 2)? - static_cast(luaL_safe_checkint(ls, 2)) : - description_type_by_name(lua_tostring(ls, 2)); + lua_isnumber(ls, 2)? + static_cast(luaL_safe_checkint(ls, 2)) : + description_type_by_name(lua_tostring(ls, 2)); const bool need_stop = lua_isboolean(ls, 3)? lua_toboolean(ls, 3) : false; const string s = feature_description(feat, NUM_TRAPS, "", dtype, need_stop); lua_pushstring(ls, s.c_str()); @@ -69,9 +69,9 @@ static int dgn_feature_desc(lua_State *ls) static int dgn_feature_desc_at(lua_State *ls) { const description_level_type dtype = - lua_isnumber(ls, 3)? - static_cast(luaL_safe_checkint(ls, 3)) : - description_type_by_name(lua_tostring(ls, 3)); + lua_isnumber(ls, 3) ? + static_cast(luaL_safe_checkint(ls, 3)) : + description_type_by_name(lua_tostring(ls, 3)); const bool need_stop = lua_isboolean(ls, 4)? lua_toboolean(ls, 4) : false; const string s = feature_description_at(coord_def(luaL_safe_checkint(ls, 1), luaL_safe_checkint(ls, 2)), diff --git a/crawl-ref/source/l-dgnit.cc b/crawl-ref/source/l-dgnit.cc index b3967f50197a..8e192959ea48 100644 --- a/crawl-ref/source/l-dgnit.cc +++ b/crawl-ref/source/l-dgnit.cc @@ -67,6 +67,13 @@ static int dgn_items_at(lua_State *ls) return 1; } +static int dgn_shop_inventory_at(lua_State *ls) +{ + // TODO: this could probably be done in a way that didn't need to be dlua. + COORDS(s, 1, 2); + return lua_push_shop_items_at(ls, s); +} + static int _dgn_item_spec(lua_State *ls) { const item_list ilist = _lua_get_ilist(ls, 1); @@ -248,6 +255,7 @@ const struct luaL_reg dgn_item_dlib[] = { { "item_from_index", dgn_item_from_index }, { "items_at", dgn_items_at }, + { "shop_inventory_at", dgn_shop_inventory_at }, { "create_item", dgn_create_item }, { "item_property_remove", dgn_item_property_remove }, { "item_property_set", dgn_item_property_set }, diff --git a/crawl-ref/source/l-food.cc b/crawl-ref/source/l-food.cc index 59f9a4a211f6..ec8303d76b34 100644 --- a/crawl-ref/source/l-food.cc +++ b/crawl-ref/source/l-food.cc @@ -137,19 +137,6 @@ static int food_isveggie(lua_State *ls) return 1; } -/*** Can we bottle blood from this? - * @tparam items.Item body - * @treturn boolean if we succeed - * @function bottleable - */ -static int food_bottleable(lua_State *ls) -{ - LUA_ITEM(ls, item, 1); - lua_pushboolean(ls, item && item->is_type(OBJ_CORPSES, CORPSE_BODY) - && can_bottle_blood_from_corpse(item->mon_type)); - return 1; -} - /*** Is this edible? * Differs from can_eat in that it checks temporary forms? * @tparam items.Item morsel @@ -175,7 +162,6 @@ static const struct luaL_reg food_lib[] = { "isfruit", food_isfruit }, { "ismeaty", food_ismeaty }, { "isveggie", food_isveggie }, - { "bottleable", food_bottleable }, { "edible", food_edible }, { nullptr, nullptr }, }; diff --git a/crawl-ref/source/l-item.cc b/crawl-ref/source/l-item.cc index a683be2cd51c..2a215bb98ea1 100644 --- a/crawl-ref/source/l-item.cc +++ b/crawl-ref/source/l-item.cc @@ -1467,16 +1467,13 @@ static int l_item_get_items_at(lua_State *ls) return 1; } -/*** See what a shop has for sale. - * Only works when standing at a shop. - * @treturn array|nil An array of @{Item} objects or nil if not on a shop - * @function shop_inventory - */ -static int l_item_shop_inventory(lua_State *ls) +int lua_push_shop_items_at(lua_State *ls, const coord_def &s) { - shop_struct *shop = shop_at(you.pos()); + // also used in l-dgnit.cc + shop_struct *shop = shop_at(s); if (!shop) return 0; + shopping_list.refresh(); // prevent crash if called during tests lua_newtable(ls); @@ -1497,6 +1494,16 @@ static int l_item_shop_inventory(lua_State *ls) return 1; } +/*** See what a shop has for sale. + * Only works when standing at a shop. + * @treturn array|nil An array of @{Item} objects or nil if not on a shop + * @function shop_inventory + */ +static int l_item_shop_inventory(lua_State *ls) +{ + return lua_push_shop_items_at(ls, you.pos()); +} + /*** Look at the shopping list. * @treturn array|nil Array of @{Item}s on the shopping list or nil if the * shopping list is empty diff --git a/crawl-ref/source/l-libs.h b/crawl-ref/source/l-libs.h index 5969ebc5bc0e..50267ac9a9cc 100644 --- a/crawl-ref/source/l-libs.h +++ b/crawl-ref/source/l-libs.h @@ -84,3 +84,5 @@ int dgn_map_add_transform(lua_State *ls, struct monster_info; void lua_push_moninf(lua_State *ls, monster_info *mi); + +int lua_push_shop_items_at(lua_State *ls, const coord_def &s); diff --git a/crawl-ref/source/l-moninf.cc b/crawl-ref/source/l-moninf.cc index a62e3c9a2437..d4f7dd603343 100644 --- a/crawl-ref/source/l-moninf.cc +++ b/crawl-ref/source/l-moninf.cc @@ -13,9 +13,15 @@ #include "l-defs.h" #include "libutil.h" // map_find #include "mon-book.h" +#include "mon-pick.h" #include "spl-util.h" #include "stringutil.h" #include "transform.h" +#include "math.h" // ceil +#include "spl-zap.h" // calc_spell_power +#include "evoke.h" // wand_mp_cost +#include "god-abil.h" // pakellas_effective_hex_power +#include "describe.h" // describe_info, get_monster_db_desc #define MONINF_METATABLE "monster.info" @@ -208,6 +214,97 @@ MIRES1(res_shock, MR_RES_ELEC) */ MIRES1(res_corr, MR_RES_ACID) +/*** The monster's max HP given in its description. + * @treturn string describing the max HP (usually "about X"). + * @function max_hp + */ +static int moninf_get_max_hp(lua_State *ls) +{ + MONINF(ls, 1, mi); + lua_pushstring(ls, mi->get_max_hp_desc().c_str()); + return 1; +} + +/*** The monster's MR level, in "pips" (number of +'s shown on its description). + * Returns a value ranging from 0 to 125 (immune). + * @treturn int MR level + * @function mr + */ +static int moninf_get_mr(lua_State *ls) +{ + MONINF(ls, 1, mi); + lua_pushnumber(ls, ceil(1.0*mi->res_magic()/MR_PIP)); + return 1; +} + +/*** Your probability of defeating the monster's MR with a given spell or zap. + * Returns a value ranging from 0 (no chance) to 100 (guaranteed success). + * Returns nil if MR does not apply or the spell can't be cast. + * @tparam string spell name + * @tparam[opt] boolean true if this spell is evoked rather than cast; + * defaults to false + * @treturn int|string|nil percent chance of success (0-100); + * returns "magic immune" if monster is immune; + * returns nil if MR does not apply. + * @function defeat_mr + */ +static int moninf_get_defeat_mr(lua_State *ls) +{ + MONINF(ls, 1, mi); + spell_type spell = spell_by_name(luaL_checkstring(ls, 2), false); + bool is_evoked = lua_isboolean(ls, 3) ? lua_toboolean(ls, 3) : false; + int power = is_evoked ? + (15 + you.skill(SK_EVOCATIONS, 7) / 2) * (wand_mp_cost() + 9) / 9 : + calc_spell_power(spell, true); + spell_flags flags = get_spell_flags(spell); + bool mr_check = testbits(flags, spflag::MR_check) + && testbits(flags, spflag::dir_or_target) + && !testbits(flags, spflag::helpful); + if (power <= 0 || !mr_check) + { + lua_pushnil(ls); + return 1; + } + int mr = mi->res_magic(); + if (mr == MAG_IMMUNE) + { + lua_pushstring(ls, "magic immune"); + return 1; + } + zap_type zap = spell_to_zap(spell); + int eff_power = zap == NUM_ZAPS ? power : zap_ench_power(zap, power, false); + int adj_power = is_evoked ? pakellas_effective_hex_power(eff_power) : eff_power; + int success = hex_success_chance(mr, adj_power, 100); + lua_pushnumber(ls, success); + return 1; +} + +/*** The monster's AC value, in "pips" (number of +'s shown on its description). + * Returns a value ranging from 0 to 5 (highest). + * @treturn int AC level + * @function ac + */ +static int moninf_get_ac(lua_State *ls) +{ + MONINF(ls, 1, mi); + lua_pushnumber(ls, ceil(mi->ac/5.0)); + return 1; +} +/*** The monster's EV value, in "pips" (number of +'s shown on its description). + * Returns a value ranging from 0 to 5 (highest). + * @treturn int evasion level + * @function ev + */ +static int moninf_get_ev(lua_State *ls) +{ + MONINF(ls, 1, mi); + int value = mi->ev; + if (!value && mi->base_ev != INT_MAX) + value = mi->base_ev; + lua_pushnumber(ls, ceil(value/5.0)); + return 1; +} + /*** Get the monster's holiness. * If passed a holiness, returns a boolean test of whether the monster has the * given holiness. Otherwise returns a string describing the monster's @@ -241,6 +338,33 @@ LUAFN(moninf_get_holiness) PLUARET(string, holiness_description(mi->holi).c_str()); } +/*** Get the monster's average depth of (random) generation in the current branch + * Returns -1 if the monster is not generated in this branch. Does not handle + * fish or zombies. + * @treturn number + * @function avg_local_depth + */ +LUAFN(moninf_get_avg_local_depth) +{ + MONINF(ls, 1, mi); + PLUARET(number, monster_pop_depth_avg(you.where_are_you, mi->type)); +} + +/*** Get the monster's probability of randomly generating on the current floor + * This can be used to estimate difficulty, but keep in mind that it is highly + * dependent on the branch's generation table. + * Returns -1 if the monster is not generated in this branch. Does not handle + * fish or zombies. + * @treturn number + * @function avg_local_prob + */ +LUAFN(moninf_get_avg_local_prob) +{ + MONINF(ls, 1, mi); + PLUARET(number, monster_probability(level_id::current(), mi->type)); +} + + // const char* here would save a tiny bit of memory, but every map // for an unique pair of types costs 35KB of code. We have // map elsewhere. @@ -494,16 +618,31 @@ LUAFN(moninf_get_damage_desc) } /*** A description of this monster. + * @tparam[opt] boolean set true to get the description information body + * displayed when examining the monster; if false (default) returns + * a short description. * @treturn string * @function desc */ LUAFN(moninf_get_desc) { MONINF(ls, 1, mi); - string desc; - int col; - mi->to_string(1, desc, col); - lua_pushstring(ls, desc.c_str()); + if (lua_isboolean(ls, 2) && lua_toboolean(ls, 2)) + { + // full description + describe_info inf; + bool has_stat_desc; + get_monster_db_desc(*mi, inf, has_stat_desc, false); + lua_pushstring(ls, inf.body.str().c_str()); + } + else + { + // short description + string desc; + int col; + mi->to_string(1, desc, col); + lua_pushstring(ls, desc.c_str()); + } return 1; } @@ -586,9 +725,16 @@ static const struct luaL_reg moninf_lib[] = MIREG(res_draining), MIREG(res_shock), MIREG(res_corr), + MIREG(max_hp), + MIREG(mr), + MIREG(defeat_mr), + MIREG(ac), + MIREG(ev), MIREG(x_pos), MIREG(y_pos), MIREG(pos), + MIREG(avg_local_depth), + MIREG(avg_local_prob), { nullptr, nullptr } }; diff --git a/crawl-ref/source/l-mons.cc b/crawl-ref/source/l-mons.cc index 3923b81dc936..8305fff38ffb 100644 --- a/crawl-ref/source/l-mons.cc +++ b/crawl-ref/source/l-mons.cc @@ -9,10 +9,12 @@ #include "cluautil.h" #include "database.h" #include "dlua.h" +#include "items.h" #include "libutil.h" #include "mon-act.h" #include "mon-behv.h" #include "mon-death.h" +#include "mon-pick.h" #include "mon-speak.h" #include "monster.h" #include "mon-util.h" @@ -117,6 +119,19 @@ MDEF(energy) PLUARET(number, (mons->speed_increment - 79)); } +MDEF(in_local_population) +{ + // TODO: should this be in moninf? need a mons for the native check... + // The nativity check is because there are various cases where monsters are + // not in the population tables, but should be considered native, e.g. + // vault gaurds in vaults, orb guardians in zot 5 + PLUARET(boolean, + mons_is_native_in_branch(*mons, you.where_are_you) + || monster_in_population(you.where_are_you, mons->type) + || monster_in_population(you.where_are_you, mons->mons_species(false)) + || monster_in_population(you.where_are_you, mons->mons_species(true))); +} + LUAFN(l_mons_add_energy) { ASSERT_DLUA; @@ -207,6 +222,23 @@ MDEF(muse) return 0; } +static int l_mons_get_inventory(lua_State *ls) +{ + monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); + lua_newtable(ls); + int index = 0; + for (mon_inv_iterator ii(*mons); ii; ++ii) + { + if (ii->defined()) + { + clua_push_item(ls, &*ii); + lua_rawseti(ls, -2, ++index); + } + } + return 1; +} +MDEFN(inventory, get_inventory) + static int l_mons_do_dismiss(lua_State *ls) { // dismiss is only callable from dlua, not from managed VMs (i.e. @@ -474,6 +506,18 @@ static int l_mons_do_speak(lua_State *ls) MDEFN(speak, do_speak) +static int l_mons_do_get_info(lua_State *ls) +{ + // for a non-dlua version, see monster.get_monster_at + ASSERT_DLUA; + monster* m = clua_get_lightuserdata(ls, lua_upvalueindex(1)); + monster_info mi(m); + lua_push_moninf(ls, &mi); + return 1; +} + +MDEFN(get_info, do_get_info) + struct MonsAccessor { const char *attribute; @@ -521,8 +565,11 @@ static MonsAccessor mons_attrs[] = { "add_ench", l_mons_add_ench }, { "del_ench", l_mons_del_ench }, { "you_can_see", l_mons_you_can_see }, + { "get_inventory", l_mons_inventory }, + { "in_local_population", l_mons_in_local_population }, - { "speak", l_mons_speak } + { "speak", l_mons_speak }, + { "get_info", l_mons_get_info } }; static int monster_get(lua_State *ls) diff --git a/crawl-ref/source/l-travel.cc b/crawl-ref/source/l-travel.cc index fe348e935de0..885484546e80 100644 --- a/crawl-ref/source/l-travel.cc +++ b/crawl-ref/source/l-travel.cc @@ -11,9 +11,17 @@ #include "player.h" #include "terrain.h" #include "travel.h" +#include "map-knowledge.h" + +static bool _in_map_bounds(const coord_def& p) +{ + std::pair b = known_map_bounds(); + return p.x >= b.first.x && p.y >= b.first.y + && p.x <= b.second.x && p.y <= b.second.y; +} /*** Set an exclusion. - * Uses player centered coordinates + * Uses player-centered coordinates * @tparam int x * @tparam int y * @tparam[opt=LOS_RADIUS] int r @@ -25,8 +33,8 @@ LUAFN(l_set_exclude) s.x = luaL_safe_checkint(ls, 1); s.y = luaL_safe_checkint(ls, 2); const coord_def p = player2grid(s); - if (!in_bounds(p)) - return 0; + if (!_in_map_bounds(p)) + return luaL_error(ls, "Coordinates out of bounds: (%d, %d)", s.x, s.y); // XXX: dedup w/_get_full_exclusion_radius()? int r = LOS_RADIUS; if (lua_gettop(ls) > 2) @@ -36,7 +44,7 @@ LUAFN(l_set_exclude) } /*** Remove an exclusion. - * Uses player centered coordinates + * Uses player-centered coordinates * @tparam int x * @tparam int y * @function del_eclude @@ -47,8 +55,8 @@ LUAFN(l_del_exclude) s.x = luaL_safe_checkint(ls, 1); s.y = luaL_safe_checkint(ls, 2); const coord_def p = player2grid(s); - if (!in_bounds(p)) - return 0; + if (!_in_map_bounds(p)) + return luaL_error(ls, "Coordinates out of bounds: (%d, %d)", s.x, s.y); del_exclude(p); return 0; } @@ -101,8 +109,8 @@ LUAFN(l_find_deepest_explored) LUAFN(l_waypoint_delta) { int waynum = luaL_safe_checkint(ls, 1); - if (waynum < 0 || waynum > 9) - return 0; + if (waynum < 0 || waynum >= TRAVEL_WAYPOINT_COUNT) + return luaL_error(ls, "Bad waypoint number: %d", waynum); const level_pos waypoint = travel_cache.get_waypoint(waynum); if (waypoint.id != level_id::current()) return 0; @@ -112,6 +120,28 @@ LUAFN(l_waypoint_delta) return 2; } +/*** Set a numbered waypoint. + * Uses player-centered coordinates + * @tparam int waynum + * @tparam int x + * @tparam int y + * @function set_waypoint + */ +LUAFN(l_set_waypoint) +{ + int waynum = luaL_safe_checkint(ls, 1); + if (waynum < 0 || waynum >= TRAVEL_WAYPOINT_COUNT) + return luaL_error(ls, "Bad waypoint number: %d", waynum); + coord_def s; + s.x = luaL_safe_checkint(ls, 2); + s.y = luaL_safe_checkint(ls, 3); + const coord_def p = player2grid(s); + if (!_in_map_bounds(p)) + return luaL_error(ls, "Coordinates out of bounds: (%d, %d)", s.x, s.y); + travel_cache.set_waypoint(waynum, p.x, p.y); + return 0; +} + static const struct luaL_reg travel_lib[] = { { "set_exclude", l_set_exclude }, @@ -120,6 +150,7 @@ static const struct luaL_reg travel_lib[] = { "feature_solid", l_feature_is_solid }, { "find_deepest_explored", l_find_deepest_explored }, { "waypoint_delta", l_waypoint_delta }, + { "set_waypoint", l_set_waypoint }, { nullptr, nullptr } }; diff --git a/crawl-ref/source/l-wiz.cc b/crawl-ref/source/l-wiz.cc index b65088b2da32..ad2b2bb1d889 100644 --- a/crawl-ref/source/l-wiz.cc +++ b/crawl-ref/source/l-wiz.cc @@ -11,7 +11,9 @@ module "crawl" #include "cluautil.h" #include "mon-util.h" #include "stringutil.h" +#include "wiz-dgn.h" #include "wiz-fsim.h" +#include "wiz-item.h" #ifdef WIZARD @@ -39,10 +41,15 @@ LUAFN(wiz_quick_fsim) PLUARET(number, fdata.player.av_eff_dam); } +LUAWRAP(wiz_identify_all_items, wizard_identify_all_items()) + +LUAWRAP(wiz_map_level, wizard_map_level()) + static const struct luaL_reg wiz_dlib[] = { { "quick_fsim", wiz_quick_fsim }, - +{ "identify_all_items", wiz_identify_all_items}, +{ "map_level", wiz_map_level}, { nullptr, nullptr } }; diff --git a/crawl-ref/source/l-you.cc b/crawl-ref/source/l-you.cc index 79016a696718..e209aa778314 100644 --- a/crawl-ref/source/l-you.cc +++ b/crawl-ref/source/l-you.cc @@ -188,7 +188,7 @@ LUARET1(you_skill_progress, number, * @function can_train_skill */ LUARET1(you_can_train_skill, boolean, - lua_isstring(ls, 1) ? you.can_train[str_to_skill(lua_tostring(ls, 1))] + lua_isstring(ls, 1) ? you.can_currently_train[str_to_skill(lua_tostring(ls, 1))] : false) /*** Best skill. * @treturn string @@ -475,7 +475,7 @@ LUARET1(you_turns_on_level, number, env.turns_on_level) /*** Interrupt the current multi-turn activity or macro sequence. * @function stop_activity */ -LUAWRAP(you_stop_activity, interrupt_activity(AI_FORCE_INTERRUPT)) +LUAWRAP(you_stop_activity, interrupt_activity(activity_interrupt::force)) /*** Are you taking the stairs? * @treturn boolean * @function taking_stairs @@ -1524,6 +1524,12 @@ LUAFN(you_init) PLUARET(string, skill_name(item_attack_skill(OBJ_WEAPONS, ng.weapon))); } +LUAFN(you_enter_wizard_mode) +{ + you.wizard = true; + return 0; +} + LUARET1(you_exp_needed, number, exp_needed(luaL_safe_checkint(ls, 1))) LUAWRAP(you_exercise, exercise(str_to_skill(luaL_checkstring(ls, 1)), 1)) LUARET1(you_skill_cost_level, number, you.skill_cost_level) @@ -1565,6 +1571,7 @@ static const struct luaL_reg you_dlib[] = { "delete_all_mutations", you_delete_all_mutations }, { "change_species", you_change_species }, #ifdef WIZARD +{ "enter_wizard_mode", you_enter_wizard_mode }, { "set_xl", you_set_xl }, #endif diff --git a/crawl-ref/source/level-state-type.h b/crawl-ref/source/level-state-type.h index 6012f094b25e..8704b95be267 100644 --- a/crawl-ref/source/level-state-type.h +++ b/crawl-ref/source/level-state-type.h @@ -6,7 +6,9 @@ enum level_state_type LSTATE_NONE = 0, LSTATE_GOLUBRIA = (1 << 0), // A Golubria trap exists. +#if TAG_MAJOR_VERSION == 34 LSTATE_GLOW_MOLD = (1 << 1), // Any glowing mold exists. +#endif LSTATE_DELETED = (1 << 2), // The level won't be saved. LSTATE_BEOGH = (1 << 3), // Possibly an orcish priest around. LSTATE_SLIMY_WALL = (1 << 4), // Any slime walls exist. diff --git a/crawl-ref/source/libconsole.h b/crawl-ref/source/libconsole.h index 61578301ec76..137658a2f792 100644 --- a/crawl-ref/source/libconsole.h +++ b/crawl-ref/source/libconsole.h @@ -31,8 +31,6 @@ void update_screen(); void set_cursor_enabled(bool enabled); bool is_cursor_enabled(); -void enable_smart_cursor(bool cursor); -bool is_smart_cursor_enabled(); void set_mouse_enabled(bool enabled); static inline void put_colour_ch(int colour, unsigned ch) diff --git a/crawl-ref/source/libgui.cc b/crawl-ref/source/libgui.cc index a7cdfb8fbfb0..f51f0a81cbcc 100644 --- a/crawl-ref/source/libgui.cc +++ b/crawl-ref/source/libgui.cc @@ -113,15 +113,6 @@ bool is_cursor_enabled() return false; } -bool is_smart_cursor_enabled() -{ - return false; -} - -void enable_smart_cursor(bool /*cursor*/) -{ -} - int wherex() { return TextRegion::wherex(); diff --git a/crawl-ref/source/libunix.cc b/crawl-ref/source/libunix.cc index fc4f5ae21697..fa0a888be5c9 100644 --- a/crawl-ref/source/libunix.cc +++ b/crawl-ref/source/libunix.cc @@ -73,13 +73,15 @@ static COLOURS BG_COL = BLACK; /** @brief The default background @em colour. */ static COLOURS BG_COL_DEFAULT = BLACK; +struct curses_style +{ + attr_t attr; + short color_pair; +}; + /** * @brief Get curses attributes for the current internal color combination. * - * @param attr - * The destination for the resulting character attributes. - * @param color_pair - * The destination for the resulting color pair index. * @param fg * The internal colour for the foreground. * @param bg @@ -87,33 +89,33 @@ static COLOURS BG_COL_DEFAULT = BLACK; * @param adjust_background * Determines which color in the pair is adjusted if adjustment is necessary. * True chooses the background, while false chooses the foreground. + * + * @return + * Returns the character attributes and colour pair index. */ -static void curs_attr(attr_t &attr, short &color_pair, COLOURS fg, COLOURS bg, - bool adjust_background = false); +static curses_style curs_attr(COLOURS fg, COLOURS bg, bool adjust_background = false); /** * @brief Change the foreground colour and returns the resulting curses info. * - * @param attr - * The destination for the resulting character attributes. - * @param color_pair - * The destination for the resulting color pair index. * @param col * An internal color with attached brand. + * + * @return + * Returns the character attributes and colour pair index. */ -static void curs_attr_bg(attr_t &attr, short &color_pair, int col); +static curses_style curs_attr_bg(int col); /** * @brief Change the background colour and returns the resulting curses info. * - * @param attr - * The destination for the resulting character attributes. - * @param color_pair - * The destination for the resulting color pair index. * @param col * An internal color with attached brand. + * + * @return + * Returns the character attributes and colour pair index. */ -static void curs_attr_fg(attr_t &attr, short &color_pair, int col); +static curses_style curs_attr_fg(int col); /** * @brief Get curses attributes for the passed internal color combination. @@ -121,19 +123,17 @@ static void curs_attr_fg(attr_t &attr, short &color_pair, int col); * Performs colour mapping for both default colours as well as the color map. * Additionally, this function takes into consideration a passed brand. * - * @param attr - * The destination for the resulting character attributes. - * @param color_pair - * The destination for the resulting color pair index. * @param fg * The internal colour for the foreground. * @param bg * The internal colour for the background. * @param brand * Internal color branding information. + * + * @return + * Returns the character attributes and colour pair index. */ -static void curs_attr_mapped(attr_t &attr, short &color_pair, COLOURS fg, - COLOURS bg, int brand); +static curses_style curs_attr_mapped(COLOURS fg, COLOURS bg, int brand); /** * @brief Returns a curses color pair index for the passed fg/bg combo. @@ -264,17 +264,15 @@ static void flip_colour(cchar_t &ch); * resulting color combination may not be a strict color swap, but one that * is guaranteed to be visible. * - * @param attr_flipped - * The location to store the resulting character attributes. - * @param color_pair_flipped - * The location to store the resulting color pair. - * @param attr_original + * @param attr * The character attributes to flip. - * @param color_pair_original + * @param color * The color pair to flip. + * + * @return + * Returns the flipped character attributes and colour pair index. */ -static void flip_colour(attr_t &attr_flipped, short &color_pair_flipped, - attr_t attr_original, short color_pair_original); +static curses_style flip_colour(curses_style style); /** * @brief Translate internal colours to flagged curses colors. @@ -305,7 +303,7 @@ static void write_char_at(int y, int x, const cchar_t &ch); static bool cursor_is_enabled = true; -static unsigned int convert_to_curses_attr(int chattr) +static unsigned int convert_to_curses_style(int chattr) { switch (chattr & CHATTR_ATTRMASK) { @@ -884,15 +882,6 @@ bool is_cursor_enabled() return cursor_is_enabled; } -bool is_smart_cursor_enabled() -{ - return false; -} - -void enable_smart_cursor(bool /*cursor*/) -{ -} - static inline unsigned get_brand(int col) { return (col & COLFLAG_FRIENDLY_MONSTER) ? Options.friend_brand : @@ -907,11 +896,11 @@ static inline unsigned get_brand(int col) } // see declaration -static void curs_attr(attr_t &attr, short &color_pair, COLOURS fg, COLOURS bg, - bool adjust_background) +static curses_style curs_attr(COLOURS fg, COLOURS bg, bool adjust_background) { - attr_t flags = 0; - short temp_color_pair = 0; + curses_style style; + style.attr = 0; + style.color_pair = 0; bool monochrome_output_requested = curs_palette_size() == 0; // Convert over to curses colors. @@ -925,10 +914,10 @@ static void curs_attr(attr_t &attr, short &color_pair, COLOURS fg, COLOURS bg, if (!monochrome_output_requested) { // Grab the color pair. - temp_color_pair = curs_calc_pair_safe(fg_curses, bg_curses); + style.color_pair = curs_calc_pair_safe(fg_curses, bg_curses); // Request decolorize if the pair doesn't actually exist. - if (temp_color_pair == 0 + if (style.color_pair == 0 && !curs_color_combo_has_pair(fg_curses, bg_curses)) { monochrome_output_requested = true; @@ -943,7 +932,7 @@ static void curs_attr(attr_t &attr, short &color_pair, COLOURS fg, COLOURS bg, && (Options.bold_brightens_foreground || Options.best_effort_brighten_foreground)) { - flags |= WA_BOLD; + style.attr |= WA_BOLD; } // curses typically uses WA_BLINK to give bright background colour, @@ -952,7 +941,7 @@ static void curs_attr(attr_t &attr, short &color_pair, COLOURS fg, COLOURS bg, && (Options.blink_brightens_background || Options.best_effort_brighten_background)) { - flags |= WA_BLINK; + style.attr |= WA_BLINK; } } @@ -960,46 +949,41 @@ static void curs_attr(attr_t &attr, short &color_pair, COLOURS fg, COLOURS bg, { // Decolorize the output if necessary. if (curs_palette_size() != 0) - temp_color_pair = curs_calc_pair_safe(COLOR_WHITE, COLOR_BLACK); + style.color_pair = curs_calc_pair_safe(COLOR_WHITE, COLOR_BLACK); // Do the best we can for backgrounds with monochrome output. if (bg_curses != COLOR_BLACK) - flags |= WA_REVERSE; + style.attr |= WA_REVERSE; } - // Got everything we need -- write out the results. - color_pair = temp_color_pair; - attr = flags; + return style; } // see declaration -static void curs_attr_bg(attr_t &attr, short &color_pair, int col) +static curses_style curs_attr_bg(int col) { BG_COL = static_cast(col & 0x00ff); - curs_attr_mapped(attr, color_pair, FG_COL, BG_COL, get_brand(col)); + return curs_attr_mapped(FG_COL, BG_COL, get_brand(col)); } // see declaration -static void curs_attr_fg(attr_t &attr, short &color_pair, int col) +static curses_style curs_attr_fg(int col) { FG_COL = static_cast(col & 0x00ff); - curs_attr_mapped(attr, color_pair, FG_COL, BG_COL, get_brand(col)); + return curs_attr_mapped(FG_COL, BG_COL, get_brand(col)); } // see declaration -static void curs_attr_mapped(attr_t &attr, short &color_pair, COLOURS fg, - COLOURS bg, int brand) +static curses_style curs_attr_mapped(COLOURS fg, COLOURS bg, int brand) { COLOURS fg_mod = fg; COLOURS bg_mod = bg; attr_t flags = 0; - attr_t temp_attr = 0; - short temp_color_pair = 0; // calculate which curses flags we need... if (brand != CHATTR_NORMAL) { - flags |= convert_to_curses_attr(brand); + flags |= convert_to_curses_style(brand); // Allow highlights to override the current background color. if ((brand & CHATTR_ATTRMASK) == CHATTR_HILITE) @@ -1023,16 +1007,14 @@ static void curs_attr_mapped(attr_t &attr, short &color_pair, COLOURS fg, bg_mod = BLACK; // Done with color mapping; get the resulting attributes. - curs_attr(temp_attr, temp_color_pair, fg_mod, bg_mod); - temp_attr |= flags; + curses_style ret = curs_attr(fg_mod, bg_mod); + ret.attr |= flags; // Reverse color manually to ensure correct brightening attrs. if ((brand & CHATTR_ATTRMASK) == CHATTR_REVERSE) - flip_colour(temp_attr, temp_color_pair, temp_attr, temp_color_pair); + return flip_colour(ret); - // Write out the results. - color_pair = temp_color_pair; - attr = temp_attr; + return ret; } // see declaration @@ -1135,9 +1117,6 @@ static bool curs_color_combo_has_pair(short fg, short bg) static void curs_adjust_color_pair_to_non_identical(short &fg, short &bg, bool adjust_background) { - // The color to assign. - short non_conflicting_color = adjust_background ? bg : fg; - // The default colors. short fg_default = translate_colour(FG_COL_DEFAULT); short bg_default = translate_colour(BG_COL_DEFAULT); @@ -1164,76 +1143,73 @@ static void curs_adjust_color_pair_to_non_identical(short &fg, short &bg, } } - // Got the adjusted colors -- resolve any conflict. - if (fg_to_compare == bg_to_compare) - { - // Choose terminal's current default colors as the default failsafe. - short failsafe_col = adjust_background ? fg_default : bg_default; + if (fg_to_compare != bg_to_compare) + return; // colours look different; no need to adjust - if (!adjust_background && fg_to_compare == bg_default_to_compare) + // Choose terminal's current default colors as the default failsafe. + short failsafe_col = adjust_background ? fg_default : bg_default; + + if (!adjust_background && fg_to_compare == bg_default_to_compare) + { + /* + * Replacing the *foreground* color with a secondary failsafe. + * + * In general, use black as a failsafe for non-black backgrounds + * and white as a failsafe for black backgrounds. Black tends to + * look good on any visible background. + * + * However, for black and white *default* background colours, + * mitigate information contrast issues with bright black + * foregrounds by using blue as a special-case failsafe color. + */ + switch (bg_default_to_compare) { - /* - * Replacing the *foreground* color with a secondary failsafe. - * - * In general, use black as a failsafe for non-black backgrounds - * and white as a failsafe for black backgrounds. Black tends to - * look good on any visible background. - * - * However, for black and white *default* background colours, - * mitigate information contrast issues with bright black - * foregrounds by using blue as a special-case failsafe color. - */ - switch (bg_default_to_compare) + case COLOR_BLACK: + if (fg_default_to_compare == COLOR_WHITE + || fg_default_to_compare == COLOR_BLACK) { - case COLOR_BLACK: - if (fg_default_to_compare == COLOR_WHITE - || fg_default_to_compare == COLOR_BLACK) - { - failsafe_col = COLOR_BLUE; - } - else - failsafe_col = COLOR_WHITE; - break; - case COLOR_WHITE: - if (fg_default_to_compare == COLOR_WHITE - || fg_default_to_compare == COLOR_BLACK) - { - failsafe_col = COLOR_BLUE; - } - else - failsafe_col = COLOR_BLACK; - break; - default: - failsafe_col = COLOR_BLACK; - break; + failsafe_col = COLOR_BLUE; } - } - else if (adjust_background && bg_to_compare == fg_default_to_compare) - { - /* - * Replacing the *background* color with a secondary failsafe. - * - * Don't bother special-casing bright black: - * - The information contrast issue is not as prevalent for - * backgrounds as foregrounds. - * - A visible bright-black-on-black glyph actively changing into - * black-on-blue when reversed looks much worse than a change to - * black-on-white. - */ - if (fg_default_to_compare == COLOR_BLACK) + else failsafe_col = COLOR_WHITE; + break; + case COLOR_WHITE: + if (fg_default_to_compare == COLOR_WHITE + || fg_default_to_compare == COLOR_BLACK) + { + failsafe_col = COLOR_BLUE; + } else failsafe_col = COLOR_BLACK; + break; + default: + failsafe_col = COLOR_BLACK; + break; } - - non_conflicting_color = failsafe_col; + } + else if (adjust_background && bg_to_compare == fg_default_to_compare) + { + /* + * Replacing the *background* color with a secondary failsafe. + * + * Don't bother special-casing bright black: + * - The information contrast issue is not as prevalent for + * backgrounds as foregrounds. + * - A visible bright-black-on-black glyph actively changing into + * black-on-blue when reversed looks much worse than a change to + * black-on-white. + */ + if (fg_default_to_compare == COLOR_BLACK) + failsafe_col = COLOR_WHITE; + else + failsafe_col = COLOR_BLACK; } // Update the appropriate color in the pair. if (adjust_background) - bg = non_conflicting_color; + bg = failsafe_col; else - fg = non_conflicting_color; + fg = failsafe_col; } // see declaration @@ -1379,10 +1355,8 @@ static COLOURS curses_color_to_internal_colour(short col) void textcolour(int col) { - attr_t attr = 0; - short color_pair = 0; - curs_attr_fg(attr, color_pair, col); - attr_set(attr, color_pair, nullptr); + const auto style = curs_attr_fg(col); + attr_set(style.attr, style.color_pair, nullptr); #ifdef USE_TILE_WEB tiles.textcolour(col); @@ -1391,10 +1365,8 @@ void textcolour(int col) void textbackground(int col) { - attr_t attr = 0; - short color_pair = 0; - curs_attr_bg(attr, color_pair, col); - attr_set(attr, color_pair, nullptr); + const auto style = curs_attr_bg(col); + attr_set(style.attr, style.color_pair, nullptr); #ifdef USE_TILE_WEB tiles.textbackground(col); @@ -1460,28 +1432,24 @@ static void flip_colour(cchar_t &ch) wch = new wchar_t[chars_to_allocate]; // Good to go. Grab the color / attr info. - getcchar(&ch, wch, &attr, &color_pair, nullptr); - - // Pass along to the more generic function for the heavy lifting. - flip_colour(attr, color_pair, attr, color_pair); + curses_style style; + getcchar(&ch, wch, &style.attr, &style.color_pair, nullptr); + style = flip_colour(style); // Assign the new, reversed info and clean up. - setcchar(&ch, wch, attr, color_pair, nullptr); + setcchar(&ch, wch, style.attr, style.color_pair, nullptr); if (chars_to_allocate > 0) delete [] wch; } // see declaration -void flip_colour(attr_t &attr_flipped, short &color_pair_flipped, - attr_t attr_original, short color_pair_original) +static curses_style flip_colour(curses_style style) { short fg = COLOR_WHITE; short bg = COLOR_BLACK; - attr_t temp_attr = attr_original; - short temp_color_pair = color_pair_original; - if (color_pair_original != 0) - pair_content(color_pair_original, &fg, &bg); + if (style.color_pair != 0) + pair_content(style.color_pair, &fg, &bg); else { // Default pair; use the current default colors. @@ -1495,22 +1463,21 @@ void flip_colour(attr_t &attr_flipped, short &color_pair_flipped, if (!curs_can_use_extended_colors()) { // Check if these were brightened colours. - if (temp_attr & WA_BOLD) + if (style.attr & WA_BOLD) fg |= COLFLAG_CURSES_BRIGHTEN; - if (temp_attr & WA_BLINK) + if (style.attr & WA_BLINK) bg |= COLFLAG_CURSES_BRIGHTEN; - temp_attr &= ~(WA_BOLD | WA_BLINK); + style.attr &= ~(WA_BOLD | WA_BLINK); } if (!need_attribute_only_flip) { // Perform a flip using the inverse color pair, preferring to adjust // the background color in case of a conflict. - attr_t brightness_attr; - curs_attr(brightness_attr, temp_color_pair, - curses_color_to_internal_colour(bg), - curses_color_to_internal_colour(fg), true); - temp_attr |= brightness_attr; + const auto out = curs_attr(curses_color_to_internal_colour(bg), + curses_color_to_internal_colour(fg), true); + style.color_pair = out.color_pair; + style.attr |= out.attr; } else { @@ -1523,26 +1490,20 @@ void flip_colour(attr_t &attr_flipped, short &color_pair_flipped, && (Options.blink_brightens_background || Options.best_effort_brighten_background)) { - temp_attr |= WA_BLINK; + style.attr |= WA_BLINK; } if ((bg & COLFLAG_CURSES_BRIGHTEN) && (Options.bold_brightens_foreground || Options.best_effort_brighten_foreground)) { - temp_attr |= WA_BOLD; + style.attr |= WA_BOLD; } } - // Toggle the reverse video bit. - if (temp_attr & WA_REVERSE) - temp_attr &= ~WA_REVERSE; - else - temp_attr |= WA_REVERSE; + style.attr ^= WA_REVERSE; } - // Write out the results. - color_pair_flipped = temp_color_pair; - attr_flipped = temp_attr; + return style; } void fakecursorxy(int x, int y) diff --git a/crawl-ref/source/libutil.cc b/crawl-ref/source/libutil.cc index f1b8f749e78f..64a8426e3881 100644 --- a/crawl-ref/source/libutil.cc +++ b/crawl-ref/source/libutil.cc @@ -218,11 +218,11 @@ int strip_number_tag(string &s, const string &tagprefix) return x; } -set parse_tags(const string &tags) +unordered_set parse_tags(const string &tags) { vector split_tags = split_string(" ", tags); - set ordered_tags(split_tags.begin(), split_tags.end()); - return ordered_tags; + unordered_set tags_set(split_tags.begin(), split_tags.end()); + return tags_set; } bool parse_int(const char *s, int &i) @@ -473,7 +473,7 @@ string unwrap_desc(string&& desc) string tag = desc.substr(1, pos - 1); desc.erase(0, pos + 1); if (tag == "nowrap") - return desc; + return move(desc); else if (desc.empty()) return ""; } diff --git a/crawl-ref/source/libutil.h b/crawl-ref/source/libutil.h index 4dc603089d6d..507398c056e2 100644 --- a/crawl-ref/source/libutil.h +++ b/crawl-ref/source/libutil.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "enum.h" @@ -62,6 +63,27 @@ static inline int toalower(int c) return isaupper(c) ? c + 'a' - 'A' : c; } +static inline char32_t toaupper(char32_t c) +{ + return isalower(c) ? c + 'A' - 'a' : c; +} + +// Same thing with signed int, so we can pass though -1 undisturbed. +static inline int toaupper(int c) +{ + return isalower(c) ? c + 'A' - 'a' : c; +} + +template inline T tolower_safe(T c) +{ + return isaupper(c) ? toalower(c) : tolower(c); +} + +template inline T toupper_safe(T c) +{ + return isalower(c) ? toaupper(c) : toupper(c); +} + int numcmp(const char *a, const char *b, int limit = 0); bool numcmpstr(const string &a, const string &b); @@ -74,7 +96,7 @@ int strip_number_tag(string &s, const string &tagprefix); vector strip_multiple_tag_prefix(string &s, const string &tagprefix); string strip_tag_prefix(string &s, const string &tagprefix); const string tag_without_prefix(const string &s, const string &tagprefix); -set parse_tags(const string &tags); +unordered_set parse_tags(const string &tags); bool parse_int(const char *s, int &i); // String 'descriptions' diff --git a/crawl-ref/source/libw32c.cc b/crawl-ref/source/libw32c.cc index 0818e597e781..3d2023834a80 100644 --- a/crawl-ref/source/libw32c.cc +++ b/crawl-ref/source/libw32c.cc @@ -90,13 +90,10 @@ static COORD screensize; static unsigned InputCP, OutputCP; static const unsigned PREFERRED_CODEPAGE = 437; -static bool w32_smart_cursor = true; - // we can do straight translation of DOS colour to win32 console colour. #define WIN32COLOR(col) (WORD)(col) static void writeChar(char32_t c); static void bFlush(); -static void _setcursortype_internal(bool curstype); // [ds] Unused for portability reasons /* @@ -122,6 +119,15 @@ static DWORD crawlColorData[16] = }; */ +/** @brief The current foreground @em colour. */ +static COLOURS FG_COL = LIGHTGREY; + +/** @brief The current background @em colour. */ +static COLOURS BG_COL = BLACK; + +/** @brief The default background @em colour. */ +static COLOURS BG_COL_DEFAULT = BLACK; + void writeChar(char32_t c) { if (c == '\t') @@ -181,16 +187,6 @@ void writeChar(char32_t c) cx = screensize.X - 1; } -void enable_smart_cursor(bool cursor) -{ - w32_smart_cursor = cursor; -} - -bool is_smart_cursor_enabled() -{ - return w32_smart_cursor; -} - void bFlush() { COORD source; @@ -324,7 +320,7 @@ static void set_w32_screen_size() static void w32_handle_resize_event() { if (crawl_state.waiting_for_command) - handle_terminal_resize(true); + handle_terminal_resize(); else crawl_state.terminal_resized = true; } @@ -393,8 +389,8 @@ void console_startup() // initialise text colour textcolour(DARKGREY); - // initialise cursor to NONE. - _setcursortype_internal(false); + cursor_is_enabled = true; // ensure cursor is set regardless of actual state + set_cursor_enabled(false); crawl_state.terminal_resize_handler = w32_term_resizer; crawl_state.terminal_resize_check = w32_check_screen_resize; @@ -432,7 +428,7 @@ void console_shutdown() _set_string_input(true); // set cursor and normal textcolour - _setcursortype_internal(true); + set_cursor_enabled(true); textcolour(DARKGREY); inbuf = nullptr; @@ -454,18 +450,12 @@ void console_shutdown() } } -void set_cursor_enabled(bool enabled) -{ - if (!w32_smart_cursor) - _setcursortype_internal(enabled); -} - bool is_cursor_enabled() { return cursor_is_enabled; } -static void _setcursortype_internal(bool curstype) +void set_cursor_enabled(bool curstype) { CONSOLE_CURSOR_INFO cci; @@ -553,25 +543,34 @@ void gotoxy_sys(int x, int y) } } -void textcolour(int c) +// Hacked-up version of corresponding function in libunix.cc +static void adjust_color_pair_to_non_identical(short &fg, short &bg) { - // change current colour used to stamp chars - short fg = c & 0xF; - short bg = (c >> 4) & 0xF; - short macro_fg = Options.colour[fg]; - short macro_bg = Options.colour[bg]; + // ignore brighness when comparing visually + if ((fg & ~COLFLAG_CURSES_BRIGHTEN) != bg) + return; // colours look different; no need to adjust + fg &= ~COLFLAG_CURSES_BRIGHTEN; + fg = fg == BG_COL_DEFAULT ? WHITE : BG_COL_DEFAULT;; +} - current_colour = macro_fg | (macro_bg << 4); +static void update_text_colours() +{ + short macro_fg = Options.colour[FG_COL]; + short macro_bg = Options.colour[BG_COL]; + adjust_color_pair_to_non_identical(macro_fg, macro_bg); + current_colour = (macro_bg << 4) | macro_fg; } -void textbackground(int c) +void textcolour(int c) { - // change current background colour used to stamp chars - // parameter does NOT come bitshifted by four - short bg = c & 0xF; - short macro_bg = Options.colour[bg]; + FG_COL = static_cast(c & 0xF); + update_text_colours(); +} - current_colour = current_colour | (macro_bg << 4); +void textbackground(int c) +{ + BG_COL = static_cast(c & 0xF); + update_text_colours(); } static void cprintf_aux(const char *s) @@ -794,8 +793,6 @@ int getch_ck() } const bool oldValue = cursor_is_enabled; - if (w32_smart_cursor) - _setcursortype_internal(true); bool waiting_for_event = true; while (waiting_for_event) @@ -840,9 +837,6 @@ int getch_ck() } } - if (w32_smart_cursor) - _setcursortype_internal(oldValue); - return key; } diff --git a/crawl-ref/source/loading-screen.cc b/crawl-ref/source/loading-screen.cc index 28e511d3c545..1d6231503b4f 100644 --- a/crawl-ref/source/loading-screen.cc +++ b/crawl-ref/source/loading-screen.cc @@ -30,8 +30,8 @@ class UIShrinkableImage : public Widget void UIShrinkableImage::_render() { int iw = m_img.orig_width()*scale, ih = m_img.orig_height()*scale; - int dx = (m_region[2]-iw)/2, dy = (m_region[3]-ih)/2; - int x = m_region[0] + dx, y = m_region[1] + dy; + int dx = (m_region.width-iw)/2, dy = (m_region.height-ih)/2; + int x = m_region.x + dx, y = m_region.y + dy; GLWPrim rect(x, y, x+iw, y+ih); rect.set_tex(0, 0, (float)m_img.orig_width()/m_img.width(), (float)m_img.orig_height()/m_img.height()); @@ -48,7 +48,7 @@ SizeReq UIShrinkableImage::_get_preferred_size(Direction dim, int prosp_width) void UIShrinkableImage::_allocate_region() { float iw = m_img.orig_width(), ih = m_img.orig_height(); - scale = min({1.0f, m_region[2]/iw, m_region[3]/ih}); + scale = min({1.0f, m_region.width/iw, m_region.height/ih}); } static shared_ptr loading_text; @@ -65,13 +65,13 @@ void loading_screen_open() { auto splash = make_shared(_get_title_image()); loading_text = make_shared(); - loading_text->set_margin_for_sdl({15, 0, 0, 0}); + loading_text->set_margin_for_sdl(15, 0, 0, 0); auto vbox = make_shared(Widget::VERT); - vbox->align_items = Widget::CENTER; + vbox->align_cross = Widget::CENTER; vbox->add_child(move(splash)); vbox->add_child(loading_text); FontWrapper *font = tiles.get_crt_font(); - vbox->min_size()[0] = font->string_width(load_complete_msg.c_str()); + vbox->min_size().width = font->string_width(load_complete_msg.c_str()); popup = make_shared(move(vbox)); ui::push_layout(popup); } diff --git a/crawl-ref/source/lookup-help.cc b/crawl-ref/source/lookup-help.cc index e7ebf5552f87..e87883ee4260 100644 --- a/crawl-ref/source/lookup-help.cc +++ b/crawl-ref/source/lookup-help.cc @@ -358,9 +358,11 @@ static vector _get_god_keys() for (int i = GOD_NO_GOD + 1; i < NUM_GODS; i++) { god_type which_god = static_cast(i); +#if TAG_MAJOR_VERSION == 34 // XXX: currently disabled. if (which_god != GOD_PAKELLAS) - names.push_back(god_name(which_god)); +#endif + names.push_back(god_name(which_god)); } return names; @@ -735,7 +737,7 @@ static MenuEntry* _branch_menu_gen(char letter, const string &str, string &key) const branch_type branch = branch_by_shortname(str); int hotkey = branches[branch].travel_shortcut; - me->hotkeys = {hotkey, tolower(hotkey)}; + me->hotkeys = {hotkey, tolower_safe(hotkey)}; #ifdef USE_TILE me->add_tile(tile_def(tileidx_branch(branch), TEX_FEAT)); #endif @@ -781,10 +783,10 @@ static MenuEntry* _cloud_menu_gen(char letter, const string &str, string &key) string LookupType::prompt_string() const { string prompt_str = lowercase_string(type); - const size_t symbol_pos = prompt_str.find(tolower(symbol)); + const size_t symbol_pos = prompt_str.find(tolower_safe(symbol)); ASSERT(symbol_pos != string::npos); - prompt_str.replace(symbol_pos, 1, make_stringf("(%c)", toupper(symbol))); + prompt_str.replace(symbol_pos, 1, make_stringf("(%c)", toupper_safe(symbol))); return prompt_str; } @@ -1424,7 +1426,7 @@ static int _lookup_prompt() ch = getchm(); } } - return toupper(ch); + return toupper_safe(ch); } /** diff --git a/crawl-ref/source/los.cc b/crawl-ref/source/los.cc index 800e2513ba70..04bcb616c7b8 100644 --- a/crawl-ref/source/los.cc +++ b/crawl-ref/source/los.cc @@ -52,6 +52,7 @@ #include "coordit.h" #include "env.h" #include "losglobal.h" +#include "mon-act.h" // These determine what rays are cast in the precomputation, // and affect start-up time significantly. @@ -934,6 +935,7 @@ void los_terrain_changed(const coord_def& p) void los_changed() { + mons_reset_just_seen(); invalidate_los(); _handle_los_change(); } diff --git a/crawl-ref/source/losglobal.cc b/crawl-ref/source/losglobal.cc index 55273e47048a..5dd10675aa5f 100644 --- a/crawl-ref/source/losglobal.cc +++ b/crawl-ref/source/losglobal.cc @@ -126,9 +126,6 @@ bool cell_see_cell(const coord_def& p, const coord_def& q, los_type l) if (!(*flags & (l << LOS_KNOWN))) _update_globallos_at(p, l); - //if (!(*flags & (l << LOS_KNOWN))) - // die("cell_see_cell %d,%d %d,%d", p.x,p.y,q.x,q.y); ASSERT(*flags & (l << LOS_KNOWN)); - return *flags & l; } diff --git a/crawl-ref/source/macro.cc b/crawl-ref/source/macro.cc index c09738c2a022..a5710515610a 100644 --- a/crawl-ref/source/macro.cc +++ b/crawl-ref/source/macro.cc @@ -31,6 +31,10 @@ #include #include +#ifdef USE_TILE_LOCAL +#include +#endif + #include "cio.h" #include "command.h" #include "files.h" @@ -261,7 +265,7 @@ static int read_key_code(string s) else if (s[0] == '^') { // ^A = 1, etc. - return 1 + toupper(s[1]) - 'A'; + return 1 + toupper_safe(s[1]) - 'A'; } char *tail; @@ -812,6 +816,24 @@ int getch_with_command_macros() return macro_buf_get(); } +static string _buffer_to_string() +{ + string s; + for (const int k : Buffer) + { + if (k > 0 && k <= numeric_limits::max()) + { + char c = static_cast(k); + if (c == '[' || c == ']') + s += "\\"; + s += c; + } + else + s += make_stringf("[%d]", k); + } + return s; +} + /* * Flush the buffer. Later we'll probably want to give the player options * as to when this happens (ex. always before command input, casting failed). @@ -841,11 +863,16 @@ void flush_input_buffer(int reason) || reason == FLUSH_REPLAY_SETUP_FAILURE || reason == FLUSH_REPEAT_SETUP_DONE) { - if (crawl_state.nonempty_buffer_flush_errors) + if (crawl_state.nonempty_buffer_flush_errors && !Buffer.empty()) { if (you.wizard) // crash -- intended for tests + { + mprf(MSGCH_ERROR, + "Flushing non-empty key buffer (Buffer is '%s')", + _buffer_to_string().c_str()); ASSERT(Buffer.empty()); - else if (!Buffer.empty()) + } + else mprf(MSGCH_ERROR, "Flushing non-empty key buffer"); } while (!Buffer.empty()) @@ -1504,6 +1531,11 @@ string command_to_string(command_type cmd, bool tutorial) const int numpad = (key - 1000); result = make_stringf("Numpad %d", numpad); } +#ifdef USE_TILE_LOCAL + // SDL allows control modifiers for some extra punctuation + else if (key < 0 && key > SDLK_EXCLAIM - SDLK_a + 1) + result = make_stringf("Ctrl-%c", (char) (key + SDLK_a - 1)); +#endif else { const int ch = key + 'A' - 1; diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc index 42c4fc8f808e..526e387d473c 100644 --- a/crawl-ref/source/main.cc +++ b/crawl-ref/source/main.cc @@ -18,6 +18,9 @@ #include // pair #include #include +#ifdef DGAMELAUNCH +# include +#endif #ifndef TARGET_OS_WINDOWS # ifndef __ANDROID__ @@ -763,6 +766,7 @@ static bool _cmd_is_repeatable(command_type cmd, bool is_again = false) return false; // Multi-turn commands + case CMD_REST: case CMD_PICKUP: case CMD_DROP: case CMD_DROP_LAST: @@ -838,7 +842,6 @@ static bool _cmd_is_repeatable(command_type cmd, bool is_again = false) return _cmd_is_repeatable(crawl_state.prev_cmd, true); - case CMD_REST: case CMD_WAIT: case CMD_SAFE_WAIT: case CMD_SAFE_MOVE_LEFT: @@ -1071,7 +1074,7 @@ static void _input() } if (!you_are_delayed()) - update_can_train(); + update_can_currently_train(); #ifdef USE_TILE_WEB tiles.flush_messages(); @@ -1189,7 +1192,7 @@ static void _input() viewwindow(); } - update_can_train(); + update_can_currently_train(); _update_replay_state(); @@ -1367,6 +1370,18 @@ static bool _prompt_stairs(dungeon_feature_type ygrd, bool down, bool shaft) } } + // Leaving ziggurat figurines behind. + if (ygrd == DNGN_EXIT_ZIGGURAT + && you.depth == brdepth[BRANCH_ZIGGURAT] + && find_floor_item(OBJ_MISCELLANY, MISC_ZIGGURAT)) + { + if (!yesno("Really leave the ziggurat figurine behind?", false, 'n')) + { + canned_msg(MSG_OK); + return false; + } + } + // Leaving Pan runes behind. if (!_prompt_unique_pan_rune(ygrd)) { @@ -1791,8 +1806,7 @@ void process_command(command_type cmd) case CMD_DISPLAY_OVERMAP: display_overview(); break; case CMD_DISPLAY_MAP: _do_display_map(); break; -#ifdef TOUCH_UI - // zoom commands +#ifdef USE_TILE case CMD_ZOOM_IN: tiles.zoom_dungeon(true); break; case CMD_ZOOM_OUT: tiles.zoom_dungeon(false); break; #endif @@ -2059,8 +2073,6 @@ static void _prep_input() you.time_taken = player_speed(); you.shield_blocks = 0; // no blocks this round - textcolour(LIGHTGREY); - you.redraw_status_lights = true; print_stats(); @@ -2115,53 +2127,6 @@ static void _check_trapped() } } -static void _update_mold_state(const coord_def & pos) -{ - if (coinflip()) - { - // Doing a weird little state thing with the two mold - // fprops. 'glowing' mold should turn back to normal after - // a couple display update (i.e. after the player makes their - // next move), since we happen to have two bits dedicated to - // mold now we may as well use them? -cao - if (env.pgrid(pos) & FPROP_MOLD) - env.pgrid(pos) &= ~FPROP_MOLD; - else - { - env.pgrid(pos) |= FPROP_MOLD; - env.pgrid(pos) &= ~FPROP_GLOW_MOLD; - } - } -} - -static void _update_mold() -{ - env.level_state &= ~LSTATE_GLOW_MOLD; // we'll restore it if any - - for (rectangle_iterator ri(0); ri; ++ri) - { - if (glowing_mold(*ri)) - { - _update_mold_state(*ri); - env.level_state |= LSTATE_GLOW_MOLD; - } - } - for (monster_iterator mon_it; mon_it; ++mon_it) - { - if (mon_it->type == MONS_HYPERACTIVE_BALLISTOMYCETE) - { - for (radius_iterator rad_it(mon_it->pos(), 2, C_SQUARE); - rad_it; ++rad_it) - { - // Matche the blast of a radius 2 explosion. - env.pgrid(*rad_it) |= FPROP_MOLD; - env.pgrid(*rad_it) |= FPROP_GLOW_MOLD; - } - env.level_state |= LSTATE_GLOW_MOLD; - } - } -} - static void _update_golubria_traps() { vector traps = find_golubria_on_level(); @@ -2267,8 +2232,6 @@ void world_reacts() handle_time(); manage_clouds(); - if (env.level_state & LSTATE_GLOW_MOLD) - _update_mold(); if (env.level_state & LSTATE_GOLUBRIA) _update_golubria_traps(); if (env.level_state & LSTATE_STILL_WINDS) @@ -2278,6 +2241,8 @@ void world_reacts() wu_jian_end_of_turn_effects(); + add_auto_excludes(); + viewwindow(); if (you.cannot_act() && any_messages() @@ -2539,16 +2504,10 @@ static void _safe_move_player(coord_def move) static int _get_num_and_char(const char* prompt, char* buf, int buf_len) { if (prompt != nullptr) - mprf(MSGCH_PROMPT, "%s", prompt); - - line_reader reader(buf, buf_len); - - reader.set_keyproc(keyfun_num_and_char); -#ifdef USE_TILE_WEB - reader.set_tag("repeat"); -#endif - - return reader.read_line(true); + msgwin_prompt(prompt); + auto ret = cancellable_get_line(buf, buf_len, nullptr, keyfun_num_and_char, "", "repeat"); + msgwin_reply(buf); + return ret; } static void _cancel_cmd_repeat() @@ -2710,6 +2669,10 @@ static void _do_cmd_repeat() for (; i < count && crawl_state.is_repeating_cmd(); ++i) { last_repeat_turn = you.num_turns; +#ifdef DGAMELAUNCH + if (i >= 100) + usleep(500000); +#endif _run_input_with_keys(repeat_keys); _check_cmd_repeat(last_repeat_turn); } diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index 9ed7c37d528e..6a147a1cad5b 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -199,8 +199,8 @@ static bool _try_make_item_unrand(item_def& item, int force_type, int agent) static bool _weapon_disallows_randart(int sub_type) { - // Clubs and blowguns are never randarts. - return sub_type == WPN_CLUB || sub_type == WPN_BLOWGUN; + // Clubs are never randarts. + return sub_type == WPN_CLUB; } // Return whether we made an artefact. @@ -247,8 +247,6 @@ static bool _try_make_weapon_artefact(item_def& item, int force_type, if (cursed) do_curse_item(item); - if (get_weapon_brand(item) == SPWPN_HOLY_WRATH) - item.flags &= (~ISFLAG_CURSED); return true; } @@ -300,9 +298,6 @@ bool is_weapon_brand_ok(int type, int brand, bool strict) if (type == WPN_QUICK_BLADE && brand == SPWPN_SPEED) return false; - if (type == WPN_BLOWGUN) - return false; - switch ((brand_type)brand) { // Universal brands. @@ -500,8 +495,6 @@ static void _generate_weapon_item(item_def& item, bool allow_uniques, } } -// Current list is based on dpeg's original post to the Wiki, found at the -// page: . // Remember to update the code in is_missile_brand_ok if adding or altering // brands that are applied to missiles. {due} static special_missile_type _determine_missile_brand(const item_def& item, @@ -511,17 +504,20 @@ static special_missile_type _determine_missile_brand(const item_def& item, if (item.brand != 0) return static_cast(item.brand); - const bool force_good = item_level >= ISPEC_GIFT; special_missile_type rc = SPMSL_NORMAL; - // "Normal weight" of SPMSL_NORMAL. - int nw = force_good ? 0 : random2(2000 - 55 * item_level); + // Weight of SPMSL_NORMAL + // Gifts from Trog/Oka can be unbranded boomerangs/javelins + // but not poisoned darts + int nw = item_level >= ISPEC_GOOD_ITEM ? 0 : + item_level == ISPEC_GIFT ? 120 + : random2(2000 - 55 * item_level); + + // Weight of SPMSL_POISONED + int pw = item_level >= ISPEC_GIFT ? 0 : random2(2000 - 55 * item_level); switch (item.sub_type) { -#if TAG_MAJOR_VERSION == 34 - case MI_DART: -#endif case MI_THROWING_NET: case MI_STONE: case MI_LARGE_ROCK: @@ -530,7 +526,7 @@ static special_missile_type _determine_missile_brand(const item_def& item, case MI_BOLT: rc = SPMSL_NORMAL; break; - case MI_NEEDLE: + case MI_DART: // Curare is special cased, all the others aren't. if (got_curare_roll(item_level)) { @@ -538,27 +534,17 @@ static special_missile_type _determine_missile_brand(const item_def& item, break; } - rc = random_choose_weighted(30, SPMSL_SLEEP, - 30, SPMSL_CONFUSION, - 10, SPMSL_PARALYSIS, - 10, SPMSL_FRENZY, - nw, SPMSL_POISONED); + rc = random_choose_weighted(60, SPMSL_BLINDING, + 20, SPMSL_FRENZY, + pw, SPMSL_POISONED); break; case MI_JAVELIN: - rc = random_choose_weighted(30, SPMSL_RETURNING, - 32, SPMSL_PENETRATION, - 32, SPMSL_POISONED, - 21, SPMSL_STEEL, - 20, SPMSL_SILVER, + rc = random_choose_weighted(90, SPMSL_SILVER, nw, SPMSL_NORMAL); break; - case MI_TOMAHAWK: - rc = random_choose_weighted(15, SPMSL_POISONED, - 10, SPMSL_SILVER, - 10, SPMSL_STEEL, - 12, SPMSL_DISPERSAL, - 28, SPMSL_RETURNING, - 15, SPMSL_EXPLODING, + case MI_BOOMERANG: + rc = random_choose_weighted(30, SPMSL_SILVER, + 30, SPMSL_DISPERSAL, nw, SPMSL_NORMAL); break; } @@ -586,36 +572,26 @@ bool is_missile_brand_ok(int type, int brand, bool strict) if (brand == SPMSL_FLAME || brand == SPMSL_FROST) return false; - // In contrast, needles should always be branded. - // And all of these brands save poison are unique to needles. + // In contrast, darts should always be branded. + // And all of these brands save poison are unique to darts. switch (brand) { case SPMSL_POISONED: - if (type == MI_NEEDLE) + if (type == MI_DART) return true; break; case SPMSL_CURARE: case SPMSL_PARALYSIS: -#if TAG_MAJOR_VERSION == 34 - case SPMSL_SLOW: -#endif - case SPMSL_SLEEP: - case SPMSL_CONFUSION: -#if TAG_MAJOR_VERSION == 34 - case SPMSL_SICKNESS: -#endif case SPMSL_FRENZY: - return type == MI_NEEDLE; + return type == MI_DART; -#if TAG_MAJOR_VERSION == 34 case SPMSL_BLINDING: // possible on ex-pies - return type == MI_TOMAHAWK && !strict; -#endif + return type == MI_DART || (type == MI_BOOMERANG && !strict); default: - if (type == MI_NEEDLE) + if (type == MI_DART) return false; } @@ -623,7 +599,7 @@ bool is_missile_brand_ok(int type, int brand, bool strict) if (brand == SPMSL_NORMAL) return true; - // In non-strict mode, everything other than needles is mostly ok. + // In non-strict mode, everything other than darts is mostly ok. if (!strict) return true; @@ -635,20 +611,13 @@ bool is_missile_brand_ok(int type, int brand, bool strict) switch (brand) { case SPMSL_POISONED: - return type == MI_JAVELIN || type == MI_TOMAHAWK; - case SPMSL_RETURNING: - return type == MI_JAVELIN || type == MI_TOMAHAWK; + return false; case SPMSL_CHAOS: - return type == MI_TOMAHAWK || type == MI_JAVELIN; - case SPMSL_PENETRATION: - return type == MI_JAVELIN; + return type == MI_BOOMERANG || type == MI_JAVELIN; case SPMSL_DISPERSAL: - return type == MI_TOMAHAWK; - case SPMSL_EXPLODING: - return type == MI_TOMAHAWK; - case SPMSL_STEEL: // deliberate fall through + return type == MI_BOOMERANG; case SPMSL_SILVER: - return type == MI_JAVELIN || type == MI_TOMAHAWK; + return type == MI_JAVELIN || type == MI_BOOMERANG; default: break; } @@ -674,8 +643,8 @@ static void _generate_missile_item(item_def& item, int force_type, 20, MI_ARROW, 12, MI_BOLT, 12, MI_SLING_BULLET, - 10, MI_NEEDLE, - 3, MI_TOMAHAWK, + 10, MI_DART, + 3, MI_BOOMERANG, 2, MI_JAVELIN, 1, MI_THROWING_NET, 1, MI_LARGE_ROCK); @@ -708,8 +677,8 @@ static void _generate_missile_item(item_def& item, int force_type, } // Reduced quantity if special. - if (item.sub_type == MI_JAVELIN || item.sub_type == MI_TOMAHAWK - || (item.sub_type == MI_NEEDLE && get_ammo_brand(item) != SPMSL_POISONED) + if (item.sub_type == MI_JAVELIN || item.sub_type == MI_BOOMERANG + || (item.sub_type == MI_DART && get_ammo_brand(item) != SPMSL_POISONED) || get_ammo_brand(item) == SPMSL_RETURNING) { item.quantity = random_range(2, 8); @@ -1150,10 +1119,26 @@ static void _generate_armour_item(item_def& item, bool allow_uniques, // Forced randart. if (item_level == ISPEC_RANDART) { + int ego = item.brand; for (int i = 0; i < 100; ++i) if (_try_make_armour_artefact(item, force_type, 0, true, agent) && is_artefact(item)) { + // borrowed from similar code for weapons -- is this really the + // best way to force an ego?? + if (ego > SPARM_NORMAL) + { + item.props[ARTEFACT_PROPS_KEY].get_vector()[ARTP_BRAND].get_short() = ego; + if (randart_is_bad(item)) // recheck, the brand changed + { + force_type = item.sub_type; + item.clear(); + item.quantity = 1; + item.base_type = OBJ_ARMOUR; + item.sub_type = force_type; + continue; + } + } return; } // fall back to an ordinary item @@ -1773,6 +1758,102 @@ void squash_plusses(int item_slot) set_equip_desc(item, ISFLAG_NO_DESC); } +static bool _ego_unrand_only(int base_type, int ego) +{ + if (base_type == OBJ_WEAPONS) + { + switch (static_cast(ego)) + { + case SPWPN_REAPING: + case SPWPN_ACID: + return true; + default: + return false; + } + } + // all armours are ok? + return false; +} + +// Make a corresponding randart instead as a fallback. +// Attempts to use base type, sub type, and ego (for armour/weapons). +// Artefacts can explicitly specify a base type and fallback type. + +// Could be even more flexible: maybe allow an item spec to describe +// complex fallbacks? +static void _setup_fallback_randart(const int unrand_id, + item_def &item, + int &force_type, + int &item_level) +{ + ASSERT(get_unrand_entry(unrand_id)); + const unrandart_entry &unrand = *get_unrand_entry(unrand_id); + dprf("Placing fallback randart for %s", unrand.name); + + uint8_t fallback_sub_type; + if (unrand.fallback_base_type != OBJ_UNASSIGNED) + { + item.base_type = unrand.fallback_base_type; + fallback_sub_type = unrand.fallback_sub_type; + } + else + { + item.base_type = unrand.base_type; + fallback_sub_type = unrand.sub_type; + } + + if (item.base_type == OBJ_WEAPONS + && fallback_sub_type == WPN_STAFF) + { + item.base_type = OBJ_STAVES; + if (unrand_id == UNRAND_WUCAD_MU) + force_type = STAFF_ENERGY; + else if (unrand_id == UNRAND_OLGREB) + force_type = STAFF_POISON; + else + force_type = OBJ_RANDOM; + // XXX: small chance of the other unrand... + // (but we won't hit this case until a new staff unrand is added) + } + else if (item.base_type == OBJ_JEWELLERY + && fallback_sub_type == AMU_NOTHING) + { + force_type = NUM_JEWELLERY; + } + else + force_type = fallback_sub_type; + + // import some brand information from the unrand. TODO: I'm doing this for + // the sake of vault designers who make themed vaults. But it also often + // makes the item more redundant with the unrand; maybe add a bit more + // flexibility in item specs so that this part is optional? However, from a + // seeding perspective, it also makes sense if the item generated is + // very comparable. + + // unset fallback_brand is -1. In that case it will try to use the regular + // brand if there is one. If the end result here is 0, the brand (for + // weapons) will be chosen randomly. So to force randomness, use + // SPWPN_NORMAL on the fallback ego, or set neither. (Randarts cannot be + // unbranded.) + item.brand = unrand.fallback_brand; // no type checking here... + + if (item.brand < 0 + && !_ego_unrand_only(item.base_type, unrand.prpty[ARTP_BRAND]) + && item.base_type == unrand.base_type // brand isn't well-defined for != case + && ((item.base_type == OBJ_WEAPONS + && is_weapon_brand_ok(item.sub_type, unrand.prpty[ARTP_BRAND], true)) + || (item.base_type == OBJ_ARMOUR + && is_armour_brand_ok(item.sub_type, unrand.prpty[ARTP_BRAND], true)))) + { + // maybe do jewellery too? + item.brand = unrand.prpty[ARTP_BRAND]; + } + if (item.brand < 0) + item.brand = 0; + + item_level = ISPEC_RANDART; +} + /** * Create an item. * @@ -1801,6 +1882,8 @@ int items(bool allow_uniques, int force_ego, int agent) { + rng::subgenerator item_rng; + ASSERT(force_ego <= 0 || force_class == OBJ_WEAPONS || force_class == OBJ_ARMOUR @@ -1883,34 +1966,8 @@ int items(bool allow_uniques, return p; } - // make a corresponding randart instead. - const unrandart_entry* unrand = get_unrand_entry(unrand_id); - ASSERT(unrand); - item.base_type = unrand->base_type; - - if (unrand->base_type == OBJ_WEAPONS - && unrand->sub_type == WPN_STAFF) - { - item.base_type = OBJ_STAVES; - if (unrand_id == UNRAND_WUCAD_MU) - force_type = STAFF_ENERGY; - else if (unrand_id == UNRAND_OLGREB) - force_type = STAFF_POISON; - else - force_type = OBJ_RANDOM; - // XXX: small chance of the other unrand... - // (but we won't hit this case until a new staff unrand is added) - } - else if (unrand->base_type == OBJ_JEWELLERY - && unrand->sub_type == AMU_NOTHING) - { - force_type = NUM_JEWELLERY; - } - else - force_type = unrand->sub_type; - - item_level = ISPEC_RANDART; - item.brand = 0; + _setup_fallback_randart(unrand_id, item, force_type, item_level); + allow_uniques = false; } // Determine sub_type accordingly. {dlb} diff --git a/crawl-ref/source/map-cell.h b/crawl-ref/source/map-cell.h index 855123f07f5d..6eb08e1e8346 100644 --- a/crawl-ref/source/map-cell.h +++ b/crawl-ref/source/map-cell.h @@ -23,8 +23,6 @@ /* these flags require more space to serialize: put infrequently used ones there */ #define MAP_EXCLUDED_STAIRS 0x10000 -#define MAP_MOLDY 0x20000 -#define MAP_GLOWING_MOLDY 0x40000 #define MAP_SANCTUARY_1 0x80000 #define MAP_SANCTUARY_2 0x100000 #define MAP_WITHHELD 0x200000 @@ -109,6 +107,16 @@ struct map_cell return *this; } + bool operator ==(const map_cell &other) const + { + return memcmp(this, &other, sizeof(map_cell)) == 0; + } + + bool operator !=(const map_cell &other) const + { + return memcmp(this, &other, sizeof(map_cell)) != 0; + } + void clear() { *this = map_cell(); diff --git a/crawl-ref/source/map-knowledge.cc b/crawl-ref/source/map-knowledge.cc index 1681c3495a38..5aa2bd43addd 100644 --- a/crawl-ref/source/map-knowledge.cc +++ b/crawl-ref/source/map-knowledge.cc @@ -326,3 +326,31 @@ bool map_cell::update_cloud_state() // TODO: track decay & vanish appropriately (based on some worst case?) return false; } + +std::pair known_map_bounds() { + int min_x = GXM, max_x = 0, min_y = 0, max_y = 0; + bool found_y = false; + + for (int j = 0; j < GYM; j++) + for (int i = 0; i < GXM; i++) + { + if (env.map_knowledge[i][j].known()) + { + if (!found_y) + { + found_y = true; + min_y = j; + } + + max_y = j; + + if (i < min_x) + min_x = i; + + if (i > max_x) + max_x = i; + } + } + + return std::make_pair(coord_def(min_x, min_y), coord_def(max_x, max_y)); +} diff --git a/crawl-ref/source/map-knowledge.h b/crawl-ref/source/map-knowledge.h index 345571370bb0..9675035520a9 100644 --- a/crawl-ref/source/map-knowledge.h +++ b/crawl-ref/source/map-knowledge.h @@ -3,6 +3,7 @@ #include "coord.h" #include "enum.h" #include "feature.h" +#include "externs.h" void set_terrain_mapped(const coord_def c); void set_terrain_seen(const coord_def c); @@ -41,3 +42,10 @@ map_feature get_cell_map_feature(const map_cell& cell); bool is_explore_horizon(const coord_def& c); void reautomap_level(); + +/** + * @brief Get the bounding box of the known map. + * + * @return pair of {topleft coord, bottomright coord} of bbox. + */ +std::pair known_map_bounds(); diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index 1f539cca74e1..7cb86d40d034 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -47,6 +47,36 @@ #include "tiledef-dngn.h" #include "tiledef-player.h" +#ifdef DEBUG_TAG_PROFILING +static map _tag_profile; + +static void _profile_inc_tag(const string &tag) +{ + if (!_tag_profile.count(tag)) + _tag_profile[tag] = 0; + _tag_profile[tag]++; +} + +void tag_profile_out() +{ + long total = 0; + vector> resort; + fprintf(stderr, "\nTag hits:\n"); + for (auto k : _tag_profile) + { + resort.emplace_back(k.second, k.first); + total += k.second; + } + sort(resort.begin(), resort.end()); + for (auto p : resort) + { + long percent = ((long) p.first) * 100 / total; + fprintf(stderr, "%8d (%2ld%%): %s\n", p.first, percent, p.second.c_str()); + } + fprintf(stderr, "Total: %ld\n", total); +} +#endif + static const char *map_section_names[] = { "", @@ -2172,7 +2202,8 @@ map_def::map_def() rock_colour(BLACK), floor_colour(BLACK), rock_tile(""), floor_tile(""), border_fill_type(DNGN_ROCK_WALL), tags(), - index_only(false), cache_offset(0L), validating_map_flag(false) + index_only(false), cache_offset(0L), validating_map_flag(false), + cache_minivault(false), cache_overwritable(false), cache_extra(false) { init(); } @@ -2231,17 +2262,18 @@ void map_def::reinit() mons.clear(); feat_renames.clear(); subvault_places.clear(); + update_cached_tags(); } bool map_def::map_already_used() const { - return you.uniq_map_names.count(name) + return get_uniq_map_names().count(name) || env.level_uniq_maps.find(name) != env.level_uniq_maps.end() || env.new_used_subvault_names.find(name) != env.new_used_subvault_names.end() - || has_any_tag(you.uniq_map_tags.begin(), - you.uniq_map_tags.end()) + || has_any_tag(get_uniq_map_tags().begin(), + get_uniq_map_tags().end()) || has_any_tag(env.level_uniq_map_tags.begin(), env.level_uniq_map_tags.end()) || has_any_tag(env.new_used_subvault_tags.begin(), @@ -2732,7 +2764,7 @@ string map_def::validate_map_placeable() // Ok, the map wants to be placed by tag. In this case it should have // at least one tag that's not a map flag. bool has_selectable_tag = false; - for (const string &piece : get_tags()) + for (const string &piece : tags) { if (_map_tag_is_selectable(piece)) { @@ -2933,16 +2965,37 @@ bool map_def::has_depth() const return !depths.empty(); } +void map_def::update_cached_tags() +{ + cache_minivault = has_tag("minivault"); + cache_overwritable = has_tag("overwritable"); + cache_extra = has_tag("extra"); +} + bool map_def::is_minivault() const { - return has_tag("minivault"); +#ifdef DEBUG_TAG_PROFILING + ASSERT(cache_minivault == has_tag("minivault")); +#endif + return cache_minivault; } // Returns true if the map is a layout that allows other vaults to be // built on it. bool map_def::is_overwritable_layout() const { - return has_tag("overwritable"); +#ifdef DEBUG_TAG_PROFILING + ASSERT(cache_overwritable == has_tag("overwritable")); +#endif + return cache_overwritable; +} + +bool map_def::is_extra_vault() const +{ +#ifdef DEBUG_TAG_PROFILING + ASSERT(cache_extra == has_tag("extra")); +#endif + return cache_extra; } // Tries to dock a floating vault - push it to one edge of the level. @@ -3245,21 +3298,18 @@ void map_def::fixup() } } -bool map_def::has_tag(const set &tagswanted) const +bool map_def::has_all_tags(const string &tagswanted) const { - if (tags.empty() || tagswanted.size() == 0) - return false; - - for (const string &tag : tagswanted) - if (!tags.count(tag)) - return false; - - return true; + const auto &tags_set = parse_tags(tagswanted); + return has_all_tags(tags_set.begin(), tags_set.end()); } -bool map_def::has_tag(const string &tagswanted) const +bool map_def::has_tag(const string &tagwanted) const { - return has_tag(parse_tags(tagswanted)); +#ifdef DEBUG_TAG_PROFILING + _profile_inc_tag(tagwanted); +#endif + return tags.count(tagwanted) > 0; } bool map_def::has_tag_prefix(const string &prefix) const @@ -3282,15 +3332,25 @@ bool map_def::has_tag_suffix(const string &suffix) const return false; } -const set map_def::get_tags() const +const unordered_set map_def::get_tags_unsorted() const { return tags; } +const vector map_def::get_tags() const +{ + // this might seem inefficient, but get_tags is not called very much; the + // hotspot revealed by profiling is actually has_tag checks. + vector result(tags.begin(), tags.end()); + sort(result.begin(), result.end()); + return result; +} + void map_def::add_tags(const string &tag) { auto parsed_tags = parse_tags(tag); tags.insert(parsed_tags.begin(), parsed_tags.end()); + update_cached_tags(); } bool map_def::remove_tags(const string &tag) @@ -3299,23 +3359,27 @@ bool map_def::remove_tags(const string &tag) auto parsed_tags = parse_tags(tag); for (auto &t : parsed_tags) removed = tags.erase(t) || removed; // would iterator overload be ok? + update_cached_tags(); return removed; } void map_def::clear_tags() { tags.clear(); + update_cached_tags(); } void map_def::set_tags(const string &tag) { clear_tags(); add_tags(tag); + update_cached_tags(); } string map_def::tags_string() const { - return join_strings(tags.begin(), tags.end()); + auto sorted_tags = get_tags(); + return join_strings(sorted_tags.begin(), sorted_tags.end()); } keyed_mapspec *map_def::mapspec_at(const coord_def &c) @@ -4927,8 +4991,8 @@ int str_to_ego(object_class_type item_type, string ego_str) "returning", #endif "chaos", - "evasion", #if TAG_MAJOR_VERSION == 34 + "evasion", "confuse", #endif "penetration", @@ -4943,23 +5007,28 @@ int str_to_ego(object_class_type item_type, string ego_str) "frost", "poisoned", "curare", +#if TAG_MAJOR_VERSION == 34 "returning", +#endif "chaos", +#if TAG_MAJOR_VERSION == 34 "penetration", +#endif "dispersal", +#if TAG_MAJOR_VERSION == 34 "exploding", "steel", +#endif "silver", - "paralysis", #if TAG_MAJOR_VERSION == 34 + "paralysis", "slow", -#endif "sleep", "confusion", -#if TAG_MAJOR_VERSION == 34 "sickness", #endif - "frenzy", + "datura", + "atropa", nullptr }; COMPILE_CHECK(ARRAYSZ(missile_brands) == NUM_REAL_SPECIAL_MISSILES); diff --git a/crawl-ref/source/mapdef.h b/crawl-ref/source/mapdef.h index fd71f3b4978e..fcf87dce36a2 100644 --- a/crawl-ref/source/mapdef.h +++ b/crawl-ref/source/mapdef.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "dlua.h" #include "enum.h" @@ -41,6 +42,10 @@ #define RANDBK_SLVLS_KEY "randbook_slevels" #define RANDBK_NSPELLS_KEY "randbook_num_spells" +#ifdef DEBUG_TAG_PROFILING +void tag_profile_out(); +#endif + class mon_enchant; extern const char *traversable_glyphs; @@ -1151,7 +1156,7 @@ class map_def vector subvault_places; private: - set tags; + unordered_set tags; // This map has been loaded from an index, and not fully realised. bool index_only; mutable long cache_offset; @@ -1164,6 +1169,14 @@ class map_def // True if this map is in the process of being validated. bool validating_map_flag; + // values cached from tags -- adding to this is only recommended if you've + // actually done the profiling... + // These are the top three worst tags, which jointly amount to about 3-4% + // of levelgen time if not cached. + bool cache_minivault; + bool cache_overwritable; + bool cache_extra; + public: map_def(); @@ -1247,11 +1260,23 @@ class map_def bool is_minivault() const; bool is_overwritable_layout() const; - bool has_tag(const string &tagswanted) const; - bool has_tag(const set &tagswanted) const; + bool is_extra_vault() const; + bool has_tag(const string &tagwanted) const; bool has_tag_prefix(const string &tag) const; bool has_tag_suffix(const string &suffix) const; + template + bool has_all_tags(TagIterator begin, TagIterator end) const + { + if (tags.empty() || begin == end) // legacy behavior for empty case + return false; + for ( ; begin != end; ++begin) + if (!has_tag(*begin)) + return false; + return true; + } + bool has_all_tags(const string &tagswanted) const; + template bool has_any_tag(TagIterator begin, TagIterator end) const { @@ -1261,7 +1286,8 @@ class map_def return false; } - const set get_tags() const; + const vector get_tags() const; + const unordered_set get_tags_unsorted() const; void add_tags(const string &tag); void set_tags(const string &tag); bool remove_tags(const string &tag); @@ -1314,6 +1340,7 @@ class map_def string apply_subvault(string_spec &); string validate_map_placeable(); bool has_exit() const; + void update_cached_tags(); }; const int CHANCE_ROLL = 10000; diff --git a/crawl-ref/source/maps.cc b/crawl-ref/source/maps.cc index 9f9fdd33038f..90c0a597587f 100644 --- a/crawl-ref/source/maps.cc +++ b/crawl-ref/source/maps.cc @@ -397,6 +397,13 @@ static bool _is_portal_place(const coord_def &c) return _marker_is_portal(env.markers.find(c, MAT_LUA_MARKER)); } +static bool _is_transporter_place(const coord_def &c) +{ + auto m = env.markers.find(c, MAT_LUA_MARKER); + return m && (!m->property(TRANSPORTER_NAME_PROP).empty() + || !m->property(TRANSPORTER_DEST_NAME_PROP).empty()); +} + static bool _map_safe_vault_place(const map_def &map, const coord_def &c, const coord_def &size) @@ -440,12 +447,14 @@ static bool _map_safe_vault_place(const map_def &map, return false; } } - else if (grd(cp) != DNGN_FLOOR || env.pgrid(cp) & FPROP_NO_TELE_INTO) + else if (grd(cp) != DNGN_FLOOR || env.pgrid(cp) & FPROP_NO_TELE_INTO + || _is_transporter_place(cp)) { // Don't place overwrite_floor_cell vaults on anything but floor or // on squares that can't be teleported into, because // overwrite_floor_cell is used for things that are expected to be - // connected. + // connected. Don't place on transporter markers, because these will + // later themselves overwrite whatever feature this vault places. return false; } @@ -721,10 +730,11 @@ mapref_vector find_maps_for_tag(const string &tag, { mapref_vector maps; level_id place = level_id::current(); + unordered_set tag_set = parse_tags(tag); for (const map_def &mapdef : vdefs) { - if (mapdef.has_tag(tag) + if (mapdef.has_all_tags(tag_set.begin(), tag_set.end()) && !mapdef.has_tag("dummy") && (!check_depth || !mapdef.has_depth() || mapdef.is_usable_in(place)) @@ -851,7 +861,7 @@ bool map_selector::accept(const map_def &mapdef) const return false; } return mapdef.is_minivault() == mini - && _is_extra_compatible(extra, mapdef.has_tag("extra")) + && _is_extra_compatible(extra, mapdef.is_extra_vault()) && mapdef.place.is_usable_in(place) && _map_matches_layout_type(mapdef) && !mapdef.map_already_used(); @@ -860,7 +870,7 @@ bool map_selector::accept(const map_def &mapdef) const { const map_chance chance(mapdef.chance(place)); return mapdef.is_minivault() == mini - && _is_extra_compatible(extra, mapdef.has_tag("extra")) + && _is_extra_compatible(extra, mapdef.is_extra_vault()) && (!chance.valid() || mapdef.has_tag("dummy")) && depth_selectable(mapdef) && !mapdef.map_already_used(); @@ -873,12 +883,12 @@ bool map_selector::accept(const map_def &mapdef) const return chance.valid() && !mapdef.has_tag("dummy") && depth_selectable(mapdef) - && _is_extra_compatible(extra, mapdef.has_tag("extra")) + && _is_extra_compatible(extra, mapdef.is_extra_vault()) && !mapdef.map_already_used(); } case TAG: - return mapdef.has_tag(tag) + return mapdef.has_all_tags(tag) // allow multiple tags, for temple overflow vaults && (!check_depth || !mapdef.has_depth() || mapdef.is_usable_in(place)) diff --git a/crawl-ref/source/melee-attack.cc b/crawl-ref/source/melee-attack.cc index 39b25d2f4399..e974b2911a45 100644 --- a/crawl-ref/source/melee-attack.cc +++ b/crawl-ref/source/melee-attack.cc @@ -694,9 +694,11 @@ static void _hydra_consider_devouring(monster &defender) if (defender.is_shapeshifter()) { // handle this carefully, so the player knows what's going on - mprf("You spit out %s as %s twists & changes in your mouth!", + mprf("You spit out %s as %s %s & %s in your mouth!", defender.name(DESC_THE).c_str(), - defender.pronoun(PRONOUN_SUBJECTIVE).c_str()); + defender.pronoun(PRONOUN_SUBJECTIVE).c_str(), + conjugate_verb("twist", defender.pronoun_plurality()).c_str(), + conjugate_verb("change", defender.pronoun_plurality()).c_str()); return; } @@ -834,7 +836,7 @@ bool melee_attack::attack() // Calculate various ev values and begin to check them to determine the // correct handle_phase_ handler. - const int ev = defender->evasion(EV_IGNORE_NONE, attacker); + const int ev = defender->evasion(ev_ignore::none, attacker); ev_margin = test_hit(to_hit, ev, !attacker->is_player()); bool shield_blocked = attack_shield_blocked(true); @@ -1280,7 +1282,7 @@ bool melee_attack::player_aux_test_hit() // XXX We're clobbering did_hit did_hit = false; - const int evasion = defender->evasion(EV_IGNORE_NONE, attacker); + const int evasion = defender->evasion(ev_ignore::none, attacker); if (player_under_penance(GOD_ELYVILON) && god_hates_your_god(GOD_ELYVILON) @@ -1475,7 +1477,7 @@ void melee_attack::player_announce_aux_hit() string melee_attack::player_why_missed() { - const int ev = defender->evasion(EV_IGNORE_NONE, attacker); + const int ev = defender->evasion(ev_ignore::none, attacker); const int combined_penalty = attacker_armour_tohit_penalty + attacker_shield_tohit_penalty; if (to_hit < ev && to_hit + combined_penalty >= ev) @@ -2949,8 +2951,7 @@ void melee_attack::mons_apply_attack_flavour() case AF_ENGULF: if (x_chance_in_y(2, 3) && attacker->can_constrict(defender, true)) { - if (defender->is_player() && !you.duration[DUR_WATER_HOLD] - && !you.duration[DUR_WATER_HOLD_IMMUNITY]) + if (defender->is_player() && !you.duration[DUR_WATER_HOLD]) { you.duration[DUR_WATER_HOLD] = 10; you.props["water_holder"].get_int() = attacker->as_monster()->mid; @@ -2980,7 +2981,7 @@ void melee_attack::mons_apply_attack_flavour() if (attacker->type == MONS_FIRE_VORTEX) attacker->as_monster()->suicide(-10); - special_damage = defender->apply_ac(base_damage, 0, AC_HALF); + special_damage = defender->apply_ac(base_damage, 0, ac_type::half); special_damage = resist_adjust_damage(defender, BEAM_FIRE, special_damage); @@ -3644,17 +3645,13 @@ bool melee_attack::_player_vampire_draws_blood(const monster* mon, const int dam } } - // Gain nutrition. - if (you.hunger_state != HS_ENGORGED) - lessen_hunger(30 + random2avg(59, 2), false); - return true; } bool melee_attack::_vamp_wants_blood_from_monster(const monster* mon) { return you.species == SP_VAMPIRE - && you.hunger_state < HS_SATIATED + && !you.vampire_alive && actor_is_susceptible_to_vampirism(*mon) && mons_has_blood(mon->type); } diff --git a/crawl-ref/source/menu-type.h b/crawl-ref/source/menu-type.h index 29fefc577578..e2bd4b282359 100644 --- a/crawl-ref/source/menu-type.h +++ b/crawl-ref/source/menu-type.h @@ -1,12 +1,12 @@ #pragma once -enum menu_type +enum class menu_type { - MT_ANY = -1, + any = -1, - MT_INVLIST, // List inventory - MT_DROP, - MT_PICKUP, - MT_KNOW, - MT_SELONE, // Select one + invlist, // List inventory + drop, + pickup, + know, + sel_one, // Select one }; diff --git a/crawl-ref/source/menu.cc b/crawl-ref/source/menu.cc index 1540ae83f521..62fbfb9ec7ca 100644 --- a/crawl-ref/source/menu.cc +++ b/crawl-ref/source/menu.cc @@ -100,7 +100,7 @@ class UIMenu : public Widget void update_item(int index); void update_items(); - void get_visible_item_range(int *vis_min, int *vis_max); + void is_visible_item_range(int *vis_min, int *vis_max); void get_item_region(int index, int *y1, int *y2); #ifndef USE_TILE_LOCAL @@ -174,9 +174,9 @@ void UIMenu::update_items() #endif } -void UIMenu::get_visible_item_range(int *vis_min, int *vis_max) +void UIMenu::is_visible_item_range(int *vis_min, int *vis_max) { - const int viewport_height = m_menu->m_ui.scroller->get_region()[3]; + const int viewport_height = m_menu->m_ui.scroller->get_region().height; const int scroll = m_menu->m_ui.scroller->get_scroll(); #ifdef USE_TILE_LOCAL @@ -381,7 +381,7 @@ int UIMenu::get_max_viewport_height() void UIMenu::_render() { #ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {1, 1, 1}; + GLW_3VF t = {(float)m_region.x, (float)m_region.y, 0}, s = {1, 1, 1}; glmanager->set_transform(t, s); m_shape_buf.draw(); @@ -395,14 +395,14 @@ void UIMenu::_render() #else int vis_min, vis_max; - get_visible_item_range(&vis_min, &vis_max); + is_visible_item_range(&vis_min, &vis_max); const int scroll = m_menu->m_ui.scroller->get_scroll(); for (int i = vis_min; i < vis_max; i++) { const MenuEntry *me = m_menu->items[i]; int y = i - vis_min + 1; - cgotoxy(m_region[0]+1, m_region[1]+scroll+y); + cgotoxy(m_region.x+1, m_region.y+scroll+y); const int col = m_menu->item_colour(me); textcolour(col); const bool needs_cursor = (m_menu->get_cursor() == i && m_menu->is_set(MF_MULTISELECT)); @@ -411,12 +411,12 @@ void UIMenu::_render() { formatted_string s = formatted_string::parse_string( me->get_text(needs_cursor), col); - s.chop(m_region[2]).display(); + s.chop(m_region.width).display(); } else { string text = me->get_text(needs_cursor); - text = chop_string(text, m_region[2]); + text = chop_string(text, m_region.width); cprintf("%s", text.c_str()); } } @@ -429,7 +429,8 @@ SizeReq UIMenu::_get_preferred_size(Direction dim, int prosp_width) if (!dim) { do_layout(INT_MAX, m_num_columns); - int max_menu_width = min(1400, m_nat_column_width * m_num_columns); + const int em = Options.tile_font_crt_size; + int max_menu_width = min(93*em, m_nat_column_width * m_num_columns); return {0, max_menu_width}; } else @@ -465,42 +466,11 @@ class UIMenuMore : public Text m_text.clear(); m_text += fs; _expose(); - m_wrapped_size = { -1, -1 }; - wrap_text_to_size(m_region[2], m_region[3]); + m_wrapped_size = Size(-1); + wrap_text_to_size(m_region.width, m_region.height); }; }; -class UIShowHide : public Bin -{ -public: - UIShowHide() : Bin() {}; - virtual ~UIShowHide() {}; - virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override - { - if (!m_visible) - return { 0, 0 }; - return m_child->get_preferred_size(dim, prosp_width); - } - virtual void _allocate_region() override - { - if (m_visible) - m_child->allocate_region(m_region); - } - virtual void _render() override - { - if (m_visible) - m_child->render(); - } - bool get_visible() const { return m_visible; } - void set_visible(bool visible) - { - m_visible = visible; - _invalidate_sizereq(); - } -protected: - bool m_visible = false; -}; - class UIMenuPopup : public ui::Popup { public: @@ -517,17 +487,17 @@ void UIMenuPopup::_allocate_region() { Popup::_allocate_region(); - int max_height = m_menu->m_ui.popup->get_max_child_size()[1]; - max_height -= m_menu->m_ui.title->get_region()[3]; - max_height -= m_menu->m_ui.title->margin[2]; - int viewport_height = m_menu->m_ui.scroller->get_region()[3]; + int max_height = m_menu->m_ui.popup->get_max_child_size().height; + max_height -= m_menu->m_ui.title->get_region().height; + max_height -= m_menu->m_ui.title->get_margin().bottom; + int viewport_height = m_menu->m_ui.scroller->get_region().height; #ifdef USE_TILE_LOCAL - int menu_w = m_menu->m_ui.menu->get_region()[2]; + int menu_w = m_menu->m_ui.menu->get_region().width; m_menu->m_ui.menu->do_layout(menu_w, 1); int m_height = m_menu->m_ui.menu->m_height; - int more_height = m_menu->m_ui.more_bin->get_region()[3]; + int more_height = m_menu->m_ui.more->get_region().height; // switch number of columns int num_cols = m_menu->m_ui.menu->get_num_columns(); if (m_menu->m_ui.menu->m_draw_tiles && m_menu->is_set(MF_USE_TWO_COLUMNS) @@ -537,31 +507,31 @@ void UIMenuPopup::_allocate_region() || (num_cols == 2 && m_height+more_height <= max_height)) { m_menu->m_ui.menu->set_num_columns(3 - num_cols); - throw RestartAllocation(); + ui::restart_layout(); } } m_menu->m_ui.menu->do_layout(menu_w, num_cols); #endif #ifndef USE_TILE_LOCAL - int menu_height = m_menu->m_ui.menu->get_region()[3]; + int menu_height = m_menu->m_ui.menu->get_region().height; // change more visibility bool can_toggle_more = !m_menu->is_set(MF_ALWAYS_SHOW_MORE) && !m_menu->m_ui.more->get_text().ops.empty(); if (can_toggle_more) { - bool more_visible = m_menu->m_ui.more_bin->get_visible(); + bool more_visible = m_menu->m_ui.more->is_visible(); if (more_visible ? menu_height <= max_height : menu_height > max_height) { - m_menu->m_ui.more_bin->set_visible(!more_visible); + m_menu->m_ui.more->set_visible(!more_visible); _invalidate_sizereq(); - m_menu->m_ui.more_bin->_queue_allocation(); - throw RestartAllocation(); + m_menu->m_ui.more->_queue_allocation(); + ui::restart_layout(); } } - if (m_menu->m_keyhelp_more && m_menu->m_ui.more_bin->get_visible()) + if (m_menu->m_keyhelp_more && m_menu->m_ui.more->is_visible()) { int scroll = m_menu->m_ui.scroller->get_scroll(); int scroll_percent = scroll*100/(menu_height-viewport_height); @@ -581,12 +551,12 @@ void UIMenuPopup::_allocate_region() #else const int max_viewport_height = 52; #endif - m_menu->m_ui.scroller->max_size() = { INT_MAX, max_viewport_height }; + m_menu->m_ui.scroller->max_size().height = max_viewport_height; if (max_viewport_height < viewport_height) { m_menu->m_ui.scroller->_invalidate_sizereq(); m_menu->m_ui.scroller->_queue_allocation(); - throw RestartAllocation(); + ui::restart_layout(); } } @@ -596,7 +566,7 @@ void UIMenu::_allocate_region() // XXX: is this needed? m_height = m_menu->items.size(); #else - do_layout(m_region[2], m_num_columns); + do_layout(m_region.width, m_num_columns); update_hovered_entry(); pack_buffers(); #endif @@ -605,17 +575,20 @@ void UIMenu::_allocate_region() #ifdef USE_TILE_LOCAL void UIMenu::update_hovered_entry() { - const int x = m_mouse_x - m_region[0], - y = m_mouse_y - m_region[1]; + const int x = m_mouse_x - m_region.x, + y = m_mouse_y - m_region.y; int vis_min, vis_max; - get_visible_item_range(&vis_min, &vis_max); + is_visible_item_range(&vis_min, &vis_max); for (int i = vis_min; i < vis_max; ++i) { const auto& entry = item_info[i]; if (entry.heading) continue; - const int w = m_region[2] / m_num_columns; + const auto me = m_menu->items[i]; + if (me->hotkeys.size() == 0) + continue; + const int w = m_region.width / m_num_columns; const int entry_x = entry.column * w; const int entry_h = row_heights[entry.row+1] - row_heights[entry.row]; if (x >= entry_x && x < entry_x+w && y >= entry.y && y < entry.y+entry_h) @@ -648,7 +621,7 @@ bool UIMenu::on_event(const wm_event& event) if (event.type == WME_MOUSEENTER) { - do_layout(m_region[2], m_num_columns); + do_layout(m_region.width, m_num_columns); update_hovered_entry(); pack_buffers(); _expose(); @@ -662,7 +635,7 @@ bool UIMenu::on_event(const wm_event& event) m_mouse_y = -1; m_mouse_pressed = false; m_mouse_idx = -1; - do_layout(m_region[2], m_num_columns); + do_layout(m_region.width, m_num_columns); pack_buffers(); _expose(); return false; @@ -670,7 +643,7 @@ bool UIMenu::on_event(const wm_event& event) if (event.type == WME_MOUSEMOTION) { - do_layout(m_region[2], m_num_columns); + do_layout(m_region.width, m_num_columns); update_hovered_entry(); pack_buffers(); _expose(); @@ -700,7 +673,7 @@ bool UIMenu::on_event(const wm_event& event) wm_event ev = {0}; ev.type = WME_KEYDOWN; ev.key.keysym.sym = key; - on_event(ev); + m_menu->m_ui.popup->on_event(ev); } return true; @@ -721,10 +694,10 @@ void UIMenu::pack_buffers() if (!item_info.size()) return; - const int col_width = m_region[2] / m_num_columns; + const int col_width = m_region.width / m_num_columns; int vis_min, vis_max; - get_visible_item_range(&vis_min, &vis_max); + is_visible_item_range(&vis_min, &vis_max); for (int i = vis_min; i < vis_max; ++i) { @@ -736,12 +709,12 @@ void UIMenu::pack_buffers() if (entry.heading) { - formatted_string split = m_font_entry->split(entry.text, m_region[2], entry_h); + formatted_string split = m_font_entry->split(entry.text, m_region.width, entry_h); // see corresponding section in do_layout() int line_y = entry.y + (i == 0 ? 0 : 5) + item_pad; if (i < (int)item_info.size()-1 && !item_info[i+1].heading) { - m_div_line_buf.add(entry.x, line_y, + m_div_line_buf.add_square(entry.x, line_y, entry.x+m_num_columns*col_width, line_y, header_div_colour); } m_text_buf.add(split, entry.x, line_y+3); @@ -825,24 +798,23 @@ Menu::Menu(int _flags, const string& tagname, KeymapContext kmc) m_ui.scroller = make_shared(); m_ui.title = make_shared(); m_ui.more = make_shared(); - m_ui.more_bin = make_shared(); + m_ui.more->set_visible(false); m_ui.vbox = make_shared(Widget::VERT); - m_ui.vbox->align_items = Widget::STRETCH; + m_ui.vbox->align_cross = Widget::STRETCH; - m_ui.title->set_margin_for_sdl({0, 0, 10, 0}); - m_ui.more->set_margin_for_sdl({10, 0, 0, 0}); + m_ui.title->set_margin_for_sdl(0, 0, 10, 0); + m_ui.more->set_margin_for_sdl(10, 0, 0, 0); m_ui.vbox->add_child(m_ui.title); #ifdef USE_TILE_LOCAL m_ui.vbox->add_child(m_ui.scroller); #else auto scroller_wrap = make_shared(Widget::VERT, Box::Expand::EXPAND_V); - scroller_wrap->align_items = Widget::STRETCH; + scroller_wrap->align_cross = Widget::STRETCH; scroller_wrap->add_child(m_ui.scroller); m_ui.vbox->add_child(scroller_wrap); #endif - m_ui.vbox->add_child(m_ui.more_bin); - m_ui.more_bin->set_child(m_ui.more); + m_ui.vbox->add_child(m_ui.more); m_ui.scroller->set_child(m_ui.menu); set_flags(flags); @@ -912,6 +884,11 @@ void Menu::set_flags(int new_flags, bool use_options) #endif } +bool Menu::minus_is_pageup() const +{ + return !is_set(MF_MULTISELECT) && !is_set(MF_SPECIAL_MINUS); +} + void Menu::set_more(const formatted_string &fs) { m_keyhelp_more = false; @@ -922,9 +899,10 @@ void Menu::set_more(const formatted_string &fs) void Menu::set_more() { m_keyhelp_more = true; + string pageup_keys = minus_is_pageup() ? "-|<<" : "<<"; more = formatted_string::parse_string( "[+|>|Space]: page down " - "[-|<<]: page up " + "[" + pageup_keys + "]: page up " "[Esc]: close [XXX]" ); update_more(); @@ -993,7 +971,7 @@ void Menu::do_menu() bool done = false; m_ui.popup = make_shared(m_ui.vbox, this); - m_ui.menu->on(Widget::slots.event, [this, &done](wm_event ev) { + m_ui.popup->on(Widget::slots.event, [this, &done](wm_event ev) { if (ev.type != WME_KEYDOWN) return false; if (m_filter) @@ -1021,11 +999,12 @@ void Menu::do_menu() return false; }; m_ui.title->on(Widget::slots.event, menu_wrap_click); - m_ui.more_bin->on(Widget::slots.event, menu_wrap_click); + m_ui.more->on(Widget::slots.event, menu_wrap_click); #endif update_menu(); ui::push_layout(m_ui.popup, m_kmc); + ui::set_focused_widget(m_ui.popup.get()); #ifdef USE_TILE_WEB tiles.push_menu(this); @@ -1188,13 +1167,13 @@ bool Menu::process_key(int keyin) sel.clear(); lastch = keyin; return is_set(MF_UNCANCEL) && !crawl_state.seen_hups; - case ' ': case CK_PGDN: case '>': + case ' ': case CK_PGDN: case '>': case '+': case CK_MOUSE_B1: case CK_MOUSE_CLICK: if (!page_down() && is_set(MF_WRAP)) m_ui.scroller->set_scroll(0); break; - case CK_PGUP: case '<': case ';': + case CK_PGUP: case '<': page_up(); break; case CK_UP: @@ -1904,7 +1883,7 @@ void Menu::update_more() #ifdef USE_TILE_LOCAL show_more = show_more && !m_keyhelp_more; #endif - m_ui.more_bin->set_visible(show_more); + m_ui.more->set_visible(show_more); #ifdef USE_TILE_WEB if (!alive) @@ -1972,8 +1951,9 @@ void Menu::update_title() } #ifdef USE_TILE_LOCAL - m_ui.title->set_margin_for_sdl({0, 0, 10, - UIMenu::item_pad + (m_indent_title ? 38 : 0)}); + const bool tile_indent = m_indent_title && Options.tile_menu_icons; + m_ui.title->set_margin_for_sdl(0, 0, 10, + UIMenu::item_pad + (tile_indent ? 38 : 0)); #endif m_ui.title->set_text(fs); #ifdef USE_TILE_WEB @@ -1985,16 +1965,16 @@ bool Menu::in_page(int index) const { int y1, y2; m_ui.menu->get_item_region(index, &y1, &y2); - int vph = m_ui.menu->get_region()[3]; + int vph = m_ui.menu->get_region().height; int vpy = m_ui.scroller->get_scroll(); return (vpy < y1 && y1 < vpy+vph) || (vpy < y2 && y2 < vpy+vph); } bool Menu::page_down() { - int dy = m_ui.scroller->get_region()[3]; + int dy = m_ui.scroller->get_region().height; int y = m_ui.scroller->get_scroll(); - bool at_bottom = y+dy >= m_ui.menu->get_region()[3]; + bool at_bottom = y+dy >= m_ui.menu->get_region().height; m_ui.scroller->set_scroll(y+dy); #ifndef USE_TILE_LOCAL if (!at_bottom) @@ -2005,7 +1985,7 @@ bool Menu::page_down() bool Menu::page_up() { - int dy = m_ui.scroller->get_region()[3]; + int dy = m_ui.scroller->get_region().height; int y = m_ui.scroller->get_scroll(); m_ui.scroller->set_scroll(y-dy); #ifndef USE_TILE_LOCAL @@ -2044,7 +2024,7 @@ bool Menu::line_up() m_ui.menu->get_item_region(index-1, &y, nullptr); m_ui.scroller->set_scroll(y); #ifndef USE_TILE_LOCAL - int dy = m_ui.scroller->get_region()[3]; + int dy = m_ui.scroller->get_region().height; m_ui.menu->set_showable_height(y+dy); #endif return true; @@ -2104,7 +2084,7 @@ void Menu::webtiles_scroll(int first) { m_ui.scroller->set_scroll(item_y); webtiles_update_scroll_pos(); - ui_force_render(); + ui::force_render(); } } @@ -2833,10 +2813,6 @@ void PrecisionMenu::draw_menu() { for (MenuObject *obj : m_attached_objects) obj->render(); - // Render everything else here - - // Reset textcolour just in case - textcolour(LIGHTGRAY); } MenuItem::MenuItem(): m_min_coord(0,0), m_max_coord(0,0), m_selected(false), @@ -3336,7 +3312,6 @@ void FormattedTextItem::render() { m_font_buf.clear(); // FIXME: m_fg_colour doesn't work here while it works in console. - textcolour(m_fg_colour); m_font_buf.add(formatted_string::parse_string(m_render_text, m_fg_colour), m_min_coord.x, m_min_coord.y + get_vertical_offset()); @@ -3422,102 +3397,6 @@ void TextTileItem::render() for (int i = 0; i < TEX_MAX; i++) m_tile_buf[i].draw(); } - -SaveMenuItem::SaveMenuItem() -{ -} - -SaveMenuItem::~SaveMenuItem() -{ - for (int t = 0; t < TEX_MAX; t++) - m_tile_buf[t].clear(); -} - -void SaveMenuItem::render() -{ - if (!m_visible) - return; - - TextTileItem::render(); -} - -void SaveMenuItem::set_doll(dolls_data doll) -{ - m_save_doll = doll; - _pack_doll(); -} - -void SaveMenuItem::_pack_doll() -{ - m_tiles.clear(); - // FIXME: Implement this logic in one place in e.g. pack_doll_buf(). - int p_order[TILEP_PART_MAX] = - { - TILEP_PART_SHADOW, // 0 - TILEP_PART_HALO, - TILEP_PART_ENCH, - TILEP_PART_DRCWING, - TILEP_PART_CLOAK, - TILEP_PART_BASE, // 5 - TILEP_PART_BOOTS, - TILEP_PART_LEG, - TILEP_PART_BODY, - TILEP_PART_ARM, - TILEP_PART_HAIR, - TILEP_PART_BEARD, - TILEP_PART_DRCHEAD, // 15 - TILEP_PART_HELM, - TILEP_PART_HAND1, // 10 - TILEP_PART_HAND2, - }; - - int flags[TILEP_PART_MAX]; - tilep_calc_flags(m_save_doll, flags); - - // For skirts, boots go under the leg armour. For pants, they go over. - if (m_save_doll.parts[TILEP_PART_LEG] < TILEP_LEG_SKIRT_OFS) - { - p_order[6] = TILEP_PART_BOOTS; - p_order[7] = TILEP_PART_LEG; - } - - // Special case bardings from being cut off. - bool is_naga = (m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_NAGA - || m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_NAGA + 1); - if (m_save_doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_NAGA_BARDING - && m_save_doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_NAGA_BARDING_RED) - { - flags[TILEP_PART_BOOTS] = is_naga ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE; - } - - bool is_cent = (m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_CENTAUR - || m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_CENTAUR + 1); - if (m_save_doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_CENTAUR_BARDING - && m_save_doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_CENTAUR_BARDING_RED) - { - flags[TILEP_PART_BOOTS] = is_cent ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE; - } - - for (int i = 0; i < TILEP_PART_MAX; ++i) - { - const int p = p_order[i]; - const tileidx_t idx = m_save_doll.parts[p]; - if (idx == 0 || idx == TILEP_SHOW_EQUIP || flags[p] == TILEP_FLAG_HIDE) - continue; - - ASSERT_RANGE(idx, TILE_MAIN_MAX, TILEP_PLAYER_MAX); - - int ymax = TILE_Y; - - if (flags[p] == TILEP_FLAG_CUT_CENTAUR - || flags[p] == TILEP_FLAG_CUT_NAGA) - { - ymax = 18; - } - - m_tiles.emplace_back(idx, TEX_PLAYER, ymax); - } -} #endif MenuObject::MenuObject() : m_dirty(false), m_allow_focus(true), m_min_coord(0,0), @@ -3800,7 +3679,7 @@ MenuObject::InputReturnValue MenuFreeform::handle_mouse(const MouseEvent& me) { if (m_active_item != nullptr) { - _set_active_item_by_index(-1); + _set_active_item(nullptr); return INPUT_FOCUS_LOST; } else @@ -3817,7 +3696,7 @@ MenuObject::InputReturnValue MenuFreeform::handle_mouse(const MouseEvent& me) { if (m_active_item != nullptr) { - _set_active_item_by_index(-1); + _set_active_item(nullptr); return INPUT_NO_ACTION; } } @@ -3878,21 +3757,14 @@ MenuItem* MenuFreeform::get_active_item() return m_active_item; } -// Predicate for std::find_if -static bool _id_comparison(MenuItem* item, int ID) -{ - return item->get_id() == ID; -} - /** * Sets item by ID * Clears active item if ID not found */ -void MenuFreeform ::set_active_item(int ID) +void MenuFreeform::set_active_item(int ID) { auto it = find_if(m_entries.begin(), m_entries.end(), - bind(_id_comparison, placeholders::_1, ID)); - + [=](const MenuItem* item) { return item->get_id() == ID; }); m_active_item = (it != m_entries.end()) ? *it : nullptr; m_dirty = true; } @@ -3901,61 +3773,34 @@ void MenuFreeform ::set_active_item(int ID) * Sets active item based on index * This function is for internal use if object does not have ID set */ -void MenuFreeform::_set_active_item_by_index(int index) +void MenuFreeform::_set_active_item(MenuItem* item) { - if (index >= 0 && index < static_cast (m_entries.size())) - { - if (m_entries.at(index)->can_be_highlighted()) - { - m_active_item = m_entries.at(index); - m_dirty = true; - return; - } - } - // Clear active selection - m_active_item = nullptr; + ASSERT(!item || item->can_be_highlighted()); + m_active_item = item; m_dirty = true; } void MenuFreeform::set_active_item(MenuItem* item) { - // Does item exist in the menu? - auto it = find(m_entries.begin(), m_entries.end(), item); - m_active_item = (it != end(m_entries) && item->can_be_highlighted()) - ? item : nullptr; + bool present = find(m_entries.begin(), m_entries.end(), item) != m_entries.end(); + m_active_item = (present && item->can_be_highlighted()) ? item : nullptr; m_dirty = true; } void MenuFreeform::activate_first_item() { - if (!m_entries.empty()) - { - // find the first activeable item - for (int i = 0; i < static_cast (m_entries.size()); ++i) - { - if (m_entries.at(i)->can_be_highlighted()) - { - _set_active_item_by_index(i); - break; // escape loop - } - } - } + auto el = find_if(m_entries.begin(), m_entries.end(), + [=](const MenuItem* item) { return item->can_be_highlighted(); }); + if (el != m_entries.end()) + _set_active_item(*el); } void MenuFreeform::activate_last_item() { - if (!m_entries.empty()) - { - // find the last activeable item - for (int i = m_entries.size() -1; i >= 0; --i) - { - if (m_entries.at(i)->can_be_highlighted()) - { - _set_active_item_by_index(i); - break; // escape loop - } - } - } + auto el = find_if(m_entries.rbegin(), m_entries.rend(), + [=](const MenuItem* item) { return item->can_be_highlighted(); }); + if (el != m_entries.rend()) + _set_active_item(*el); } bool MenuFreeform::select_item(int index) @@ -4115,582 +3960,6 @@ MenuItem* MenuFreeform::_find_item_by_direction(const MenuItem* start, return closest; } -MenuScroller::MenuScroller(): m_topmost_visible(0), m_currently_active(0), - m_items_shown(0) -{ -#ifdef USE_TILE_LOCAL - m_arrow_up = new TextTileItem(); - m_arrow_down = new TextTileItem(); - m_arrow_up->add_tile(tile_def(TILE_MI_ARROW0, TEX_DEFAULT)); - m_arrow_down->add_tile(tile_def(TILE_MI_ARROW4, TEX_DEFAULT)); -#endif -} - -MenuScroller::~MenuScroller() -{ -#ifdef USE_TILE_LOCAL - delete m_arrow_up; - delete m_arrow_down; -#endif - deleteAll(m_entries); -} - -MenuObject::InputReturnValue MenuScroller::process_input(int key) -{ - if (!m_allow_focus || !m_visible) - return INPUT_NO_ACTION; - - if (m_currently_active < 0) - { - if (m_entries.empty()) - { - // nothing to process - return MenuObject::INPUT_NO_ACTION; - } - else - { - // pick the first item possible - for (unsigned int i = 0; i < m_entries.size(); ++i) - { - if (m_entries.at(i)->can_be_highlighted()) - { - _set_active_item_by_index(i); - break; - } - } - } - } - - MenuItem* find_entry = nullptr; - switch (key) - { - case CK_ENTER: - if (m_currently_active < 0) - return MenuObject::INPUT_NO_ACTION; - - select_item(m_currently_active); - if (get_active_item()->selected()) - return MenuObject::INPUT_SELECTED; - else - return MenuObject::INPUT_DESELECTED; - break; - case CK_UP: - case CONTROL('K'): - case CONTROL('P'): - find_entry = _find_item_by_direction(m_currently_active, UP); - if (find_entry != nullptr) - { - set_active_item(find_entry); - return MenuObject::INPUT_ACTIVE_CHANGED; - } - else - return MenuObject::INPUT_FOCUS_RELEASE_UP; - break; - case CK_DOWN: - case CONTROL('J'): - case CONTROL('N'): - find_entry = _find_item_by_direction(m_currently_active, DOWN); - if (find_entry != nullptr) - { - set_active_item(find_entry); - return MenuObject::INPUT_ACTIVE_CHANGED; - } - else - return MenuObject::INPUT_FOCUS_RELEASE_DOWN; - break; - case CK_LEFT: - return MenuObject::INPUT_FOCUS_RELEASE_LEFT; - case CK_RIGHT: - return MenuObject::INPUT_FOCUS_RELEASE_RIGHT; - case CK_PGUP: - if (m_currently_active != m_topmost_visible) - { - set_active_item(m_topmost_visible); - return MenuObject::INPUT_ACTIVE_CHANGED; - } - else - { - if (m_currently_active == 0) - return MenuObject::INPUT_FOCUS_RELEASE_UP; - else - { - int new_active = m_currently_active - m_items_shown; - if (new_active < 0) - new_active = 0; - set_active_item(new_active); - return MenuObject::INPUT_ACTIVE_CHANGED; - } - } - case CK_PGDN: - { - int last_item_visible = m_topmost_visible + m_items_shown - 1; - int last_menu_item = m_entries.size() - 1; - if (last_item_visible > m_currently_active) - { - set_active_item(last_item_visible); - return MenuObject::INPUT_ACTIVE_CHANGED; - } - else if (m_currently_active == last_menu_item) - return MenuObject::INPUT_FOCUS_RELEASE_DOWN; - else - { - int new_active = m_currently_active + m_items_shown - 1; - if (new_active > last_menu_item) - new_active = last_menu_item; - set_active_item(new_active); - return MenuObject::INPUT_ACTIVE_CHANGED; - } - } - default: - find_entry = select_item_by_hotkey(key); - if (find_entry != nullptr) - { - if (find_entry->selected()) - return MenuObject::INPUT_SELECTED; - else - return MenuObject::INPUT_DESELECTED; - } - break; - } - return MenuObject::INPUT_NO_ACTION; -} - -#ifdef USE_TILE_LOCAL -MenuObject::InputReturnValue MenuScroller::handle_mouse(const MouseEvent &me) -{ - if (!m_allow_focus || !m_visible) - return INPUT_NO_ACTION; - - if (!_is_mouse_in_bounds(coord_def(me.px, me.py))) - { - if (m_currently_active >= 0) - { - _set_active_item_by_index(-1); - return INPUT_FOCUS_LOST; - } - else - return INPUT_NO_ACTION; - } - - MenuItem* find_item = nullptr; - - if (me.event == MouseEvent::MOVE) - { - find_item = _find_item_by_mouse_coords(coord_def(me.px, me.py)); - if (find_item == nullptr) - { - if (m_currently_active >= 0) - { - // Do not signal on cleared active events - _set_active_item_by_index(-1); - return INPUT_NO_ACTION; - } - } - else - { - if (m_currently_active >= 0) - { - // prevent excess _place_item calls if the mouse over is already - // active - if (find_item != m_entries.at(m_currently_active)) - { - set_active_item(find_item); - return INPUT_ACTIVE_CHANGED; - } - else - return INPUT_NO_ACTION; - } - else - { - set_active_item(find_item); - return INPUT_ACTIVE_CHANGED; - } - - } - return INPUT_NO_ACTION; - } - - if (me.event == MouseEvent::PRESS && me.button == MouseEvent::LEFT) - { - find_item = _find_item_by_mouse_coords(coord_def(me.px, - me.py)); - if (find_item != nullptr) - { - select_item(find_item); - if (find_item->selected()) - return INPUT_SELECTED; - else - return INPUT_DESELECTED; - } - else - { - // handle clicking on the scrollbar (top half of region => scroll up) - if (static_cast(me.py)-m_min_coord.y > (m_max_coord.y-m_min_coord.y)/2) - return process_input(CK_DOWN); - else - return process_input(CK_UP); - } - } - else if (me.event == MouseEvent::PRESS && me.button == MouseEvent::RIGHT) - return INPUT_END_MENU_ABORT; - // all the other Mouse Events are uninteresting and are ignored - return INPUT_NO_ACTION; -} -#endif - -void MenuScroller::render() -{ - if (!m_visible) - return; - - if (m_dirty) - _place_items(); - - for (MenuItem *item : m_entries) - item->render(); - -#ifdef USE_TILE_LOCAL - // draw scrollbar - m_arrow_up->render(); - m_arrow_down->render(); -#endif -} - -MenuItem* MenuScroller::get_active_item() -{ - if (m_currently_active >= 0 - && m_currently_active < static_cast (m_entries.size())) - { - return m_entries.at(m_currently_active); - } - return nullptr; -} - -void MenuScroller::set_active_item(int ID) -{ - if (m_currently_active >= 0) - { - if (m_entries.at(m_currently_active)->get_id() == ID) - { - // prevent useless _place_items - return; - } - } - - auto it = find_if(m_entries.begin(), m_entries.end(), - bind(_id_comparison, placeholders::_1, ID)); - if (it != m_entries.end()) - { - set_active_item(*it); - return; - } - m_currently_active = 0; - m_dirty = true; - return; -} - -void MenuScroller::_set_active_item_by_index(int index) -{ - // prevent useless _place_items - if (index == m_currently_active) - return; - - if (index >= 0 && index < static_cast (m_entries.size())) - { - m_currently_active = index; - if (m_currently_active < m_topmost_visible) - m_topmost_visible = m_currently_active; - } - else - m_currently_active = -1; - m_dirty = true; -} - -void MenuScroller::set_active_item(MenuItem* item) -{ - if (item == nullptr) - { - _set_active_item_by_index(-1); - return; - } - - for (int i = 0; i < static_cast (m_entries.size()); ++i) - { - if (item == m_entries.at(i)) - { - _set_active_item_by_index(i); - return; - } - } - _set_active_item_by_index(-1); -} - -void MenuScroller::activate_first_item() -{ - if (!m_entries.empty()) - { - // find the first activeable item - for (int i = 0; i < static_cast (m_entries.size()); ++i) - { - if (m_entries.at(i)->can_be_highlighted()) - { - _set_active_item_by_index(i); - break; // escape loop - } - } - } -} - -void MenuScroller::activate_last_item() -{ - if (!m_entries.empty()) - { - // find the last activeable item - for (int i = m_entries.size() -1; i >= 0; --i) - { - if (m_entries.at(i)->can_be_highlighted()) - { - _set_active_item_by_index(i); - break; // escape loop - } - } - } -} - -bool MenuScroller::select_item(int index) -{ - if (index >= 0 && index < static_cast (m_entries.size())) - { - // Flip the flag - m_entries.at(index)->select(!m_entries.at(index)->selected()); - return m_entries.at(index)->selected(); - } - return false; -} - -bool MenuScroller::select_item(MenuItem* item) -{ - ASSERT(item != nullptr); - // Is the item in the menu? - for (int i = 0; i < static_cast (m_entries.size()); ++i) - { - if (item == m_entries.at(i)) - return select_item(i); - } - return false; -} - -bool MenuScroller::attach_item(MenuItem* item) -{ - // _place_entries controls visibility, hide it until it's processed - item->set_visible(false); - m_entries.push_back(item); - m_dirty = true; - return true; -} - -/** - * Changes the bounds of the items that are to be visible - * preserves user set item heigth - * does not preserve width - */ -void MenuScroller::_place_items() -{ - m_dirty = false; - m_items_shown = 0; - - int item_height = 0; - int space_used = 0; - int one_past_last = 0; - const int space_available = m_max_coord.y - m_min_coord.y; - coord_def min_coord(0,0); - coord_def max_coord(0,0); - - // Hide all the items - for (MenuItem *item : m_entries) - { - item->set_visible(false); - item->allow_highlight(false); - } - - // calculate how many entries we can fit - for (one_past_last = m_topmost_visible; - one_past_last < static_cast (m_entries.size()); - ++one_past_last) - { - space_used += m_entries.at(one_past_last)->get_max_coord().y - - m_entries.at(one_past_last)->get_min_coord().y; - if (space_used > space_available) - { - if (m_currently_active < 0) - break; // all space allocated - if (one_past_last > m_currently_active) - break; // we included our active one, ok! - else - { - // active one didn't fit, chop the first one and run the loop - // again - ++m_topmost_visible; - one_past_last = m_topmost_visible - 1; - space_used = 0; - continue; - } - } - } - - space_used = 0; - int work_index = 0; - - for (work_index = m_topmost_visible; work_index < one_past_last; - ++work_index) - { - min_coord = m_entries.at(work_index)->get_min_coord(); - max_coord = m_entries.at(work_index)->get_max_coord(); - item_height = max_coord.y - min_coord.y; - - min_coord.y = m_min_coord.y + space_used; - max_coord.y = min_coord.y + item_height; - min_coord.x = m_min_coord.x; - max_coord.x = m_max_coord.x; -#ifdef USE_TILE_LOCAL - // reserve one tile space for scrollbar - max_coord.x -= 32; -#endif - m_entries.at(work_index)->set_bounds_no_multiply(min_coord, max_coord); - m_entries.at(work_index)->set_visible(true); - m_entries.at(work_index)->allow_highlight(true); - space_used += item_height; - ++m_items_shown; - } - -#ifdef USE_TILE_LOCAL - // arrows - m_arrow_down->set_bounds_no_multiply(coord_def(m_max_coord.x-32,m_max_coord.y-32),coord_def(m_max_coord.x,m_max_coord.y)); - m_arrow_down->set_visible(m_topmost_visible + m_items_shown < (int)m_entries.size()); - - m_arrow_up->set_bounds_no_multiply(coord_def(m_max_coord.x-32,m_min_coord.y),coord_def(m_max_coord.x,m_min_coord.y+32)); - m_arrow_up->set_visible(m_topmost_visible>0); -#endif -} - -MenuItem* MenuScroller::_find_item_by_direction(int start_index, - MenuObject::Direction dir) -{ - MenuItem* find_item = nullptr; - switch (dir) - { - case UP: - if ((start_index - 1) >= 0) - find_item = m_entries.at(start_index - 1); - break; - case DOWN: - if ((start_index + 1) < static_cast (m_entries.size())) - find_item = m_entries.at(start_index + 1); - break; - default: - break; - } - return find_item; -} - -MenuDescriptor::MenuDescriptor(PrecisionMenu* parent): m_parent(parent), - m_active_item(nullptr), override_text("") -{ - ASSERT(m_parent != nullptr); -} - -MenuDescriptor::~MenuDescriptor() -{ -} - -vector MenuDescriptor::get_selected_items() -{ - vector ret_val; - return ret_val; -} - -void MenuDescriptor::init(const coord_def& min_coord, const coord_def& max_coord, - const string& name) -{ - MenuObject::init(min_coord, max_coord, name); - m_desc_item.set_bounds(min_coord, max_coord); - m_desc_item.set_fg_colour(WHITE); - m_desc_item.set_visible(true); -} - -MenuObject::InputReturnValue MenuDescriptor::process_input(int key) -{ - // just in case we somehow end up processing input of this item - return MenuObject::INPUT_NO_ACTION; -} - -#ifdef USE_TILE_LOCAL -MenuObject::InputReturnValue MenuDescriptor::handle_mouse(const MouseEvent &me) -{ - if (me.event == MouseEvent::PRESS && me.button == MouseEvent::RIGHT) - return INPUT_END_MENU_ABORT; - // we have nothing interesting to do on mouse events because render() - // always checks if the active has changed override for things like - // tooltips - return INPUT_NO_ACTION; -} -#endif - -void MenuDescriptor::render() -{ - if (!m_visible) - return; - - _place_items(); - - m_desc_item.render(); -} - -/** - * This allows an arbitrary string to show up in the descriptor, temporarily - * overriding whatever is there. The override will last until the descriptor - * next changes. Empty strings are not used. - * - * @param t a string to show in the MenuDescriptor. The empty string will clear - * any existing override. - */ -void MenuDescriptor::override_description(const string &t) -{ - override_text = t; - render(); -} - - -void MenuDescriptor::_place_items() -{ - MenuItem* tmp = m_parent->get_active_item(); - if (tmp != m_active_item) - { - // the active item has changed -- update - m_active_item = tmp; - override_text = ""; -#ifndef USE_TILE_LOCAL - textcolour(BLACK); - textbackground(BLACK); - for (int i = 0; i < m_desc_item.get_max_coord().y - - m_desc_item.get_min_coord().y; ++i) - { - cgotoxy(m_desc_item.get_min_coord().x, - m_desc_item.get_min_coord().y + i); - clear_to_end_of_line(); - } - textcolour(LIGHTGRAY); -#endif - - if (tmp == nullptr) - m_desc_item.set_text(""); - else - m_desc_item.set_text(m_active_item->get_description_text()); - } - else if (override_text.size() > 0) - m_desc_item.set_text(override_text); -} - BoxMenuHighlighter::BoxMenuHighlighter(PrecisionMenu *parent): m_parent(parent), m_active_item(nullptr) { @@ -4775,62 +4044,3 @@ void BoxMenuHighlighter::_place_items() #endif m_active_item = tmp; } - -BlackWhiteHighlighter::BlackWhiteHighlighter(PrecisionMenu* parent): - BoxMenuHighlighter(parent) -{ - ASSERT(m_parent != nullptr); -} - -BlackWhiteHighlighter::~BlackWhiteHighlighter() -{ -} - -void BlackWhiteHighlighter::render() -{ - if (!m_visible) - return; - - _place_items(); - - if (m_active_item != nullptr) - { -#ifdef USE_TILE_LOCAL - m_shape_buf.draw(); -#endif - m_active_item->render(); - } -} - -void BlackWhiteHighlighter::_place_items() -{ - MenuItem* tmp = m_parent->get_active_item(); - if (tmp == m_active_item) - return; - -#ifdef USE_TILE_LOCAL - m_shape_buf.clear(); -#endif - // we had an active item before - if (m_active_item != nullptr) - { - // clear the highlight trickery - m_active_item->set_fg_colour(m_old_fg_colour); - m_active_item->set_bg_colour(m_old_bg_colour); - // redraw the old item - m_active_item->render(); - } - if (tmp != nullptr) - { -#ifdef USE_TILE_LOCAL - m_shape_buf.add(tmp->get_min_coord().x, tmp->get_min_coord().y, - tmp->get_max_coord().x, tmp->get_max_coord().y, - term_colours[LIGHTGRAY]); -#endif - m_old_bg_colour = tmp->get_bg_colour(); - m_old_fg_colour = tmp->get_fg_colour(); - tmp->set_bg_colour(LIGHTGRAY); - tmp->set_fg_colour(BLACK); - } - m_active_item = tmp; -} diff --git a/crawl-ref/source/menu.h b/crawl-ref/source/menu.h index f243225c16a6..500127b47a3c 100644 --- a/crawl-ref/source/menu.h +++ b/crawl-ref/source/menu.h @@ -274,6 +274,7 @@ enum MenuFlag MF_USE_TWO_COLUMNS = 0x08000, ///< Only valid for tiles menus MF_UNCANCEL = 0x10000, ///< Menu is uncancellable + MF_SPECIAL_MINUS = 0x20000, ///< '-' isn't PGUP or clear multiselect }; class UIMenu; @@ -308,6 +309,7 @@ class Menu virtual bool is_set(int flag) const; void set_tag(const string& t) { tag = t; } + bool minus_is_pageup() const; // Sets a replacement for the default -more- string. void set_more(const formatted_string &more); // Shows a stock message about scrolling the menu instead of -more- @@ -438,7 +440,7 @@ class Menu void deselect_all(bool update_view = true); virtual void select_items(int key, int qty = -1); - void select_item_index(int idx, int qty, bool draw_cursor = true); + virtual void select_item_index(int idx, int qty, bool draw_cursor = true); void select_index(int index, int qty = -1); bool is_hotkey(int index, int key); @@ -705,27 +707,6 @@ class TextTileItem : public TextItem vector m_tiles; FixedVector m_tile_buf; }; - -/** - * Specialization of TextTileItem that knows how to pack a player doll - * TODO: reform _pack_doll() since it currently holds duplicate code from - * tilereg.cc pack_doll_buf() - */ -class SaveMenuItem : public TextTileItem -{ -public: - friend class TilesFramework; - SaveMenuItem(); - virtual ~SaveMenuItem(); - - virtual void render() override; - - void set_doll(dolls_data doll); - -protected: - void _pack_doll(); - dolls_data m_save_doll; -}; #endif class PrecisionMenu; @@ -854,7 +835,7 @@ class MenuFreeform : public MenuObject protected: virtual void _place_items() override; - virtual void _set_active_item_by_index(int index); + virtual void _set_active_item(MenuItem *item); virtual MenuItem* _find_item_by_direction( const MenuItem* start, MenuObject::Direction dir) override; @@ -863,137 +844,6 @@ class MenuFreeform : public MenuObject MenuItem* m_default_item; }; -/** - * Container that can hold any number of objects in a scroll list style. - * Only certain number of items are visible at the same time. - * Navigating the list works with ARROW_UP and ARROW_DOWN keys. - * Eventually it should also support scrollbars. - */ -class MenuScroller : public MenuObject -{ -public: - MenuScroller(); - virtual ~MenuScroller(); - - virtual InputReturnValue process_input(int key) override; -#ifdef USE_TILE_LOCAL - virtual InputReturnValue handle_mouse(const MouseEvent& me) override; -#endif - virtual void render() override; - virtual MenuItem* get_active_item() override; - virtual void set_active_item(int ID) override; - virtual void set_active_item(MenuItem* item) override; - virtual void activate_first_item() override; - virtual void activate_last_item() override; - - virtual bool select_item(int index) override; - virtual bool select_item(MenuItem* item) override; - virtual bool attach_item(MenuItem* item) override; -protected: - virtual void _place_items() override; - virtual void _set_active_item_by_index(int index); - virtual MenuItem* _find_item_by_direction(int start_index, - MenuObject::Direction dir); - virtual MenuItem* _find_item_by_direction( - const MenuItem* start, MenuObject::Direction dir) override - { - return nullptr; - } - - int m_topmost_visible; - int m_currently_active; - int m_items_shown; - -#ifdef USE_TILE_LOCAL - TextTileItem *m_arrow_up; - TextTileItem *m_arrow_down; -#endif -}; - -/** - * Base class for various descriptor and highlighter objects. - * These should probably be attached last to the menu to be rendered last. - */ -class MenuDescriptor : public MenuObject -{ -public: - MenuDescriptor(PrecisionMenu* parent); - virtual ~MenuDescriptor(); - - void init(const coord_def& min_coord, const coord_def& max_coord, - const string& name); - - virtual InputReturnValue process_input(int key) override; -#ifdef USE_TILE_LOCAL - virtual InputReturnValue handle_mouse(const MouseEvent& me) override; -#endif - virtual void render() override; - - // these are not used, clear them - virtual vector get_selected_items() override; - virtual MenuItem* get_active_item() override { return nullptr; } - virtual bool attach_item(MenuItem* item) override { return false; } - virtual void set_active_item(int index) override {} - virtual void set_active_item(MenuItem* item) override {} - virtual void activate_first_item() override {} - virtual void activate_last_item() override {} - - virtual bool select_item(int index) override { return false; } - virtual bool select_item(MenuItem* item) override { return false;} - virtual MenuItem* select_item_by_hotkey(int key) override - { - return nullptr; - } - virtual void clear_selections() override {} - - // Do not allow focus - virtual void allow_focus(bool toggle) override {} - virtual bool can_be_focused() override { return false; } - - void override_description(const string &t); - -protected: - virtual void _place_items() override; - virtual MenuItem* _find_item_by_mouse_coords(const coord_def& pos) override - { - return nullptr; - } - virtual MenuItem* _find_item_by_direction( - const MenuItem* start, MenuObject::Direction dir) override - { - return nullptr; - } - - // Used to pull out currently active item - PrecisionMenu* m_parent; - MenuItem* m_active_item; - NoSelectTextItem m_desc_item; - string override_text; -}; - -/** - * Class for mouse over tooltips, does nothing if USE_TILE_LOCAL is not defined - * TODO: actually implement render() and _place_items() - */ -class MenuTooltip : public MenuDescriptor -{ -public: - MenuTooltip(PrecisionMenu* parent); - virtual ~MenuTooltip(); - -#ifdef USE_TILE_LOCAL - virtual InputReturnValue handle_mouse(const MouseEvent& me) override; -#endif - virtual void render() override; -protected: - virtual void _place_items() override; - -#ifdef USE_TILE_LOCAL - ShapeBuffer m_background; - FontBuffer m_font_buf; -#endif -}; - /** * Highlighter object. * TILES: It will create a colored rectangle around the currently active item. @@ -1057,24 +907,6 @@ class BoxMenuHighlighter : public MenuObject #endif }; -class BlackWhiteHighlighter : public BoxMenuHighlighter -{ -public: - BlackWhiteHighlighter(PrecisionMenu* parent); - virtual ~BlackWhiteHighlighter(); - - virtual void render() override; -protected: - virtual void _place_items() override; - -#ifdef USE_TILE_LOCAL - // Tiles does not seem to support background colors - ShapeBuffer m_shape_buf; -#endif - COLOURS m_old_bg_colour; - COLOURS m_old_fg_colour; -}; - /** * Inheritable root node of a menu that holds MenuObjects. * It is always full screen. diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc index 9c849242820a..924d29857c41 100644 --- a/crawl-ref/source/message.cc +++ b/crawl-ref/source/message.cc @@ -680,6 +680,8 @@ class message_window */ void more(bool full, bool user=false) { + rng::generator rng(rng::UI); + if (_pre_more()) return; @@ -943,6 +945,49 @@ static msg_colour_type prepare_message(const string& imsg, msg_channel_type channel, int param); +static unordered_set current_message_tees; + +message_tee::message_tee() + : target(nullptr) +{ + current_message_tees.insert(this); +} + +message_tee::message_tee(string &_target) + : target(&_target) +{ + current_message_tees.insert(this); +} + +message_tee::~message_tee() +{ + if (target) + *target += get_store(); + current_message_tees.erase(this); +} + +void message_tee::append(const string &s, msg_channel_type ch) +{ + // could use a more c++y external interface -- but that just complicates things + store << s; +} + +void message_tee::append_line(const string &s, msg_channel_type ch) +{ + append(s + "\n", ch); +} + +string message_tee::get_store() const +{ + return store.str(); +} + +static void _append_to_tees(const string &s, msg_channel_type ch) +{ + for (auto tee : current_message_tees) + tee->append(s, ch); +} + no_messages::no_messages() : msuppressed(suppress_messages) { suppress_messages = true; @@ -1229,6 +1274,8 @@ static bool _updating_view = false; static bool _check_option(const string& line, msg_channel_type channel, const vector& option) { + if (crawl_state.generating_level) + return false; return any_of(begin(option), end(option), bind(mem_fn(&message_filter::is_filtered), @@ -1355,6 +1402,8 @@ static int _last_msg_turn = -1; // Turn of last message. static void _mpr(string text, msg_channel_type channel, int param, bool nojoin, bool cap) { + rng::generator rng(rng::UI); + if (_msg_dump_file != nullptr) fprintf(_msg_dump_file, "%s\n", text.c_str()); @@ -1410,6 +1459,9 @@ static void _mpr(string text, msg_channel_type channel, int param, bool nojoin, string col = colour_to_str(colour_msg(colour)); text = "<" + col + ">" + text + ""; // XXX + if (current_message_tees.size()) + _append_to_tees(text + "\n", channel); + formatted_string fs = formatted_string::parse_string(text); // TODO: this kind of check doesn't really belong in logging code... @@ -1430,7 +1482,7 @@ static void _mpr(string text, msg_channel_type channel, int param, bool nojoin, _last_msg_turn = msg.turn; if (channel == MSGCH_ERROR) - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); if (channel == MSGCH_PROMPT || channel == MSGCH_ERROR) set_more_autoclear(false); @@ -1594,6 +1646,8 @@ static void mpr_check_patterns(const string& message, msg_channel_type channel, int param) { + if (crawl_state.generating_level) + return; for (const text_pattern &pat : Options.note_messages) { if (channel == MSGCH_EQUIPMENT || channel == MSGCH_FLOOR_ITEMS @@ -1612,7 +1666,10 @@ static void mpr_check_patterns(const string& message, } if (channel != MSGCH_DIAGNOSTICS && channel != MSGCH_EQUIPMENT) - interrupt_activity(AI_MESSAGE, channel_to_str(channel) + ":" + message); + { + interrupt_activity(activity_interrupt::message, + channel_to_str(channel) + ":" + message); + } } static bool channel_message_history(msg_channel_type channel) @@ -1648,12 +1705,15 @@ static msg_colour_type prepare_message(const string& imsg, if (colour != MSGCOL_MUTED) mpr_check_patterns(imsg, channel, param); - for (const message_colour_mapping &mcm : Options.message_colour_mappings) + if (!crawl_state.generating_level) { - if (mcm.message.is_filtered(channel, imsg)) + for (const message_colour_mapping &mcm : Options.message_colour_mappings) { - colour = mcm.colour; - break; + if (mcm.message.is_filtered(channel, imsg)) + { + colour = mcm.colour; + break; + } } } @@ -1759,6 +1819,8 @@ static bool _pre_more() void more(bool user_forced) { + rng::generator rng(rng::UI); + if (!crawl_state.io_inited) return; flush_prev_message(); @@ -2114,7 +2176,7 @@ static void _replay_messages_core(formatted_scroller &hist) } } - hist.add_formatted_string(lines, !lines.empty()); + hist.add_formatted_string(lines); hist.show(); } @@ -2131,8 +2193,7 @@ void replay_messages_during_startup() formatted_scroller hist(FS_PREWRAPPED_TEXT); hist.set_more(); hist.set_more(formatted_string::parse_string( - "Press Esc or Enter to continue, " - "arrows/pgup/pgdn to scroll.")); + "Press Esc to close, arrows/pgup/pgdn to scroll.")); hist.set_title(formatted_string::parse_string(recent_error_messages() ? "Crawl encountered errors during initialization:" : "Initialization log:")); diff --git a/crawl-ref/source/message.h b/crawl-ref/source/message.h index 93b626f9dcbd..67de88e4b146 100644 --- a/crawl-ref/source/message.h +++ b/crawl-ref/source/message.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "canned-message-type.h" #include "enum.h" @@ -95,6 +96,21 @@ void msgwin_new_turn(); bool msgwin_errors_to_stderr(); +class message_tee +{ +public: + message_tee(); + message_tee(string &_target); + virtual ~message_tee(); + virtual void append(const string &s, msg_channel_type ch = MSGCH_PLAIN); + virtual void append_line(const string &s, msg_channel_type ch = MSGCH_PLAIN); + virtual string get_store() const; + +private: + stringstream store; + string *target; +}; + class no_messages { public: diff --git a/crawl-ref/source/mgen-data.h b/crawl-ref/source/mgen-data.h index 7081140e94af..8828c5ed731a 100644 --- a/crawl-ref/source/mgen-data.h +++ b/crawl-ref/source/mgen-data.h @@ -172,7 +172,7 @@ struct mgen_data ASSERT(summon_type == 0 || abjuration_dur >= 1 && abjuration_dur <= 6 || cls == MONS_BALL_LIGHTNING || cls == MONS_ORB_OF_DESTRUCTION - || cls == MONS_BATTLESPHERE + || cls == MONS_BATTLESPHERE || cls == MONS_BALLISTOMYCETE_SPORE || summon_type == SPELL_STICKS_TO_SNAKES || summon_type == SPELL_DEATH_CHANNEL || summon_type == SPELL_BIND_SOULS diff --git a/crawl-ref/source/mon-act.cc b/crawl-ref/source/mon-act.cc index 9065592bcdbf..3ebf8816109c 100644 --- a/crawl-ref/source/mon-act.cc +++ b/crawl-ref/source/mon-act.cc @@ -161,7 +161,7 @@ static void _monster_regenerate(monster* mons) if (mons_is_hepliaklqana_ancestor(mons->type)) { if (mons->hit_points == mons->max_hit_points && you.can_see(*mons)) - interrupt_activity(AI_ANCESTOR_HP); + interrupt_activity(activity_interrupt::ancestor_hp); } } @@ -1039,55 +1039,8 @@ static bool _handle_scroll(monster& mons) return read; } -static bolt& _generate_item_beem(bolt &beem, bolt& from, monster& mons) -{ - beem.name = from.name; - beem.source_id = mons.mid; - beem.source = mons.pos(); - beem.colour = from.colour; - beem.range = from.range; - beem.damage = from.damage; - beem.ench_power = from.ench_power; - beem.hit = from.hit; - beem.glyph = from.glyph; - beem.flavour = from.flavour; - beem.thrower = from.thrower; - beem.pierce = from.pierce ; - beem.is_explosion = from.is_explosion; - beem.origin_spell = from.origin_spell; - return beem; -} - -static bool _setup_wand_beam(bolt& beem, monster& mons, const item_def& wand) -{ - if (item_type_removed(wand.base_type, wand.sub_type)) - return false; - - //XXX: implement these for monsters... (: - if (wand.sub_type == WAND_ICEBLAST - || wand.sub_type == WAND_RANDOM_EFFECTS - || wand.sub_type == WAND_CLOUDS - || wand.sub_type == WAND_SCATTERSHOT) - { - return false; - } - - const spell_type mzap = - spell_in_wand(static_cast(wand.sub_type)); - - // set up the beam - int power = 30 + mons.get_hit_dice(); - bolt theBeam = mons_spell_beam(&mons, mzap, power); - beem = _generate_item_beem(beem, theBeam, mons); - - beem.aux_source = - wand.name(DESC_QUALNAME, false, true, false, false); - - return true; -} - static void _mons_fire_wand(monster& mons, item_def &wand, bolt &beem, - bool was_visible, bool niceWand) + bool was_visible) { if (!simple_monster_message(mons, " zaps a wand.")) { @@ -1097,8 +1050,10 @@ static void _mons_fire_wand(monster& mons, item_def &wand, bolt &beem, // charge expenditure {dlb} wand.charges--; - beem.is_tracer = false; - beem.fire(); + const spell_type mzap = + spell_in_wand(static_cast(wand.sub_type)); + + mons_cast(&mons, beem, mzap, MON_SPELL_EVOKE, false); if (was_visible) { @@ -1137,13 +1092,29 @@ static bool _handle_wand(monster& mons) if (wand->charges <= 0) return false; - bool niceWand = false; - bool zap = false; - bool was_visible = you.can_see(mons); - bolt beem = setup_targetting_beam(mons); + if (item_type_removed(wand->base_type, wand->sub_type)) + return false; - if (!_setup_wand_beam(beem, mons, *wand)) + // XXX: Teach monsters to use random effects + // Digging is handled elsewhere so that sensible (wall) targets are + // chosen. + if (wand->sub_type == WAND_RANDOM_EFFECTS + || wand->sub_type == WAND_DIGGING) + { return false; + } + + bolt beem; + + const spell_type mzap = + spell_in_wand(static_cast(wand->sub_type)); + + if (!setup_mons_cast(&mons, beem, mzap, true)) + return false; + + beem.source = mons.pos(); + beem.aux_source = + wand->name(DESC_QUALNAME, false, true, false, false); const wand_type kind = (wand_type)wand->sub_type; switch (kind) @@ -1154,26 +1125,16 @@ static bool _handle_wand(monster& mons) beem.damage.size = beem.damage.size * 2 / 3; break; - case WAND_DIGGING: - // This is handled elsewhere. - return false; - default: break; } - if (!niceWand) - { - // Fire tracer, if necessary. - fire_tracer(&mons, beem); - - // Good idea? - zap = mons_should_fire(beem); - } + // Fire tracer, if necessary. + fire_tracer(&mons, beem); - if (niceWand || zap) + if (mons_should_fire(beem)) { - _mons_fire_wand(mons, *wand, beem, was_visible, niceWand); + _mons_fire_wand(mons, *wand, beem, you.see_cell(mons.pos())); return true; } @@ -1806,8 +1767,6 @@ void handle_monster_move(monster* mons) || mons->type == MONS_TEST_SPAWNER // Slime creatures can split when offscreen. || mons->type == MONS_SLIME_CREATURE - // Let monsters who have Dig use it off-screen. - || mons->has_spell(SPELL_DIG) // Let monsters who have Awaken Earth use it off-screen. || mons->has_spell(SPELL_AWAKEN_EARTH) ) @@ -2332,24 +2291,34 @@ void mons_set_just_seen(monster *mon) just_seen_queue.push_back(mon); } +void mons_reset_just_seen() +{ + // this may be called when the pointers are not valid, so don't mess with + // seen_context. + just_seen_queue.clear(); +} + static void _display_just_seen() { // these are monsters that were marked as SC_JUST_SEEN at some point since // last time this was called. We announce any that leave all at once so // as to handle monsters that may move multiple times per world_reacts. - for (auto m : just_seen_queue) + if (in_bounds(you.pos())) { - if (!m || invalid_monster(m) || !m->alive()) - continue; - // can't use simple_monster_message here, because m is out of view. - // The monster should be visible to be in this queue. - if (in_bounds(m->pos()) && !you.see_cell(m->pos())) + for (auto m : just_seen_queue) { - mprf(MSGCH_PLAIN, "%s moves out of view.", - m->name(DESC_THE, true).c_str()); + if (!m || invalid_monster(m) || !m->alive()) + continue; + // can't use simple_monster_message here, because m is out of view. + // The monster should be visible to be in this queue. + if (in_bounds(m->pos()) && !you.see_cell(m->pos())) + { + mprf(MSGCH_PLAIN, "%s moves out of view.", + m->name(DESC_THE, true).c_str()); + } } } - just_seen_queue.clear(); + mons_reset_just_seen(); } /** @@ -2670,7 +2639,7 @@ static void _mons_open_door(monster& mons, const coord_def &pos) if (!you.can_see(mons)) { mprf("Something unseen %s", open_str.c_str()); - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); } else if (!you_are_delayed()) { @@ -3021,6 +2990,7 @@ static bool _may_cutdown(monster* mons, monster* targ) // (but don't try to attack briars unless their damage will be insignificant) return mons_is_firewood(*targ) && (targ->type != MONS_BRIAR_PATCH + || (targ->friendly() && !mons_aligned(mons, targ)) || mons->type == MONS_THORN_HUNTER || mons->armour_class() * mons->hit_points >= 400); } @@ -3125,60 +3095,6 @@ static void _jelly_grows(monster& mons) _jelly_divide(mons); } -/** - * Possibly place mold & ballistomycetes in ballistomycete spores' wake. - * - * @param mons The ballistomycete spore in question. - * @param position Its last location. (Where to place the ballistomycete.) - */ -static void _ballisto_on_move(monster& mons, const coord_def& position) -{ - if (mons.type != MONS_BALLISTOMYCETE_SPORE - || mons.is_summoned()) - { - return; - } - - // place mold under the spore's current tile, if there isn't any now. - const dungeon_feature_type current_ftype = env.grid(mons.pos()); - if (current_ftype == DNGN_FLOOR) - env.pgrid(mons.pos()) |= FPROP_MOLD; - - if (mons.spore_cooldown > 0) - { - mons.spore_cooldown--; - return; - } - - if (!one_chance_in(4)) - return; - - // Try to make a ballistomycete. - beh_type attitude = attitude_creation_behavior(mons.attitude); - // Make Fedhas ballistos neutral, so as not to inflict extra piety loss. - if (mons_is_god_gift(mons, GOD_FEDHAS)) - attitude = BEH_GOOD_NEUTRAL; - - monster *plant = create_monster(mgen_data(MONS_BALLISTOMYCETE, attitude, - position, MHITNOT, - MG_FORCE_PLACE)); - - if (!plant) - return; - - // Don't leave mold on squares we place ballistos on - remove_mold(position); - - if (you.can_see(*plant)) - { - mprf("%s grows in the wake of %s.", - plant->name(DESC_A).c_str(), mons.name(DESC_THE).c_str()); - } - - // reset the cooldown. - mons.spore_cooldown = 40; -} - bool monster_swaps_places(monster* mon, const coord_def& delta, bool takes_time, bool apply_effects) { @@ -3283,7 +3199,7 @@ static bool _do_move_monster(monster& mons, const coord_def& delta) if (!you.can_see(mons)) { mpr("The door bursts into shrapnel!"); - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); } else simple_monster_message(mons, " bursts through the door, destroying it!"); @@ -3308,7 +3224,7 @@ static bool _do_move_monster(monster& mons, const coord_def& delta) if (!you.can_see(mons)) { mpr("The door mysteriously vanishes."); - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); } else simple_monster_message(mons, " eats the door!"); @@ -3341,12 +3257,9 @@ static bool _do_move_monster(monster& mons, const coord_def& delta) mons.seen_context = SC_NONSWIMMER_SURFACES_FROM_DEEP; } - coord_def old_pos = mons.pos(); - mons.move_to_pos(f, false); mons.check_clinging(true); - _ballisto_on_move(mons, old_pos); // Let go of all constrictees; only stop *being* constricted if we are now // too far away (done in move_to_pos above). @@ -3547,9 +3460,12 @@ static bool _monster_move(monster* mons) { ASSERT(mons->mslot_item(MSLOT_WAND)); item_def &wand = *mons->mslot_item(MSLOT_WAND); + setup_mons_cast(mons, beem, SPELL_DIG, true); beem.target = mons->pos() + mmov; - _setup_wand_beam(beem, *mons, wand); - _mons_fire_wand(*mons, wand, beem, you.can_see(*mons), false); + _mons_fire_wand(*mons, wand, beem, you.can_see(*mons)); + + //_setup_wand_beam(beem, *mons, wand); + //_mons_fire_wand(*mons, wand, beem, you.can_see(*mons), false); } else simple_monster_message(*mons, " falters for a moment."); diff --git a/crawl-ref/source/mon-act.h b/crawl-ref/source/mon-act.h index 88b9d0568cc6..e4a36ee71631 100644 --- a/crawl-ref/source/mon-act.h +++ b/crawl-ref/source/mon-act.h @@ -17,6 +17,7 @@ class MonsterActionQueueCompare }; void mons_set_just_seen(monster *mon); +void mons_reset_just_seen(); bool mon_can_move_to_pos(const monster* mons, const coord_def& delta, bool just_check = false); diff --git a/crawl-ref/source/mon-behv.cc b/crawl-ref/source/mon-behv.cc index 8c92ad07b6e2..8f15108e0952 100644 --- a/crawl-ref/source/mon-behv.cc +++ b/crawl-ref/source/mon-behv.cc @@ -1055,13 +1055,8 @@ void behaviour_event(monster* mon, mon_event_type event, const actor *src, #endif // Assumes disturbed by noise... if (mon->asleep()) - { mon->behaviour = BEH_WANDER; - if (you.can_see(*mon)) - remove_auto_exclude(mon, true); - } - // A bit of code to make Projected Noise actually do // something again. Basically, dumb monsters and // monsters who aren't otherwise occupied will at @@ -1105,9 +1100,6 @@ void behaviour_event(monster* mon, mon_event_type event, const actor *src, mon->foe = src_idx; - if (mon->asleep() && you.can_see(*mon)) - remove_auto_exclude(mon, true); - // If the monster can't reach its target and can't attack it // either, retreat. try_pathfind(mon); @@ -1186,9 +1178,6 @@ void behaviour_event(monster* mon, mon_event_type event, const actor *src, break; } - if (mon->asleep() && you.can_see(*mon)) - remove_auto_exclude(mon, true); - // Will alert monster to and turn them // against them, unless they have a current foe. // It won't turn friends hostile either. diff --git a/crawl-ref/source/mon-cast.cc b/crawl-ref/source/mon-cast.cc index 6a06736b7c80..3001b6dfce7a 100644 --- a/crawl-ref/source/mon-cast.cc +++ b/crawl-ref/source/mon-cast.cc @@ -14,6 +14,7 @@ #include "act-iter.h" #include "areas.h" +#include "attack.h" #include "bloodspatter.h" #include "branch.h" #include "cleansing-flame-source-type.h" @@ -867,7 +868,7 @@ void init_mons_spells() #if TAG_MAJOR_VERSION == 34 spell == SPELL_MELEE || #endif - setup_mons_cast(&fake_mon, pbolt, spell, true)) + setup_mons_cast(&fake_mon, pbolt, spell, false, true)) { _valid_mon_spells[i] = true; } @@ -1048,16 +1049,13 @@ static bool _set_hex_target(monster* caster, bolt& pbolt) * What value do monsters multiply their hd with to get spellpower, for the * given spell? * - * XXX: everything except SPELL_CONFUSION_GAZE could be trivially exported to - * data. + * XXX: everything could be trivially exported to data. * * @param spell The spell in question. - * @param random Whether to randomize powers for weird spells. - * If false, the average value is used. * @return A multiplier to HD for spellpower. * Value may exceed 200. */ -static int _mons_power_hd_factor(spell_type spell, bool random) +static int _mons_power_hd_factor(spell_type spell) { const mons_spell_logic* logic = map_find(spell_to_logic, spell); if (logic && logic->power_hd_factor) @@ -1066,9 +1064,7 @@ static int _mons_power_hd_factor(spell_type spell, bool random) switch (spell) { case SPELL_CONFUSION_GAZE: - if (random) - return 5 * (2 + random2(3)) * ENCH_POW_FACTOR; - return 5 * (2 + 1) * ENCH_POW_FACTOR; + return 8 * ENCH_POW_FACTOR; case SPELL_CAUSE_FEAR: return 18 * ENCH_POW_FACTOR; @@ -1077,6 +1073,7 @@ static int _mons_power_hd_factor(spell_type spell, bool random) return 10 * ENCH_POW_FACTOR; case SPELL_SIREN_SONG: + case SPELL_AVATAR_SONG: return 9 * ENCH_POW_FACTOR; case SPELL_MASS_CONFUSION: @@ -1115,13 +1112,11 @@ static int _mons_power_hd_factor(spell_type spell, bool random) * * @param spell The spell in question. * @param hd The spell_hd of the given monster. - * @param random Whether to randomize powers for weird spells. - * If false, the average value is used. * @return A spellpower value for the spell. */ -int mons_power_for_hd(spell_type spell, int hd, bool random) +int mons_power_for_hd(spell_type spell, int hd) { - const int power = hd * _mons_power_hd_factor(spell, random); + const int power = hd * _mons_power_hd_factor(spell); if (spell == SPELL_PAIN) return max(50 * ENCH_POW_FACTOR, power); return power; @@ -1733,6 +1728,7 @@ bolt mons_spell_beam(const monster* mons, spell_type spell_cast, int power, // Set up bolt structure for monster spell casting. bool setup_mons_cast(const monster* mons, bolt &pbolt, spell_type spell_cast, + bool evoke, bool check_validity) { // always set these -- used by things other than fire_beam() @@ -1881,7 +1877,9 @@ bool setup_mons_cast(const monster* mons, bolt &pbolt, spell_type spell_cast, case SPELL_SEAL_DOORS: case SPELL_BERSERK_OTHER: case SPELL_SPELLFORGED_SERVITOR: +#if TAG_MAJOR_VERSION == 34 case SPELL_THROW: +#endif case SPELL_THROW_ALLY: case SPELL_CORRUPTING_PULSE: case SPELL_SIREN_SONG: @@ -1915,6 +1913,7 @@ bool setup_mons_cast(const monster* mons, bolt &pbolt, spell_type spell_cast, case SPELL_GREATER_SERVANT_MAKHLEB: case SPELL_BIND_SOULS: case SPELL_DREAM_DUST: + case SPELL_SPORULATE: pbolt.range = 0; pbolt.glyph = 0; return true; @@ -1937,7 +1936,8 @@ bool setup_mons_cast(const monster* mons, bolt &pbolt, spell_type spell_cast, } } - const int power = mons_spellpower(*mons, spell_cast); + const int power = evoke ? 30 + mons->get_hit_dice() + : mons_spellpower(*mons, spell_cast); bolt theBeam = mons_spell_beam(mons, spell_cast, power); @@ -3191,8 +3191,8 @@ monster* cast_phantom_mirror(monster* mons, monster* targ, int hp_perc, int summ mirror->mark_summoned(5, true, summ_type); mirror->add_ench(ENCH_PHANTOM_MIRROR); mirror->summoner = mons->mid; - mirror->hit_points = mirror->hit_points * 100 / hp_perc; - mirror->max_hit_points = mirror->max_hit_points * 100 / hp_perc; + mirror->hit_points = max(mirror->hit_points * hp_perc / 100, 1); + mirror->max_hit_points = max(mirror->max_hit_points * hp_perc / 100, 1); // Sometimes swap the two monsters, so as to disguise the original and the // copy. @@ -5720,9 +5720,10 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, false); return; } + bool evoke {slot_flags & MON_SPELL_EVOKE}; // Always do setup. It might be done already, but it doesn't hurt // to do it again (cheap). - setup_mons_cast(mons, pbolt, spell_cast); + setup_mons_cast(mons, pbolt, spell_cast, evoke); // single calculation permissible {dlb} const spell_flags flags = get_spell_flags(spell_cast); @@ -5770,7 +5771,8 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, } const god_type god = _find_god(*mons, slot_flags); - const int splpow = mons_spellpower(*mons, spell_cast); + const int splpow = evoke ? 30 + mons->get_hit_dice() + : mons_spellpower(*mons, spell_cast); switch (spell_cast) { @@ -5779,20 +5781,20 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, case SPELL_WATERSTRIKE: { - if (you.can_see(*foe)) - { - if (foe->airborne()) - mprf("The water rises up and strikes %s!", foe->name(DESC_THE).c_str()); - else - mprf("The water swirls and strikes %s!", foe->name(DESC_THE).c_str()); - } - pbolt.flavour = BEAM_WATER; int damage_taken = waterstrike_damage(*mons).roll(); damage_taken = foe->beam_resists(pbolt, damage_taken, false); damage_taken = foe->apply_ac(damage_taken); + if (you.can_see(*foe)) + { + mprf("The water %s and strikes %s%s", + foe->airborne() ? "rises up" : "swirls", + foe->name(DESC_THE).c_str(), + attack_strength_punctuation(damage_taken).c_str()); + } + foe->hurt(mons, damage_taken, BEAM_MISSILE, KILLED_BY_BEAM, "", "by the raging water"); return; @@ -5800,28 +5802,22 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, case SPELL_AIRSTRIKE: { - // Damage averages 14 for 5HD, 18 for 10HD, 28 for 20HD, +50% if flying. - if (foe->is_player()) - { - if (you.airborne()) - mpr("The air twists around and violently strikes you in flight!"); - else - mpr("The air twists around and strikes you!"); - } - else - { - simple_monster_message(*foe->as_monster(), - " is struck by the twisting air!"); - } - pbolt.flavour = BEAM_AIR; - int damage_taken = 10 + 2 * mons->get_hit_dice(); + int damage_taken = 8 + random2(2 + div_rand_round(splpow, 7)); damage_taken = foe->beam_resists(pbolt, damage_taken, false); - // Previous method of damage calculation (in line with player - // airstrike) had absurd variance. - damage_taken = foe->apply_ac(random2avg(damage_taken, 3)); + damage_taken = foe->apply_ac(damage_taken); + + if (you.can_see(*foe)) + { + mprf("The air twists around and %sstrikes %s%s%s", + foe->airborne() ? "violently " : "", + foe->name(DESC_THE).c_str(), + foe->airborne() ? " in flight" : "", + attack_strength_punctuation(damage_taken).c_str()); + } + foe->hurt(mons, damage_taken, BEAM_MISSILE, KILLED_BY_BEAM, "", "by the air"); return; @@ -6816,7 +6812,7 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, case SPELL_CLEANSING_FLAME: simple_monster_message(*mons, " channels a blast of cleansing flame!"); cleansing_flame(5 + (5 * mons->spell_hd(spell_cast) / 12), - CLEANSING_FLAME_SPELL, mons->pos(), mons); + cleansing_flame_source::spell, mons->pos(), mons); return; case SPELL_GRAVITAS: @@ -6879,6 +6875,22 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, _mons_upheaval(*mons, *foe); return; + case SPELL_SPORULATE: + { + mgen_data mgen (MONS_BALLISTOMYCETE_SPORE, + mons->friendly() ? BEH_FRIENDLY : BEH_HOSTILE, mons->pos(), + mons->foe); + mgen.set_summoned(mons, 0, SPELL_SPORULATE); + // Add 1HD to the spore for each additional HD the spawner has. + mgen.hd = mons_class_hit_dice(MONS_BALLISTOMYCETE_SPORE) + + max(0, mons->spell_hd() - mons_class_hit_dice(mons->type)); + + if (monster* const spore = create_monster(mgen)) + spore->add_ench(ENCH_SHORT_LIVED); + + return; + } + } if (spell_is_direct_explosion(spell_cast)) @@ -8091,7 +8103,8 @@ static bool _ms_waste_of_time(monster* mon, mon_spell_slot slot) bolt tracer; setup_cleansing_flame_beam(tracer, 5 + (7 * mon->spell_hd(monspell)) / 12, - CLEANSING_FLAME_SPELL, mon->pos(), mon); + cleansing_flame_source::spell, + mon->pos(), mon); fire_tracer(mon, tracer, true); return !mons_should_fire(tracer); } diff --git a/crawl-ref/source/mon-cast.h b/crawl-ref/source/mon-cast.h index 2368ddb50b90..660b6d08f6a5 100644 --- a/crawl-ref/source/mon-cast.h +++ b/crawl-ref/source/mon-cast.h @@ -27,7 +27,7 @@ void flay(const monster &caster, actor &defender, int damage); bool handle_mon_spell(monster* mons); static const int ENCH_POW_FACTOR = 3; -int mons_power_for_hd(spell_type spell, int hd, bool random = true); +int mons_power_for_hd(spell_type spell, int hd); int mons_spellpower(const monster &mons, spell_type spell); int mons_spell_range_for_hd(spell_type spell, int hd); bolt mons_spell_beam(const monster* mons, spell_type spell_cast, int power, @@ -37,6 +37,7 @@ void mons_cast(monster* mons, bolt pbolt, spell_type spell_cast, void mons_cast_noise(monster* mons, const bolt &pbolt, spell_type spell_cast, mon_spell_slot_flags slot_flags); bool setup_mons_cast(const monster* mons, bolt &pbolt, spell_type spell_cast, + bool evoke = false, bool check_validity = false); void mons_cast_haunt(monster* mons); diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h index 2882a1155d9d..986ae2671cf0 100644 --- a/crawl-ref/source/mon-data.h +++ b/crawl-ref/source/mon-data.h @@ -278,6 +278,7 @@ static monsterentry mondata[] = AXED_MON(MONS_DRACONIAN_ZEALOT) AXED_MON(MONS_HILL_GIANT) AXED_MON(MONS_BULTUNGIN) + AXED_MON(MONS_HYPERACTIVE_BALLISTOMYCETE) #endif // Used for genus monsters (which are used for grouping monsters by how they @@ -1781,7 +1782,7 @@ DUMMY(MONS_GIANT_LIZARD, 'l', LIGHTGREY, "giant lizard", TILEP_MONS_LEOPARD_GECK 10, MONS_HUMAN, MONS_HUMAN, MH_NATURAL, 20, { {AT_HIT, AF_PLAIN, 10}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, 5, 275, - 2, 12, MST_NO_SPELLS, CE_CLEAN, S_SHOUT, + 4, 12, MST_NO_SPELLS, CE_CLEAN, S_SHOUT, I_HUMAN, HT_LAND, 10, DEFAULT_ENERGY, MONUSE_STARTING_EQUIPMENT, SIZE_MEDIUM, MON_SHAPE_HUMANOID, {TILEP_MONS_SLAVE}, TILE_ERROR @@ -3974,29 +3975,15 @@ DUMMY(MONS_MERGED_SLIME_CREATURE, 'J', LIGHTGREEN, "merged slime creature", { MONS_BALLISTOMYCETE, 'P', MAGENTA, "ballistomycete", - M_NOT_DANGEROUS | M_STATIONARY, + M_STATIONARY, MR_RES_POISON, 10, MONS_FUNGUS, MONS_BALLISTOMYCETE, MH_PLANT, MAG_IMMUNE, { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, 4, 260, - 1, 0, MST_NO_SPELLS, CE_NOCORPSE, S_SILENT, - I_BRAINLESS, HT_LAND, 0, DEFAULT_ENERGY, - MONUSE_NOTHING, SIZE_TINY, MON_SHAPE_FUNGUS, - {TILEP_MONS_BALLISTOMYCETE_INACTIVE}, TILE_ERROR -}, - -{ - MONS_HYPERACTIVE_BALLISTOMYCETE, 'P', LIGHTRED, "hyperactive ballistomycete", - M_NO_EXP_GAIN | M_STATIONARY | M_NO_POLY_TO, - MR_RES_POISON, - 10, MONS_FUNGUS, MONS_BALLISTOMYCETE, MH_PLANT, MAG_IMMUNE, - { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, - // expected explosion damage: 25, expected HP: 60 - 6, 600, - 1, 0, MST_NO_SPELLS, CE_NOCORPSE, S_SILENT, - I_BRAINLESS, HT_LAND, 0, DEFAULT_ENERGY, + 1, 0, MST_BALLISTOMYCETE, CE_NOCORPSE, S_SILENT, + I_BRAINLESS, HT_LAND, 10, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_TINY, MON_SHAPE_FUNGUS, - {TILEP_MONS_HYPERACTIVE_BALLISTOMYCETE}, TILE_ERROR + {TILEP_MONS_BALLISTOMYCETE}, TILE_ERROR }, { @@ -6202,7 +6189,7 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) // "c"entaurs. { MONS_NESSOS, 'c', MAGENTA, "Nessos", - M_UNIQUE | M_WARM_BLOOD | M_ARCHER | M_SPEAKS | M_MALE, + M_UNIQUE | M_WARM_BLOOD | M_ARCHER | M_SPEAKS | M_GENDER_NEUTRAL, MR_NO_FLAGS, 18, MONS_CENTAUR, MONS_CENTAUR, MH_NATURAL, 20, { {AT_HIT, AF_PLAIN, 16}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -6288,7 +6275,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) // "D"ragons and hydras. { MONS_XTAHUA, 'D', RED, "Xtahua", - M_UNIQUE | M_SEE_INVIS | M_WARM_BLOOD | M_SPEAKS | M_FEMALE | M_CRASH_DOORS | M_FLIES, + M_UNIQUE | M_SEE_INVIS | M_WARM_BLOOD | M_SPEAKS | M_GENDER_NEUTRAL + | M_CRASH_DOORS | M_FLIES, MR_RES_POISON | mrd(MR_RES_FIRE, 2) | MR_VUL_COLD, 18, MONS_DRAGON, MONS_FIRE_DRAGON, MH_NATURAL, 180, { {AT_BITE, AF_PLAIN, 35}, {AT_CLAW, AF_PLAIN, 17}, @@ -6412,7 +6400,7 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_FANNAR, 'e', LIGHTBLUE, "Fannar", - M_UNIQUE | M_WARM_BLOOD | M_SPEAKS | M_MALE, + M_UNIQUE | M_WARM_BLOOD | M_SPEAKS | M_GENDER_NEUTRAL, MR_NO_FLAGS, 16, MONS_ELF, MONS_ELF, MH_NATURAL, 80, { {AT_HIT, AF_PLAIN, 8}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -6453,7 +6441,7 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_ROBIN, 'g', LIGHTCYAN, "Robin", - M_UNIQUE | M_WARM_BLOOD | M_SPEAKS | M_FEMALE, + M_UNIQUE | M_WARM_BLOOD | M_SPEAKS | M_GENDER_NEUTRAL, MR_NO_FLAGS, 10, MONS_GOBLIN, MONS_HOBGOBLIN, MH_NATURAL, 10, { {AT_HIT, AF_PLAIN, 5}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -6589,7 +6577,7 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_DISSOLUTION, 'J', LIGHTMAGENTA, "Dissolution", M_UNIQUE | M_SEE_INVIS | M_UNBLINDABLE | M_ACID_SPLASH | M_BURROWS - | M_SPEAKS | M_FAST_REGEN | M_MALE | M_EAT_DOORS, + | M_SPEAKS | M_FAST_REGEN | M_GENDER_NEUTRAL | M_EAT_DOORS, MR_RES_POISON | mrd(MR_RES_ACID, 3), 60, MONS_JELLY, MONS_JELLY, MH_NATURAL, 120, { {AT_HIT, AF_ACID, 50}, {AT_HIT, AF_ACID, 30}, AT_NO_ATK, AT_NO_ATK }, @@ -6757,7 +6745,7 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_SAINT_ROKA, 'o', LIGHTBLUE, "Saint Roka", - M_UNIQUE | M_FIGHTER | M_WARM_BLOOD | M_SPEAKS | M_MALE, + M_UNIQUE | M_FIGHTER | M_WARM_BLOOD | M_SPEAKS | M_GENDER_NEUTRAL, MR_NO_FLAGS, 15, MONS_ORC, MONS_ORC, MH_NATURAL | MH_EVIL, 80, { {AT_HIT, AF_PLAIN, 35}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -7226,7 +7214,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) // unique major demons ('&') { MONS_MNOLEG, '&', LIGHTGREEN, "Mnoleg", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_TALL_TILE + | M_GENDER_NEUTRAL, MR_RES_ELEC | MR_RES_POISON | MR_RES_FIRE, 15, MONS_PANDEMONIUM_LORD, MONS_MNOLEG, MH_DEMONIC, MAG_IMMUNE, { {AT_CLAW, AF_PLAIN, 40}, {AT_TENTACLE_SLAP, AF_MUTATE, 35}, @@ -7240,7 +7229,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_LOM_LOBON, '&', LIGHTBLUE, "Lom Lobon", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_FLIES | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_FLIES | M_TALL_TILE + | M_GENDER_NEUTRAL, MR_RES_POISON | MR_RES_FIRE | mrd(MR_RES_COLD | MR_RES_ELEC, 3), 15, MONS_PANDEMONIUM_LORD, MONS_LOM_LOBON, MH_DEMONIC, MAG_IMMUNE, { {AT_HIT, AF_ANTIMAGIC, 40}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -7253,7 +7243,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_CEREBOV, '&', RED, "Cerebov", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_TALL_TILE + | M_GENDER_NEUTRAL, MR_RES_POISON | MR_RES_DAMNATION | mrd(MR_RES_FIRE, 3), 15, MONS_PANDEMONIUM_LORD, MONS_CEREBOV, MH_DEMONIC, MAG_IMMUNE, { {AT_HIT, AF_PLAIN, 60}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -7266,7 +7257,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_GLOORX_VLOQ, '&', LIGHTGREY, "Gloorx Vloq", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_FLIES | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_FLIES | M_TALL_TILE + | M_GENDER_NEUTRAL, MR_RES_POISON | MR_RES_COLD | MR_RES_ELEC, 15, MONS_PANDEMONIUM_LORD, MONS_GLOORX_VLOQ, MH_DEMONIC, MAG_IMMUNE, { {AT_HIT, AF_PLAIN, 45}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -7279,7 +7271,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_GERYON, '&', GREEN, "Geryon", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_MALE | M_FLIES | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_MALE | M_FLIES + | M_TALL_TILE, MR_NO_FLAGS, 15, MONS_HELL_LORD, MONS_GERYON, MH_DEMONIC, 120, { {AT_TAIL_SLAP, AF_REACH, 35}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, @@ -7293,7 +7286,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_DISPATER, '&', MAGENTA, "Dispater", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_TALL_TILE + | M_GENDER_NEUTRAL, mrd(MR_RES_ELEC | MR_RES_FIRE, 3) | MR_RES_POISON | MR_RES_DAMNATION | MR_RES_COLD, 15, MONS_HELL_LORD, MONS_DISPATER, MH_DEMONIC, MAG_IMMUNE, @@ -7307,7 +7301,8 @@ DUMMY(MONS_HELL_LORD, '&', COLOUR_UNDEF, "hell lord", TILEP_MONS_PROGRAM_BUG) { MONS_ASMODEUS, '&', LIGHTRED, "Asmodeus", - M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_MALE | M_FLIES | M_TALL_TILE, + M_UNIQUE | M_FIGHTER | M_SEE_INVIS | M_SPEAKS | M_MALE | M_FLIES + | M_TALL_TILE, MR_RES_ELEC | MR_RES_POISON | MR_RES_DAMNATION | mrd(MR_RES_FIRE, 3), 25, MONS_HELL_LORD, MONS_ASMODEUS, MH_DEMONIC, MAG_IMMUNE, { {AT_HIT, AF_PLAIN, 50}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, diff --git a/crawl-ref/source/mon-death.cc b/crawl-ref/source/mon-death.cc index e9f39d57a581..8d6a0e7004a1 100644 --- a/crawl-ref/source/mon-death.cc +++ b/crawl-ref/source/mon-death.cc @@ -529,7 +529,8 @@ static void _maybe_drop_monster_hide(const item_def &corpse, bool silent) item_def* place_monster_corpse(const monster& mons, bool silent, bool force) { if (mons.is_summoned() - || mons.flags & (MF_BANISHED | MF_HARD_RESET)) + || mons.flags & (MF_BANISHED | MF_HARD_RESET) + || mons.props.exists("pikel_band")) { return nullptr; } @@ -541,14 +542,9 @@ item_def* place_monster_corpse(const monster& mons, bool silent, bool force) && mons_gives_xp(mons, you) && !force; - const bool no_coinflip = - mons.props.exists("always_corpse") - || force - || goldify - || mons_class_flag(mons.type, M_ALWAYS_CORPSE) - || mons_is_demonspawn(mons.type) - && mons_class_flag(draco_or_demonspawn_subspecies(mons), - M_ALWAYS_CORPSE); + const bool no_coinflip = mons.props.exists("always_corpse") + || force + || goldify; // 50/50 chance of getting a corpse, usually. if (!no_coinflip && coinflip()) @@ -1061,128 +1057,6 @@ static void _search_dungeon(const coord_def & start, } } -static bool _ballisto_at(const coord_def & target) -{ - monster* mons = monster_at(target); - return mons && mons->type == MONS_BALLISTOMYCETE - && mons->alive(); -} - -static bool _mold_connected(const coord_def & target) -{ - return is_moldy(target) || _ballisto_at(target); -} - -// If 'monster' is a ballistomycete or spore, activate some number of -// ballistomycetes on the level. -static void _activate_ballistomycetes(monster* mons, const coord_def& origin, - bool player_kill) -{ - if (!mons || mons->is_summoned() - || mons->mons_species() != MONS_BALLISTOMYCETE - && mons->type != MONS_BALLISTOMYCETE_SPORE) - { - return; - } - - // If a spore or inactive ballisto died we will only activate one - // other ballisto. If it was an active ballisto we will distribute - // its count to others on the level. - int activation_count = 1; - if (mons->type == MONS_BALLISTOMYCETE) - activation_count += mons->ballisto_activity; - if (mons->type == MONS_HYPERACTIVE_BALLISTOMYCETE) - activation_count = 0; - - int non_activable_count = 0; - int ballisto_count = 0; - - for (monster_iterator mi; mi; ++mi) - { - if (mi->mindex() != mons->mindex() && mi->alive()) - { - if (mi->type == MONS_BALLISTOMYCETE) - ballisto_count++; - else if (mi->type == MONS_BALLISTOMYCETE_SPORE - || mi->type == MONS_HYPERACTIVE_BALLISTOMYCETE) - { - non_activable_count++; - } - } - } - - bool exhaustive = true; - bool (*valid_target)(const coord_def &) = _ballisto_at; - bool (*connecting_square) (const coord_def &) = _mold_connected; - - set visited; - vector::iterator > candidates; - - _search_dungeon(origin, valid_target, connecting_square, visited, - candidates, exhaustive); - - if (candidates.empty()) - { - if (non_activable_count == 0 - && ballisto_count == 0 - && mons->attitude == ATT_HOSTILE) - { - if (player_kill) - mpr("The fungal colony is destroyed."); - - // Get rid of the mold, so it'll be more useful when new fungi - // spawn. - for (rectangle_iterator ri(1); ri; ++ri) - remove_mold(*ri); - } - - return; - } - - // A (very) soft cap on colony growth, no activations if there are - // already a lot of ballistos on level. - if (candidates.size() > 25) - return; - - shuffle_array(candidates); - - int index = 0; - - for (int i = 0; i < activation_count; ++i) - { - index = i % candidates.size(); - - monster* spawner = monster_at(candidates[index]->pos); - - // This may be the players position, in which case we don't - // have to mess with spore production on anything - if (spawner) - { - spawner->ballisto_activity++; - - // Change color and start the spore production timer if we - // are moving from 0 to 1. - if (spawner->ballisto_activity == 1) - { - spawner->colour = LIGHTMAGENTA; - // Reset the spore production timer. - spawner->del_ench(ENCH_SPORE_PRODUCTION, false); - spawner->add_ench(ENCH_SPORE_PRODUCTION); - } - } - - const position_node* thread = &(*candidates[index]); - while (thread) - { - if (!one_chance_in(3)) - env.pgrid(thread->pos) |= FPROP_GLOW_MOLD; - - thread = thread->last; - } - env.level_state |= LSTATE_GLOW_MOLD; - } -} - static void _setup_base_explosion(bolt & beam, const monster& origin) { beam.is_tracer = false; @@ -1210,10 +1084,10 @@ void setup_spore_explosion(bolt & beam, const monster& origin) { _setup_base_explosion(beam, origin); beam.flavour = BEAM_SPORE; - beam.damage = dice_def(3, 15); + beam.damage = dice_def(3, 5 + origin.get_hit_dice()); beam.name = "explosion of spores"; beam.colour = LIGHTGREY; - beam.ex_size = 2; + beam.ex_size = 1; } static void _setup_lightning_explosion(bolt & beam, const monster& origin) @@ -1408,7 +1282,6 @@ static bool _explode_monster(monster* mons, killer_type killer, else beam.explode(); - _activate_ballistomycetes(mons, beam.target, YOU_KILL(beam.killer())); // Monster died in explosion, so don't re-attach it to the grid. return true; } @@ -1802,8 +1675,8 @@ static void _special_corpse_messaging(monster &mons) if (!(mons.flags & MF_KNOWN_SHIFTER)) { const string message = "'s shape twists and changes as " - + mons.pronoun(PRONOUN_SUBJECTIVE) - + " dies."; + + mons.pronoun(PRONOUN_SUBJECTIVE) + " " + + conjugate_verb("die", mons.pronoun_plurality()) + "."; simple_monster_message(mons, message.c_str()); } @@ -1819,8 +1692,9 @@ static void _special_corpse_messaging(monster &mons) const string message = " returns to " + mons.pronoun(PRONOUN_POSSESSIVE) + " original shape as " + - mons.pronoun(PRONOUN_SUBJECTIVE) + - " dies."; + mons.pronoun(PRONOUN_SUBJECTIVE) + " " + + conjugate_verb("die", mons.pronoun_plurality()) + + "."; simple_monster_message(mons, message.c_str()); } @@ -1914,10 +1788,6 @@ item_def* monster_die(monster& mons, killer_type killer, // Uniques leave notes and milestones, so this information is already leaked. remove_unique_annotation(&mons); - // Clear auto exclusion now the monster is killed - if we know about it. - if (you.see_cell(mons.pos()) || wizard || mons_is_unique(mons.type)) - remove_auto_exclude(&mons); - int duration = 0; const bool summoned = mons.is_summoned(&duration); const int monster_killed = mons.mindex(); @@ -2231,8 +2101,12 @@ item_def* monster_die(monster& mons, killer_type killer, && mons.evil()) && !mons_is_object(mons.type) && !player_under_penance() - && (you_worship(GOD_PAKELLAS) - || random2(you.piety) >= piety_breakpoint(0))) + && (random2(you.piety) >= piety_breakpoint(0) +#if TAG_MAJOR_VERSION == 34 + || you_worship(GOD_PAKELLAS) +#endif + ) + ) { int hp_heal = 0, mp_heal = 0; @@ -2249,16 +2123,11 @@ item_def* monster_die(monster& mons, killer_type killer, if (have_passive(passive_t::mp_on_kill)) { - switch (you.religion) - { - case GOD_PAKELLAS: + mp_heal = 1 + random2(mons.get_experience_level() / 2); +#if TAG_MAJOR_VERSION == 34 + if (you.religion == GOD_PAKELLAS) mp_heal = random2(2 + mons.get_experience_level() / 6); - break; - case GOD_VEHUMET: - default: - mp_heal = 1 + random2(mons.get_experience_level() / 2); - break; - } +#endif } if (hp_heal && you.hp < you.hp_max @@ -2277,6 +2146,7 @@ item_def* monster_die(monster& mons, killer_type killer, mp_heal -= tmp; } +#if TAG_MAJOR_VERSION == 34 // perhaps this should go to its own function if (mp_heal && have_passive(passive_t::bottle_mp) @@ -2306,6 +2176,7 @@ item_def* monster_die(monster& mons, killer_type killer, } } } +#endif } if (gives_player_xp && you_worship(GOD_RU) && you.piety < 200 @@ -2345,21 +2216,6 @@ item_def* monster_die(monster& mons, killer_type killer, _fire_kill_conducts(mons, killer, killer_index, gives_player_xp); - // Kill conducts do not assess piety loss for friends - // killed by other monsters. - if (mons.friendly()) - { - const bool sentient = mons_class_intel(mons.type) >= I_HUMAN; - // plant HD aren't very meaningful. (fedhas hack) - const int severity = mons.holiness() & MH_PLANT ? - 1 : - 1 + (mons.get_experience_level() / 4); - - did_god_conduct(sentient ? DID_SOULED_FRIEND_DIED - : DID_FRIEND_DIED, - severity, true, &mons); - } - // Trying to prevent summoning abuse here, so we're trying to // prevent summoned creatures from being done_good kills. Only // affects creatures which were friendly when summoned. @@ -2619,8 +2475,11 @@ item_def* monster_die(monster& mons, killer_type killer, } else if (!mons.is_summoned() && mummy_curse_power(mons.type) > 0) { + // TODO: set attacker better? (Player attacker is handled by checking + // killer when running the fineff.) mummy_death_curse_fineff::schedule( - actor_by_mid(killer_index), + invalid_monster_index(killer_index) + ? nullptr : &menv[killer_index], mons.name(DESC_A), killer, mummy_curse_power(mons.type)); @@ -2635,12 +2494,6 @@ item_def* monster_die(monster& mons, killer_type killer, _make_derived_undead(&mons, !death_message, false); } - if (mons.mons_species() == MONS_BALLISTOMYCETE) - { - _activate_ballistomycetes(&mons, mons.pos(), - YOU_KILL(killer) || pet_kill); - } - if (!wizard && !submerged && !was_banished) { _monster_die_cloud(&mons, !fake_abjure && !timeout && !mons_reset, @@ -2875,6 +2728,8 @@ void monster_cleanup(monster* mons) // cleaned up first, we wouldn't get a message anyway. mons->stop_constricting_all(false, true); + mons->clear_far_engulf(); + if (mons_is_tentacle_head(mons_base_type(*mons))) destroy_tentacles(mons); diff --git a/crawl-ref/source/mon-ench.cc b/crawl-ref/source/mon-ench.cc index 6f312e94198f..afedd0a8b7e4 100644 --- a/crawl-ref/source/mon-ench.cc +++ b/crawl-ref/source/mon-ench.cc @@ -632,7 +632,7 @@ void monster::remove_enchantment_effect(const mon_enchant &me, bool quiet) if (you.can_see(*this)) { // and fire activity interrupts - interrupt_activity(AI_SEE_MONSTER, + interrupt_activity(activity_interrupt::see_monster, activity_interrupt_data(this, SC_UNCHARM)); } @@ -752,7 +752,7 @@ void monster::remove_enchantment_effect(const mon_enchant &me, bool quiet) else if (you.see_cell(pos()) && feat_is_watery(grd(pos()))) { mpr("Something invisible bursts forth from the water."); - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); } break; @@ -1541,69 +1541,6 @@ void monster::apply_enchantment(const mon_enchant &me) } break; - case ENCH_SPORE_PRODUCTION: - // Reduce the timer, if that means we lose the enchantment then - // spawn a spore and re-add the enchantment. - if (decay_enchantment(en)) - { - bool re_add = true; - - for (fair_adjacent_iterator ai(pos()); ai; ++ai) - { - if (mons_class_can_pass(MONS_BALLISTOMYCETE_SPORE, grd(*ai)) - && !actor_at(*ai)) - { - beh_type plant_attitude = SAME_ATTITUDE(this); - - if (monster *plant = create_monster(mgen_data(MONS_BALLISTOMYCETE_SPORE, - plant_attitude, - *ai, - MHITNOT, - MG_FORCE_PLACE))) - { - if (mons_is_god_gift(*this, GOD_FEDHAS)) - { - plant->flags |= MF_NO_REWARD; - - if (plant_attitude == BEH_FRIENDLY) - { - plant->flags |= MF_ATT_CHANGE_ATTEMPT; - - mons_make_god_gift(*plant, GOD_FEDHAS); - } - } - - plant->behaviour = BEH_WANDER; - plant->spore_cooldown = 20; - - if (you.see_cell(*ai) && you.see_cell(pos())) - mpr("A ballistomycete spawns a ballistomycete spore."); - - // Decrease the count and maybe become inactive - // again. - if (ballisto_activity) - { - ballisto_activity--; - if (ballisto_activity == 0) - { - colour = MAGENTA; - del_ench(ENCH_SPORE_PRODUCTION); - re_add = false; - } - } - - } - break; - } - } - // Re-add the enchantment (this resets the spore production - // timer). - if (re_add) - add_ench(ENCH_SPORE_PRODUCTION); - } - - break; - case ENCH_EXPLODING: { // Reduce the timer, if that means we lose the enchantment then @@ -1648,10 +1585,12 @@ void monster::apply_enchantment(const mon_enchant &me) maybe_bloodify_square(base_position); add_ench(ENCH_SEVERED); - // Severed tentacles immediately become "hostile" to everyone (or insane) + // Severed tentacles immediately become "hostile" to everyone + // (or insane) attitude = ATT_NEUTRAL; mons_att_changed(this); - behaviour_event(this, ME_ALERT); + if (!crawl_state.game_is_arena()) + behaviour_event(this, ME_ALERT); } } break; @@ -1676,7 +1615,8 @@ void monster::apply_enchantment(const mon_enchant &me) attitude = ATT_HOSTILE; mons_att_changed(this); - behaviour_event(this, ME_ALERT, &you); + if (!crawl_state.game_is_arena()) + behaviour_event(this, ME_ALERT, &you); } } break; @@ -1808,8 +1748,9 @@ void monster::apply_enchantment(const mon_enchant &me) if (res_water_drowning() <= 0) { lose_ench_duration(me, -speed_to_duration(speed)); + int dur = speed_to_duration(speed); // sequence point for randomness int dam = div_rand_round((50 + stepdown((float)me.duration, 30.0)) - * speed_to_duration(speed), + * dur, BASELINE_DELAY * 10); if (res_water_drowning() < 0) dam = dam * 3 / 2; @@ -1842,7 +1783,7 @@ void monster::apply_enchantment(const mon_enchant &me) break; case ENCH_TOXIC_RADIANCE: - toxic_radiance_effect(this, 1); + toxic_radiance_effect(this, 10); decay_enchantment(en); break; @@ -2069,7 +2010,11 @@ static const char *enchant_names[] = "control_winds", "wind_aided", #endif "summon_capped", - "toxic_radiance", "grasping_roots_source", "grasping_roots", + "toxic_radiance", +#if TAG_MAJOR_VERSION == 34 + "grasping_roots_source", +#endif + "grasping_roots", "iood_charged", "fire_vuln", "tornado_cooldown", "merfolk_avatar_song", "barbs", #if TAG_MAJOR_VERSION == 34 @@ -2292,15 +2237,13 @@ int mon_enchant::calc_duration(const monster* mons, cturn = 1200 / _mod_speed(200, mons->speed); break; case ENCH_SLOWLY_DYING: + { // This may be a little too direct but the randomization at the end // of this function is excessive for toadstools. -cao + int dur = speed_to_duration(mons->speed); // uses div_rand_round, so we need a sequence point return (2 * FRESHEST_CORPSE + random2(10)) - * speed_to_duration(mons->speed); - case ENCH_SPORE_PRODUCTION: - // This is used as a simple timer, when the enchantment runs out - // the monster will create a ballistomycete spore. - return random_range(475, 525) * 10; - + * dur; + } case ENCH_EXPLODING: return random_range(3, 7) * 10; diff --git a/crawl-ref/source/mon-flags.h b/crawl-ref/source/mon-flags.h index 2e8bc14950b0..5443f4ac5d30 100644 --- a/crawl-ref/source/mon-flags.h +++ b/crawl-ref/source/mon-flags.h @@ -113,8 +113,7 @@ enum monclass_flag_type : uint64_t /// An ancestor granted by Hepliaklqana M_ANCESTOR = BIT(35), - /// always leaves a corpse - M_ALWAYS_CORPSE = BIT(36), + //BIT(36), // was M_ALWAYS_CORPSE /// mostly doesn't try to melee M_DONT_MELEE = BIT(37), @@ -170,6 +169,9 @@ enum monclass_flag_type : uint64_t /// monster always receives a wand M_ALWAYS_WAND = BIT(56), + + /// uses they/them pronouns + M_GENDER_NEUTRAL = BIT(57), }; DEF_BITFIELD(monclass_flags_t, monclass_flag_type); diff --git a/crawl-ref/source/mon-gear.cc b/crawl-ref/source/mon-gear.cc index 356c2847f74a..c5a9dbf294e3 100644 --- a/crawl-ref/source/mon-gear.cc +++ b/crawl-ref/source/mon-gear.cc @@ -172,20 +172,7 @@ static void _give_wand(monster* mon, int level) static void _give_potion(monster* mon, int level) { - if (mons_species(mon->type) == MONS_VAMPIRE - && (one_chance_in(5) || mon->type == MONS_JORY)) - { - // This handles initialization of stack timer. - const int thing_created = - items(false, OBJ_POTIONS, POT_BLOOD, level); - - if (thing_created == NON_ITEM) - return; - - mitm[thing_created].flags = ISFLAG_KNOW_TYPE; - give_specific_item(mon, thing_created); - } - else if (mons_is_unique(mon->type) && one_chance_in(4) + if (mons_is_unique(mon->type) && one_chance_in(4) && _should_give_unique_item(mon)) { const int thing_created = items(false, OBJ_POTIONS, OBJ_RANDOM, @@ -298,6 +285,8 @@ static bool _apply_weapon_spec(const mon_weapon_spec &spec, item_def &item, */ int make_mons_weapon(monster_type type, int level, bool melee_only) { + rng::subgenerator item_rng; + static const weapon_list GOBLIN_WEAPONS = // total 10 { { WPN_DAGGER, 3 }, { WPN_CLUB, 3 }, @@ -970,7 +959,6 @@ int make_mons_weapon(monster_type type, int level, bool melee_only) { WPN_SHORTBOW, 1 }, { WPN_LONGBOW, 1 }, } } }, - { MONS_SONJA, { { { WPN_BLOWGUN, 1 } } } }, // salamanders only have secondary weapons; melee or bow, not both { MONS_SALAMANDER, { { { WPN_HALBERD, 5 }, @@ -980,10 +968,6 @@ int make_mons_weapon(monster_type type, int level, bool melee_only) { WPN_SHORTBOW, 5 }, }, { 4, 0, 4 }, } }, - { MONS_SPRIGGAN_RIDER, { - { { WPN_BLOWGUN, 1 }, - { NUM_WEAPONS, 14 }, }, - } }, { MONS_WARMONGER, { { { WPN_LONGBOW, 10 }, // total 60 { WPN_ARBALEST, 9 }, @@ -1022,14 +1006,6 @@ int make_mons_weapon(monster_type type, int level, bool melee_only) switch (type) { case MONS_KOBOLD: - // A few of the smarter kobolds have blowguns. - if (one_chance_in(10) && level > 1) - { - item.base_type = OBJ_WEAPONS; - item.sub_type = WPN_BLOWGUN; - break; - } - // intentional fallthrough case MONS_BIG_KOBOLD: if (x_chance_in_y(3, 5)) // give hand weapon { @@ -1345,33 +1321,6 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) if (thing_created == NON_ITEM) return; - - if (xitt == MI_NEEDLE) - { - if (mon->type == MONS_SONJA) - { - set_item_ego_type(mitm[thing_created], OBJ_MISSILES, - SPMSL_CURARE); - - mitm[thing_created].quantity = random_range(4, 10); - } - else if (mon->type == MONS_SPRIGGAN_RIDER) - { - set_item_ego_type(mitm[thing_created], OBJ_MISSILES, - SPMSL_CURARE); - - mitm[thing_created].quantity = random_range(2, 4); - } - else - { - set_item_ego_type(mitm[thing_created], OBJ_MISSILES, - got_curare_roll(level) ? SPMSL_CURARE - : SPMSL_POISONED); - - if (get_ammo_brand(mitm[thing_created]) == SPMSL_CURARE) - mitm[thing_created].quantity = random_range(2, 8); - } - } else { // Sanity check to avoid useless brands. @@ -1406,9 +1355,19 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) // Give some monsters throwables. int weap_type = -1; int qty = 0; + int brand = SPMSL_NORMAL; + switch (mon->type) { case MONS_KOBOLD: + if (one_chance_in(10) && level > 1) + { + weap_type = MI_DART; + qty = random_range(2, 8); + brand = got_curare_roll(level) ? SPMSL_CURARE : SPMSL_POISONED; + break; + } + // intentional fallthrough case MONS_BIG_KOBOLD: if (x_chance_in_y(2, 5)) { @@ -1417,11 +1376,26 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) } break; + case MONS_SONJA: + weap_type = MI_DART; + brand = SPMSL_CURARE; + qty = random_range(4, 10); + break; + + case MONS_SPRIGGAN_RIDER: + if (one_chance_in(15)) + { + weap_type = MI_DART; + brand = SPMSL_CURARE; + qty = random_range(2, 8); + } + break; + case MONS_ORC_WARRIOR: if (one_chance_in( player_in_branch(BRANCH_ORC)? 9 : 20)) { - weap_type = MI_TOMAHAWK; + weap_type = MI_BOOMERANG; qty = random_range(4, 8); } break; @@ -1429,7 +1403,7 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) case MONS_ORC: if (one_chance_in(20)) { - weap_type = MI_TOMAHAWK; + weap_type = MI_BOOMERANG; qty = random_range(2, 5); } break; @@ -1441,6 +1415,7 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) case MONS_CHUCK: weap_type = MI_LARGE_ROCK; + brand = SPMSL_RETURNING; qty = 2; break; @@ -1461,7 +1436,7 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) if (one_chance_in(3) || active_monster_band == BAND_MERFOLK_JAVELINEER) { - weap_type = MI_TOMAHAWK; + weap_type = MI_BOOMERANG; qty = random_range(4, 8); if (active_monster_band == BAND_MERFOLK_JAVELINEER) break; @@ -1507,8 +1482,8 @@ static void _give_ammo(monster* mon, int level, bool mons_summoned) { item_def& w(mitm[thing_created]); - if (mon->type == MONS_CHUCK) - set_item_ego_type(w, OBJ_MISSILES, SPMSL_RETURNING); + if (brand != SPMSL_NORMAL) + set_item_ego_type(w, OBJ_MISSILES, brand); w.quantity = qty; give_specific_item(mon, thing_created); @@ -1804,7 +1779,6 @@ int make_mons_armour(monster_type type, int level) 2, ARM_CHAIN_MAIL); break; - case MONS_SLAVE: case MONS_GRUM: case MONS_SPRIGGAN_BERSERKER: item.base_type = OBJ_ARMOUR; diff --git a/crawl-ref/source/mon-info.cc b/crawl-ref/source/mon-info.cc index dc3be459677b..edb50b3ae739 100644 --- a/crawl-ref/source/mon-info.cc +++ b/crawl-ref/source/mon-info.cc @@ -543,7 +543,7 @@ monster_info::monster_info(const monster* m, int milev) mintel = mons_intel(*m); hd = m->get_hit_dice(); ac = m->armour_class(false); - ev = m->evasion(EV_IGNORE_UNIDED); + ev = m->evasion(ev_ignore::unided); base_ev = m->base_evasion(); mr = m->res_magic(false); can_see_invis = m->can_see_invisible(false); @@ -1551,7 +1551,11 @@ string monster_info::wounds_description_sentence() const if (wounds.empty()) return ""; else - return string(pronoun(PRONOUN_SUBJECTIVE)) + " is " + wounds + "."; + { + return string(pronoun(PRONOUN_SUBJECTIVE)) + " " + + conjugate_verb("are", pronoun_plurality()) + + " " + wounds + "."; + } } string monster_info::wounds_description(bool use_colour) const @@ -1841,3 +1845,11 @@ const char *monster_info::pronoun(pronoun_type variant) const } return mons_pronoun(type, variant, true); } + +const bool monster_info::pronoun_plurality() const +{ + if (props.exists(MON_GENDER_KEY)) + return props[MON_GENDER_KEY].get_int() == GENDER_NEUTRAL; + + return mons_class_gender(type) == GENDER_NEUTRAL; +} diff --git a/crawl-ref/source/mon-info.h b/crawl-ref/source/mon-info.h index 6138f9dc77d8..864164cc193b 100644 --- a/crawl-ref/source/mon-info.h +++ b/crawl-ref/source/mon-info.h @@ -174,6 +174,7 @@ enum monster_info_flags MB_SLOWLY_DYING, MB_PINNED, MB_VILE_CLUTCH, + MB_HIGHLIGHTED_SUMMONER, NUM_MB_FLAGS }; @@ -304,6 +305,7 @@ struct monster_info : public monster_info_base vector attributes() const; const char *pronoun(pronoun_type variant) const; + const bool pronoun_plurality() const; string wounds_description_sentence() const; string wounds_description(bool colour = false) const; diff --git a/crawl-ref/source/mon-pathfind.cc b/crawl-ref/source/mon-pathfind.cc index 0da20a350714..cc25a6f13acb 100644 --- a/crawl-ref/source/mon-pathfind.cc +++ b/crawl-ref/source/mon-pathfind.cc @@ -433,9 +433,15 @@ bool monster_pathfind::traversable(const coord_def& p) // its preferred habit and capability of flight or opening doors. bool monster_pathfind::mons_traversable(const coord_def& p) { + if (cell_is_runed(p)) + return false; + if (!mons->is_habitable(p)) + return false; + return mons_can_traverse(*mons, p, traverse_in_sight) || mons->can_cling_to_walls() - && cell_is_clingable(pos) + && cell_is_clingable(p) + && mons->can_pass_through_feat(grd(p)) && cell_can_cling_to(pos, p); } diff --git a/crawl-ref/source/mon-pick.cc b/crawl-ref/source/mon-pick.cc index 6aa45e1584ae..fc7443751488 100644 --- a/crawl-ref/source/mon-pick.cc +++ b/crawl-ref/source/mon-pick.cc @@ -184,6 +184,45 @@ const pop_entry* zombie_population(branch_type br) return population_zombie[br].pop; } +bool monster_in_population(branch_type branch, monster_type m, bool check_noncore) +{ + for (const pop_entry *pop = population[branch].pop; pop->value; pop++) + if (pop->value == m) + return true; + if (check_noncore) + { + for (const pop_entry *pop = population_water[branch].pop; pop->value; pop++) + if (pop->value == m) + return true; + for (const pop_entry *pop = population_lava[branch].pop; pop->value; pop++) + if (pop->value == m) + return true; + for (const pop_entry *pop = population_zombie[branch].pop; pop->value; pop++) + if (pop->value == m) + return true; + } + return false; +} + +int monster_probability(level_id place, monster_type m) +{ + // TODO: does not check fish, zombies etc... + if (!monster_in_population(place.branch, m, false)) + return -1; + monster_picker picker = monster_picker(); + return picker.probability_at(m, population[place.branch].pop, place.depth); +} + +int monster_pop_depth_avg(branch_type branch, monster_type m) +{ + for (const pop_entry *pop = population[branch].pop; pop->value; pop++) + if (pop->value == m) + return max(0, (pop->minr + pop->maxr) / 2); + // negative values *are* sometimes used in the data, but the above max will + // prevent them from ever being naturally returned as an average. + return -1; +} + #if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_TESTS) static bool _not_skeletonable(monster_type mt) { diff --git a/crawl-ref/source/mon-pick.h b/crawl-ref/source/mon-pick.h index d77a9264d93e..ce52caad77f6 100644 --- a/crawl-ref/source/mon-pick.h +++ b/crawl-ref/source/mon-pick.h @@ -12,8 +12,9 @@ typedef random_pick_entry pop_entry; typedef bool (*mon_pick_vetoer)(monster_type); typedef bool (*mon_pick_pos_vetoer)(monster_type, coord_def); -int mons_rarity(monster_type mcls, branch_type branch); -int mons_depth(monster_type mcls, branch_type branch); +bool monster_in_population(branch_type branch, monster_type m, bool check_noncore=true); +int monster_probability(level_id place, monster_type m); +int monster_pop_depth_avg(branch_type branch, monster_type m); monster_type pick_monster(level_id place, mon_pick_vetoer veto = nullptr); monster_type pick_monster_from(const pop_entry *fpop, int depth, diff --git a/crawl-ref/source/mon-place.cc b/crawl-ref/source/mon-place.cc index 0abec6137b98..bd83bca86ba5 100644 --- a/crawl-ref/source/mon-place.cc +++ b/crawl-ref/source/mon-place.cc @@ -264,7 +264,7 @@ static void _apply_ood(level_id &place) if (fuzz) { place.depth += fuzz; - dprf(DIAG_MONPLACE, "Monster level fuzz: %d (old: %s, new: %s)", + dprf("Monster level fuzz: %d (old: %s, new: %s)", fuzz, old_place.describe().c_str(), place.describe().c_str()); } } @@ -578,6 +578,10 @@ static bool _valid_monster_generation_location(const mgen_data &mg, || monster_at(mg_pos) || you.pos() == mg_pos && !fedhas_passthrough_class(mg.cls)) { + ASSERT(!crawl_state.generating_level + || !in_bounds(mg_pos) + || you.pos() != mg_pos + || you.where_are_you == BRANCH_ABYSS); return false; } @@ -594,6 +598,7 @@ static bool _valid_monster_generation_location(const mgen_data &mg, if (mg.proximity == PROX_AWAY_FROM_PLAYER && close_to_player || mg.proximity == PROX_CLOSE_TO_PLAYER && !close_to_player) { + ASSERT(!crawl_state.generating_level || you.where_are_you == BRANCH_ABYSS); return false; } // Check that the location is not proximal to level stairs. @@ -646,6 +651,7 @@ monster* place_monster(mgen_data mg, bool force_pos, bool dont_place) mg.cls = resolve_monster_type(mg.cls, mg.base_type, mg.proximity, &mg.pos, mg.map_mask, &place, &want_band, allow_ood); + // TODO: it doesn't seem that this check can ever come out to be true?? bool chose_ood_monster = place.absdepth() > mg.place.absdepth() + 5; if (want_band) mg.flags |= MG_PERMIT_BANDS; @@ -737,6 +743,9 @@ monster* place_monster(mgen_data mg, bool force_pos, bool dont_place) if (mg.props.exists(MAP_KEY)) mon->set_originating_map(mg.props[MAP_KEY].get_string()); + if (chose_ood_monster) + mon->props[MON_OOD_KEY].get_bool() = true; + if (mg.needs_patrol_point() || (mon->type == MONS_ALLIGATOR && !testbits(mon->flags, MF_BAND_MEMBER))) @@ -824,6 +833,8 @@ monster* place_monster(mgen_data mg, bool force_pos, bool dont_place) member->props["kirke_band"] = true; } } + dprf(DIAG_DNGN, "Placing %s at %d,%d", mon->name(DESC_PLAIN, true).c_str(), + mon->pos().x, mon->pos().y); // Placement of first monster, at least, was a success. return mon; @@ -1214,7 +1225,7 @@ static monster* _place_monster_aux(const mgen_data &mg, const monster *leader, if (mon->type == MONS_HELLBINDER || mon->type == MONS_CLOUD_MAGE) { mon->props[MON_GENDER_KEY] = random_choose(GENDER_FEMALE, GENDER_MALE, - GENDER_NEUTER); + GENDER_NEUTRAL); } if (mon->has_spell(SPELL_OZOCUBUS_ARMOUR)) @@ -1506,7 +1517,7 @@ static monster* _place_pghost_aux(const mgen_data &mg, const monster *leader, // since depending on the ghost, the aux call can trigger variation in // things like whether an enchantment (with a random duration) is // triggered. - rng_generator rng(RNG_SYSTEM_SPECIFIC); + rng::generator rng(rng::SYSTEM_SPECIFIC); return _place_monster_aux(mg, leader, place, force_pos, dont_place); } @@ -2822,13 +2833,6 @@ conduct_type player_will_anger_monster(const monster &mon) if (is_good_god(you.religion) && mon.evil()) return DID_EVIL; - if (you_worship(GOD_FEDHAS) - && ((mon.holiness() & MH_UNDEAD && !mon.is_insubstantial()) - || mon.has_corpse_violating_spell())) - { - return DID_CORPSE_VIOLATION; - } - if (is_evil_god(you.religion) && mon.is_holy()) return DID_HOLY; @@ -2845,17 +2849,27 @@ conduct_type player_will_anger_monster(const monster &mon) return DID_NOTHING; } -bool player_angers_monster(monster* mon) +bool player_angers_monster(monster* mon, bool real) { ASSERT(mon); // XXX: change to monster &mon // Get the drawbacks, not the benefits... (to prevent e.g. demon-scumming). conduct_type why = player_will_anger_monster(*mon); - if (why && mon->wont_attack()) + if (why && (!real || mon->wont_attack())) { - mon->attitude = ATT_HOSTILE; - mon->del_ench(ENCH_CHARM); - behaviour_event(mon, ME_ALERT, &you); + if (real) + { + mon->attitude = ATT_HOSTILE; + mon->del_ench(ENCH_CHARM); + behaviour_event(mon, ME_ALERT, &you); + } + const string modal = real + ? ((why == DID_SACRIFICE_LOVE) ? "can " : "") + : "would "; + const string verb = (why == DID_SACRIFICE_LOVE) + ? "feel" + : real ? "is" : "be"; + const string vcomplex = modal + verb; if (you.can_see(*mon)) { @@ -2864,26 +2878,29 @@ bool player_angers_monster(monster* mon) switch (why) { case DID_EVIL: - mprf("%s is enraged by your holy aura!", mname.c_str()); - break; - case DID_CORPSE_VIOLATION: - mprf("%s is revulsed by your support of nature!", mname.c_str()); + mprf("%s %s enraged by your holy aura!", + mname.c_str(), vcomplex.c_str()); break; case DID_HOLY: - mprf("%s is enraged by your evilness!", mname.c_str()); + mprf("%s %s enraged by your evilness!", + mname.c_str(), vcomplex.c_str()); break; case DID_UNCLEAN: case DID_CHAOS: - mprf("%s is enraged by your lawfulness!", mname.c_str()); + mprf("%s %s enraged by your lawfulness!", + mname.c_str(), vcomplex.c_str()); break; case DID_SPELL_CASTING: - mprf("%s is enraged by your magic-hating god!", mname.c_str()); + mprf("%s %s enraged by your magic-hating god!", + mname.c_str(), vcomplex.c_str()); break; case DID_SACRIFICE_LOVE: - mprf("%s can only feel hate for you!", mname.c_str()); + mprf("%s %s only hate for you!", + mname.c_str(), vcomplex.c_str()); break; default: - mprf("%s is enraged by a buggy thing about you!", mname.c_str()); + mprf("%s %s enraged by a buggy thing about you!", + mname.c_str(), vcomplex.c_str()); break; } } diff --git a/crawl-ref/source/mon-place.h b/crawl-ref/source/mon-place.h index 7e18bbef54c3..ab620a5312c8 100644 --- a/crawl-ref/source/mon-place.h +++ b/crawl-ref/source/mon-place.h @@ -82,7 +82,7 @@ monster_type pick_random_monster(level_id place, conduct_type player_will_anger_monster(monster_type type); conduct_type player_will_anger_monster(const monster &mon); -bool player_angers_monster(monster* mon); +bool player_angers_monster(monster* mon, bool real = true); bool find_habitable_spot_near(const coord_def& where, monster_type mon_type, int radius, bool allow_centre, coord_def& empty, @@ -124,6 +124,8 @@ void replace_boris(); // Active monster band may influence gear generation on band followers. extern band_type active_monster_band; +#define MON_OOD_KEY "mons_is_ood" + #if TAG_MAJOR_VERSION == 34 #define VAULT_MON_TYPES_KEY "vault_mon_types" #define VAULT_MON_BASES_KEY "vault_mon_bases" diff --git a/crawl-ref/source/mon-poly.cc b/crawl-ref/source/mon-poly.cc index 5e0cef9941ca..d07f5523d468 100644 --- a/crawl-ref/source/mon-poly.cc +++ b/crawl-ref/source/mon-poly.cc @@ -103,13 +103,6 @@ void monster_drop_things(monster* mons, static bool _valid_morph(monster* mons, monster_type new_mclass) { - const dungeon_feature_type current_tile = grd(mons->pos()); - - monster_type old_mclass = mons->type; - if (mons_class_is_zombified(old_mclass)) - old_mclass = mons->base_monster; - // don't force spectral shapeshifters to become natural|undead mons only - // Shapeshifters cannot polymorph into glowing shapeshifters or // vice versa. if ((new_mclass == MONS_GLOWING_SHAPESHIFTER @@ -130,7 +123,7 @@ static bool _valid_morph(monster* mons, monster_type new_mclass) } // Various inappropriate polymorph targets. - if ( !(mons_class_holiness(new_mclass) & mons_class_holiness(old_mclass)) + if ( !(mons_class_holiness(new_mclass) & mons_class_holiness(mons->type)) // normally holiness just needs to overlap, but we don't want // shapeshifters to become demons || mons->is_shapeshifter() && !(mons_class_holiness(new_mclass) & MH_NATURAL) @@ -145,7 +138,7 @@ static bool _valid_morph(monster* mons, monster_type new_mclass) // 'morph targets are _always_ "base" classes, not derived ones. || new_mclass != mons_species(new_mclass) - || new_mclass == mons_species(old_mclass) + || new_mclass == mons_species(mons->type) // They act as separate polymorph classes on their own. || mons_class_is_zombified(new_mclass) @@ -167,7 +160,7 @@ static bool _valid_morph(monster* mons, monster_type new_mclass) } // Determine if the monster is happy on current tile. - return monster_habitable_grid(new_mclass, current_tile); + return monster_habitable_grid(new_mclass, grd(mons->pos())); } static bool _is_poly_power_unsuitable(poly_power_type power, @@ -624,9 +617,6 @@ void slimify_monster(monster* mon) void seen_monster(monster* mons) { - // If the monster is in the auto_exclude list, automatically - // set an exclusion. - set_auto_exclude(mons); set_unique_annotation(mons); // id equipment (do this every time we see them, it may have changed) diff --git a/crawl-ref/source/mon-spell.h b/crawl-ref/source/mon-spell.h index 735db82a2f54..c59f8388e42d 100644 --- a/crawl-ref/source/mon-spell.h +++ b/crawl-ref/source/mon-spell.h @@ -631,7 +631,7 @@ static const mon_spellbook mspell_list[] = { SPELL_BOLT_OF_MAGMA, 13, MON_SPELL_WIZARD }, { SPELL_FIREBALL, 13, MON_SPELL_WIZARD }, { SPELL_HURL_DAMNATION, 13, MON_SPELL_WIZARD }, - { SPELL_CALL_DOWN_DAMNATION, 13, MON_SPELL_WIZARD | MON_SPELL_EMERGENCY }, + { SPELL_CALL_DOWN_DAMNATION, 13, MON_SPELL_WIZARD }, } }, @@ -1205,6 +1205,12 @@ static const mon_spellbook mspell_list[] = } }, + { MST_BALLISTOMYCETE, + { + { SPELL_SPORULATE, 67, MON_SPELL_NATURAL }, + } + }, + // ('Q') Tengu. { MST_TENGU_CONJURER_I, { @@ -1968,7 +1974,7 @@ static const mon_spellbook mspell_list[] = { SPELL_STICKY_FLAME_RANGE, 13, MON_SPELL_MAGICAL }, { SPELL_FIREBALL, 13, MON_SPELL_MAGICAL }, { SPELL_HURL_DAMNATION, 13, MON_SPELL_MAGICAL }, - { SPELL_CALL_DOWN_DAMNATION, 13, MON_SPELL_MAGICAL | MON_SPELL_EMERGENCY }, + { SPELL_CALL_DOWN_DAMNATION, 13, MON_SPELL_MAGICAL }, } }, diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index e31ee632b888..30c18f644c8d 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -832,25 +832,6 @@ bool cheibriados_thinks_mons_is_fast(const monster& mon) return cheibriados_monster_player_speed_delta(mon) > 0; } -// Dithmenos also hates fire users, flaming weapons, and generally fiery beings. -bool mons_is_fiery(const monster& mon) -{ - if (mons_genus(mon.type) == MONS_DRACONIAN - && draco_or_demonspawn_subspecies(mon) == MONS_RED_DRACONIAN) - { - return true; - } - if (mons_genus(mon.type) == MONS_DANCING_WEAPON - && mon.weapon() && mon.weapon()->brand == SPWPN_FLAMING) - { - return true; - } - return mon.has_attack_flavour(AF_FIRE) - || mon.has_attack_flavour(AF_PURE_FIRE) - || mon.has_attack_flavour(AF_STICKY_FLAME) - || mon.has_spell_of_type(spschool::fire); -} - bool mons_is_projectile(monster_type mc) { return mons_class_flag(mc, M_PROJECTILE); @@ -1103,7 +1084,7 @@ static void _mimic_vanish(const coord_def& pos, const string& name) mprf("The %s mimic %svanishes%s!", name.c_str(), cackle.c_str(), smoke_str); - interrupt_activity(AI_MIMIC); + interrupt_activity(activity_interrupt::mimic); } /** @@ -2023,14 +2004,19 @@ mon_attack_def mons_attack_spec(const monster& m, int attk_number, attk.damage = max(1, you.skill_rdiv(SK_UNARMED_COMBAT, 10, 20)); } - if (attk.type == AT_RANDOM) - attk.type = random_choose(AT_HIT, AT_GORE); + if (!base_flavour) + { + // TODO: randomization here is not the greatest way of doing any of + // these... + if (attk.type == AT_RANDOM) + attk.type = random_choose(AT_HIT, AT_GORE); - if (attk.type == AT_CHERUB) - attk.type = random_choose(AT_HIT, AT_BITE, AT_PECK, AT_GORE); + if (attk.type == AT_CHERUB) + attk.type = random_choose(AT_HIT, AT_BITE, AT_PECK, AT_GORE); - if (attk.flavour == AF_DRAIN_STAT) - attk.flavour = random_choose(AF_DRAIN_STR, AF_DRAIN_INT,AF_DRAIN_DEX); + if (attk.flavour == AF_DRAIN_STAT) + attk.flavour = random_choose(AF_DRAIN_STR, AF_DRAIN_INT,AF_DRAIN_DEX); + } // Slime creature attacks are multiplied by the number merged. if (mon.type == MONS_SLIME_CREATURE && mon.blob_size > 1) @@ -2088,12 +2074,12 @@ string mon_attack_name(attack_type attack, bool with_object) #if TAG_MAJOR_VERSION == 34 "sting", #endif - "hit", // AT_CHERUB + "hit, bite, peck, or gore", // AT_CHERUB #if TAG_MAJOR_VERSION == 34 "hit", // AT_SHOOT #endif "hit", // AT_WEAP_ONLY, - "hit", // AT_RANDOM + "hit or gore", // AT_RANDOM }; COMPILE_CHECK(ARRAYSZ(attack_types) == NUM_ATTACK_TYPES - AT_FIRST_ATTACK); @@ -3079,7 +3065,11 @@ void ugly_thing_apply_uniform_band_colour(mgen_data &mg, static const char *drac_colour_names[] = { - "black", "", "yellow", "green", "purple", "red", "white", "grey", "pale" + "black", +#if TAG_MAJOR_VERSION == 34 + "", +#endif + "yellow", "green", "purple", "red", "white", "grey", "pale" }; string draconian_colour_name(monster_type mon_type) @@ -3110,7 +3100,11 @@ monster_type draconian_colour_by_name(const string &name) // TODO: Remove "putrid" when TAG_MAJOR_VERSION > 34 static const char *demonspawn_base_names[] = { - "monstrous", "gelid", "infernal", "putrid", "torturous", + "monstrous", "gelid", "infernal", +#if TAG_MAJOR_VERSION == 34 + "putrid", +#endif + "torturous", }; string demonspawn_base_name(monster_type mon_type) @@ -3884,7 +3878,7 @@ bool mons_has_incapacitating_ranged_attack(const monster& mon, const actor& foe) if (missile && missile->sub_type == MI_THROWING_NET) return true; - else if (missile && missile->sub_type == MI_NEEDLE) + else if (missile && missile->sub_type == MI_DART) { switch (get_ammo_brand(*missile)) { @@ -3895,13 +3889,11 @@ bool mons_has_incapacitating_ranged_attack(const monster& mon, const actor& foe) return true; break; - case SPMSL_SLEEP: - if (foe.can_sleep()) - return true; - break; - + case SPMSL_BLINDING: +#if TAG_MAJOR_VERSION == 34 case SPMSL_CONFUSION: case SPMSL_PARALYSIS: +#endif return true; default: @@ -3930,15 +3922,17 @@ bool mons_can_attack(const monster& mon) * Used for pronoun selection. * * @param mc The type of monster in question - * @return GENDER_NEUTER, _FEMALE, or _MALE. + * @return GENDER_NEUTER, _NEUTRAL, _FEMALE, or _MALE. */ -static gender_type _mons_class_gender(monster_type mc) +gender_type mons_class_gender(monster_type mc) { const bool female = mons_class_flag(mc, M_FEMALE); const bool male = mons_class_flag(mc, M_MALE); - ASSERT(!(male && female)); + const bool neutral = mons_class_flag(mc, M_GENDER_NEUTRAL); + ASSERT(male + female + neutral <= 1); return male ? GENDER_MALE : female ? GENDER_FEMALE : + neutral ? GENDER_NEUTRAL : GENDER_NEUTER; } @@ -3951,7 +3945,7 @@ const char *mons_pronoun(monster_type mon_type, pronoun_type variant, bool visible) { const gender_type gender = !visible ? GENDER_NEUTER - : _mons_class_gender(mon_type); + : mons_class_gender(mon_type); return decline_pronoun(gender, variant); } @@ -4484,7 +4478,7 @@ string do_mon_str_replacements(const string &in_msg, const monster& mons, msg = replace_all(msg, "@a_something@", mons.name(DESC_A)); msg = replace_all(msg, "@the_something@", mons.name(nocap)); - something[0] = toupper(something[0]); + something[0] = toupper_safe(something[0]); msg = replace_all(msg, "@Something@", something); msg = replace_all(msg, "@A_something@", mons.name(DESC_A)); msg = replace_all(msg, "@The_something@", mons.name(cap)); @@ -4497,7 +4491,7 @@ string do_mon_str_replacements(const string &in_msg, const monster& mons, msg = replace_all(msg, "@a_monster@", mons.name(DESC_A)); msg = replace_all(msg, "@the_monster@", mons.name(nocap)); - plain[0] = toupper(plain[0]); + plain[0] = toupper_safe(plain[0]); msg = replace_all(msg, "@Monster@", plain); msg = replace_all(msg, "@A_monster@", mons.name(DESC_A)); msg = replace_all(msg, "@The_monster@", mons.name(cap)); @@ -4980,8 +4974,9 @@ void debug_mondata() const bool male = mons_class_flag(mc, M_MALE); const bool female = mons_class_flag(mc, M_FEMALE); - if (male && female) - fails += make_stringf("%s is both male and female\n", name); + const bool neutral = mons_class_flag(mc, M_GENDER_NEUTRAL); + if (male + female + neutral > 1) + fails += make_stringf("%s has too many genders\n", name); if (md->shape == MON_SHAPE_BUGGY) fails += make_stringf("%s has no defined shape\n", name); @@ -5004,7 +4999,8 @@ void debug_mondata() fails += make_stringf("%s has a corpse tile & no corpse\n", name); } - } else if (!has_corpse_tile) + } + else if (!has_corpse_tile) fails += make_stringf("%s has a corpse but no corpse tile\n", name); } diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h index 33cd476d4185..db8bf77a03c9 100644 --- a/crawl-ref/source/mon-util.h +++ b/crawl-ref/source/mon-util.h @@ -8,6 +8,7 @@ #include #include "enum.h" +#include "gender-type.h" #include "los-type.h" #include "mon-enum.h" #include "mon-inv-type.h" @@ -244,7 +245,7 @@ bool mons_class_sees_invis(monster_type type, monster_type base); bool mons_immune_magic(const monster& mon); -mon_attack_def mons_attack_spec(const monster& mon, int attk_number, bool base_flavour = false); +mon_attack_def mons_attack_spec(const monster& mon, int attk_number, bool base_flavour = true); string mon_attack_name(attack_type attack, bool with_object = true); bool flavour_triggers_damageless(attack_flavour flavour); int flavour_damage(attack_flavour flavour, int HD, bool random = true); @@ -350,6 +351,7 @@ bool mons_can_attack(const monster& mon); bool mons_has_incapacitating_spell(const monster& mon, const actor& foe); bool mons_has_incapacitating_ranged_attack(const monster& mon, const actor& foe); +gender_type mons_class_gender(monster_type mc); const char *mons_pronoun(monster_type mon_type, pronoun_type variant, bool visible = true); @@ -411,7 +413,6 @@ bool herd_monster(const monster& mon); int cheibriados_monster_player_speed_delta(const monster& mon); bool cheibriados_thinks_mons_is_fast(const monster& mon); -bool mons_is_fiery(const monster& mon); bool mons_is_projectile(monster_type mc); bool mons_is_projectile(const monster& mon); bool mons_can_cling_to_walls(const monster& mon); diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc index f37d45022871..94d444d72c8a 100644 --- a/crawl-ref/source/monster.cc +++ b/crawl-ref/source/monster.cc @@ -639,11 +639,6 @@ bool monster::could_wield(const item_def &item, bool ignore_brand, if ((is_holy() || is_good_god(god)) && is_evil_item(item)) return false; - // Monsters that are gifts/worshippers of Fedhas won't use - // corpse-violating weapons. - if (god == GOD_FEDHAS && is_corpse_violating_item(item)) - return false; - // Monsters that are gifts/worshippers of Zin won't use unclean // weapons. if (god == GOD_ZIN && is_unclean_item(item)) @@ -847,13 +842,14 @@ void monster::equip_weapon_message(item_def &item) { bool plural = true; string hand = hand_name(true, &plural); - mprf("%s %s briefly %s through it before %s manages to get a " + mprf("%s %s briefly %s through it before %s %s to get a " "firm grip on it.", pronoun(PRONOUN_POSSESSIVE).c_str(), hand.c_str(), // Not conj_verb: the monster isn't the subject. conjugate_verb("pass", plural).c_str(), - pronoun(PRONOUN_SUBJECTIVE).c_str()); + pronoun(PRONOUN_SUBJECTIVE).c_str(), + conjugate_verb("manage", pronoun_plurality()).c_str()); } break; case SPWPN_REAPING: @@ -1378,12 +1374,9 @@ static bool _is_signature_weapon(const monster* mons, const item_def &weapon) if (mons->type == MONS_DONALD) return mons->hands_reqd(weapon) == HANDS_ONE; - // What kind of assassin would forget her blowgun or dagger somewhere else? + // What kind of assassin would forget her dagger somewhere else? if (mons->type == MONS_SONJA) - { - return item_attack_skill(weapon) == SK_SHORT_BLADES - || wtype == WPN_BLOWGUN; - } + return item_attack_skill(weapon) == SK_SHORT_BLADES; if (mons->type == MONS_IMPERIAL_MYRMIDON) return item_attack_skill(weapon) == SK_LONG_BLADES; @@ -1973,7 +1966,7 @@ bool monster::pickup_missile(item_def &item, bool msg, bool force) // Allow upgrading throwing weapon brands (XXX: improve this!) if (item.sub_type == miss->sub_type - && (item.sub_type == MI_TOMAHAWK || item.sub_type == MI_JAVELIN) + && (item.sub_type == MI_BOOMERANG || item.sub_type == MI_JAVELIN) && get_ammo_brand(*miss) == SPMSL_NORMAL && get_ammo_brand(item) != SPMSL_NORMAL) { @@ -2354,6 +2347,15 @@ string monster::pronoun(pronoun_type pro, bool force_visible) const return mons_pronoun(type, pro, seen); } +bool monster::pronoun_plurality(bool force_visible) const +{ + const bool seen = force_visible || you.can_see(*this); + if (seen && props.exists(MON_GENDER_KEY)) + return props[MON_GENDER_KEY].get_int() == GENDER_NEUTRAL; + + return seen && mons_class_gender(type) == GENDER_NEUTRAL; +} + string monster::conj_verb(const string &verb) const { return conjugate_verb(verb, false); @@ -2925,11 +2927,6 @@ bool monster::has_chaotic_spell() const return search_spells(is_chaotic_spell); } -bool monster::has_corpse_violating_spell() const -{ - return search_spells(is_corpse_violating_spell); -} - bool monster::has_attack_flavour(int flavour) const { for (int i = 0; i < 4; ++i) @@ -3120,7 +3117,6 @@ bool monster::pacified() const bool monster::shielded() const { return shield() - || has_ench(ENCH_BONE_ARMOUR) || wearing(EQ_AMULET_PLUS, AMU_REFLECTION) > 0; } @@ -3349,8 +3345,6 @@ int monster::armour_class(bool calc_unid) const ac += 4 + get_hit_dice() / 3; if (has_ench(ENCH_ICEMAIL)) ac += ICEMAIL_MAX; - if (has_ench(ENCH_BONE_ARMOUR)) - ac += 6 + get_hit_dice() / 3; if (has_ench(ENCH_IDEALISED)) ac += 4 + get_hit_dice() / 3; @@ -3436,7 +3430,7 @@ int monster::base_evasion() const **/ int monster::evasion(ev_ignore_type evit, const actor* /*act*/) const { - const bool calc_unid = !(evit & EV_IGNORE_UNIDED); + const bool calc_unid = !testbits(evit, ev_ignore::unided); int ev = base_evasion(); @@ -3465,7 +3459,7 @@ int monster::evasion(ev_ignore_type evit, const actor* /*act*/) const if (has_ench(ENCH_AGILE)) ev += 5; - if (evit & EV_IGNORE_HELPLESS) + if (evit & ev_ignore::helpless) return max(ev, 0); if (paralysed() || petrified() || petrifying() || asleep()) @@ -4348,9 +4342,10 @@ bool monster::corrode_equipment(const char* corrosion_source, int degree) if (you.see_cell(pos())) { - mprf("%s corrodes %s!", - corrosion_source, - name(DESC_THE).c_str()); + if (!has_ench(ENCH_CORROSION)) + mprf("%s corrodes %s!", corrosion_source, name(DESC_THE).c_str()); + else + mprf("%s seems to be corroded for longer.", name(DESC_THE).c_str()); } add_ench(mon_enchant(ENCH_CORROSION, 0)); @@ -5601,12 +5596,9 @@ bool monster::do_shaft() if (!is_valid_shaft_level()) return false; - // Tentacles & player ghosts are immune to shafting - if (mons_is_tentacle_or_tentacle_segment(type) - || type == MONS_PLAYER_GHOST) - { + // Tentacles are immune to shafting + if (mons_is_tentacle_or_tentacle_segment(type)) return false; - } // Handle instances of do_shaft() being invoked magically when // the monster isn't standing over a shaft. @@ -5797,11 +5789,11 @@ bool monster::can_drink_potion(potion_type ptype) const case POT_CURING: case POT_HEAL_WOUNDS: return !(holiness() & (MH_NONLIVING | MH_PLANT)); - case POT_BLOOD: #if TAG_MAJOR_VERSION == 34 + case POT_BLOOD: case POT_BLOOD_COAGULATED: -#endif return mons_species() == MONS_VAMPIRE; +#endif case POT_BERSERK_RAGE: return can_go_berserk(); case POT_HASTE: @@ -5830,11 +5822,11 @@ bool monster::should_drink_potion(potion_type ptype) const || has_ench(ENCH_CONFUSION); case POT_HEAL_WOUNDS: return hit_points <= max_hit_points / 2; - case POT_BLOOD: #if TAG_MAJOR_VERSION == 34 + case POT_BLOOD: case POT_BLOOD_COAGULATED: -#endif return hit_points <= max_hit_points / 2; +#endif case POT_BERSERK_RAGE: // this implies !berserk() return !has_ench(ENCH_MIGHT) && !has_ench(ENCH_HASTE) @@ -5887,16 +5879,16 @@ bool monster::drink_potion_effect(potion_type pot_eff, bool card) simple_monster_message(*this, " is healed!"); break; - case POT_BLOOD: #if TAG_MAJOR_VERSION == 34 + case POT_BLOOD: case POT_BLOOD_COAGULATED: -#endif if (mons_species() == MONS_VAMPIRE) { heal(10 + random2avg(28, 3)); simple_monster_message(*this, " is healed!"); } break; +#endif case POT_BERSERK_RAGE: enchant_actor_with_flavour(this, this, BEAM_BERSERK); @@ -6070,7 +6062,8 @@ void monster::react_to_damage(const actor *oppressor, int damage, { if (hit_points + damage > max_hit_points / 2) damage = max_hit_points / 2 - hit_points; - if (damage > 0 && x_chance_in_y(damage, damage + hit_points)) + if (damage > 0 && x_chance_in_y(damage, damage + hit_points) + && flavour != BEAM_TORMENT_DAMAGE) { bool fly_died = coinflip(); int old_hp = hit_points; @@ -6167,6 +6160,8 @@ void monster::react_to_damage(const actor *oppressor, int damage, } if (caught()) check_net_will_hold_monster(this); + if (this->is_constricted()) + this->stop_being_constricted(); add_ench(ENCH_RING_OF_THUNDER); } diff --git a/crawl-ref/source/monster.h b/crawl-ref/source/monster.h index aa1906d82ed8..c31d3bbfe453 100644 --- a/crawl-ref/source/monster.h +++ b/crawl-ref/source/monster.h @@ -104,7 +104,7 @@ class monster : public actor unique_ptr ghost; // Ghost information. seen_context_type seen_context; // Non-standard context for - // AI_SEE_MONSTER + // activity_interrupt::see_monster int damage_friendly; // Damage taken, x2 you, x1 pets, x0 else. int damage_total; @@ -332,6 +332,7 @@ class monster : public actor // will return "Arbolt the orc priest". string full_name(description_level_type type) const; string pronoun(pronoun_type pro, bool force_visible = false) const override; + bool pronoun_plurality(bool force_visible = false) const; string conj_verb(const string &verb) const override; string hand_name(bool plural, bool *can_plural = nullptr) const override; string foot_name(bool plural, bool *can_plural = nullptr) const override; @@ -446,7 +447,6 @@ class monster : public actor mon_spell_slot_flags spell_slot_flags(spell_type spell) const; bool has_unclean_spell() const; bool has_chaotic_spell() const; - bool has_corpse_violating_spell() const; bool has_attack_flavour(int flavour) const; bool has_damage_type(int dam_type); @@ -461,7 +461,7 @@ class monster : public actor int armour_class(bool calc_unid = true) const override; int gdr_perc() const override { return 0; } int base_evasion() const; - int evasion(ev_ignore_type evit = EV_IGNORE_NONE, + int evasion(ev_ignore_type evit = ev_ignore::none, const actor* /*attacker*/ = nullptr) const override; bool poison(actor *agent, int amount = 1, bool force = false) override; diff --git a/crawl-ref/source/movement.cc b/crawl-ref/source/movement.cc index 55a2d24a17ba..1c988ab7a6af 100644 --- a/crawl-ref/source/movement.cc +++ b/crawl-ref/source/movement.cc @@ -537,8 +537,13 @@ void move_player_action(coord_def move) const coord_def targ = you.pos() + move; string wall_jump_err; - bool can_wall_jump = Options.wall_jump_move && - wu_jian_can_wall_jump(targ, wall_jump_err); + // Don't allow wall jump against close doors via movement -- need to use + // the ability. Also, if moving into a closed door, don't call + // wu_jian_can_wall_jump, to avoid printing a spurious message (see 11940). + bool can_wall_jump = Options.wall_jump_move + && (!in_bounds(targ) + || !feat_is_closed_door(grd(targ))) + && wu_jian_can_wall_jump(targ, wall_jump_err); bool did_wall_jump = false; // You can't walk out of bounds! if (!in_bounds(targ) && !can_wall_jump) @@ -549,13 +554,6 @@ void move_player_action(coord_def move) return; } - const dungeon_feature_type targ_grid = grd(targ); - - // don't allow wall jump against close doors via movement -- need to use - // the ability - if (can_wall_jump && feat_is_closed_door(targ_grid)) - can_wall_jump = false; - const string walkverb = you.airborne() ? "fly" : you.swimming() ? "swim" : you.form == transformation::spider ? "crawl" @@ -592,12 +590,8 @@ void move_player_action(coord_def move) you.digging = false; canned_msg(MSG_TOO_HUNGRY); } - else if (grd(targ) == DNGN_ROCK_WALL - || grd(targ) == DNGN_CLEAR_ROCK_WALL - || grd(targ) == DNGN_GRATE) - { + else if (feat_is_diggable(grd(targ))) targ_pass = true; - } else // moving or attacking ends dig { you.digging = false; @@ -723,8 +717,8 @@ void move_player_action(coord_def move) if (you.duration[DUR_WATER_HOLD]) { mpr("You slip free of the water engulfing you."); - you.duration[DUR_WATER_HOLD] = 1; you.props.erase("water_holder"); + you.clear_far_engulf(); } if (you.digging) @@ -821,7 +815,7 @@ void move_player_action(coord_def move) // BCR - Easy doors single move if ((Options.travel_open_doors || !you.running) && !attacking - && feat_is_closed_door(targ_grid)) + && feat_is_closed_door(grd(targ))) { open_door_action(move); move.reset(); diff --git a/crawl-ref/source/mutation-data.h b/crawl-ref/source/mutation-data.h index 5f87b1aaa1ae..401aba72458f 100644 --- a/crawl-ref/source/mutation-data.h +++ b/crawl-ref/source/mutation-data.h @@ -1833,8 +1833,8 @@ static const mutation_def mut_data[] = "MP-powered wands", {"You expend magic power (3 MP) to strengthen your wands.", "", ""}, - {"You feel your magical essence link to your wands.", "", ""}, - {"Your magical essence is no longer linked to your wands.", "", ""}, + {"You feel your magical essence link to the dungeon's wands.", "", ""}, + {"Your magical essence no longer links to wands of the dungeon.", "", ""}, }, { MUT_UNSKILLED, 0, 3, mutflag::bad, false, diff --git a/crawl-ref/source/mutation.cc b/crawl-ref/source/mutation.cc index ba1b67050f89..a95f51e8ed5c 100644 --- a/crawl-ref/source/mutation.cc +++ b/crawl-ref/source/mutation.cc @@ -585,9 +585,6 @@ void validate_mutations(bool debug_msg) } } ASSERT(total_temp == you.attribute[ATTR_TEMP_MUTATIONS]); - ASSERT(you.attribute[ATTR_TEMP_MUT_XP] >=0); - if (total_temp > 0) - ASSERT(you.attribute[ATTR_TEMP_MUT_XP] > 0); } string describe_mutations(bool drop_title) @@ -644,11 +641,13 @@ string describe_mutations(bool drop_title) if (you.species == SP_VAMPIRE) { - if (you.hunger_state <= HS_STARVING) - result += "You do not heal naturally.\n"; - else if (you.hunger_state < HS_SATIATED) - result += "You heal slowly.\n"; - else if (you.hunger_state >= HS_FULL) + if (!you.vampire_alive) + { + result += "You do not regenerate when monsters are visible.\n"; + result += "You are frail without blood (-20% HP).\n"; + result += "You can heal yourself when you bite living creatures.\n"; + } + else result += "Your natural rate of healing is unusually fast.\n"; } @@ -768,23 +767,7 @@ static formatted_string _vampire_Ascreen_footer(bool first_page) static int _vampire_bloodlessness() { - switch (you.hunger_state) - { - case HS_ENGORGED: - case HS_VERY_FULL: - case HS_FULL: - return 1; - case HS_SATIATED: - return 2; - case HS_HUNGRY: - case HS_VERY_HUNGRY: - case HS_NEAR_STARVING: - return 3; - case HS_STARVING: - case HS_FAINTING: - return 4; - } - die("bad hunger state %d", you.hunger_state); + return you.vampire_alive ? 1 : 2; } static string _display_vampire_attributes() @@ -794,41 +777,41 @@ static string _display_vampire_attributes() string result; const int lines = 12; - string column[lines][5] = + string column[lines][3] = { - {" ", "Full ", "Satiated ", "Thirsty ", "Bloodless"}, - //Full Satiated Thirsty Bloodless - {"Metabolism ", "fast ", "normal ", "slow ", "none "}, + {" ", "Alive ", "Bloodless"}, + //Full Bloodless + {"Regeneration ", "fast ", "none with monsters in sight"}, - {"Regeneration ", "fast ", "normal ", "slow ", "none "}, + {"HP modifier ", "none ", "-20%"}, - {"Stealth boost ", "none ", "none ", "minor ", "major "}, + {"Stealth boost ", "none ", "major "}, - {"Hunger costs ", "full ", "full ", "halved ", "none "}, + {"Heal on bite ", "no ", "yes "}, {"\nResistances\n" - "Poison resistance ", " ", " ", "+ ", "immune"}, + "Poison resistance ", " ", "immune"}, - {"Cold resistance ", " ", " ", "+ ", "++ "}, + {"Cold resistance ", " ", "++ "}, - {"Negative resistance ", " ", " + ", "++ ", "+++ "}, + {"Negative resistance ", " ", "+++ "}, - {"Rotting resistance ", " ", " ", "+ ", "+ "}, + {"Rotting resistance ", " ", "+ "}, - {"Torment resistance ", " ", " ", " ", "+ "}, + {"Torment resistance ", " ", "+ "}, {"\nTransformations\n" - "Bat form ", "no ", "yes ", "yes ", "yes "}, + "Bat form ", "no ", "yes "}, {"Other forms and \n" - "berserk ", "yes ", "yes ", "no ", "no "} + "berserk ", "yes ", "no "} }; int current = _vampire_bloodlessness(); for (int y = 0; y < lines; y++) // lines (properties) { - for (int x = 0; x < 5; x++) // columns (hunger states) + for (int x = 0; x < 3; x++) // columns (hunger states) { if (y > 0 && x == current) result += ""; @@ -863,10 +846,10 @@ void display_mutations() trim_string_right(mutation_s); auto vbox = make_shared(Widget::VERT); + vbox->align_cross = Widget::CENTER; const char *title_text = "Innate Abilities, Weirdness & Mutations"; auto title = make_shared(formatted_string(title_text, WHITE)); - title->align_self = Widget::CENTER; vbox->add_child(move(title)); auto switcher = make_shared(); @@ -878,23 +861,23 @@ void display_mutations() auto scroller = make_shared(); auto text = make_shared(formatted_string::parse_string( descs[static_cast(i)])); - text->wrap_text = true; + text->set_wrap_text(true); scroller->set_child(text); switcher->add_child(move(scroller)); } switcher->current() = 0; - switcher->set_margin_for_sdl({20, 0, 0, 0}); - switcher->set_margin_for_crt({1, 0, 0, 0}); + switcher->set_margin_for_sdl(20, 0, 0, 0); + switcher->set_margin_for_crt(1, 0, 0, 0); switcher->expand_h = false; #ifdef USE_TILE_LOCAL - switcher->max_size()[0] = tiles.get_crt_font()->char_width()*80; + switcher->max_size().width = tiles.get_crt_font()->char_width()*80; #endif vbox->add_child(switcher); auto bottom = make_shared(_vampire_Ascreen_footer(true)); - bottom->set_margin_for_sdl({20, 0, 0, 0}); - bottom->set_margin_for_crt({1, 0, 0, 0}); + bottom->set_margin_for_sdl(20, 0, 0, 0); + bottom->set_margin_for_crt(1, 0, 0, 0); if (you.species == SP_VAMPIRE) vbox->add_child(bottom); @@ -908,16 +891,18 @@ void display_mutations() lastch = ev.key.keysym.sym; if (you.species == SP_VAMPIRE && (lastch == '!' || lastch == CK_MOUSE_CMD || lastch == '^')) { - int c = 1 - switcher->current(); - switcher->current() = c; + int& c = switcher->current(); + + bottom->set_text(_vampire_Ascreen_footer(c)); + + c = 1 - c; #ifdef USE_TILE_WEB tiles.json_open_object(); tiles.json_write_int("pane", c); tiles.ui_state_change("mutations", 0); #endif - bottom->set_text(_vampire_Ascreen_footer(c)); } else - done = !vbox->on_event(ev); + done = !switcher->current_widget()->on_event(ev); return true; }); @@ -2557,6 +2542,12 @@ _schedule_ds_mutations(vector muts) void roll_demonspawn_mutations() { + // intentionally create the subgenerator either way, so that this has the + // same impact on the current main rng for all chars. + rng::subgenerator ds_rng; + + if (you.species != SP_DEMONSPAWN) + return; you.demonic_traits = _schedule_ds_mutations( _order_ds_mutations( _select_ds_mutations())); @@ -2789,7 +2780,7 @@ void check_monster_detect() if (you.see_cell(*ri2)) { mon->flags |= MF_SENSED; - interrupt_activity(AI_SENSE_MONSTER); + interrupt_activity(activity_interrupt::sense_monster); break; } } diff --git a/crawl-ref/source/nearby-danger.cc b/crawl-ref/source/nearby-danger.cc index bd438dee0bbb..d883c0cb832a 100644 --- a/crawl-ref/source/nearby-danger.cc +++ b/crawl-ref/source/nearby-danger.cc @@ -214,15 +214,6 @@ vector get_nearby_monsters(bool want_move, return mons; } -static bool _exposed_monsters_nearby(bool want_move) -{ - const int radius = want_move ? 2 : 1; - for (radius_iterator ri(you.pos(), radius, C_SQUARE, LOS_DEFAULT); ri; ++ri) - if (env.map_knowledge(*ri).flags & MAP_INVISIBLE_MONSTER) - return true; - return false; -} - bool i_feel_safe(bool announce, bool want_move, bool just_monsters, bool check_dist, int range) { @@ -286,10 +277,19 @@ bool i_feel_safe(bool announce, bool want_move, bool just_monsters, } // Monster check. - vector visible = - get_nearby_monsters(want_move, !announce, true, true, true, + vector monsters = + get_nearby_monsters(want_move, !announce, true, true, false, check_dist, range); + vector visible; + copy_if(monsters.begin(), monsters.end(), back_inserter(visible), + [](const monster *mon){ return mon->visible_to(&you); }); + + const bool sensed_monster = any_of(monsters.begin(), monsters.end(), + [](const monster *mon){ + return env.map_knowledge(mon->pos()).flags & MAP_INVISIBLE_MONSTER; + }); + // Announce the presence of monsters (Eidolos). string msg; if (visible.size() == 1) @@ -299,7 +299,7 @@ bool i_feel_safe(bool announce, bool want_move, bool just_monsters, } else if (visible.size() > 1) msg = "There are monsters nearby!"; - else if (_exposed_monsters_nearby(want_move)) + else if (sensed_monster) msg = "There is a strange disturbance nearby!"; else return true; diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index 241adba7958f..a8ae0c0dc5cc 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -27,16 +27,21 @@ #include "ng-restr.h" #include "options.h" #include "prompt.h" +#include "skills.h" #include "species-groups.h" #include "state.h" #include "stringutil.h" -#ifdef USE_TILE_LOCAL +#ifdef USE_TILE #include "tilereg-crt.h" #include "tilepick.h" +#include "tilepick-p.h" #include "tilefont.h" +#include "tiledef-main.h" +#include "tiledef-feat.h" #endif #include "version.h" #include "ui.h" +#include "outer-menu.h" using namespace ui; @@ -130,7 +135,7 @@ static bool _char_defined(const newgame_def& ng) return ng.species != SP_UNKNOWN && ng.job != JOB_UNKNOWN; } -static string _char_description(const newgame_def& ng) +string newgame_char_description(const newgame_def& ng) { if (_is_random_viable_choice(ng)) return "Recommended character"; @@ -365,7 +370,7 @@ static void _choose_species_job(newgame_def& ng, newgame_def& ng_choice, // Either an invalid combination was passed in through options, // or we messed up. end(1, false, "Incompatible species and background (%s) selected.", - _char_description(ng).c_str()); + newgame_char_description(ng).c_str()); } } @@ -376,33 +381,52 @@ static bool _reroll_random(newgame_def& ng) string specs = chop_string(species_name(ng.species), 79, false); formatted_string prompt; - prompt.cprintf("You are a%s %s %s.\n", + prompt.cprintf("You are a%s %s %s.", (is_vowel(specs[0])) ? "n" : "", specs.c_str(), get_job_name(ng.job)); - prompt.cprintf("\nDo you want to play this combination? (ynq) [y]"); - auto prompt_ui = make_shared(); - prompt_ui->set_text(prompt); + auto title_hbox = make_shared(Widget::HORZ); +#ifdef USE_TILE + dolls_data doll; + fill_doll_for_newgame(doll, ng); +#ifdef USE_TILE_LOCAL + auto tile = make_shared(doll); + tile->set_margin_for_sdl(0, 10, 0, 0); + title_hbox->add_child(move(tile)); +#endif +#endif + title_hbox->add_child(make_shared(prompt)); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_sdl(0, 0, 20, 0); + title_hbox->set_margin_for_crt(0, 0, 1, 0); + + auto vbox = make_shared(Box::VERT); + vbox->add_child(move(title_hbox)); + vbox->add_child(make_shared("Do you want to play this combination? [Y/n/q]")); + auto popup = make_shared(move(vbox)); bool done = false; char c; - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { + popup->on(Widget::slots.event, [&](wm_event ev) { if (ev.type != WME_KEYDOWN) return false; c = ev.key.keysym.sym; return done = true; }); - auto popup = make_shared(prompt_ui); +#ifdef USE_TILE_WEB + tiles.json_open_object(); + tiles.json_write_string("prompt", prompt.to_colour_string()); + tiles.send_doll(doll, false, false); + tiles.push_ui_layout("newgame-random-combo", 0); +#endif ui::run_layout(move(popup), done); - - if (key_is_escape(c) || toalower(c) == 'q' || crawl_state.seen_hups) - { #ifdef USE_TILE_WEB - tiles.send_exit_reason("cancel"); + tiles.pop_ui_layout(); #endif + + if (key_is_escape(c) || toalower(c) == 'q' || crawl_state.seen_hups) game_ended(game_exit::abort); - } return toalower(c) == 'n' || c == '\t' || c == '!' || c == '#'; } @@ -433,9 +457,6 @@ static void _choose_char(newgame_def& ng, newgame_def& choice, if (!yesno("Trunk games don't count for the tournament, you want " TOURNEY ". Play trunk anyway? (Y/N)", false, 'n')) { -#ifdef USE_TILE_WEB - tiles.send_exit_reason("cancel"); -#endif game_ended(game_exit::abort); } } @@ -537,6 +558,22 @@ static void _choose_char(newgame_def& ng, newgame_def& choice, } } +static void _add_menu_sub_item(shared_ptr& menu, int x, int y, const string& text, const string& description, char letter, int id) +{ + auto tmp = make_shared(); + tmp->set_text(formatted_string(text, BROWN)); + tmp->set_margin_for_sdl(4,8); + tmp->set_margin_for_crt(0, 2, 0, 0); + + auto btn = make_shared(); + btn->set_child(move(tmp)); + btn->id = id; + btn->description = description; + btn->hotkey = letter; + btn->highlight_colour = STARTUP_HIGHLIGHT_CONTROL; + menu->add_button(move(btn), x, y); +} + #ifndef DGAMELAUNCH /** * Attempt to generate a random name for a character that doesn't collide with @@ -569,36 +606,110 @@ static void _choose_name(newgame_def& ng, newgame_def& choice) bool good_name = true; bool cancel = false; + string specs = chop_string(species_name(ng.species), 79, false); + + formatted_string title; + title.cprintf("You are a%s %s %s.", + (is_vowel(specs[0])) ? "n" : "", specs.c_str(), + get_job_name(ng.job)); + + auto title_hbox = make_shared(Widget::HORZ); +#ifdef USE_TILE + dolls_data doll; + fill_doll_for_newgame(doll, ng); +#ifdef USE_TILE_LOCAL + auto tile = make_shared(doll); + tile->set_margin_for_sdl(0, 10, 0, 0); + title_hbox->add_child(move(tile)); +#endif +#endif + title_hbox->add_child(make_shared(title)); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_sdl(0, 0, 20, 0); + title_hbox->set_margin_for_crt(0, 0, 1, 0); + + auto vbox = make_shared(Box::VERT); + vbox->add_child(move(title_hbox)); auto prompt_ui = make_shared(); - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { - if (ev.type != WME_KEYDOWN) - return false; - int key = ev.key.keysym.sym; + vbox->add_child(prompt_ui); - if (!overwrite_prompt) - { - key = reader.putkey(key); - good_name = is_good_name(buf, true); - if (key != -1) - { - if (key_is_escape(key)) - return done = cancel = true; + auto sub_items = make_shared(false, 3, 1); + vbox->add_child(sub_items); + + _add_menu_sub_item(sub_items, 0, 0, + "Esc - Quit", "", CK_ESCAPE, CK_ESCAPE); + _add_menu_sub_item(sub_items, 1, 0, + "* - Random name", "", '*', '*'); + auto ok_switcher = make_shared(); + ok_switcher->align_y = Widget::STRETCH; + { + auto tmp = make_shared(); + tmp->set_text(formatted_string("Enter - Begin!", BROWN)); + + auto btn = make_shared(); + tmp->set_margin_for_sdl(4,8); + tmp->set_margin_for_crt(0, 2, 0, 0); + btn->set_child(move(tmp)); + btn->id = CK_ENTER; + btn->description = ""; + btn->hotkey = CK_ENTER; + btn->highlight_colour = STARTUP_HIGHLIGHT_CONTROL; + + auto err = make_shared( + formatted_string("That's a silly name!", LIGHTRED)); + err->set_margin_for_sdl(0, 0, 0, 10); + auto box = make_shared(Box::HORZ); + box->align_cross = Widget::CENTER; + box->add_child(err); + + ok_switcher->add_child(btn); + ok_switcher->add_child(box); + sub_items->add_button(btn, 2, 0); + } + + auto popup = make_shared(move(vbox)); + + sub_items->on_button_activated = [&](int id) { + switch (id) + { + case '*': + reader.putkey(CK_END); + reader.putkey(CONTROL('U')); + for (char ch : _random_name()) + reader.putkey(ch); + return; + case CK_ENTER: choice.name = buf; trim_string(choice.name); - if (choice.name.empty()) choice.name = _random_name(); - if (good_name) { ng.name = choice.name; ng.filename = get_save_filename(choice.name); overwrite_prompt = save_exists(ng.filename); if (!overwrite_prompt) - return done = true; + done = true; } - } + return; + case CK_ESCAPE: + done = cancel = true; + return; + } + + }; + + popup->on(Widget::slots.event, [&](wm_event ev) { + if (ev.type != WME_KEYDOWN) + return false; + int key = ev.key.keysym.sym; + + if (!overwrite_prompt) + { + key = reader.putkey(key); + good_name = is_good_name(buf, true); + ok_switcher->current() = good_name ? 0 : 1; } else { @@ -609,24 +720,17 @@ static void _choose_name(newgame_def& ng, newgame_def& choice) return true; }); - auto popup = make_shared(prompt_ui); ui::push_layout(move(popup)); + ui::set_focused_widget(sub_items.get()); while (!done && !crawl_state.seen_hups) { formatted_string prompt; - string specs = chop_string(species_name(ng.species), 79, false); - prompt.cprintf("You are a%s %s %s.\n", - (is_vowel(specs[0])) ? "n" : "", specs.c_str(), - get_job_name(ng.job)); prompt.textcolour(CYAN); - prompt.cprintf("\nWhat is your name today? "); + prompt.cprintf("What is your name today? "); prompt.textcolour(LIGHTGREY); - prompt.cprintf("%s", buf); - prompt.cprintf("\n\nLeave blank for a random name, or use Escape to cancel this character.\n\n"); + prompt.cprintf("%s\n", buf); prompt.textcolour(LIGHTRED); - if (!good_name) - prompt.cprintf("That's a silly name!"); - else if (overwrite_prompt) + if (overwrite_prompt) prompt.cprintf("You have an existing game under this name; really overwrite? [Y/n]"); prompt_ui->set_text(prompt); @@ -666,10 +770,16 @@ static void _choose_seed(newgame_def& ng, newgame_def& choice, choice.seed = Options.seed_from_rc; reader.set_text(make_stringf("%" PRIu64, choice.seed)); reader.set_keyproc(_keyfun_seed_input); + const bool show_pregen_toggle = +#ifdef DEBUG + true; +#else + false; +#endif bool done = false; bool cancel = false; - choice.pregenerate = true; // default for this menu + choice.pregenerate = Options.pregen_dungeon; auto prompt_ui = make_shared(); prompt_ui->on(Widget::slots.event, [&](wm_event ev) { @@ -677,7 +787,7 @@ static void _choose_seed(newgame_def& ng, newgame_def& choice, return false; int key = ev.key.keysym.sym; - if (key == CONTROL('I')) + if (show_pregen_toggle && key == CONTROL('I')) { choice.pregenerate = !choice.pregenerate; return done = false; @@ -692,11 +802,31 @@ static void _choose_seed(newgame_def& ng, newgame_def& choice, char timebuf[9]; strftime(timebuf, sizeof(timebuf), "%Y%m%d", timeinfo); reader.set_text(timebuf); +#ifdef USE_TILE_WEB + // TODO: can this be done more automatically somehow? + tiles.json_open_object(); + tiles.json_write_string("msg", "update_input"); + tiles.json_write_string("input_text", string(timebuf)); + tiles.json_write_bool("select", true); + tiles.json_close_object(); + tiles.finish_message(); +#endif return done = false; } + else if (key == '?') + show_help('D', "Seeded play"); // TODO: scroll to section else if (key == '-') { reader.set_text(""); +#ifdef USE_TILE_WEB + // TODO: can this be done more automatically somehow? + tiles.json_open_object(); + tiles.json_write_string("msg", "update_input"); + tiles.json_write_string("input_text", string("")); + tiles.json_write_bool("select", true); + tiles.json_close_object(); + tiles.finish_message(); +#endif return done = false; } #ifdef USE_TILE_LOCAL @@ -727,48 +857,96 @@ static void _choose_seed(newgame_def& ng, newgame_def& choice, auto box = make_shared(ui::Widget::VERT); box->add_child(prompt_ui); - auto pregen_choice = make_shared( - "Pregenerate the dungeon ([tab] to switch)? Yes | No"); + auto pregen_choice = make_shared(""); box->add_child(pregen_choice); + if (show_pregen_toggle) + { + pregen_choice->set_text( + "Pregenerate the dungeon ([tab] to switch)? Yes | No"); + } + const string title_text = make_stringf( + "Play a game with a custom seed for version %s.", + Version::Long); + const string body_text = + "Choose 0 for a random seed. Press [d] for today's daily seed.\n" +#ifdef USE_TILE_LOCAL + "Press [p] to paste a seed from the clipboard (overwriting the\n" + "current value).\n" +#endif + ; + const string prompt_text = "Seed ([-] to clear):"; + const string footer_text = + "The seed will determine the dungeon layout, monsters, and items\n" + "that you discover, relative to this version of crawl. (See the \n" + "manual for more details.)" +#ifdef SEEDING_UNRELIABLE + "Warning: your build of crawl does not support stable seeding!\n" + "Levels may differ from 'official' seeded games." +#endif + ; auto popup = make_shared(box); ui::push_layout(move(popup)); + ui::set_focused_widget(prompt_ui.get()); +#ifdef USE_TILE_WEB + // activate seed selection popup + tiles.json_open_object(); + tiles.json_write_string("body", body_text); + tiles.json_write_string("title", title_text); + tiles.json_write_string("footer", footer_text); + tiles.push_ui_layout("seed-selection", 1); + // activate input box. TODO: have this happen automatically on the js side + // when the popup is in place? + tiles.json_open_object(); + tiles.json_write_string("msg", "init_input"); + tiles.json_write_string("type", "seed-selection"); + tiles.json_write_string("prefill", string(buf)); + tiles.json_write_string("prompt", prompt_text); + tiles.json_write_int("maxlen", sizeof(buf) - 1); + tiles.json_write_bool("select_prefill", true); + tiles.json_close_object(); + tiles.finish_message(); +#endif while (!done && !crawl_state.seen_hups) { + // TODO: rewrite with widgets formatted_string prompt; prompt.textcolour(CYAN); - prompt.cprintf("Play a game with a custom seed for version %s.\n\n", - Version::Long); + prompt.cprintf(title_text + "\n\n"); prompt.textcolour(LIGHTGREY); - prompt.cprintf( - "Choose 0 for a random seed. Press [d] for today's daily seed.\n" -#ifdef USE_TILE_LOCAL - "Press [p] to paste a seed from the clipboard (overwriting the\n" - "current value).\n" -#endif - "\n" - "The seed will determine the dungeon layout, monsters, and items\n" - "that you discover, relative to this version of the game.\n" - "Pregeneration will ensure that these remain the same no matter\n" - "what order you explore the dungeon in. (See the manual for more\n" - "details.)\n\n"); - prompt.cprintf("Seed ([-] to clear):"); + prompt.cprintf(body_text + "\n"); + + prompt.cprintf(prompt_text); string seed_text = make_stringf("%-20s", buf); prompt.cprintf("\n%s\n\n", seed_text.c_str()); + + prompt.cprintf(footer_text + "\n\n"); prompt_ui->set_text(prompt); // yes this appalling, some day we will have real buttons and text // input. The seed highlight doesn't do much on console, but makes // tiles look a lot better. N.b. the newline before the seed above // is really so that an empty seed string won't get multiple highlights. + // TODO: not implemented on the webtiles side. prompt_ui->set_highlight_pattern(seed_text, false); - if (choice.pregenerate) - pregen_choice->set_highlight_pattern("Yes", false); - else - pregen_choice->set_highlight_pattern("No", false); + if (show_pregen_toggle) + { + if (choice.pregenerate) + pregen_choice->set_highlight_pattern("Yes", false); + else + pregen_choice->set_highlight_pattern("No", false); + } ui::pump_events(); } ui::pop_layout(); +#ifdef USE_TILE_WEB + // decomission input box, if it's still around + tiles.json_open_object(); + tiles.json_write_string("msg", "close_input"); + tiles.json_close_object(); + tiles.finish_message(); + tiles.pop_ui_layout(); +#endif string result = reader.get_text(); uint64_t tmp_seed = 0; // TODO: if the user types in a number that exceeds the max value, sscanf @@ -789,7 +967,6 @@ bool choose_game(newgame_def& ng, newgame_def& choice, const newgame_def& defaults) { #ifdef USE_TILE_WEB - tiles_crt_popup show_as_popup; tiles.set_ui_state(UI_CRT); #endif @@ -868,290 +1045,12 @@ static void _mark_fully_random(newgame_def& ng, newgame_def& ng_choice, } } - -/** - * Helper function for _choose_species - * Constructs the menu screen - */ -static const int COLUMN_WIDTH = 35; -static const int X_MARGIN = 4; -static const int CHAR_DESC_START_Y = 16; -static const int CHAR_DESC_HEIGHT = 3; -static const int SPECIAL_KEYS_START_Y = CHAR_DESC_START_Y - + CHAR_DESC_HEIGHT + 1; - -static void _add_choice_menu_options(int choice_type, - const newgame_def& ng, - const newgame_def& defaults, - MenuFreeform* menu) -{ - string choice_name = choice_type == C_JOB ? "background" : "species"; - string other_choice_name = choice_type == C_JOB ? "species" : "background"; - - // Add all the special button entries - TextItem* tmp = new TextItem(); - if (choice_type == C_SPECIES) - tmp->set_text("+ - Recommended species"); - else - tmp->set_text("+ - Recommended background"); - coord_def min_coord = coord_def(X_MARGIN, SPECIAL_KEYS_START_Y); - coord_def max_coord = coord_def(min_coord.x + tmp->get_text().size(), - min_coord.y + 1); - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('+'); - // If the player has species chosen, use VIABLE, otherwise use RANDOM - if ((choice_type == C_SPECIES && ng.job != JOB_UNKNOWN) - || (choice_type == C_JOB && ng.species != SP_UNKNOWN)) - { - tmp->set_id(M_VIABLE); - } - else - tmp->set_id(M_RANDOM); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Picks a random recommended " + other_choice_name + " based on your current " + choice_name + " choice."); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("# - Recommended character"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 1; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('#'); - tmp->set_id(M_VIABLE_CHAR); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Shuffles through random recommended character combinations " - "until you accept one."); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("% - List aptitudes"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 2; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('%'); - tmp->set_id(M_APTITUDES); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Lists the numerical skill train aptitudes for all races."); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("? - Help"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 3; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('?'); - tmp->set_id(M_HELP); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Opens the help screen."); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("* - Random " + choice_name); - min_coord.x = X_MARGIN + COLUMN_WIDTH; - min_coord.y = SPECIAL_KEYS_START_Y; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('*'); - tmp->set_id(M_RANDOM); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Picks a random " + choice_name + "."); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("! - Random character"); - min_coord.x = X_MARGIN + COLUMN_WIDTH; - min_coord.y = SPECIAL_KEYS_START_Y + 1; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('!'); - tmp->set_id(M_RANDOM_CHAR); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Shuffles through random character combinations " - "until you accept one."); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - if ((choice_type == C_JOB && ng.species != SP_UNKNOWN) - || (choice_type == C_SPECIES && ng.job != JOB_UNKNOWN)) - { - tmp->set_text("Space - Change " + other_choice_name); - tmp->set_description_text("Lets you change your " + other_choice_name + - " choice."); - } - else - { - tmp->set_text("Space - Pick " + other_choice_name + " first"); - tmp->set_description_text("Lets you pick your " + other_choice_name + - " first."); - } - // Adjust the end marker to align the - because Space text is longer by 4 - min_coord.x = X_MARGIN + COLUMN_WIDTH - 4; - min_coord.y = SPECIAL_KEYS_START_Y + 2; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey(' '); - tmp->set_id(M_ABORT); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - menu->attach_item(tmp); - tmp->set_visible(true); - - if (_char_defined(defaults)) - { - tmp = new TextItem(); - tmp->set_text("Tab - " + _char_description(defaults)); - // Adjust the end marker to align the - because Tab text is longer by 2 - min_coord.x = X_MARGIN + COLUMN_WIDTH - 2; - min_coord.y = SPECIAL_KEYS_START_Y + 3; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('\t'); - tmp->set_id(M_DEFAULT_CHOICE); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Play a new game with your previous choice."); - menu->attach_item(tmp); - tmp->set_visible(true); - } -} - -static void _add_group_title(MenuFreeform* menu, - const char* name, - coord_def position, - int width) -{ - TextItem* tmp = new NoSelectTextItem(); - string text; - tmp->set_text(name); - tmp->set_fg_colour(LIGHTBLUE); - coord_def min_coord(2 + position.x, 3 + position.y); - coord_def max_coord(min_coord.x + width, min_coord.y + 1); - tmp->set_bounds(min_coord, max_coord); - menu->attach_item(tmp); - tmp->set_visible(true); -} - -static void _attach_group_item(MenuFreeform* menu, - menu_letter &letter, - int id, - int item_status, - string item_name, - bool is_active_item, - coord_def min_coord, - coord_def max_coord) - -{ - TextItem* tmp = new TextItem(); - - if (item_status == ITEM_STATUS_UNKNOWN) - { - tmp->set_fg_colour(LIGHTGRAY); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_NORMAL); - } - else if (item_status == ITEM_STATUS_RESTRICTED) - { - tmp->set_fg_colour(DARKGRAY); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_BAD); - } - else - { - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_GOOD); - } - - string text; - text += letter; - text += " - "; - text += item_name; - tmp->set_text(text); - tmp->set_bounds(min_coord, max_coord); - tmp->add_hotkey(letter); - tmp->set_id(id); - tmp->set_description_text(unwrap_desc(getGameStartDescription(item_name))); - menu->attach_item(tmp); - tmp->set_visible(true); - if (is_active_item) - menu->set_active_item(tmp); -} - -void species_group::attach(const newgame_def& ng, const newgame_def& defaults, - MenuFreeform* menu, menu_letter &letter) -{ - _add_group_title(menu, name, position, width); - - coord_def min_coord(2 + position.x, 3 + position.y); - coord_def max_coord(min_coord.x + width, min_coord.y + 1); - - for (species_type &this_species : species_list) - { - if (this_species == SP_UNKNOWN) - break; - - if (ng.job == JOB_UNKNOWN && !is_starting_species(this_species)) - continue; - - if (ng.job != JOB_UNKNOWN - && species_allowed(ng.job, this_species) == CC_BANNED) - { - continue; - } - - int item_status; - if (ng.job == JOB_UNKNOWN) - item_status = ITEM_STATUS_UNKNOWN; - else if (species_allowed(ng.job, this_species) == CC_RESTRICTED) - item_status = ITEM_STATUS_RESTRICTED; - else - item_status = ITEM_STATUS_ALLOWED; - - const bool is_active_item = defaults.species == this_species; - - ++min_coord.y; - ++max_coord.y; - - _attach_group_item( - menu, - letter, - this_species, - item_status, - species_name(this_species), - is_active_item, - min_coord, - max_coord - ); - - ++letter; - } -} +class UINewGameMenu; static void _construct_species_menu(const newgame_def& ng, const newgame_def& defaults, - MenuFreeform* menu) + UINewGameMenu* ng_menu) { - ASSERT(menu != nullptr); - menu_letter letter = 'a'; // Add entries for any species groups with at least one playable species. for (species_group& group : species_groups) @@ -1164,58 +1063,8 @@ static void _construct_species_menu(const newgame_def& ng, ) ) { - group.attach(ng, defaults, menu, letter); - } - } - - _add_choice_menu_options(C_SPECIES, ng, defaults, menu); -} - -void job_group::attach(const newgame_def& ng, const newgame_def& defaults, - MenuFreeform* menu, menu_letter &letter) -{ - _add_group_title(menu, name, position, width); - - coord_def min_coord(2 + position.x, 3 + position.y); - coord_def max_coord(min_coord.x + width, min_coord.y + 1); - - for (job_type &job : jobs) - { - if (job == JOB_UNKNOWN) - break; - - if (ng.species != SP_UNKNOWN - && job_allowed(ng.species, job) == CC_BANNED) - { - continue; + group.attach(ng, defaults, ng_menu, letter); } - - int item_status; - if (ng.species == SP_UNKNOWN) - item_status = ITEM_STATUS_UNKNOWN; - else if (job_allowed(ng.species, job) == CC_RESTRICTED) - item_status = ITEM_STATUS_RESTRICTED; - else - item_status = ITEM_STATUS_ALLOWED; - - string job_name = get_job_name(job); - const bool is_active_item = defaults.job == job; - - ++min_coord.y; - ++max_coord.y; - - _attach_group_item( - menu, - letter, - job, - item_status, - job_name, - is_active_item, - min_coord, - max_coord - ); - - ++letter; } } @@ -1223,28 +1072,28 @@ static job_group jobs_order[] = { { "Warrior", - coord_def(0, 0), 15, + coord_def(0, 0), 20, { JOB_FIGHTER, JOB_GLADIATOR, JOB_MONK, JOB_HUNTER, JOB_ASSASSIN } }, { "Adventurer", - coord_def(0, 7), 15, + coord_def(0, 7), 20, { JOB_ARTIFICER, JOB_WANDERER } }, { "Zealot", - coord_def(15, 0), 20, + coord_def(1, 0), 25, { JOB_BERSERKER, JOB_ABYSSAL_KNIGHT, JOB_CHAOS_KNIGHT } }, { "Warrior-mage", - coord_def(35, 0), 21, + coord_def(1, 5), 26, { JOB_SKALD, JOB_TRANSMUTER, JOB_WARPER, JOB_ARCANE_MARKSMAN, JOB_ENCHANTER } }, { "Mage", - coord_def(56, 0), 22, + coord_def(2, 0), 22, { JOB_WIZARD, JOB_CONJURER, JOB_SUMMONER, JOB_NECROMANCER, JOB_FIRE_ELEMENTALIST, JOB_ICE_ELEMENTALIST, JOB_AIR_ELEMENTALIST, JOB_EARTH_ELEMENTALIST, JOB_VENOM_MAGE } @@ -1257,7 +1106,7 @@ static job_group jobs_order[] = */ static void _construct_backgrounds_menu(const newgame_def& ng, const newgame_def& defaults, - MenuFreeform* menu) + UINewGameMenu* ng_menu) { menu_letter letter = 'a'; // Add entries for any job groups with at least one playable background. @@ -1267,279 +1116,509 @@ static void _construct_backgrounds_menu(const newgame_def& ng, || any_of(begin(group.jobs), end(group.jobs), [&ng](job_type job) { return job_allowed(ng.species, job) != CC_BANNED; })) { - group.attach(ng, defaults, menu, letter); + group.attach(ng, defaults, ng_menu, letter); } } - - _add_choice_menu_options(C_JOB, ng, defaults, menu); } class UINewGameMenu : public Widget { public: - UINewGameMenu(int _choice_type, newgame_def& _ng, newgame_def& _ng_choice, const newgame_def& _defaults) : done(false), end_game(false), cancel(false), choice_type(_choice_type), ng(_ng), ng_choice(_ng_choice), defaults(_defaults) {}; + UINewGameMenu(int _choice_type, newgame_def& _ng, newgame_def& _ng_choice, const newgame_def& _defaults) : m_choice_type(_choice_type), m_ng(_ng), m_ng_choice(_ng_choice), m_defaults(_defaults) + { + m_vbox = make_shared(Box::VERT); + m_vbox->_set_parent(this); + m_vbox->align_cross = Widget::Align::STRETCH; + + welcome.textcolour(BROWN); + welcome.cprintf("%s", _welcome(m_ng).c_str()); + welcome.textcolour(YELLOW); + welcome.cprintf(" Please select your "); + welcome.cprintf(m_choice_type == C_JOB ? "background." : "species."); + m_vbox->add_child(make_shared(welcome)); + + descriptions = make_shared(); + + m_main_items = make_shared(true, 3, 20); + m_main_items->menu_id = m_choice_type == C_JOB ? + "background-main" : "species-main"; + m_main_items->set_margin_for_crt(1, 0); + m_main_items->set_margin_for_sdl(15, 0); + m_main_items->descriptions = descriptions; + m_vbox->add_child(m_main_items); + +#ifndef USE_TILE_LOCAL + m_vbox->expand_h = true; + max_size() = { 80, INT_MAX }; +#endif + + descriptions->set_margin_for_crt(1, 0); + descriptions->set_margin_for_sdl(0, 0, 15, 0); + descriptions->current() = -1; + descriptions->shrink_h = true; + m_vbox->add_child(descriptions); + + if (m_choice_type == C_JOB) + _construct_backgrounds_menu(m_ng, m_defaults, this); + else + _construct_species_menu(m_ng, m_defaults, this); + + m_sub_items = make_shared(false, 2, 4); + m_sub_items->menu_id = m_choice_type == C_JOB ? + "background-sub" : "species-sub"; + m_sub_items->descriptions = descriptions; + m_vbox->add_child(m_sub_items); + _add_choice_menu_options(m_choice_type, m_ng, m_defaults); + + m_main_items->linked_menus[2] = m_sub_items; + m_sub_items->linked_menus[0] = m_main_items; + + m_main_items->on_button_activated = m_sub_items->on_button_activated = + [this](int id) { this->menu_item_activated(id); }; + + for (auto &w : m_main_items->get_buttons()) + { + w->on(Widget::slots.event, [w, this](wm_event ev) { + return this->button_event_hook(ev, w); + }); + } + for (auto &w : m_sub_items->get_buttons()) + { + w->on(Widget::slots.event, [w, this](wm_event ev) { + return this->button_event_hook(ev, w); + }); + } + }; + + virtual shared_ptr get_child_at_offset(int x, int y) override { + return static_pointer_cast(m_vbox); + } virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; virtual bool on_event(const wm_event& event) override; - bool done; - bool end_game; - bool cancel; -private: - PrecisionMenu menu; +#ifdef USE_TILE_WEB + void serialize(); +#endif - int choice_type; - newgame_def& ng; - newgame_def& ng_choice; - const newgame_def& defaults; -}; + void menu_item_activated(int id); -void UINewGameMenu::_render() -{ -#ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {1, 1, 1}; - glmanager->set_transform(t, s); -#endif - menu.draw_menu(); -#ifdef USE_TILE_LOCAL - glmanager->reset_transform(); + bool done = false; + bool end_game = false; + bool cancel = false; + +protected: + friend struct job_group; + friend struct species_group; + void _add_group_item(menu_letter &letter, + int id, + int item_status, + string item_name, +#ifdef USE_TILE + tile_def item_tile, #endif -} + bool is_active_item, + coord_def position) -SizeReq UINewGameMenu::_get_preferred_size(Direction dim, int prosp_width) -{ - SizeReq ret; - if (!dim) - ret = { 80, 80 }; - else - ret = { 24, 24 }; -#ifdef USE_TILE_LOCAL - const FontWrapper* font = tiles.get_crt_font(); - const int f = !dim ? font->char_width() : font->char_height(); - ret.min *= f; - ret.nat *= f; + { + auto label = make_shared(); + +#ifdef USE_TILE + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + hbox->align_main = Widget::Align::STRETCH; + auto tile = make_shared(); + tile->set_tile(item_tile); + tile->set_margin_for_sdl(0, 6, 0, 0); + tile->flex_grow = 0; + hbox->add_child(move(tile)); + hbox->add_child(label); #endif - return ret; -} -void UINewGameMenu::_allocate_region() -{ - menu.clear(); + COLOURS fg, hl; -#ifdef USE_TILE_LOCAL - const FontWrapper* font = tiles.get_crt_font(); - const int num_cols = m_region[2]/font->char_width(); - const int num_lines = m_region[3]/font->char_height(); + if (item_status == ITEM_STATUS_UNKNOWN) + { + fg = LIGHTGRAY; + hl = STARTUP_HIGHLIGHT_NORMAL; + } + else if (item_status == ITEM_STATUS_RESTRICTED) + { + fg = DARKGRAY; + hl = STARTUP_HIGHLIGHT_BAD; + } + else + { + fg = WHITE; + hl = STARTUP_HIGHLIGHT_GOOD; + } + + string text; + text += letter; + text += " - "; + text += item_name; + label->set_text(formatted_string(text, fg)); + + string desc = unwrap_desc(getGameStartDescription(item_name)); + trim_string(desc); + + auto btn = make_shared(); +#ifdef USE_TILE + hbox->set_margin_for_sdl(2, 10, 2, 2); + btn->set_child(move(hbox)); #else - const int num_cols = m_region[2], num_lines = m_region[3]; + btn->set_child(move(label)); #endif + btn->id = id; + btn->description = desc; + btn->hotkey = letter; + btn->highlight_colour = hl; + btn->set_margin_for_crt(0, 1, 0, 0); - menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT); - MenuFreeform* freeform = new MenuFreeform(); - freeform->init(coord_def(0,0), coord_def(num_cols + 1, num_lines + 1), "freeform"); - menu.attach_object(freeform); - menu.set_active_object(freeform); + m_main_items->add_button(btn, position.x, position.y); - auto welcome_text = new FormattedTextItem(); - formatted_string welcome; - welcome.textcolour(BROWN); - welcome.cprintf("%s", _welcome(ng).c_str()); + if (is_active_item || position == coord_def(0, 1)) + m_main_items->set_initial_focus(btn.get()); + } - welcome.textcolour(YELLOW); - if (choice_type == C_JOB) + void _add_group_title(const char* name, coord_def position) { - welcome.cprintf(" Please select your background."); - _construct_backgrounds_menu(ng, defaults, freeform); + auto text = make_shared(formatted_string(name, LIGHTBLUE)); + text->set_margin_for_sdl(7, 0, 7, 32+2+6); + m_main_items->add_label(move(text), position.x, position.y); } - else + + void _add_choice_menu_option(int x, int y, const string& text, char letter, + int id, const string& desc) { - welcome.cprintf(" Please select your species."); - _construct_species_menu(ng, defaults, freeform); + _add_menu_sub_item(m_sub_items, x, y, text, desc, letter, id); } - welcome_text->set_text(welcome.to_colour_string()); - welcome_text->set_bounds(coord_def(1, 1), coord_def(num_cols+1, 2)); - welcome_text->set_visible(true); - welcome_text->allow_highlight(false); - freeform->attach_item(welcome_text); - - MenuDescriptor* descriptor = new MenuDescriptor(&menu); - descriptor->init( - coord_def(X_MARGIN, CHAR_DESC_START_Y), - coord_def(num_cols, CHAR_DESC_START_Y + CHAR_DESC_HEIGHT), - "descriptor" - ); - menu.attach_object(descriptor); - - BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu); - highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter"); - menu.attach_object(highlighter); - - // Did we have a previous background? - if (menu.get_active_item() == nullptr) - freeform->activate_first_item(); - - freeform->set_visible(true); - descriptor->set_visible(true); - highlighter->set_visible(true); -} - -bool UINewGameMenu::on_event(const wm_event& ev) -{ -#ifdef USE_TILE_LOCAL - if (ev.type == WME_MOUSEMOTION - || ev.type == WME_MOUSEBUTTONDOWN - || ev.type == WME_MOUSEWHEEL) + void _add_choice_menu_options(int choice_type, + const newgame_def& ng, + const newgame_def& defaults) { - MouseEvent mouse_ev = ev.mouse_event; - mouse_ev.px -= m_region[0]; - mouse_ev.py -= m_region[1]; + string choice_name = choice_type == C_JOB ? "background" : "species"; + string other_choice_name = choice_type == C_JOB ? "species" : "background"; - int key = menu.handle_mouse(mouse_ev); - if (key && key != CK_NO_KEY) + string text, desc; + + if (choice_type == C_SPECIES) + text = "+ - Recommended species"; + else + text = "+ - Recommended background"; + + int id; + // If the player has species chosen, use VIABLE, otherwise use RANDOM + if ((choice_type == C_SPECIES && ng.job != JOB_UNKNOWN) + || (choice_type == C_JOB && ng.species != SP_UNKNOWN)) { - wm_event fake_key = {0}; - fake_key.type = WME_KEYDOWN; - fake_key.key.keysym.sym = key; - on_event(fake_key); + id = M_VIABLE; } + else + id = M_RANDOM; + desc = "Picks a random recommended " + other_choice_name + " based on your current " + choice_name + " choice."; - if (ev.type == WME_MOUSEMOTION) - _expose(); - return true; + _add_choice_menu_option(0, 0, + text, '+', id, desc); + + _add_choice_menu_option(0, 1, + "# - Recommended character", '#', M_VIABLE_CHAR, + "Shuffles through random recommended character combinations " + "until you accept one."); + + _add_choice_menu_option(0, 2, + "% - List aptitudes", '%', M_APTITUDES, + "Lists the numerical skill train aptitudes for all races."); + + _add_choice_menu_option(0, 3, + "? - Help", '?', M_HELP, + "Opens the help screen."); + + _add_choice_menu_option(1, 0, + " * - Random " + choice_name, '*', M_RANDOM, + "Picks a random " + choice_name + "."); + + _add_choice_menu_option(1, 1, + " ! - Random character", '!', M_RANDOM_CHAR, + "Shuffles through random character combinations " + "until you accept one."); + + if ((choice_type == C_JOB && ng.species != SP_UNKNOWN) + || (choice_type == C_SPECIES && ng.job != JOB_UNKNOWN)) + { + text = "Space - Change " + other_choice_name; + desc = "Lets you change your " + other_choice_name + " choice."; + } + else + { + text = "Space - Pick " + other_choice_name + " first"; + desc = "Lets you pick your " + other_choice_name + " first."; + } + _add_choice_menu_option(1, 2, + text, ' ', M_ABORT, desc); + + if (_char_defined(defaults)) + { + _add_choice_menu_option(1, 3, + " Tab - " + newgame_char_description(defaults), '\t', + M_DEFAULT_CHOICE, + "Play a new game with your previous choice."); + } + } + +private: + bool button_event_hook(const wm_event& ev, MenuButton* btn) + { + if (ev.type == WME_KEYDOWN) + return on_event(ev); + return false; } -#endif + formatted_string welcome; + int m_choice_type; + newgame_def& m_ng; + newgame_def& m_ng_choice; + const newgame_def& m_defaults; + + shared_ptr m_vbox; + shared_ptr m_main_items; + shared_ptr m_sub_items; + shared_ptr description; + shared_ptr descriptions; +}; + +void UINewGameMenu::_render() +{ + m_vbox->render(); +} + +SizeReq UINewGameMenu::_get_preferred_size(Direction dim, int prosp_width) +{ + return m_vbox->get_preferred_size(dim, prosp_width); +} + +void UINewGameMenu::_allocate_region() +{ + m_vbox->allocate_region(m_region); +} + +bool UINewGameMenu::on_event(const wm_event& ev) +{ if (ev.type != WME_KEYDOWN) return false; int keyn = ev.key.keysym.sym; - _expose(); - // First process all the menu entries available - if (!menu.process_key(keyn)) + if (keyn != CK_ENTER) { // Process all the other keys that are not assigned to the menu switch (keyn) { case 'X': case CONTROL('Q'): - cprintf("\nGoodbye!"); #ifdef USE_TILE_WEB tiles.send_exit_reason("cancel"); #endif return done = end_game = true; CASE_ESCAPE case CK_MOUSE_CMD: -#ifdef USE_TILE_WEB - tiles.send_exit_reason("cancel"); -#endif return done = cancel = true; case CK_BKSP: - if (choice_type == C_JOB) - ng_choice.job = JOB_UNKNOWN; + if (m_choice_type == C_JOB) + m_ng_choice.job = JOB_UNKNOWN; else - ng_choice.species = SP_UNKNOWN; + m_ng_choice.species = SP_UNKNOWN; return done = true; default: - // if we get this far, we did not get a significant selection - // from the menu, nor did we get an escape character - // continue the while loop from the beginning and poll a new key - return true; + break; } } - // We have had a significant input key event - // construct the return vector - vector selection = menu.get_selected_items(); - menu.clear_selections(); - if (!selection.empty()) - { - // we have a selection! - // we only care about the first selection (there should be only one) - int selection_key = selection.at(0)->get_id(); - bool viable = false; - switch (selection_key) + return false; +} + + +#ifdef USE_TILE_WEB +void UINewGameMenu::serialize() +{ + tiles.json_write_string("title", welcome.to_colour_string()); + m_main_items->serialize("main-items"); + m_sub_items->serialize("sub-items"); +} +#endif + +void UINewGameMenu::menu_item_activated(int id) +{ + bool viable = false; + switch (id) + { + case M_VIABLE_CHAR: + viable = true; + // intentional fall-through + case M_RANDOM_CHAR: + _mark_fully_random(m_ng, m_ng_choice, viable); + done = true; + return; + case M_DEFAULT_CHOICE: + if (_char_defined(m_defaults)) { - case M_VIABLE_CHAR: - viable = true; - // intentional fall-through - case M_RANDOM_CHAR: - _mark_fully_random(ng, ng_choice, viable); - return done = true; - case M_DEFAULT_CHOICE: - if (_char_defined(defaults)) - { - _set_default_choice(ng, ng_choice, defaults); - return done = true; - } - else - { - // ignore default because we don't have previous start options - return true; - } - case M_ABORT: - ng.species = ng_choice.species = SP_UNKNOWN; - ng.job = ng_choice.job = JOB_UNKNOWN; - return done = true; - case M_HELP: - // access to the help files - if (choice_type == C_JOB) - show_help('2'); - else - show_help('1'); - return true; - case M_APTITUDES: - show_help('%', _highlight_pattern(ng)); - return true; - case M_VIABLE: - if (choice_type == C_JOB) - ng_choice.job = JOB_VIABLE; - else - ng_choice.species = SP_VIABLE; - return done = true; - case M_RANDOM: - if (choice_type == C_JOB) - ng_choice.job = JOB_RANDOM; - else - ng_choice.species = SP_RANDOM; - return done = true; - default: - // we have a selection - if (choice_type == C_JOB) + _set_default_choice(m_ng, m_ng_choice, m_defaults); + done = true; + } + // ignore default because we don't have previous start options + return; + case M_ABORT: + m_ng.species = m_ng_choice.species = SP_UNKNOWN; + m_ng.job = m_ng_choice.job = JOB_UNKNOWN; + done = true; + return; + case M_HELP: + show_help(m_choice_type == C_JOB ? '2' : '1'); + return; + case M_APTITUDES: + show_help('%', _highlight_pattern(m_ng)); + return; + case M_VIABLE: + if (m_choice_type == C_JOB) + m_ng_choice.job = JOB_VIABLE; + else + m_ng_choice.species = SP_VIABLE; + done = true; + return; + case M_RANDOM: + if (m_choice_type == C_JOB) + m_ng_choice.job = JOB_RANDOM; + else + m_ng_choice.species = SP_RANDOM; + done = true; + return; + default: + // we have a selection + if (m_choice_type == C_JOB) + { + job_type job = static_cast (id); + if (m_ng.species == SP_UNKNOWN + || job_allowed(m_ng.species, job) != CC_BANNED) { - job_type job = static_cast (selection_key); - if (ng.species == SP_UNKNOWN - || job_allowed(ng.species, job) != CC_BANNED) - { - ng_choice.job = job; - return done = true; - } - else - { - selection.at(0)->select(false); - return true; - } + m_ng_choice.job = job; + done = true; } - else + } + else + { + species_type species = static_cast (id); + if (m_ng.job == JOB_UNKNOWN + || species_allowed(m_ng.job, species) != CC_BANNED) { - species_type species = static_cast (selection_key); - if (ng.job == JOB_UNKNOWN - || species_allowed(ng.job, species) != CC_BANNED) - { - ng_choice.species = species; - return done = true; - } - else - return true; + m_ng_choice.species = species; + done = true; } } + return; + } +} + +void job_group::attach(const newgame_def& ng, const newgame_def& defaults, + UINewGameMenu* ng_menu, menu_letter &letter) +{ + ng_menu->_add_group_title(name, position); + + coord_def pos(position); + + for (job_type &job : jobs) + { + if (job == JOB_UNKNOWN) + break; + + if (ng.species != SP_UNKNOWN + && job_allowed(ng.species, job) == CC_BANNED) + { + continue; + } + + int item_status; + if (ng.species == SP_UNKNOWN) + item_status = ITEM_STATUS_UNKNOWN; + else if (job_allowed(ng.species, job) == CC_RESTRICTED) + item_status = ITEM_STATUS_RESTRICTED; + else + item_status = ITEM_STATUS_ALLOWED; + + const bool is_active_item = defaults.job == job; + + ++pos.y; + + ng_menu->_add_group_item( + letter, + job, + item_status, + get_job_name(job), +#ifdef USE_TILE + tile_def(tileidx_player_job(job, + item_status != ITEM_STATUS_RESTRICTED), TEX_GUI), +#endif + is_active_item, + pos + ); + + ++letter; } - return true; } +void species_group::attach(const newgame_def& ng, const newgame_def& defaults, + UINewGameMenu* ng_menu, menu_letter &letter) +{ + ng_menu->_add_group_title(name, position); + + coord_def pos(position); + + for (species_type &this_species : species_list) + { + if (this_species == SP_UNKNOWN) + break; + + if (ng.job == JOB_UNKNOWN && !is_starting_species(this_species)) + continue; + + if (ng.job != JOB_UNKNOWN + && species_allowed(ng.job, this_species) == CC_BANNED) + { + continue; + } + + int item_status; + if (ng.job == JOB_UNKNOWN) + item_status = ITEM_STATUS_UNKNOWN; + else if (species_allowed(ng.job, this_species) == CC_RESTRICTED) + item_status = ITEM_STATUS_RESTRICTED; + else + item_status = ITEM_STATUS_ALLOWED; + + const bool is_active_item = defaults.species == this_species; + + ++pos.y; + + ng_menu->_add_group_item( + letter, + this_species, + item_status, + species_name(this_species), +#ifdef USE_TILE + tile_def(tileidx_player_species(this_species, + item_status != ITEM_STATUS_RESTRICTED), TEX_GUI), +#endif + is_active_item, + pos + ); + + ++letter; + } +} + + /** * Prompt for job or species menu * Saves the choice to ng_choice, doesn't resolve random choices. @@ -1553,7 +1632,21 @@ static void _prompt_choice(int choice_type, newgame_def& ng, newgame_def& ng_cho auto newgame_ui = make_shared(choice_type, ng, ng_choice, defaults); auto popup = make_shared(newgame_ui); - ui::run_layout(move(popup), newgame_ui->done); +#ifdef USE_TILE_WEB + tiles.json_open_object(); + newgame_ui->serialize(); + tiles.push_ui_layout("newgame-choice", 1); +#endif + + ui::push_layout(move(popup)); + ui::set_focused_widget(newgame_ui.get()); + while (!newgame_ui->done && !crawl_state.seen_hups) + ui::pump_events(); + ui::pop_layout(); + +#ifdef USE_TILE_WEB + tiles.pop_ui_layout(); +#endif if (newgame_ui->end_game) end(0); @@ -1574,94 +1667,68 @@ static weapon_type _fixup_weapon(weapon_type wp, return WPN_UNKNOWN; } -static const int WEAPON_COLUMN_WIDTH = 40; static void _construct_weapon_menu(const newgame_def& ng, const weapon_type& defweapon, const vector& weapons, - MenuFreeform* menu) + shared_ptr& main_items, + shared_ptr& sub_items) { -#ifdef USE_TILE_LOCAL - static const int ITEMS_START_Y = 4; -#else - static const int ITEMS_START_Y = 5; -#endif - string text; - const char *thrown_name = nullptr; - coord_def min_coord(0,0); - coord_def max_coord(0,0); + struct weapon_menu_item { + skill_type skill; + string label; + tileidx_t tile; + weapon_menu_item(skill_type _skill, string _label, tileidx_t _tile) + : skill(move(_skill)), label(move(_label)), tile(move(_tile)) {}; + weapon_menu_item(skill_type _skill, string _label) + : skill(move(_skill)), label(move(_label)), tile(0) {}; + }; + vector choices; + + string thrown_name; for (unsigned int i = 0; i < weapons.size(); ++i) { weapon_type wpn_type = weapons[i].first; - char_choice_restriction wpn_restriction = weapons[i].second; -#ifdef USE_TILE_LOCAL - TextTileItem *tmp = new TextTileItem(); -#else - TextItem *tmp = new TextItem(); -#endif - text.clear(); - if (wpn_restriction == CC_UNRESTRICTED) - { - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_GOOD); - } - else - { - tmp->set_fg_colour(DARKGRAY); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_BAD); - } - const char letter = 'a' + i; - tmp->add_hotkey(letter); - tmp->set_id(wpn_type); - - text += make_stringf(" %c - ", letter); switch (wpn_type) { case WPN_UNARMED: - text += species_has_claws(ng.species) ? "claws" : "unarmed"; -#ifdef USE_TILE_LOCAL - tmp->add_tile(tile_def(DNGN_UNSEEN, TEX_DEFAULT)); -#endif + choices.emplace_back(SK_UNARMED_COMBAT, species_has_claws(ng.species) ? "claws" : "unarmed"); break; case WPN_THROWN: + { // We don't support choosing among multiple thrown weapons. - ASSERT(!thrown_name); -#ifdef USE_TILE_LOCAL - tmp->add_tile(tile_def(TILE_MI_THROWING_NET, TEX_DEFAULT)); -#endif + tileidx_t tile = 0; if (species_can_throw_large_rocks(ng.species)) { thrown_name = "large rocks"; -#ifdef USE_TILE_LOCAL - tmp->add_tile(tile_def(TILE_MI_LARGE_ROCK, TEX_DEFAULT)); +#ifdef USE_TILE + tile = TILE_MI_LARGE_ROCK; #endif } else if (species_size(ng.species, PSIZE_TORSO) <= SIZE_SMALL) { - thrown_name = "tomahawks"; -#ifdef USE_TILE_LOCAL - tmp->add_tile(tile_def(TILE_MI_TOMAHAWK, TEX_DEFAULT)); + thrown_name = "boomerangs"; +#ifdef USE_TILE + tile = TILE_MI_BOOMERANG; #endif } else { thrown_name = "javelins"; -#ifdef USE_TILE_LOCAL - tmp->add_tile(tile_def(TILE_MI_JAVELIN, TEX_DEFAULT)); +#ifdef USE_TILE + tile = TILE_MI_JAVELIN; #endif } - text += thrown_name; - text += " and throwing nets"; + choices.emplace_back(SK_THROWING, + thrown_name + " and throwing nets", tile); break; + } default: - text += weapon_base_name(wpn_type); -#ifdef USE_TILE_LOCAL + string text = weapon_base_name(wpn_type); item_def dummy; dummy.base_type = OBJ_WEAPONS; dummy.sub_type = wpn_type; - tmp->add_tile(tile_def(tileidx_item(dummy), TEX_DEFAULT)); -#endif if (is_ranged_weapon_type(wpn_type)) { text += " and "; @@ -1669,205 +1736,121 @@ static void _construct_weapon_menu(const newgame_def& ng, : ammo_name(wpn_type); text += "s"; } + choices.emplace_back(item_attack_skill(dummy), text +#ifdef USE_TILE + , tileidx_item(dummy) +#endif + ); break; } - // Fill to column width to give extra padding for the highlight - text.append(WEAPON_COLUMN_WIDTH - text.size() - 1 , ' '); - tmp->set_text(text); + } - min_coord.x = X_MARGIN; - min_coord.y = ITEMS_START_Y + i; - max_coord.x = min_coord.x + text.size(); -#ifdef USE_TILE_LOCAL - const int cw = tiles.get_crt_font()->char_width(); - max_coord.x += (TILE_Y+cw-1)/cw; + int max_text_width = 0; + for (const auto& choice : choices) + max_text_width = max(max_text_width, strwidth(choice.label)); + + for (unsigned int i = 0; i < weapons.size(); ++i) + { + const auto& choice = choices[i]; + + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + hbox->set_margin_for_sdl(2, 10, 2, 2); + +#ifdef USE_TILE + auto tile_stack = make_shared(); + tile_stack->set_margin_for_sdl(0, 6, 0, 0); + tile_stack->flex_grow = 0; + hbox->add_child(tile_stack); + + if (choice.skill == SK_THROWING) + { + tile_stack->add_child(make_shared( + tile_def(TILE_MI_THROWING_NET, TEX_DEFAULT))); + } + if (choice.skill == SK_UNARMED_COMBAT) + { + tile_stack->min_size() = +#ifdef USE_TILE_WEB + // these dimensions are apparently unused for + // webtiles, we do this so they're not interpreted + // as characters for webtiles console. + 0; +#else + TILE_Y; #endif - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); + } + else + { + tile_stack->add_child(make_shared( + tile_def(choice.tile, TEX_DEFAULT))); + } +#endif + + auto label = make_shared(); + hbox->add_child(label); + + const char letter = 'a' + i; + + string text = make_stringf(" %c - %s", letter, + chop_string(choice.label, max_text_width, true).c_str() + ); + + weapon_type wpn_type = weapons[i].first; + char_choice_restriction wpn_restriction = weapons[i].second; + + const auto fg = wpn_restriction == CC_UNRESTRICTED ? WHITE : LIGHTGREY; + const auto bg = wpn_restriction == CC_UNRESTRICTED ? + STARTUP_HIGHLIGHT_GOOD : STARTUP_HIGHLIGHT_BAD; + + label->set_text(formatted_string(text, fg)); + + hbox->align_main = Widget::Align::STRETCH; + string apt_text = make_stringf("(%+d apt)", + species_apt(choice.skill, ng.species)); + auto suffix = make_shared(formatted_string(apt_text, fg)); + hbox->add_child(suffix); + + auto btn = make_shared(); + btn->set_child(move(hbox)); + btn->id = wpn_type; + btn->hotkey = letter; + btn->highlight_colour = bg; - menu->attach_item(tmp); - tmp->set_visible(true); // Is this item our default weapon? - if (wpn_type == defweapon) - menu->set_active_item(tmp); + if (wpn_type == defweapon || (defweapon == WPN_UNKNOWN && i == 0)) + main_items->set_initial_focus(btn.get()); + main_items->add_button(move(btn), 0, i); } - // Add all the special button entries - TextItem *tmp = new TextItem(); - tmp->set_text("+ - Recommended random choice"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('+'); - tmp->set_id(M_VIABLE); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Picks a random recommended weapon"); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("% - List aptitudes"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 1; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('%'); - tmp->set_id(M_APTITUDES); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Lists the numerical skill train aptitudes for all races"); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("? - Help"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 2; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('?'); - tmp->set_id(M_HELP); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Opens the help screen"); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("* - Random weapon"); - min_coord.x = X_MARGIN + COLUMN_WIDTH; - min_coord.y = SPECIAL_KEYS_START_Y; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('*'); - tmp->set_id(WPN_RANDOM); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Picks a random weapon"); - menu->attach_item(tmp); - tmp->set_visible(true); - // Adjust the end marker to align the - because Bksp text is longer by 3 - tmp = new TextItem(); - tmp->set_text("Bksp - Return to character menu"); - tmp->set_description_text("Lets you return back to Character choice menu"); - min_coord.x = X_MARGIN + COLUMN_WIDTH - 3; - min_coord.y = SPECIAL_KEYS_START_Y + 1; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey(CK_BKSP); - tmp->set_id(M_ABORT); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - menu->attach_item(tmp); - tmp->set_visible(true); + _add_menu_sub_item(sub_items, 0, 0, "+ - Recommended random choice", + "Picks a random recommended weapon", '+', M_VIABLE); + _add_menu_sub_item(sub_items, 0, 1, "% - List aptitudes", + "Lists the numerical skill train aptitudes for all races", '%', + M_APTITUDES); + _add_menu_sub_item(sub_items, 0, 2, "? - Help", + "Opens the help screen", '?', M_HELP); + _add_menu_sub_item(sub_items, 1, 0, "* - Random weapon", + "Picks a random weapon", '*', WPN_RANDOM); + _add_menu_sub_item(sub_items, 1, 1, "Bksp - Return to character menu", + "Lets you return back to Character choice menu", CK_BKSP, M_ABORT); if (defweapon != WPN_UNKNOWN) { - text.clear(); - text = "Tab - "; + string text = "Tab - "; - ASSERT(defweapon != WPN_THROWN || thrown_name); + ASSERT(defweapon != WPN_THROWN || thrown_name != ""); text += defweapon == WPN_RANDOM ? "Random" : defweapon == WPN_VIABLE ? "Recommended" : defweapon == WPN_UNARMED ? "unarmed" : defweapon == WPN_THROWN ? thrown_name : weapon_base_name(defweapon); - // Adjust the end marker to aling the - because - // Tab text is longer by 2 - tmp = new TextItem(); - tmp->set_text(text); - min_coord.x = X_MARGIN + COLUMN_WIDTH - 2; - min_coord.y = SPECIAL_KEYS_START_Y + 2; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('\t'); - tmp->set_id(M_DEFAULT_CHOICE); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_CONTROL); - tmp->set_description_text("Select your old weapon"); - menu->attach_item(tmp); - tmp->set_visible(true); + _add_menu_sub_item(sub_items, 1, 2, text, + "Select your old weapon", '\t', M_DEFAULT_CHOICE); } } -class UIPrecisionMenuWrapper : public Widget -{ -public: - UIPrecisionMenuWrapper(int _w, int _h, PrecisionMenu &_menu) : w(_w), h(_h), menu(_menu) {}; - - virtual void _render() override; - virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; - virtual bool on_event(const wm_event& event) override; -private: - int w, h; - PrecisionMenu& menu; -}; - -SizeReq UIPrecisionMenuWrapper::_get_preferred_size(Direction dim, int prosp_width) -{ - SizeReq ret; - if (!dim) - ret = { w, w }; - else - ret = { h, h }; -#ifdef USE_TILE_LOCAL - const FontWrapper* font = tiles.get_crt_font(); - const int f = !dim ? font->char_width() : font->char_height(); - ret.min *= f; - ret.nat *= f; -#endif - return ret; -} - -void UIPrecisionMenuWrapper::_render() -{ -#ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {1, 1, 1}; - glmanager->set_transform(t, s); -#endif - menu.draw_menu(); -#ifdef USE_TILE_LOCAL - glmanager->reset_transform(); -#endif -} - -bool UIPrecisionMenuWrapper::on_event(const wm_event& ev) -{ -#ifdef USE_TILE_LOCAL - if (ev.type == WME_MOUSEMOTION - || ev.type == WME_MOUSEBUTTONDOWN - || ev.type == WME_MOUSEWHEEL) - { - MouseEvent mouse_ev = ev.mouse_event; - mouse_ev.px -= m_region[0]; - mouse_ev.py -= m_region[1]; - - int key = menu.handle_mouse(mouse_ev); - if (key && key != CK_NO_KEY) - { - wm_event fake_key = {0}; - fake_key.type = WME_KEYDOWN; - fake_key.key.keysym.sym = key; - on_event(fake_key); - } - - if (ev.type == WME_MOUSEMOTION) - _expose(); - return true; - } -#endif - return Widget::on_event(ev); -} - /** * Returns false if user escapes */ @@ -1875,58 +1858,94 @@ static bool _prompt_weapon(const newgame_def& ng, newgame_def& ng_choice, const newgame_def& defaults, const vector& weapons) { - const int ui_w = 80, ui_h = 24; - PrecisionMenu menu; - menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT); - MenuFreeform* freeform = new MenuFreeform(); - freeform->init(coord_def(1,1), coord_def(ui_w+1, ui_h+1), "freeform"); - menu.attach_object(freeform); - menu.set_active_object(freeform); - weapon_type defweapon = _fixup_weapon(defaults.weapon, weapons); - _construct_weapon_menu(ng, defweapon, weapons, freeform); + auto title_hbox = make_shared(Widget::HORZ); +#ifdef USE_TILE + dolls_data doll; + fill_doll_for_newgame(doll, ng); +#ifdef USE_TILE_LOCAL + auto tile = make_shared(doll); + tile->set_margin_for_sdl(0, 10, 0, 0); + title_hbox->add_child(move(tile)); +#endif +#endif + auto title = make_shared(formatted_string(_welcome(ng), BROWN)); + title_hbox->add_child(title); + title_hbox->align_cross = Widget::CENTER; + title_hbox->set_margin_for_sdl(0, 0, 20, 0); + title_hbox->set_margin_for_crt(0, 0, 1, 0); + + auto vbox = make_shared(Box::VERT); + vbox->align_cross = Widget::Align::STRETCH; + vbox->add_child(title_hbox); + auto prompt = make_shared(formatted_string("You have a choice of weapons.", CYAN)); + vbox->add_child(prompt); - BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu); - highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter"); - menu.attach_object(highlighter); + auto main_items = make_shared(true, 1, weapons.size()); + main_items->menu_id = "weapon-main"; + main_items->set_margin_for_sdl(15, 0); + main_items->set_margin_for_crt(1, 0); + vbox->add_child(main_items); - // Did we have a previous weapon? - if (menu.get_active_item() == nullptr) - freeform->activate_first_item(); + auto sub_items = make_shared(false, 2, 3); + sub_items->menu_id = "weapon-sub"; + vbox->add_child(sub_items); - auto welcome_text = new FormattedTextItem(); - formatted_string welcome; - welcome.textcolour(BROWN); - welcome.cprintf("%s\n", _welcome(ng).c_str()); - welcome.textcolour(CYAN); - welcome.cprintf("\nYou have a choice of weapons: "); - welcome_text->set_text(welcome.to_colour_string()); - welcome_text->set_bounds(coord_def(1, 1), coord_def(ui_w+1, 5)); - welcome_text->set_visible(true); - welcome_text->allow_highlight(false); - freeform->attach_item(welcome_text); + main_items->linked_menus[2] = sub_items; + sub_items->linked_menus[0] = main_items; - freeform->set_visible(true); - highlighter->set_visible(true); + _construct_weapon_menu(ng, defweapon, weapons, main_items, sub_items); bool done = false, ret = false; - auto prompt_ui = make_shared(ui_w, ui_h, menu); - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { + + auto menu_item_activated = [&](int id) { + switch (id) + { + case M_ABORT: + ret = false; + done = true; + return; + case M_APTITUDES: + show_help('%', _highlight_pattern(ng)); + return; + case M_HELP: + show_help('?'); + return; + case M_DEFAULT_CHOICE: + if (defweapon != WPN_UNKNOWN) + { + ng_choice.weapon = defweapon; + break; + } + // No default weapon defined. + // This case should never happen in those cases but just in case + return; + case M_VIABLE: + ng_choice.weapon = WPN_VIABLE; + break; + case M_RANDOM: + ng_choice.weapon = WPN_RANDOM; + break; + default: + ng_choice.weapon = static_cast (id); + break; + } + ret = done = true; + }; + main_items->on_button_activated = menu_item_activated; + sub_items->on_button_activated = menu_item_activated; + + auto popup = make_shared(vbox); + popup->add_event_filter([&](wm_event ev) { if (ev.type != WME_KEYDOWN) return false; - int keyn = ev.key.keysym.sym; - prompt_ui->_expose(); + int key = ev.key.keysym.sym; - // First process menu entries - if (!menu.process_key(keyn)) + switch (key) { - // Process all the keys that are not attached to items - switch (keyn) - { case 'X': case CONTROL('Q'): - cprintf("\nGoodbye!"); #ifdef USE_TILE_WEB tiles.send_exit_reason("cancel"); #endif @@ -1938,66 +1957,26 @@ static bool _prompt_weapon(const newgame_def& ng, newgame_def& ng_choice, ret = false; return done = true; default: - // if we get this far, we did not get a significant selection - // from the menu, nor did we get an escape character - // continue the while loop from the beginning and poll a new key - return true; - } - } - // We have a significant key input! - // Construct selection vector - vector selection = menu.get_selected_items(); - menu.clear_selections(); - // There should only be one selection, otherwise something broke - if (selection.size() != 1) - { - // poll a new key - return true; - } - - // Get the stored id from the selection - int selection_ID = selection.at(0)->get_id(); - switch (selection_ID) - { - case M_ABORT: - ret = false; - return done = true; - case M_APTITUDES: - show_help('%', _highlight_pattern(ng)); - return true; - case M_HELP: - show_help('G', "weapons"); - return true; - case M_DEFAULT_CHOICE: - if (defweapon != WPN_UNKNOWN) - { - ng_choice.weapon = defweapon; - ret = true; - return done = true; - } - // No default weapon defined. - // This case should never happen in those cases but just in case - return true; - case M_VIABLE: - ng_choice.weapon = WPN_VIABLE; - ret = true; - return done = true; - case M_RANDOM: - ng_choice.weapon = WPN_RANDOM; - ret = true; - return done = true; - default: - // We got an item selection - ng_choice.weapon = static_cast (selection_ID); - ret = true; - return done = true; + break; } - return true; + return false; }); - auto popup = make_shared(prompt_ui); +#ifdef USE_TILE_WEB + tiles.json_open_object(); + tiles.json_write_string("title", title->get_text().to_colour_string()); + tiles.json_write_string("prompt", prompt->get_text().to_colour_string()); + main_items->serialize("main-items"); + sub_items->serialize("sub-items"); + tiles.send_doll(doll, false, false); + tiles.push_ui_layout("newgame-choice", 1); +#endif ui::run_layout(move(popup), done); +#ifdef USE_TILE_WEB + tiles.pop_ui_layout(); +#endif + return ret; } @@ -2141,58 +2120,91 @@ static bool _choose_weapon(newgame_def& ng, newgame_def& ng_choice, return true; } +#ifdef USE_TILE +static tile_def tile_for_map_name(string name) +{ + if (starts_with(name, "Lesson ")) + { + const int i = name[7]-'1'; + ASSERT_RANGE(i, 0, 5); + constexpr tileidx_t tutorial_tiles[5] = { + TILEG_TUT_MOVEMENT, + TILEG_TUT_COMBAT, + TILEG_CMD_DISPLAY_INVENTORY, + TILEG_CMD_CAST_SPELL, + TILEG_CMD_USE_ABILITY, + }; + return tile_def(tutorial_tiles[i], TEX_GUI); + } + + if (name == "Sprint I: \"Red Sonja\"") + return tile_def(TILEP_MONS_SONJA, TEX_PLAYER); + if (name == "Sprint II: \"The Violet Keep of Menkaure\"") + return tile_def(TILEP_MONS_MENKAURE, TEX_PLAYER); + if (name == "Sprint III: \"The Ten Rune Challenge\"") + return tile_def(TILE_MISC_RUNE_OF_ZOT, TEX_DEFAULT); + if (name == "Sprint IV: \"Fedhas' Mad Dash\"") + return tile_def(TILE_DNGN_ALTAR_FEDHAS, TEX_FEAT); + if (name == "Sprint V: \"Ziggurat Sprint\"") + return tile_def(TILE_DNGN_PORTAL_ZIGGURAT, TEX_FEAT); + if (name == "Sprint VI: \"Thunderdome\"") + return tile_def(TILE_GOLD16, TEX_DEFAULT); + if (name == "Sprint VII: \"The Pits\"") + return tile_def(TILE_WALL_CRYPT_METAL + 2, TEX_WALL); + if (name == "Sprint VIII: \"Arena of Blood\"") + return tile_def(TILE_UNRAND_WOE, TEX_DEFAULT); + if (name == "Sprint IX: \"|||||||||||||||||||||||||||||\"") + return tile_def(TILE_WALL_LAB_METAL + 2, TEX_WALL); + return tile_def(0, TEX_GUI); +} +#endif + static void _construct_gamemode_map_menu(const mapref_vector& maps, const newgame_def& defaults, - MenuFreeform* menu) + shared_ptr& main_items, + shared_ptr& sub_items) { - static const int ITEMS_START_Y = 5; - static const int MENU_COLUMN_WIDTH = get_number_of_cols(); - TextItem* tmp = nullptr; string text; - coord_def min_coord(0,0); - coord_def max_coord(0,0); - bool activate_next = false; - - unsigned int padding_width = 0; - for (int i = 0; i < static_cast (maps.size()); i++) - { - padding_width = max(padding_width, - strwidth(maps.at(i)->desc_or_name())); - } - padding_width += 4; // Count the letter and " - " - padding_width = min(padding_width, MENU_COLUMN_WIDTH - 1); + bool activate_next = defaults.map == ""; for (int i = 0; i < static_cast (maps.size()); i++) { - tmp = new TextItem(); - text.clear(); - - tmp->set_fg_colour(LIGHTGREY); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_GOOD); + auto label = make_shared(); const char letter = 'a' + i; + + const string map_name = maps[i]->desc_or_name(); + text = " "; text += letter; text += " - "; + text += map_name; + +#ifdef USE_TILE + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + auto tile = make_shared(); + tile->set_tile(tile_for_map_name(map_name)); + tile->set_margin_for_sdl(0, 6, 0, 0); + hbox->add_child(move(tile)); + hbox->add_child(label); +#endif - text += maps[i]->desc_or_name(); - text = chop_string(text, padding_width); - - tmp->set_text(text); - tmp->add_hotkey(letter); - tmp->set_id(i); // ID corresponds to location in vector - - min_coord.x = X_MARGIN; - min_coord.y = ITEMS_START_Y + i; - max_coord.x = min_coord.x + text.size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); + label->set_text(formatted_string(text, LIGHTGREY)); - menu->attach_item(tmp); - tmp->set_visible(true); + auto btn = make_shared(); +#ifdef USE_TILE + hbox->set_margin_for_sdl(2, 10, 2, 2); + btn->set_child(move(hbox)); +#else + btn->set_child(move(label)); +#endif + btn->id = i; // ID corresponds to location in vector + btn->hotkey = letter; + main_items->add_button(btn, 0, i); if (activate_next) { - menu->set_active_item(tmp); + main_items->set_initial_focus(btn.get()); activate_next = false; } // Is this item our default map? @@ -2201,57 +2213,20 @@ static void _construct_gamemode_map_menu(const mapref_vector& maps, if (crawl_state.last_game_exit.exit_reason == game_exit::win) activate_next = true; else - menu->set_active_item(tmp); + main_items->set_initial_focus(btn.get()); } } // Don't overwhelm new players with aptitudes or the full list of commands! if (!crawl_state.game_is_tutorial()) { - tmp = new TextItem(); - tmp->set_text("% - List aptitudes"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 1; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('%'); - tmp->set_id(M_APTITUDES); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_NORMAL); - tmp->set_description_text("Lists the numerical skill train aptitudes for all races"); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("? - Help"); - min_coord.x = X_MARGIN; - min_coord.y = SPECIAL_KEYS_START_Y + 2; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('?'); - tmp->set_id(M_HELP); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_NORMAL); - tmp->set_description_text("Opens the help screen"); - menu->attach_item(tmp); - tmp->set_visible(true); - - tmp = new TextItem(); - tmp->set_text("* - Random map"); - min_coord.x = X_MARGIN + COLUMN_WIDTH; - min_coord.y = SPECIAL_KEYS_START_Y + 1; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('*'); - tmp->set_id(M_RANDOM); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_NORMAL); - tmp->set_description_text("Picks a random sprint map"); - menu->attach_item(tmp); - tmp->set_visible(true); + _add_menu_sub_item(sub_items, 0, 0, "% - List aptitudes", + "Lists the numerical skill train aptitudes for all races", + '%', M_APTITUDES); + _add_menu_sub_item(sub_items, 0, 1, "? - Help", + "Opens the help screen", '?', M_HELP); + _add_menu_sub_item(sub_items, 1, 0, "* - Random map", + "Picks a random sprint map", '*', M_RANDOM); } // TODO: let players escape back to first screen menu @@ -2259,42 +2234,22 @@ static void _construct_gamemode_map_menu(const mapref_vector& maps, //tmp = new TextItem(); //tmp->set_text("Bksp - Return to character menu"); //tmp->set_description_text("Lets you return back to Character choice menu"); - //min_coord.x = X_MARGIN + COLUMN_WIDTH - 3; - //min_coord.y = SPECIAL_KEYS_START_Y + 1; - //max_coord.x = min_coord.x + tmp->get_text().size(); - //max_coord.y = min_coord.y + 1; - //tmp->set_bounds(min_coord, max_coord); //tmp->set_fg_colour(BROWN); //tmp->add_hotkey(CK_BKSP); //tmp->set_id(M_ABORT); //tmp->set_highlight_colour(LIGHTGRAY); //menu->attach_item(tmp); - //tmp->set_visible(true); // Only add tab entry if we have a previous map choice if (crawl_state.game_is_sprint() && !defaults.map.empty() && defaults.type == GAME_TYPE_SPRINT && _char_defined(defaults)) { - tmp = new TextItem(); text.clear(); text += "Tab - "; text += defaults.map; - - // Adjust the end marker to align the - because - // Tab text is longer by 2 - tmp->set_text(text); - min_coord.x = X_MARGIN + COLUMN_WIDTH - 2; - min_coord.y = SPECIAL_KEYS_START_Y + 2; - max_coord.x = min_coord.x + tmp->get_text().size(); - max_coord.y = min_coord.y + 1; - tmp->set_bounds(min_coord, max_coord); - tmp->set_fg_colour(BROWN); - tmp->add_hotkey('\t'); - tmp->set_id(M_DEFAULT_CHOICE); - tmp->set_highlight_colour(STARTUP_HIGHLIGHT_NORMAL); - tmp->set_description_text("Select your previous sprint map and character"); - menu->attach_item(tmp); - tmp->set_visible(true); + _add_menu_sub_item(sub_items, 1, 1, text, + "Select your previous sprint map and character", '\t', + M_DEFAULT_CHOICE); } } @@ -2309,120 +2264,104 @@ static void _prompt_gamemode_map(newgame_def& ng, newgame_def& ng_choice, const newgame_def& defaults, mapref_vector maps) { - const int ui_w = 53, ui_h = 24; - PrecisionMenu menu; - menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT); - MenuFreeform* freeform = new MenuFreeform(); - freeform->init(coord_def(1,1), coord_def(ui_w+1, ui_h+1), "freeform"); - menu.attach_object(freeform); - menu.set_active_object(freeform); - - sort(maps.begin(), maps.end(), _cmp_map_by_order); - _construct_gamemode_map_menu(maps, defaults, freeform); - - BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu); - highlighter->init(coord_def(0,0), coord_def(0,0), "highlighter"); - menu.attach_object(highlighter); - - // Did we have a previous sprint map? - if (menu.get_active_item() == nullptr) - freeform->activate_first_item(); - - auto welcome_text = new FormattedTextItem(); formatted_string welcome; welcome.textcolour(BROWN); welcome.cprintf("%s\n", _welcome(ng).c_str()); + if (Options.seed_from_rc) + welcome.cprintf("Custom seed: %" PRIu64 "\n", Options.seed_from_rc); + welcome.textcolour(CYAN); - welcome.cprintf("\nYou have a choice of %s:\n\n", + welcome.cprintf("\nYou have a choice of %s:", ng_choice.type == GAME_TYPE_TUTORIAL ? "lessons" : "maps"); - welcome_text->set_text(welcome.to_colour_string()); - welcome_text->set_bounds(coord_def(1, 1), coord_def(ui_w+1, 5)); - welcome_text->set_visible(true); - welcome_text->allow_highlight(false); - freeform->attach_item(welcome_text); - freeform->set_visible(true); - highlighter->set_visible(true); + auto vbox = make_shared(Box::VERT); + vbox->align_cross = Widget::Align::STRETCH; + vbox->add_child(make_shared(welcome)); + + auto main_items = make_shared(true, 1, maps.size()); + main_items->menu_id = "map-main"; + main_items->set_margin_for_sdl(15, 0); + main_items->set_margin_for_crt(1, 0); + vbox->add_child(main_items); + + auto sub_items = make_shared(false, 2, 2); + sub_items->menu_id = "map-sub"; + vbox->add_child(sub_items); + + main_items->linked_menus[2] = sub_items; + sub_items->linked_menus[0] = main_items; + + sort(maps.begin(), maps.end(), _cmp_map_by_order); + _construct_gamemode_map_menu(maps, defaults, main_items, sub_items); bool done = false, cancel = false; - auto prompt_ui = make_shared(ui_w, ui_h, menu); - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { + + auto menu_item_activated = [&](int id) { + switch (id) + { + case M_ABORT: + // TODO: fix + break; + case M_APTITUDES: + show_help('%', _highlight_pattern(ng)); + return; + case M_HELP: + show_help('?'); + return; + case M_DEFAULT_CHOICE: + _set_default_choice(ng, ng_choice, defaults); + break; + case M_RANDOM: + // FIXME setting this to "random" is broken + ng_choice.map.clear(); + break; + default: + // We got an item selection + ng_choice.map = maps.at(id)->name; + break; + } + done = true; + }; + main_items->on_button_activated = menu_item_activated; + sub_items->on_button_activated = menu_item_activated; + + auto popup = make_shared(vbox); + popup->add_event_filter([&](wm_event ev) { if (ev.type != WME_KEYDOWN) return false; int keyn = ev.key.keysym.sym; - prompt_ui->_expose(); - // First process menu entries - if (!menu.process_key(keyn)) + switch (keyn) { - // Process all the keys that are not attached to items - switch (keyn) - { case 'X': case CONTROL('Q'): - cprintf("\nGoodbye!"); #ifdef USE_TILE_WEB tiles.send_exit_reason("cancel"); #endif end(0); break; CASE_ESCAPE -#ifdef USE_TILE_WEB - tiles.send_exit_reason("cancel"); -#endif return done = cancel = true; break; case ' ': return done = true; default: - // if we get this far, we did not get a significant selection - // from the menu, nor did we get an escape character - // continue the while loop from the beginning and poll a new key - return true; - } - } - // We have a significant key input! - // Construct selection vector - vector selection = menu.get_selected_items(); - menu.clear_selections(); - // There should only be one selection, otherwise something broke - if (selection.size() != 1) - { - // poll a new key - return true; - } - - // Get the stored id from the selection - int selection_ID = selection.at(0)->get_id(); - switch (selection_ID) - { - case M_ABORT: - // TODO: fix - return done = true; - case M_APTITUDES: - show_help('%', _highlight_pattern(ng)); - return true; - case M_HELP: - show_help('6'); - return true; - case M_DEFAULT_CHOICE: - _set_default_choice(ng, ng_choice, defaults); - return done = true; - case M_RANDOM: - // FIXME setting this to "random" is broken - ng_choice.map.clear(); - return done = true; - default: - // We got an item selection - ng_choice.map = maps.at(selection_ID)->name; - return done = true; + break; } - return true; + return false; }); - - auto popup = make_shared(prompt_ui); +#ifdef USE_TILE_WEB + tiles.json_open_object(); + tiles.json_write_string("title", welcome.to_colour_string()); + main_items->serialize("main-items"); + sub_items->serialize("sub-items"); + tiles.push_ui_layout("newgame-choice", 1); +#endif ui::run_layout(move(popup), done); +#ifdef USE_TILE_WEB + tiles.pop_ui_layout(); +#endif if (cancel || crawl_state.seen_hups) game_ended(game_exit::abort); diff --git a/crawl-ref/source/newgame.h b/crawl-ref/source/newgame.h index 46e7452bcc00..627dcf207a92 100644 --- a/crawl-ref/source/newgame.h +++ b/crawl-ref/source/newgame.h @@ -8,10 +8,12 @@ #include "job-type.h" #include "species-type.h" -class MenuFreeform; +class UINewGameMenu; struct menu_letter; struct newgame_def; +string newgame_char_description(const newgame_def& ng); + void choose_tutorial_character(newgame_def& ng_choice); bool choose_game(newgame_def& ng, newgame_def& choice, @@ -29,7 +31,7 @@ struct job_group /// A method to attach the group to a freeform. void attach(const newgame_def& ng, const newgame_def& defaults, - MenuFreeform* menu, menu_letter &letter); + UINewGameMenu* menu, menu_letter &letter); }; struct species_group @@ -41,5 +43,5 @@ struct species_group /// A method to attach the group to a freeform. void attach(const newgame_def& ng, const newgame_def& defaults, - MenuFreeform* menu, menu_letter &letter); + UINewGameMenu* menu, menu_letter &letter); }; diff --git a/crawl-ref/source/ng-init.cc b/crawl-ref/source/ng-init.cc index aacd18320355..d88022a60ca2 100644 --- a/crawl-ref/source/ng-init.cc +++ b/crawl-ref/source/ng-init.cc @@ -142,7 +142,7 @@ void initialise_temples() if (main_temple->has_tag("temple_variable")) { vector sizes; - for (auto &tag : main_temple->get_tags()) + for (const auto &tag : main_temple->get_tags()) { if (starts_with(tag, "temple_altars_")) { @@ -268,58 +268,97 @@ void initialise_temples() overflow_weights[i] = 0; } - // Try to find combinations of overflow gods that have specialised - // overflow vaults. -multi_overflow: - for (unsigned int i = 1, size = 1 << overflow_gods.size(); - i <= size; i++) + // Check for temple_overflow vaults that specify certain gods. + mapref_vector maps; + // the >1 range is based on previous code; 1-altar temple_overflow maps + // are placed by the next part, though their weight is not used here. There + // are currently no such vaults with more than 3 altars, but there's not + // much cost to checking a few higher. + for (int num = 2; num <= 5; num++) { - unsigned int num = count_bits(i); - - // TODO: possibly make this place single-god vaults too? - // XXX: upper limit on num here because this code gets really - // slow otherwise. - if (num <= 1 || num > 3) - continue; + mapref_vector num_maps = find_maps_for_tag( + make_stringf("temple_overflow_%d", num)); + maps.insert(maps.end(), num_maps.begin(), num_maps.end()); + } + for (const map_def *map : maps) + { + if (overflow_gods.size() < 2) + break; + unsigned int num = 0; vector this_temple_gods; - vector new_overflow_gods; - - string tags = make_stringf("temple_overflow_%d", num); - for (unsigned int j = 0; j < overflow_gods.size(); j++) + for (const auto &tag : map->get_tags()) { - if (i & (1 << j)) + if (!starts_with(tag, "temple_overflow_")) + continue; + string temple_tag = tag_without_prefix(tag, "temple_overflow_"); + if (temple_tag.empty()) { - string name = replace_all(god_name(overflow_gods[j]), " ", "_"); - lowercase(name); - tags = tags + " temple_overflow_" + name; - this_temple_gods.push_back(overflow_gods[j]); + mprf(MSGCH_ERROR, "Malformed temple tag '%s' in map %s", + tag.c_str(), map->name.c_str()); + continue; } + int test_num; + if (parse_int(temple_tag.c_str(), test_num) && test_num > 0) + num = test_num; else - new_overflow_gods.push_back(overflow_gods[j]); - } - - mapref_vector maps = find_maps_for_tag(tags); - if (maps.empty()) - continue; - - if (overflow_weights[num] > 0) - { - int chance = 0; - for (auto map : maps) { - chance += map->weight(level_id(BRANCH_DUNGEON, - MAX_OVERFLOW_LEVEL)); + replace(temple_tag.begin(), temple_tag.end(), '_', ' '); + god_type this_god = str_to_god(temple_tag); + if (this_god == GOD_NO_GOD) + { + mprf(MSGCH_ERROR, "Malformed temple tag '%s' in map %s", + tag.c_str(), map->name.c_str()); + continue; + } + this_temple_gods.push_back(this_god); } - if (!x_chance_in_y(chance, overflow_weights[num] + chance)) + if (num == 0) + { + if (this_temple_gods.size() > 0) + { + mprf(MSGCH_ERROR, + "Map %s has temple_overflow_god tags but no count tag", + map->name.c_str()); + } continue; + } } + // there is one vault that currently triggers this, where it allows + // one of two specified gods on a particular altar. This code won't + // handle (or error) on that case right now. + if (num != this_temple_gods.size()) + continue; - _use_overflow_temple(this_temple_gods); - - overflow_gods = new_overflow_gods; + // does this temple place only gods that we need to place? + bool ok = true; + for (auto god : this_temple_gods) + if (count(overflow_gods.begin(), overflow_gods.end(), god) == 0) + { + ok = false; + break; + } + if (!ok) + continue; + // finally: this overflow vault will place a subset of our current + // overflow list. Do we actually place it? + // TODO: The weight calculation here is kind of odd, though based on + // what it is directly replacing. It should sum all compatible + // maps first. But, the end result of this choice isn't a map anyways... + // More generally, I wonder if this list should be shuffled before this + // step, so that it's not prioritizing smaller vaults? + int chance = map->weight(level_id(BRANCH_DUNGEON, + MAX_OVERFLOW_LEVEL)); + if (x_chance_in_y(chance, overflow_weights[num] + chance)) + { + vector new_overflow_gods; + for (auto god : overflow_gods) + if (count(this_temple_gods.begin(), this_temple_gods.end(), god) == 0) + new_overflow_gods.push_back(god); + _use_overflow_temple(this_temple_gods); - goto multi_overflow; + overflow_gods = new_overflow_gods; + } } // NOTE: The overflow temples don't have to contain only one @@ -392,7 +431,6 @@ static int _get_random_coagulated_blood_desc() return desc; } } -#endif static int _get_random_blood_desc() { @@ -400,15 +438,16 @@ static int _get_random_blood_desc() 1, PDQ_VISCOUS, 1, PDQ_SEDIMENTED), PDC_RED); } +#endif void initialise_item_descriptions() { // Must remember to check for already existing colours/combinations. you.item_description.init(255); +#if TAG_MAJOR_VERSION == 34 you.item_description[IDESC_POTIONS][POT_BLOOD] = _get_random_blood_desc(); -#if TAG_MAJOR_VERSION == 34 you.item_description[IDESC_POTIONS][POT_BLOOD_COAGULATED] = _get_random_coagulated_blood_desc(); you.item_description[IDESC_POTIONS][POT_PORRIDGE] @@ -501,6 +540,6 @@ void initialise_item_descriptions() void fix_up_jiyva_name() { - you.jiyva_second_name = make_name(get_uint32(), MNAME_JIYVA); + you.jiyva_second_name = make_name(rng::get_uint32(), MNAME_JIYVA); ASSERT(you.jiyva_second_name[0] == 'J'); } diff --git a/crawl-ref/source/ng-input.cc b/crawl-ref/source/ng-input.cc index da7263a8a329..efeed5d454e2 100644 --- a/crawl-ref/source/ng-input.cc +++ b/crawl-ref/source/ng-input.cc @@ -17,19 +17,23 @@ #include "version.h" // Eventually, this should be something more grand. {dlb} -string opening_screen() +formatted_string opening_screen() { string msg = "Hello, welcome to " CRAWL " " + string(Version::Long) + "!\n" - "(c) Copyright 1997-2002 Linley Henzell, 2002-2019 Crawl DevTeam\n" - "Read the instructions for legal details. "; + "(c) Copyright 1997-2002 Linley Henzell, 2002-2019 Crawl DevTeam"; + return formatted_string::parse_string(msg); +} +formatted_string options_read_status() +{ + string msg; FileLineInput f(Options.filename.c_str()); if (!f.error()) { - msg += "(Options read from "; + msg += "Options read from \""; #ifdef DGAMELAUNCH // For dgl installs, show only the last segment of the .crawlrc // file name so that we don't leak details of the directory @@ -38,11 +42,11 @@ string opening_screen() #else msg += Options.filename; #endif - msg += ".)"; + msg += "\"."; } else { - msg += "(Options file "; + msg += "Options file "; if (!Options.filename.empty()) { msg += make_stringf("\"%s\" is not readable", @@ -50,12 +54,12 @@ string opening_screen() } else msg += "not found"; - msg += "; using defaults.)"; + msg += "; using defaults."; } msg += "\n"; - return msg; + return formatted_string::parse_string(msg); } bool is_good_name(const string& name, bool blankOK) @@ -64,10 +68,10 @@ bool is_good_name(const string& name, bool blankOK) // Disallow names that would result in a save named just ".cs". if (strip_filename_unsafe_chars(name).empty()) return blankOK && name.empty(); - return validate_player_name(name, false); + return validate_player_name(name); } -bool validate_player_name(const string &name, bool verbose) +bool validate_player_name(const string &name) { #if defined(TARGET_OS_WINDOWS) // Quick check for CON -- blows up real good under DOS/Windows. @@ -76,18 +80,12 @@ bool validate_player_name(const string &name, bool verbose) || strcasecmp(name.c_str(), "prn") == 0 || strnicmp(name.c_str(), "LPT", 3) == 0) { - if (verbose) - cprintf("\nSorry, that name gives your OS a headache.\n"); return false; } #endif if (strwidth(name) > MAX_NAME_LENGTH) - { - if (verbose) - cprintf("\nThat name is too long.\n"); return false; - } char32_t c; for (const char *str = name.c_str(); int l = utf8towc(&c, str); str += l) @@ -95,16 +93,7 @@ bool validate_player_name(const string &name, bool verbose) // The technical reasons are gone, but enforcing some sanity doesn't // hurt. if (!iswalnum(c) && c != '-' && c != '.' && c != '_' && c != ' ') - { - if (verbose) - { - cprintf("\n" - "Alpha-numerics, spaces, hyphens, periods " - "and underscores only, please." - "\n"); - } return false; - } } return true; diff --git a/crawl-ref/source/ng-input.h b/crawl-ref/source/ng-input.h index 5b1023853ae3..b7dbb79af8ec 100644 --- a/crawl-ref/source/ng-input.h +++ b/crawl-ref/source/ng-input.h @@ -1,8 +1,10 @@ #pragma once struct newgame_def; +class formatted_string; -string opening_screen(); -bool validate_player_name(const string &name, bool verbose); +formatted_string opening_screen(); +formatted_string options_read_status(); +bool validate_player_name(const string &name); bool is_good_name(const string &name, bool blankOK); void enter_player_name(newgame_def& ng); diff --git a/crawl-ref/source/ng-restr.cc b/crawl-ref/source/ng-restr.cc index e967d5b68c42..509662da0141 100644 --- a/crawl-ref/source/ng-restr.cc +++ b/crawl-ref/source/ng-restr.cc @@ -126,7 +126,7 @@ char_choice_restriction weapon_restriction(weapon_type wpn, return CC_BANNED; } - // Javelins are always good, tomahawks not so much. + // Javelins are always good, boomerangs not so much. if (wpn == WPN_THROWN) { return species_size(ng.species) >= SIZE_MEDIUM ? CC_UNRESTRICTED diff --git a/crawl-ref/source/ng-setup.cc b/crawl-ref/source/ng-setup.cc index e618a3e76364..55cf6279f709 100644 --- a/crawl-ref/source/ng-setup.cc +++ b/crawl-ref/source/ng-setup.cc @@ -22,9 +22,7 @@ #include "options.h" #include "prompt.h" #include "religion.h" -#if TAG_MAJOR_VERSION == 34 -# include "shopping.h" // REMOVED_DEAD_SHOPS_KEY -#endif +#include "shopping.h" #include "skills.h" #include "spl-book.h" #include "spl-util.h" @@ -73,7 +71,7 @@ static void _give_bonus_items() static void _autopickup_ammo(missile_type missile) { if (Options.autopickup_starting_ammo) - you.force_autopickup[OBJ_MISSILES][missile] = 1; + you.force_autopickup[OBJ_MISSILES][missile] = AP_FORCE_ON; } /** @@ -100,7 +98,9 @@ item_def* newgame_make_item(object_class_type base, return nullptr; // not an actual item - if (sub_type == WPN_UNARMED) + // the WPN_UNKNOWN case is used when generating a paper doll during + // character creation + if (sub_type == WPN_UNARMED || sub_type == WPN_UNKNOWN) return nullptr; int slot; @@ -215,7 +215,7 @@ static void _give_ammo(weapon_type weapon, int plus) if (species_can_throw_large_rocks(you.species)) newgame_make_item(OBJ_MISSILES, MI_LARGE_ROCK, 4 + plus); else if (you.body_size(PSIZE_TORSO) <= SIZE_SMALL) - newgame_make_item(OBJ_MISSILES, MI_TOMAHAWK, 8 + 2 * plus); + newgame_make_item(OBJ_MISSILES, MI_BOOMERANG, 8 + 2 * plus); else newgame_make_item(OBJ_MISSILES, MI_JAVELIN, 5 + plus); newgame_make_item(OBJ_MISSILES, MI_THROWING_NET, 2); @@ -234,8 +234,10 @@ static void _give_ammo(weapon_type weapon, int plus) } } -static void _give_items_skills(const newgame_def& ng) +void give_items_skills(const newgame_def& ng) { + create_wanderer(); + switch (you.char_class) { case JOB_BERSERKER: @@ -279,10 +281,6 @@ static void _give_items_skills(const newgame_def& ng) break; - case JOB_WANDERER: - create_wanderer(); - break; - default: break; } @@ -326,11 +324,6 @@ static void _give_starting_food() object_class_type base_type = OBJ_FOOD; int sub_type = FOOD_RATION; int quantity = 1; - if (you.species == SP_VAMPIRE) - { - base_type = OBJ_POTIONS; - sub_type = POT_BLOOD; - } // Give another one for hungry species. if (you.get_mutation_level(MUT_FAST_METABOLISM)) @@ -365,9 +358,6 @@ static void _give_basic_knowledge() { identify_inventory(); - // Recognisable by appearance. - you.type_ids[OBJ_POTIONS][POT_BLOOD] = true; - // Removed item types are handled in _set_removed_types_as_identified. } @@ -380,17 +370,25 @@ static void _setup_generic(const newgame_def& ng); // Initialise a game based on the choice stored in ng. void setup_game(const newgame_def& ng) { - if (Options.seed_from_rc && ng.type == GAME_TYPE_NORMAL) + crawl_state.type = ng.type; // by default + if (Options.seed_from_rc && ng.type != GAME_TYPE_CUSTOM_SEED) { + // rc seed overrides seed from other sources, and forces a normal game + // to be a custom seed game. There is currently no special designation + // for sprint etc. games that involve a custom seed. + // TODO: does this make any sense for hints mode? Options.seed = Options.seed_from_rc; - crawl_state.type = GAME_TYPE_CUSTOM_SEED; + if (ng.type == GAME_TYPE_NORMAL) + crawl_state.type = GAME_TYPE_CUSTOM_SEED; } - else if (Options.seed && ng.type == GAME_TYPE_NORMAL) + else if (Options.seed && ng.type != GAME_TYPE_CUSTOM_SEED) + { + // there's a seed lingering in the options, but we shouldn't use it. Options.seed = 0; + } else if (!Options.seed && ng.type == GAME_TYPE_CUSTOM_SEED) crawl_state.type = GAME_TYPE_NORMAL; - else - crawl_state.type = ng.type; + crawl_state.map = ng.map; switch (crawl_state.type) @@ -464,7 +462,7 @@ static void _free_up_slot(char letter) void initial_dungeon_setup() { - rng_generator levelgen_rng(BRANCH_DUNGEON); + rng::generator levelgen_rng(BRANCH_DUNGEON); initialise_branch_depths(); initialise_temples(); @@ -474,9 +472,10 @@ void initial_dungeon_setup() static void _setup_generic(const newgame_def& ng) { - reset_rng(); // initialize rng from Options.seed + rng::reset(); // initialize rng from Options.seed _init_player(); you.game_seed = crawl_state.seed; + you.deterministic_levelgen = Options.incremental_pregen; #if TAG_MAJOR_VERSION == 34 // Avoid the remove_dead_shops() Gozag fixup in new games: see @@ -508,10 +507,9 @@ static void _setup_generic(const newgame_def& ng) give_basic_mutations(you.species); // This function depends on stats and mutations being finalised. - _give_items_skills(ng); + give_items_skills(ng); - if (you.species == SP_DEMONSPAWN) - roll_demonspawn_mutations(); + roll_demonspawn_mutations(); _give_starting_food(); @@ -566,7 +564,7 @@ static void _setup_generic(const newgame_def& ng) reassess_starting_skills(); init_skill_order(); - init_can_train(); + init_can_currently_train(); init_train(); init_training(); diff --git a/crawl-ref/source/ng-setup.h b/crawl-ref/source/ng-setup.h index d138c3bae5bd..10950e8b6172 100644 --- a/crawl-ref/source/ng-setup.h +++ b/crawl-ref/source/ng-setup.h @@ -16,3 +16,5 @@ item_def* newgame_make_item(object_class_type base, struct newgame_def; void setup_game(const newgame_def& ng); void initial_dungeon_setup(); + +void give_items_skills(const newgame_def& ng); diff --git a/crawl-ref/source/ng-wanderer.cc b/crawl-ref/source/ng-wanderer.cc index 3a811b22103c..3dcf5fdc9077 100644 --- a/crawl-ref/source/ng-wanderer.cc +++ b/crawl-ref/source/ng-wanderer.cc @@ -19,13 +19,13 @@ static void _give_wanderer_weapon(skill_type wpn_skill, int plus) // get curare here. if (plus) { - newgame_make_item(OBJ_MISSILES, MI_NEEDLE, 1 + random2(4), + newgame_make_item(OBJ_MISSILES, MI_DART, 1 + random2(4), 0, SPMSL_CURARE); } - // Otherwise, we just get some poisoned needles. + // Otherwise, we just get some poisoned darts. else { - newgame_make_item(OBJ_MISSILES, MI_NEEDLE, 5 + roll_dice(2, 5), + newgame_make_item(OBJ_MISSILES, MI_DART, 5 + roll_dice(2, 5), 0, SPMSL_POISONED); } } @@ -59,10 +59,6 @@ static void _give_wanderer_weapon(skill_type wpn_skill, int plus) sub_type = WPN_QUARTERSTAFF; break; - case SK_THROWING: - sub_type = WPN_BLOWGUN; - break; - case SK_BOWS: sub_type = WPN_SHORTBOW; break; @@ -687,6 +683,12 @@ static void _wanderer_cover_equip_holes() // levels/equipment, but pretty randomised. void create_wanderer() { + // intentionally create the subgenerator either way, so that this has the + // same impact on the current main rng for all chars. + rng::subgenerator wn_rng; + if (you.char_class != JOB_WANDERER) + return; + // Decide what our character roles are. stat_type primary_role = _wanderer_choose_role(); stat_type secondary_role = _wanderer_choose_role(); diff --git a/crawl-ref/source/notes.cc b/crawl-ref/source/notes.cc index 67823cf064f7..aa03496b4853 100644 --- a/crawl-ref/source/notes.cc +++ b/crawl-ref/source/notes.cc @@ -414,7 +414,7 @@ void Note::check_milestone() const string branch = place.describe(true, false); if (starts_with(branch, "The ")) - branch[0] = tolower(branch[0]); + branch[0] = tolower_safe(branch[0]); if (dep == 1) { @@ -426,7 +426,7 @@ void Note::check_milestone() const { string level = place.describe(true, true); if (starts_with(level, "Level ")) - level[0] = tolower(level[0]); + level[0] = tolower_safe(level[0]); mark_milestone(br == BRANCH_ZIGGURAT ? "zig" : "br.end", "reached " + level + "."); diff --git a/crawl-ref/source/options.h b/crawl-ref/source/options.h index 3c0cfb390b8f..fe2e193a6376 100644 --- a/crawl-ref/source/options.h +++ b/crawl-ref/source/options.h @@ -4,8 +4,9 @@ #include "activity-interrupt-type.h" #include "char-set-type.h" -#include "confirm-level-type.h" +#include "confirm-butcher-type.h" #include "confirm-prompt-type.h" +#include "easy-confirm-type.h" #include "feature.h" #include "flang-t.h" #include "flush-reason-type.h" @@ -172,7 +173,8 @@ struct game_options uint64_t seed; // Non-random games. uint64_t seed_from_rc; - bool pregen_dungeon; // Is the dungeon generated at the beginning? + bool pregen_dungeon; // Is the dungeon completely generated at the beginning? + bool incremental_pregen; // Does the dungeon always generate in a specified order? #ifdef DGL_SIMPLE_MESSAGING bool messaging; // Check for messages. @@ -232,7 +234,7 @@ struct game_options bool easy_door; // 'O', 'C' don't prompt with just one door. bool warn_hatches; // offer a y/n prompt when the player uses an escape hatch bool enable_recast_spell; // Allow recasting spells with 'z' Enter. - int confirm_butcher; // When to prompt for butchery + confirm_butcher_type confirm_butcher; // When to prompt for butchery hunger_state_t auto_butcher; // auto-butcher corpses while travelling bool easy_eat_chunks; // make 'e' auto-eat the oldest safe chunk bool auto_eat_chunks; // allow eating chunks while resting or travelling @@ -246,7 +248,7 @@ struct game_options bool note_xom_effects; // take note of all Xom effects bool note_chat_messages; // log chat in Webtiles bool note_dgl_messages; // log chat in DGL - confirm_level_type easy_confirm; // make yesno() confirming easier + easy_confirm_type easy_confirm; // make yesno() confirming easier bool easy_quit_item_prompts; // make item prompts quitable on space confirm_prompt_type allow_self_target; // yes, no, prompt bool simple_targeting; // disable smart spell targeting @@ -400,7 +402,6 @@ struct game_options int dump_item_origins; // Show where items came from? int dump_item_origin_price; - bool dump_book_spells; // Order of sections in the character dump. vector dump_order; @@ -429,7 +430,7 @@ struct game_options vector drop_filter; - map> activity_interrupts; + map> activity_interrupts; #ifdef DEBUG_DIAGNOSTICS FixedBitVector quiet_debug_messages; #endif @@ -552,6 +553,8 @@ struct game_options maybe_bool tile_use_small_layout; #endif int tile_cell_pixels; + int tile_viewport_scale; + int tile_map_scale; bool tile_filter_scaling; int tile_map_pixels; @@ -615,7 +618,7 @@ struct game_options message_filter parse_message_filter(const string &s); void set_default_activity_interrupts(); - void set_activity_interrupt(FixedBitVector &eints, + void set_activity_interrupt(FixedBitVector &eints, const string &interrupt); void set_activity_interrupt(const string &activity_name, const string &interrupt_names, diff --git a/crawl-ref/source/ouch.cc b/crawl-ref/source/ouch.cc index 4cbb02000224..9d3abdb07604 100644 --- a/crawl-ref/source/ouch.cc +++ b/crawl-ref/source/ouch.cc @@ -533,8 +533,7 @@ static void _maybe_ru_retribution(int dam, mid_t death_source) } } -static void _maybe_spawn_monsters(int dam, const bool is_torment, - kill_method_type death_type, +static void _maybe_spawn_monsters(int dam, kill_method_type death_type, mid_t death_source) { monster* damager = monster_by_mid(death_source); @@ -543,10 +542,6 @@ static void _maybe_spawn_monsters(int dam, const bool is_torment, if (!damager) return; - // Exclude torment damage. Ugh. - if (is_torment) - return; - monster_type mon; int how_many = 0; @@ -797,10 +792,6 @@ void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux, int drain_amount = 0; - const bool is_torment = (aux && (strstr(aux, "torment") - || strstr(aux, "Torment") - || strstr(aux, "exploding lurking horror"))); - // Multiply damage if amulet of harm is in play if (dam != INSTANT_DEATH) dam = _apply_extra_harm(dam, source); @@ -824,7 +815,7 @@ void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux, dam = dam * 10 / 15; } ait_hp_loss hpl(dam, death_type); - interrupt_activity(AI_HP_LOSS, &hpl); + interrupt_activity(activity_interrupt::hp_loss, &hpl); // Don't wake the player with fatal or poison damage. if (dam > 0 && dam < you.hp && death_type != KILLED_BY_POISON) @@ -958,7 +949,7 @@ void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux, _deteriorate(dam); _yred_mirrors_injury(dam, source); _maybe_ru_retribution(dam, source); - _maybe_spawn_monsters(dam, is_torment, death_type, source); + _maybe_spawn_monsters(dam, death_type, source); _maybe_fog(dam); _powered_by_pain(dam); if (sanguine_armour_valid()) diff --git a/crawl-ref/source/outer-menu.cc b/crawl-ref/source/outer-menu.cc new file mode 100644 index 000000000000..2e6ce829abf8 --- /dev/null +++ b/crawl-ref/source/outer-menu.cc @@ -0,0 +1,459 @@ +/** + * @file + * @brief Menu button used in non-game menus +**/ + +#include "AppHdr.h" + +#include + +#include "cio.h" +#include "outer-menu.h" +#include "tileweb.h" +#include "unwind.h" + +using namespace ui; + +bool MenuButton::focus_on_mouse = false; + +void MenuButton::_render() +{ +#ifdef USE_TILE_LOCAL + m_buf.draw(); + m_line_buf.draw(); +#endif + m_child->render(); +} + +SizeReq MenuButton::_get_preferred_size(Direction dim, int prosp_width) +{ + return m_child->get_preferred_size(dim, prosp_width); +} + +void MenuButton::_allocate_region() +{ + m_child->allocate_region(m_region); +#ifdef USE_TILE_LOCAL + m_buf.clear(); + m_line_buf.clear(); + if (focused) + { + const VColour bg = active ? + VColour(0, 0, 0, 255) : VColour(255, 255, 255, 25); + m_buf.add(m_region.x, m_region.y, m_region.ex(), m_region.ey(), bg); + const VColour mouse_colour = active ? + VColour(34, 34, 34, 255) : term_colours[highlight_colour]; + m_line_buf.add_square(m_region.x+1, m_region.y+1, + m_region.ex(), m_region.ey(), mouse_colour); + } +#endif +} + +#ifndef USE_TILE_LOCAL +void MenuButton::recolour_descendants(const shared_ptr& node) +{ + auto tw = dynamic_pointer_cast(node); + if (tw) + { + if (focused) + { + // keep the original colour so we can restore it on unfocus + const auto& first_op = tw->get_text().ops[0]; + fg_normal = first_op.type == FSOP_COLOUR ? first_op.colour : LIGHTGREY; + } + + const colour_t fg = focused ? fg_highlight : fg_normal; + const colour_t bg = focused ? highlight_colour : BLACK; + formatted_string new_contents; + new_contents.textcolour(fg); + new_contents.cprintf("%s", tw->get_text().tostring().c_str()); + tw->set_text(move(new_contents)); + tw->set_bg_colour(static_cast(bg)); + return; + } + auto container = dynamic_pointer_cast(node); + if (container) + { + container->foreach([this](shared_ptr& child) { + recolour_descendants(child); + }); + } +} +#endif + +bool MenuButton::on_event(const wm_event& event) +{ + if (Bin::on_event(event)) + return true; + + bool old_focused = focused, old_active = active; + + if (event.type == WME_MOUSEENTER && focus_on_mouse) + ui::set_focused_widget(this); + if (event.type == WME_MOUSEMOTION) + ui::set_focused_widget(this); + + if (event.type == WME_FOCUSIN) + focused = true; + if (event.type == WME_FOCUSOUT) + focused = false; + +#ifndef USE_TILE_LOCAL + if (event.type == WME_FOCUSIN || event.type == WME_FOCUSOUT) + recolour_descendants(shared_from_this()); +#endif + +#ifdef USE_TILE_LOCAL + if (event.type == WME_MOUSEENTER) + wm->set_mouse_cursor(MOUSE_CURSOR_POINTER); + if (event.type == WME_MOUSELEAVE) + wm->set_mouse_cursor(MOUSE_CURSOR_ARROW); +#endif + + if (event.type == WME_MOUSEBUTTONDOWN && event.mouse_event.button == MouseEvent::LEFT) + active = true; + if (event.type == WME_MOUSEBUTTONUP && event.mouse_event.button == MouseEvent::LEFT) + active = false; + + if (old_focused != focused || old_active != active) + _queue_allocation(); + + return old_active != active; +} + +#ifdef USE_TILE_WEB +static void serialize_image(const Image* image) +{ + tile_def tile = image->get_tile(); + tiles.json_open_object(); + tiles.json_write_int("t", tile.tile); + tiles.json_write_int("tex", tile.tex); + if (tile.ymax != TILE_Y) + tiles.json_write_int("ymax", tile.ymax); + tiles.json_close_object(); +} + +void MenuButton::serialize() +{ + tiles.json_write_string("description", description); + tiles.json_write_int("hotkey", hotkey); + tiles.json_write_int("highlight_colour", highlight_colour); + if (auto text = dynamic_cast(m_child.get())) + tiles.json_write_string("label", text->get_text().to_colour_string()); + else if (auto box = dynamic_cast(m_child.get())) + { + if (box->num_children() <= 1) + return; + tiles.json_open_array("tile"); + if (auto tile = dynamic_cast((*box)[0].get())) + serialize_image(tile); + else if (auto tilestack = dynamic_cast(((*box)[0].get()))) + { + for (const auto it : *tilestack) + if (auto t = dynamic_cast(it.get())) + serialize_image(t); + } + tiles.json_close_array(); + + tiles.json_open_array("labels"); + for (size_t i = 1; i < box->num_children(); i++) + if (auto text2 = dynamic_cast((*box)[i].get())) + tiles.json_write_string(text2->get_text().to_colour_string()); + tiles.json_close_array(); + } +} +#endif + +OuterMenu::OuterMenu(bool can_shrink, int width, int height) +{ + m_grid = make_shared(); + m_grid->stretch_h = true; + + m_grid->on(Widget::slots.event, [this](wm_event ev){ + return this->scroller_event_hook(ev); + }); + + if (can_shrink) + { + auto scroller = make_shared(); + scroller->set_child(m_grid); + m_root = move(scroller); + } + else + m_root = m_grid; + + m_root->_set_parent(this); + + m_width = width; + m_height = height; + m_buttons.resize(width * height, nullptr); + m_description_indexes.resize(width * height, -1); +} + +#ifdef USE_TILE_WEB +static bool from_client {false}; +static unordered_map open_menus; + +OuterMenu::~OuterMenu() +{ + if (menu_id) + open_menus.erase(string(menu_id)); +} +#endif + +void OuterMenu::_render() +{ + m_root->render(); +} + +SizeReq OuterMenu::_get_preferred_size(Direction dim, int prosp_width) +{ + return m_root->get_preferred_size(dim, prosp_width); +} + +void OuterMenu::_allocate_region() +{ + if (!have_allocated && on_button_activated) + { + have_allocated = true; + Layout *layout = nullptr; + for (Widget *w = _get_parent(); w && !layout; w = w->_get_parent()) + layout = dynamic_cast(w); + ASSERT(layout); + layout->add_event_filter([this](wm_event ev) { + if (ev.type != WME_KEYDOWN) + return false; + for (const auto& btn : this->m_buttons) + if (btn && ev.key.keysym.sym == btn->hotkey) + { + this->on_button_activated(btn->id); + return true; + } + return false; + }); +#ifdef USE_TILE_WEB + if (menu_id) + open_menus.emplace(menu_id, this); +#endif + if (m_initial_focus) + scroll_button_into_view(m_initial_focus); + } + m_root->allocate_region(m_region); +} + +bool OuterMenu::on_event(const wm_event& event) +{ + return Widget::on_event(event); +} + +void OuterMenu::add_label(shared_ptr label, int x, int y) +{ + ASSERT(x >= 0 && x < m_width); + ASSERT(y >= 0 && y < m_height); + ASSERT(m_buttons[y*m_width + x] == nullptr); + m_labels.emplace_back(label->get_text(), coord_def(x, y)); + m_grid->add_child(move(label), x, y); +} + +void OuterMenu::add_button(shared_ptr btn, int x, int y) +{ + ASSERT(x >= 0 && x < m_width); + ASSERT(y >= 0 && y < m_height); + + MenuButton*& item = m_buttons[y*m_width + x]; + ASSERT(item == nullptr); + item = btn.get(); + + const int btn_id = btn->id; + btn->on(Widget::slots.event, [btn_id, this, x, y](const wm_event& ev) { + if (on_button_activated) + if ((ev.type == WME_MOUSEBUTTONUP + && ev.mouse_event.button == MouseEvent::LEFT) + || (ev.type == WME_KEYDOWN && ev.key.keysym.sym == CK_ENTER)) + { + this->on_button_activated(btn_id); + return ev.type == WME_KEYDOWN; // button needs to catch clicks + } + if (ev.type == WME_FOCUSIN && m_description_indexes[y*this->m_width+x] != -1) + descriptions->current() = m_description_indexes[y*this->m_width+x]; + return false; + }); + + if (descriptions) + { + auto desc_text = make_shared(formatted_string(btn->description, WHITE)); + desc_text->set_wrap_text(true); + descriptions->add_child(move(desc_text)); + m_description_indexes[y*m_width + x] = descriptions->num_children()-1; + } + + Widget *r; + for (Widget *p = btn.get(); p; p = p->_get_parent()) + r = p; + m_grid->add_child(r->get_shared(), x, y); +} + +void OuterMenu::scroll_button_into_view(MenuButton *btn) +{ + ui::set_focused_widget(btn); + +#ifdef USE_TILE_WEB + if (menu_id) + { + tiles.json_open_object(); + tiles.json_write_bool("from_client", from_client); + tiles.json_write_string("menu_id", menu_id); + tiles.json_write_int("button_focus", btn->hotkey); + tiles.ui_state_change("newgame-choice", 0); + } +#endif + + Widget* gp = btn->_get_parent()->_get_parent(); + Scroller* scroller = dynamic_cast(gp); + if (!scroller) + return; + const auto btn_reg = btn->get_region(), scr_reg = scroller->get_region(); + +#ifdef USE_TILE_LOCAL + constexpr int shade_height = 12; +#else + constexpr int shade_height = 0; +#endif + + const int btn_top = btn_reg.y, btn_bot = btn_top + btn_reg.height, + scr_top = scr_reg.y + shade_height, + scr_bot = scr_reg.y + scr_reg.height - shade_height; + const int delta = btn_top < scr_top ? btn_top - scr_top : + btn_bot > scr_bot ? btn_bot - scr_bot : 0; + scroller->set_scroll(scroller->get_scroll() + delta); +} + +bool OuterMenu::move_button_focus(int fx, int fy, int dx, int dy, int limit) +{ + MenuButton* btn = nullptr; + int nx, ny; + for (nx = fx+dx, ny = fy+dy; + nx >= 0 && nx < m_width && ny >= 0 && ny < m_height; + nx += dx, ny += dy) + { + if (m_buttons[ny*m_width + nx]) + { + btn = m_buttons[ny*m_width + nx]; + if (--limit == 0) + break; + } + } + + if (btn) + scroll_button_into_view(btn); + else + { + int idx = dx == 0 ? 1+dy : 2-dx; // dx,dy --> CSS direction index + if (auto linked = linked_menus[idx].lock()) + { + int lfx = dx == 0 ? min(fx, linked->m_width-1) : dx < 0 ? linked->m_width : -1; + int lfy = dy == 0 ? min(fy, linked->m_height-1) : dy < 0 ? linked->m_height : -1; + return linked->move_button_focus(lfx, lfy, dx, dy, 1); + } + } + return btn != nullptr; +} + +bool OuterMenu::scroller_event_hook(const wm_event& ev) +{ + MenuButton::focus_on_mouse = ev.type == WME_MOUSEWHEEL; + + if (ev.type != WME_KEYDOWN) + return false; + int key = ev.key.keysym.sym; + + if (key == CK_DOWN || key == CK_UP || key == CK_LEFT || key == CK_RIGHT + || key == CK_HOME || key == CK_END || key == CK_PGUP || key == CK_PGDN) + { + const int dx = key == CK_LEFT ? -1 : key == CK_RIGHT ? 1 : 0; + const int dy = key == CK_UP || key == CK_PGUP || key == CK_HOME ? -1 + : key == CK_DOWN || key == CK_PGDN || key == CK_END ? 1 : 0; + ASSERT((dx == 0) != (dy == 0)); + + const Widget* focus = ui::get_focused_widget(); + int fx = -1, fy = -1; + for (int x = 0; x < m_width; x++) + for (int y = 0; y < m_height; y++) + if (focus == m_buttons[y*m_width + x]) + { + fx = x; + fy = y; + break; + } + ASSERT(fx >= 0 && fy >= 0); + + const int pagesz = m_root->get_region().height / focus->get_region().height - 1; + const int limit = (key == CK_HOME || key == CK_END) ? INT_MAX + : (key == CK_PGUP || key == CK_PGDN) ? pagesz : 1; + + return move_button_focus(fx, fy, dx, dy, limit); + } + + return false; +} + +MenuButton* OuterMenu::get_button(int x, int y) +{ + ASSERT(x >= 0 && x < m_width); + ASSERT(y >= 0 && y < m_height); + return m_buttons[y*m_width + x]; +} + +MenuButton* OuterMenu::get_button_by_id(int id) +{ + auto it = find_if(m_buttons.begin(), m_buttons.end(), + [id](MenuButton*& btn) { return btn && btn->id == id; }); + return it == m_buttons.end() ? nullptr : *it; +} + +#ifdef USE_TILE_WEB +void OuterMenu::serialize(string name) +{ + tiles.json_open_object(name); + tiles.json_write_string("menu_id", menu_id); + tiles.json_write_int("width", m_width); + tiles.json_write_int("height", m_height); + tiles.json_open_array("buttons"); + for (size_t i = 0; i < m_buttons.size(); i++) + { + if (!m_buttons[i]) + continue; + tiles.json_open_object(); + tiles.json_write_int("x", i % m_width); + tiles.json_write_int("y", i / m_width); + m_buttons[i]->serialize(); + tiles.json_close_object(); + } + tiles.json_close_array(); + tiles.json_open_array("labels"); + for (auto label : m_labels) + { + tiles.json_open_object(); + tiles.json_write_int("x", label.second.x); + tiles.json_write_int("y", label.second.y); + tiles.json_write_string("label", label.first.to_colour_string()); + tiles.json_close_object(); + } + tiles.json_close_array(); + tiles.json_close_object(); +} + +void OuterMenu::recv_outer_menu_focus(const char *menu_id, int hotkey) +{ + auto open_menu = open_menus.find(menu_id); + if (open_menu == open_menus.end()) + return; + + unwind_bool tmp(from_client, true); + + auto menu = open_menu->second; + for (auto btn : menu->m_buttons) + if (btn && btn->hotkey == hotkey) + menu->scroll_button_into_view(btn); +} +#endif diff --git a/crawl-ref/source/outer-menu.h b/crawl-ref/source/outer-menu.h new file mode 100644 index 000000000000..44e45ae86d2d --- /dev/null +++ b/crawl-ref/source/outer-menu.h @@ -0,0 +1,101 @@ +/** + * @file + * @brief Menu button used in non-game menus +**/ + +#pragma once + +#include "ui.h" +#include "tilefont.h" + +class MenuButton : public ui::Bin +{ + friend class OuterMenu; +public: + MenuButton() {}; + + virtual void _render() override; + virtual ui::SizeReq _get_preferred_size(ui::Widget::Direction, int) override; + virtual void _allocate_region() override; + virtual bool on_event(const wm_event& event) override; + + int id = 0; + int hotkey = 0; + colour_t highlight_colour = LIGHTGREY; +#ifndef USE_TILE_LOCAL + colour_t fg_highlight = BLACK; +#endif + string description; + +#ifdef USE_TILE_WEB + void serialize(); +#endif + +protected: + bool focused = false; + bool active = false; +#ifdef USE_TILE_LOCAL + ShapeBuffer m_buf; + LineBuffer m_line_buf; +#endif + static bool focus_on_mouse; + +#ifndef USE_TILE_LOCAL + void recolour_descendants(const shared_ptr& node); + colour_t fg_normal = LIGHTGREY; +#endif +}; + +class OuterMenu : public ui::Widget +{ +public: + OuterMenu(bool can_shrink, int width, int height); + +#ifdef USE_TILE_WEB + ~OuterMenu(); + void serialize(string name); + static void recv_outer_menu_focus(const char *menu_id, int hotkey); +#endif + + virtual void _render() override; + virtual ui::SizeReq _get_preferred_size(ui::Widget::Direction, int) override; + virtual void _allocate_region() override; + virtual bool on_event(const wm_event& event) override; + + virtual shared_ptr get_child_at_offset(int x, int y) override { + return m_root; + } + + void add_button(shared_ptr btn, int x, int y); + void add_label(shared_ptr label, int x, int y); + MenuButton* get_button(int x, int y); + MenuButton* get_button_by_id(int id); + const vector& get_buttons() { return m_buttons; }; + void set_initial_focus(MenuButton *btn) { + ASSERT(!have_allocated); + m_initial_focus = btn; + }; + + void scroll_button_into_view(MenuButton *btn); + + function on_button_activated; + + weak_ptr linked_menus[4]; + shared_ptr descriptions; + const char *menu_id {nullptr}; + +protected: + bool scroller_event_hook(const wm_event& ev); + bool move_button_focus(int fx, int fy, int dx, int dy, int limit); + + shared_ptr m_grid; + shared_ptr m_root; + vector m_buttons; + vector> m_labels; + vector m_description_indexes; + int m_width; + int m_height; + MenuButton* m_initial_focus {nullptr}; + + bool have_allocated {false}; +}; diff --git a/crawl-ref/source/output.cc b/crawl-ref/source/output.cc index e3e421534857..545fcba50fae 100644 --- a/crawl-ref/source/output.cc +++ b/crawl-ref/source/output.cc @@ -511,8 +511,7 @@ static bool _boosted_ev() static bool _boosted_sh() { return you.duration[DUR_DIVINE_SHIELD] - || qazlal_sh_boost() > 0 - || you.attribute[ATTR_BONE_ARMOUR] > 0; + || qazlal_sh_boost() > 0; } #ifdef DGL_SIMPLE_MESSAGING @@ -550,14 +549,14 @@ void update_turn_count() // Show the turn count starting from 1. You can still quit on turn 0. textcolour(HUD_VALUE_COLOUR); if (Options.show_game_time) - { - CPRINTF("%.1f (%.1f)%s", you.elapsed_time / 10.0, - (you.elapsed_time - you.elapsed_time_at_last_input) / 10.0, - // extra spaces to erase excess if previous output was longer - " "); - } + CPRINTF("%.1f", you.elapsed_time / 10.0); else CPRINTF("%d", you.num_turns); + + CPRINTF(" (%.1f)%s", + (you.elapsed_time - you.elapsed_time_at_last_input) / 10.0, + // extra spaces to erase excess if previous output was longer + " "); textcolour(LIGHTGREY); } @@ -1418,7 +1417,6 @@ void print_stats() you.redraw_status_lights = false; _print_status_lights(11 + yhack); } - textcolour(LIGHTGREY); #ifdef USE_TILE_LOCAL if (has_changed) @@ -2504,7 +2502,8 @@ class overview_popup : public formatted_scroller if (find(equip_chars.begin(), equip_chars.end(), ch) != equip_chars.end()) { item_def& item = you.inv[letter_to_index(ch)]; - describe_item(item); + if (!describe_item(item)) + return false; return true; } return formatted_scroller::process_key(ch); diff --git a/crawl-ref/source/pcg.cc b/crawl-ref/source/pcg.cc index 3796548f77d2..f4cd5015b67e 100644 --- a/crawl-ref/source/pcg.cc +++ b/crawl-ref/source/pcg.cc @@ -1,65 +1,143 @@ -/* This class implements a 128-bit pseudo-random number generator. - * Despite a reduction in state space, it still passes TestU01 BigCrush. +/* This file implements a 32-bit pseudo-random number generator with 64 bit + * internal state: see http://www.pcg-random.org/. * - * get_uint32 is derived from M.E. O'Neill's minimal PCG implementation. - * That function (c) 2014 M.E. O'Neill / pcg-random.org + * "PCG is a family of simple fast space-efficient statistically good + * algorithms for random number generation. Unlike many general-purpose RNGs, + * they are also hard to predict." + * + * TODO: should we eventually switch to just directly using the official c++ + * implementation? + * + * PcgRNG::PcgRNG() and get_uint32 are derived/modified from M.E. O'Neill's + * minimal PCG implementation: + * https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.c + * Original source is (c) 2014 Melissa O'Neill * Licensed under Apache License 2.0 + * + * get_bounded_uint32 is derived/modified from an implemention by Melissa + * O'Neill of an algorithm by Daniel Lemire as part of a comparison of bounded + * random functions: + * http://www.pcg-random.org/posts/bounded-rands.html + * https://github.com/imneme/bounded-rands/blob/master/bounded32.cpp + * Original source is Copyright (c) 2018 Melissa E. O'Neill + * Licensed under The MIT License */ #include "AppHdr.h" #include "pcg.h" -uint32_t -PcgRNG::get_uint32() +namespace rng { - count_++; - uint64_t oldstate = state_; - // Advance internal state - state_ = oldstate * static_cast(6364136223846793005ULL) + (inc_|1); - // Calculate output function (XSH RR), uses old state for max ILP - uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; - uint32_t rot = oldstate >> 59u; - return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); -} + /** + * Generate a uniformly distributed 32-bit random number. + */ + uint32_t PcgRNG::get_uint32() + { + count_++; + uint64_t oldstate = state_; + // Advance internal state. Use the 'official' multiplier. Don't change + // this without carefully consulting official sources, as not all + // multipliers are ok: see + // http://www.pcg-random.org/posts/critiquing-pcg-streams.html + state_ = oldstate * static_cast(6364136223846793005ULL) + + (inc_|1); + // Calculate output function (XSH RR), uses old state for max ILP + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); + } -uint64_t -PcgRNG::get_uint64() -{ - return static_cast(get_uint32()) << 32 | get_uint32(); -} + uint64_t + PcgRNG::get_uint64() + { + return static_cast(get_uint32()) << 32 | get_uint32(); + } -PcgRNG::PcgRNG() - // Choose base state arbitrarily. There's nothing up my sleeve. - : state_(static_cast(18446744073709551557ULL)), // Largest 64-bit prime. - inc_(static_cast(2305843009213693951ULL)), // Largest Mersenne prime under 64-bits. - count_(0) + /** + * Generate a uniformly distributed number, r, where 0 <= r < range. + * This uses a technique due to Daniel Lemire, with implementation and + * additional tweaks from Melissa O'Neil. It's designed to avoid /,% for + * small values of `range`. + * + * See: + * http://www.pcg-random.org/posts/bounded-rands.html + * https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + * https://arxiv.org/abs/1805.10941 or https://dl.acm.org/citation.cfm?id=3230636 + */ + uint32_t PcgRNG::get_bounded_uint32(uint32_t range) + { + uint32_t x = get_uint32(); + uint64_t m = uint64_t(x) * uint64_t(range); + uint32_t l = uint32_t(m); + if (l < range) + { + // TODO: will this generate warnings somewhere? the PCG c++ + // implementation has a different version of this step that may be + // useful. + uint32_t t = -range; -{ } + if (t >= range) + { + t -= range; + if (t >= range) + t %= range; + } + while (l < t) + { + x = get_uint32(); + m = uint64_t(x) * uint64_t(range); + l = uint32_t(m); + } + } + return m >> 32; + } -PcgRNG::PcgRNG(uint64_t init_key[], int key_length) - : PcgRNG() -{ - if (key_length > 0) - state_ = init_key[0]; - if (key_length > 1) - inc_ = init_key[1]; - else - inc_ ^= get_uint32(); -} + // Initialization values only. + // don't generate unseeded versions of this. But if you must, go get the + // constants from the official PCG implementation... + PcgRNG::PcgRNG() + : state_(static_cast(0)), + inc_(static_cast(1)), + count_(0) + { } -PcgRNG::PcgRNG(const CrawlVector &v) - : PcgRNG() -{ - ASSERT(v.size() == 2); - state_ = static_cast(v[0].get_int64()); - inc_ = static_cast(v[1].get_int64()); -} + /** + * Seed a PCG RNG instance based on a seed (`init_state`) and a sequence/ + * stream id. Both of these can be any value, but the highest bit of + * `sequence` will be discarded. + * + * @param init_state the seed value to initialize PCG state with. + * @param sequence the sequence number/stream id. + */ + PcgRNG::PcgRNG(uint64_t init_state, uint64_t sequence) + : PcgRNG() + { + inc_ = sequence << 1u | 1u; // any odd value is ok + get_uint32(); + state_ += init_state; + get_uint32(); + count_ = 0; + } -CrawlVector PcgRNG::to_vector() -{ - CrawlVector store; - store.push_back(static_cast(state_)); - store.push_back(static_cast(inc_)); - return store; + PcgRNG::PcgRNG(uint64_t init_state) + : PcgRNG(init_state, static_cast(2305843009213693951ULL)) + // Largest Mersenne prime under 64-bits. + { } + + PcgRNG::PcgRNG(const CrawlVector &v) + : PcgRNG() + { + ASSERT(v.size() == 2); + state_ = static_cast(v[0].get_int64()); + inc_ = static_cast(v[1].get_int64()); + } + + CrawlVector PcgRNG::to_vector() + { + CrawlVector store; + store.push_back(static_cast(state_)); + store.push_back(static_cast(inc_)); + return store; + } } diff --git a/crawl-ref/source/pcg.h b/crawl-ref/source/pcg.h index ae4a403af4fe..05670fcb774e 100644 --- a/crawl-ref/source/pcg.h +++ b/crawl-ref/source/pcg.h @@ -1,16 +1,21 @@ #pragma once class CrawlVector; -class PcgRNG +namespace rng { + class PcgRNG + { public: PcgRNG(); - PcgRNG(uint64_t init_key[], int key_length); + PcgRNG(uint64_t init_state, uint64_t sequence); + PcgRNG(uint64_t init_state); PcgRNG(const CrawlVector &v); CrawlVector to_vector(); uint32_t get_uint32(); + uint32_t get_bounded_uint32(uint32_t bound); uint64_t get_uint64(); uint32_t operator()() { return get_uint32(); } + uint32_t operator()(uint32_t bound) { return get_bounded_uint32(bound); } typedef uint32_t result_type; static constexpr uint32_t min() { return 0; } @@ -24,4 +29,5 @@ class PcgRNG uint64_t state_; uint64_t inc_; uint64_t count_; -}; + }; +} diff --git a/crawl-ref/source/place.cc b/crawl-ref/source/place.cc index d98ef59d8a5f..735cd2d58425 100644 --- a/crawl-ref/source/place.cc +++ b/crawl-ref/source/place.cc @@ -8,6 +8,7 @@ #include "place.h" #include "branch.h" +#include "libutil.h" #include "player.h" // Prepositional form of branch level name. For example, "in the @@ -16,7 +17,7 @@ string prep_branch_level_name(level_id id) { string place = id.describe(true, true); if (!place.empty() && place != "Pandemonium") - place[0] = tolower(place[0]); + place[0] = tolower_safe(place[0]); return place.find("level") == 0 ? "on " + place : "in " + place; } diff --git a/crawl-ref/source/player-act.cc b/crawl-ref/source/player-act.cc index 507503167cfe..8fd2e3843a27 100644 --- a/crawl-ref/source/player-act.cc +++ b/crawl-ref/source/player-act.cc @@ -169,10 +169,8 @@ bool player::can_pass_through_feat(dungeon_feature_type grid) const bool player::is_habitable_feat(dungeon_feature_type actual_grid) const { - if (!can_pass_through_feat(actual_grid)) - return false; - - return !is_feat_dangerous(actual_grid); + return can_pass_through_feat(actual_grid) + && !is_feat_dangerous(actual_grid); } size_type player::body_size(size_part_type psize, bool base) const @@ -256,7 +254,7 @@ random_var player::attack_delay(const item_def *projectile, bool rescale) const attk_delay = rv::max(attk_delay, random_var(FASTEST_PLAYER_THROWING_SPEED)); } - else if (!weap) + else if (!projectile && !weap) { int sk = form_uses_xl() ? experience_level * 10 : skill(SK_UNARMED_COMBAT, 10); diff --git a/crawl-ref/source/player-equip.cc b/crawl-ref/source/player-equip.cc index 4dd94e9dc5e0..3b29080e8483 100644 --- a/crawl-ref/source/player-equip.cc +++ b/crawl-ref/source/player-equip.cc @@ -83,6 +83,7 @@ bool unequip_item(equipment_type slot, bool msg) else you.melded.set(slot, false); ash_check_bondage(); + you.last_unequip = item_slot; return true; } } @@ -161,6 +162,8 @@ void equip_effect(equipment_type slot, int item_slot, bool unmeld, bool msg) if (msg) _equip_use_warning(item); + const interrupt_block block_unmeld_interrupts(unmeld); + if (slot == EQ_WEAPON) _equip_weapon_effect(item, msg, unmeld); else if (slot >= EQ_CLOAK && slot <= EQ_BODY_ARMOUR) @@ -179,6 +182,8 @@ void unequip_effect(equipment_type slot, int item_slot, bool meld, bool msg) _assert_valid_slot(eq, slot); + const interrupt_block block_meld_interrupts(meld); + if (slot == EQ_WEAPON) _unequip_weapon_effect(item, msg, meld); else if (slot >= EQ_CLOAK && slot <= EQ_BODY_ARMOUR) @@ -387,8 +392,6 @@ static void _equip_use_warning(const item_def& item) { if (is_holy_item(item) && you_worship(GOD_YREDELEMNUL)) mpr("You really shouldn't be using a holy item like this."); - else if (is_corpse_violating_item(item) && you_worship(GOD_FEDHAS)) - mpr("You really shouldn't be using a corpse-violating item like this."); else if (is_evil_item(item) && is_good_god(you.religion)) mpr("You really shouldn't be using an evil item like this."); else if (is_unclean_item(item) && you_worship(GOD_ZIN)) @@ -397,8 +400,12 @@ static void _equip_use_warning(const item_def& item) mpr("You really shouldn't be using a chaotic item like this."); else if (is_hasty_item(item) && you_worship(GOD_CHEIBRIADOS)) mpr("You really shouldn't be using a hasty item like this."); + else if (is_wizardly_item(item) && you_worship(GOD_TROG)) + mpr("You really shouldn't be using a wizardly item like this."); +#if TAG_MAJOR_VERSION == 34 else if (is_channeling_item(item) && you_worship(GOD_PAKELLAS)) mpr("You really shouldn't be trying to channel magic like this."); +#endif } static void _wield_cursed(item_def& item, bool known_cursed, bool unmeld) @@ -531,10 +538,8 @@ static void _equip_weapon_effect(item_def& item, bool showMsgs, bool unmeld) case SPWPN_VAMPIRISM: if (you.species == SP_VAMPIRE) mpr("You feel a bloodthirsty glee!"); - else if (you.undead_state() == US_ALIVE && !you_foodless()) - mpr("You feel a dreadful hunger."); else - mpr("You feel an empty sense of dread."); + mpr("You feel a sense of dread."); break; case SPWPN_PAIN: @@ -599,16 +604,6 @@ static void _equip_weapon_effect(item_def& item, bool showMsgs, bool unmeld) // effect second switch (special) { - case SPWPN_VAMPIRISM: - if (you.species != SP_VAMPIRE - && you.undead_state() == US_ALIVE - && !you_foodless() - && !unmeld) - { - make_hungry(4500, false, false); - } - break; - case SPWPN_DISTORTION: if (!was_known) { @@ -782,12 +777,14 @@ static void _spirit_shield_message(bool unmeld) { dec_mp(you.magic_points); mpr("You feel your power drawn to a protective spirit."); +#if TAG_MAJOR_VERSION == 34 if (you.species == SP_DEEP_DWARF && !(have_passive(passive_t::no_mp_regen) || player_under_penance(GOD_PAKELLAS))) { mpr("Now linked to your health, your magic stops regenerating."); } +#endif } else if (!unmeld && you.get_mutation_level(MUT_MANA_SHIELD)) mpr("You feel the presence of a powerless spirit."); @@ -1279,8 +1276,7 @@ static void _equip_jewellery_effect(item_def &item, bool unmeld, break; case AMU_THE_GOURMAND: - if (you.species == SP_VAMPIRE - || you_foodless() // Mummy or in lichform + if (you_foodless() // Mummy, vampire, or in lichform || you.get_mutation_level(MUT_HERBIVOROUS) > 0) // Spriggan { mpr("After a brief, frighteningly intense craving, " diff --git a/crawl-ref/source/player-reacts.cc b/crawl-ref/source/player-reacts.cc index 306fb97cc019..95b99c91a086 100644 --- a/crawl-ref/source/player-reacts.cc +++ b/crawl-ref/source/player-reacts.cc @@ -289,26 +289,19 @@ static void _maybe_melt_armour() */ static int _current_horror_level() { - const coord_def& center = you.pos(); - const int radius = LOS_RADIUS; int horror_level = 0; - for (radius_iterator ri(center, radius, C_SQUARE); ri; ++ri) + for (monster_near_iterator mi(&you, LOS_NO_TRANS); mi; ++mi) { - const monster* const mon = monster_at(*ri); - if (mon == nullptr - || mons_aligned(mon, &you) - || !mons_is_threatening(*mon) - || !you.can_see(*mon) - || mons_is_tentacle_or_tentacle_segment(mon->type)) + if (mons_aligned(*mi, &you) + || !mons_is_threatening(**mi) + || mons_is_tentacle_or_tentacle_segment(mi->type)) { continue; } - ASSERT(mon); - - const mon_threat_level_type threat_level = mons_threat_level(*mon); + const mon_threat_level_type threat_level = mons_threat_level(**mi); if (threat_level == MTHRT_NASTY) horror_level += 3; else if (threat_level == MTHRT_TOUGH) @@ -461,7 +454,7 @@ void player_reacts_to_monsters() { // In case Maurice managed to steal a needed item for example. if (!you_are_delayed()) - update_can_train(); + update_can_currently_train(); if (you.duration[DUR_FIRE_SHIELD] > 0) manage_fire_shield(you.time_taken); @@ -755,11 +748,18 @@ static void _decrement_durations() } } - if (you.duration[DUR_DEATHS_DOOR] && you.hp > allowed_deaths_door_hp()) + if (you.duration[DUR_DEATHS_DOOR] + && you.attribute[ATTR_DEATHS_DOOR_HP] > 0 + && you.hp > you.attribute[ATTR_DEATHS_DOOR_HP]) { - set_hp(allowed_deaths_door_hp()); + set_hp(you.attribute[ATTR_DEATHS_DOOR_HP]); you.redraw_hit_points = true; } + else if (!you.duration[DUR_DEATHS_DOOR] + && you.attribute[ATTR_DEATHS_DOOR_HP] > 0) + { + you.attribute[ATTR_DEATHS_DOOR_HP] = 0; + } if (_decrement_a_duration(DUR_CLOUD_TRAIL, delay, "Your trail of clouds dissipates.")) @@ -799,11 +799,7 @@ static void _decrement_durations() } if (you.duration[DUR_TOXIC_RADIANCE]) - { - const int ticks = (you.duration[DUR_TOXIC_RADIANCE] / 10) - - ((you.duration[DUR_TOXIC_RADIANCE] - delay) / 10); - toxic_radiance_effect(&you, ticks); - } + toxic_radiance_effect(&you, min(delay, you.duration[DUR_TOXIC_RADIANCE])); if (you.duration[DUR_RECITE] && _check_recite()) { @@ -829,11 +825,15 @@ static void _decrement_durations() doom_howl(min(delay, you.duration[DUR_DOOM_HOWL])); dec_elixir_player(delay); - extract_manticore_spikes("You carefully extract the barbed spikes from " - "your body."); - if (!env.sunlight.empty()) - process_sunlights(); + if (!you.cannot_move() + && !you.confused() + && !you.asleep()) + { + extract_manticore_spikes( + make_stringf("You %s the barbed spikes from your body.", + you.berserk() ? "rip and tear" : "carefully extract").c_str()); + } if (!you.duration[DUR_ANCESTOR_DELAY] && in_good_standing(GOD_HEPLIAKLQANA) diff --git a/crawl-ref/source/player-save-info.h b/crawl-ref/source/player-save-info.h index 447b042e2d07..df5cf3a754ec 100644 --- a/crawl-ref/source/player-save-info.h +++ b/crawl-ref/source/player-save-info.h @@ -37,4 +37,5 @@ struct player_save_info player_save_info& operator=(const player& rhs); bool operator<(const player_save_info& rhs) const; string short_desc() const; + string really_short_desc() const; }; diff --git a/crawl-ref/source/player-stats.cc b/crawl-ref/source/player-stats.cc index 51b801714c9d..b1ade9dfb781 100644 --- a/crawl-ref/source/player-stats.cc +++ b/crawl-ref/source/player-stats.cc @@ -156,7 +156,7 @@ bool attribute_increase() { string result; clua.fnreturns(">s", &result); - keyin = toupper(result[0]); + keyin = toupper_safe(result[0]); } else { @@ -326,7 +326,7 @@ void modify_stat(stat_type which_stat, int amount, bool suppress_msg) // Stop delays if a stat drops. if (amount < 0) - interrupt_activity(AI_STAT_CHANGE); + interrupt_activity(activity_interrupt::stat_change); if (which_stat == STAT_RANDOM) which_stat = static_cast(random2(NUM_STATS)); @@ -353,7 +353,7 @@ void notify_stat_change(stat_type which_stat, int amount, bool suppress_msg) // Stop delays if a stat drops. if (amount < 0) - interrupt_activity(AI_STAT_CHANGE); + interrupt_activity(activity_interrupt::stat_change); if (which_stat == STAT_RANDOM) which_stat = static_cast(random2(NUM_STATS)); diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index 5034926969ba..8e4fc8d7453f 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -407,8 +407,8 @@ bool swap_check(monster* mons, coord_def &loc, bool quiet) // Might not be ideal, but it's better than insta-killing // the monster... maybe try for a short blink instead? - bwr simple_monster_message(*mons, " cannot make way for you."); - // FIXME: AI_HIT_MONSTER isn't ideal. - interrupt_activity(AI_HIT_MONSTER, mons); + // FIXME: activity_interrupt::hit_monster isn't ideal. + interrupt_activity(activity_interrupt::hit_monster, mons); } return swap; @@ -549,7 +549,7 @@ bool is_feat_dangerous(dungeon_feature_type grid, bool permanently, bool is_map_persistent() { - return !(your_branch().branch_flags & BFLAG_NO_MAP) + return !testbits(your_branch().branch_flags, brflag::no_map) || env.properties.exists(FORCE_MAPPABLE_KEY); } @@ -1090,12 +1090,9 @@ static int _player_bonus_regen() // Inhibited regeneration: stops regeneration when monsters are visible bool regeneration_is_inhibited() { - switch (you.get_mutation_level(MUT_INHIBITED_REGENERATION)) + if (you.get_mutation_level(MUT_INHIBITED_REGENERATION) == 1 + || (you.species == SP_VAMPIRE && !you.vampire_alive)) { - case 0: - return false; - case 1: - { for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi) { if (mons_is_threatening(**mi) @@ -1106,12 +1103,9 @@ bool regeneration_is_inhibited() return true; } } - return false; - } - default: - die("Unknown inhibited regeneration level."); - break; } + + return false; } int player_regen() @@ -1131,17 +1125,9 @@ int player_regen() // to heal. rr = max(1, rr); - // Healing depending on satiation. - // The better-fed you are, the faster you heal. - if (you.species == SP_VAMPIRE) - { - if (you.hunger_state <= HS_STARVING) - rr = 0; // No regeneration for starving vampires. - else if (you.hunger_state < HS_SATIATED) - rr /= 2; // Halved regeneration for hungry vampires. - else if (you.hunger_state >= HS_FULL) - rr += 20; // Bonus regeneration for full vampires. - } + // Bonus regeneration for alive vampires. + if (you.species == SP_VAMPIRE && you.vampire_alive) + rr += 20; if (you.duration[DUR_COLLAPSE]) rr /= 4; @@ -1239,33 +1225,8 @@ int player_hunger_rate(bool temp) hunger += 4; } - if (you.species == SP_VAMPIRE) - { - switch (you.hunger_state) - { - case HS_FAINTING: - case HS_STARVING: - hunger -= 3; - break; - case HS_NEAR_STARVING: - case HS_VERY_HUNGRY: - case HS_HUNGRY: - hunger--; - break; - case HS_SATIATED: - break; - case HS_FULL: - case HS_VERY_FULL: - case HS_ENGORGED: - hunger += 2; - break; - } - } - else - { - hunger += you.get_mutation_level(MUT_FAST_METABOLISM) - - you.get_mutation_level(MUT_SLOW_METABOLISM); - } + hunger += you.get_mutation_level(MUT_FAST_METABOLISM) + - you.get_mutation_level(MUT_SLOW_METABOLISM); // If Cheibriados has slowed your life processes, you will hunger less. if (have_passive(passive_t::slow_metabolism)) @@ -1442,13 +1403,8 @@ int player_res_cold(bool calc_unid, bool temp, bool items) rc += get_form()->res_cold(); - if (you.species == SP_VAMPIRE) - { - if (you.hunger_state <= HS_STARVING) + if (you.species == SP_VAMPIRE && !you.vampire_alive) rc += 2; - else if (you.hunger_state < HS_SATIATED) - rc++; - } } if (items) @@ -1590,7 +1546,7 @@ bool player_res_torment(bool random) } return get_form()->res_neg() == 3 - || you.species == SP_VAMPIRE && you.hunger_state <= HS_STARVING + || you.species == SP_VAMPIRE && !you.vampire_alive || you.petrified() #if TAG_MAJOR_VERSION == 34 || player_equip_unrand(UNRAND_ETERNAL_TORMENT) @@ -1617,7 +1573,7 @@ int player_res_poison(bool calc_unid, bool temp, bool items) case US_UNDEAD: // mummies & lichform return 3; case US_SEMI_UNDEAD: // vampire - if (you.hunger_state <= HS_STARVING) // XXX: && temp? + if (!you.vampire_alive) // XXX: && temp? return 3; break; } @@ -1660,11 +1616,6 @@ int player_res_poison(bool calc_unid, bool temp, bool items) rp += you.get_mutation_level(MUT_POISON_RESISTANCE, temp); rp += you.get_mutation_level(MUT_SLIMY_GREEN_SCALES, temp) == 3 ? 1 : 0; - // Only thirsty vampires are naturally poison resistant. - // XXX: && temp? - if (you.species == SP_VAMPIRE && you.hunger_state < HS_SATIATED) - rp++; - if (temp) { // potions/cards: @@ -1787,6 +1738,9 @@ int player_spec_conj() // Staves sc += you.wearing(EQ_STAFF, STAFF_CONJURATION); + if (player_equip_unrand(UNRAND_BATTLE)) + sc++; + return sc; } @@ -1843,26 +1797,8 @@ int player_prot_life(bool calc_unid, bool temp, bool items) // Hunger is temporary, true, but that's something you can control, // especially as life protection only increases the hungrier you // get. - if (you.species == SP_VAMPIRE) - { - switch (you.hunger_state) - { - case HS_FAINTING: - case HS_STARVING: + if (you.species == SP_VAMPIRE && !you.vampire_alive) pl = 3; - break; - case HS_NEAR_STARVING: - case HS_VERY_HUNGRY: - case HS_HUNGRY: - pl = 2; - break; - case HS_SATIATED: - pl = 1; - break; - default: - break; - } - } // Same here. Your piety status, and, hence, TSO's protection, is // something you can more or less control. @@ -2228,7 +2164,7 @@ static int _player_evasion(ev_ignore_type evit) // Size is all that matters when paralysed or at 0 dex. if ((you.cannot_move() || you.duration[DUR_CLUMSY] || you.form == transformation::tree) - && !(evit & EV_IGNORE_HELPLESS)) + && !(evit & ev_ignore::helpless)) { return max(1, 2 + size_factor / 2); } @@ -2730,7 +2666,8 @@ void calc_hp(bool scale, bool set) you.hp_max = get_real_hp(true, true); - if (scale) { + if (scale) + { int hp = you.hp * 100 + you.hit_points_regeneration; int new_max = you.hp_max; hp = hp * new_max / old_max; @@ -2877,9 +2814,9 @@ void level_change(bool skip_attribute_increase) case SP_VAMPIRE: if (you.experience_level == 3) { - if (you.hunger_state > HS_SATIATED) + if (you.vampire_alive) { - mprf(MSGCH_INTRINSIC_GAIN, "If you weren't so full, " + mprf(MSGCH_INTRINSIC_GAIN, "If you were bloodless " "you could now transform into a vampire bat."); } else @@ -2912,7 +2849,7 @@ void level_change(bool skip_attribute_increase) // do_level_up = false) but save the old values; then when // we want the messages later, we restore the old skill // levels and call check_skill_level_change() again, this - // time passing do_update = true. + // time passing do_level_up = true. uint8_t saved_skills[NUM_SKILLS]; for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk) @@ -2948,6 +2885,11 @@ void level_change(bool skip_attribute_increase) check_skill_level_change(sk); } + // It's possible we passed a training target due to + // skills being rescaled to new aptitudes. Thus, we must + // check the training targets. + check_training_targets(); + // Tell the player about their new species for (auto &mut : fake_mutations(you.species, false)) mprf(MSGCH_INTRINSIC_GAIN, "%s", mut.c_str()); @@ -3186,14 +3128,9 @@ int player_stealth() if (you.duration[DUR_SILENCE]) stealth -= STEALTH_PIP; - // Thirsty vampires are stealthier. - if (you.species == SP_VAMPIRE) - { - if (you.hunger_state <= HS_STARVING || you.form == transformation::bat) + // Bloodless vampires are stealthier. + if (you.species == SP_VAMPIRE && !you.vampire_alive) stealth += STEALTH_PIP * 2; - else if (you.hunger_state <= HS_HUNGRY) - stealth += STEALTH_PIP; - } if (!you.airborne()) { @@ -3289,38 +3226,19 @@ static void _display_char_status(int value, const char *fmt, ...) static void _display_vampire_status() { - string msg = "At your current hunger state you "; + string msg = "At your current blood state you "; vector attrib; - switch (you.hunger_state) + if (!you.vampire_alive) { - case HS_FAINTING: - case HS_STARVING: - attrib.push_back("are immune to poison"); - attrib.push_back("significantly resist cold"); - attrib.push_back("are immune to negative energy"); - attrib.push_back("resist torment"); - attrib.push_back("do not heal."); - break; - case HS_NEAR_STARVING: - case HS_VERY_HUNGRY: - case HS_HUNGRY: - attrib.push_back("resist poison"); - attrib.push_back("resist cold"); - attrib.push_back("significantly resist negative energy"); - attrib.push_back("have a slow metabolism"); - attrib.push_back("heal slowly."); - break; - case HS_SATIATED: - attrib.push_back("resist negative energy."); - break; - case HS_FULL: - case HS_VERY_FULL: - case HS_ENGORGED: - attrib.push_back("have a fast metabolism"); - attrib.push_back("heal quickly."); - break; + attrib.push_back("are immune to poison"); + attrib.push_back("significantly resist cold"); + attrib.push_back("are immune to negative energy"); + attrib.push_back("resist torment"); + attrib.push_back("do not heal."); } + else + attrib.push_back("heal quickly."); if (!attrib.empty()) { @@ -3782,7 +3700,7 @@ void inc_mp(int mp_gain, bool silent) if (!silent) { if (_should_stop_resting(you.magic_points, you.max_magic_points)) - interrupt_activity(AI_FULL_MP); + interrupt_activity(activity_interrupt::full_mp); you.redraw_magic_points = true; } } @@ -3803,7 +3721,7 @@ void inc_hp(int hp_gain) you.hp = you.hp_max; if (_should_stop_resting(you.hp, you.hp_max)) - interrupt_activity(AI_FULL_HP); + interrupt_activity(activity_interrupt::full_hp); you.redraw_hit_points = true; } @@ -3929,7 +3847,7 @@ int get_real_hp(bool trans, bool rotted) hitp /= 10; const bool hep_frail = have_passive(passive_t::frail) - || player_under_penance(GOD_HEPLIAKLQANA); + || player_under_penance(GOD_HEPLIAKLQANA); // Mutations that increase HP by a percentage hitp *= 100 + (you.get_mutation_level(MUT_ROBUST) * 10) @@ -3937,7 +3855,8 @@ int get_real_hp(bool trans, bool rotted) + (you.get_mutation_level(MUT_RUGGED_BROWN_SCALES) ? you.get_mutation_level(MUT_RUGGED_BROWN_SCALES) * 2 + 1 : 0) - (you.get_mutation_level(MUT_FRAIL) * 10) - - (hep_frail ? 10 : 0); + - (hep_frail ? 10 : 0) + - (!you.vampire_alive ? 20 : 0); hitp /= 100; @@ -3960,9 +3879,6 @@ int get_real_hp(bool trans, bool rotted) hitp = hitp * 4 / 5; #endif - if (trans && you.duration[DUR_DEATHS_DOOR] > 0) - hitp = allowed_deaths_door_hp(); - return max(1, hitp); } @@ -4015,8 +3931,6 @@ bool player_regenerates_hp() { if (you.has_mutation(MUT_NO_REGENERATION)) return false; - if (you.species == SP_VAMPIRE && you.hunger_state <= HS_STARVING) - return false; return true; } @@ -4026,9 +3940,11 @@ bool player_regenerates_mp() // damage shaving is enough. (due, dpeg) if (you.spirit_shield() && you.species == SP_DEEP_DWARF) return false; +#if TAG_MAJOR_VERSION == 34 // Pakellas blocks MP regeneration. if (have_passive(passive_t::no_mp_regen) || player_under_penance(GOD_PAKELLAS)) return false; +#endif return true; } @@ -4328,13 +4244,8 @@ void handle_player_poison(int delay) // Transforming into a form with no metabolism merely suspends the poison // but doesn't let your body get rid of it. - // Hungry vampires are less affected by poison (not at all when bloodless). - if (you.is_nonliving() || you.undead_state() - && (you.undead_state() != US_SEMI_UNDEAD - || x_chance_in_y(4 - you.hunger_state, 4))) - { + if (you.is_nonliving() || (you.undead_state() && !you.vampire_alive)) return; - } // Other sources of immunity (Zin, staff of Olgreb) let poison dissipate. bool do_dmg = (player_res_poison() >= 3 ? false : true); @@ -4946,7 +4857,6 @@ bool land_player(bool quiet) static void _end_water_hold() { you.duration[DUR_WATER_HOLD] = 0; - you.duration[DUR_WATER_HOLD_IMMUNITY] = 1; you.props.erase("water_holder"); } @@ -4956,7 +4866,7 @@ bool player::clear_far_engulf() return false; monster * const mons = monster_by_mid(you.props["water_holder"].get_int()); - if (!mons || !adjacent(mons->pos(), you.pos())) + if (!mons || !mons->alive() || !adjacent(mons->pos(), you.pos())) { if (you.res_water_drowning()) mpr("The water engulfing you falls away."); @@ -4971,40 +4881,22 @@ bool player::clear_far_engulf() void handle_player_drowning(int delay) { - if (you.duration[DUR_WATER_HOLD] == 1) + if (you.clear_far_engulf()) + return; + if (you.res_water_drowning()) { - if (!you.res_water_drowning()) - mpr("You gasp with relief as air once again reaches your lungs."); - _end_water_hold(); + // Reset so damage doesn't ramp up while able to breathe + you.duration[DUR_WATER_HOLD] = 10; } else { - monster* mons = monster_by_mid(you.props["water_holder"].get_int()); - if (!mons || !adjacent(mons->pos(), you.pos())) - { - if (you.res_water_drowning()) - mpr("The water engulfing you falls away."); - else - mpr("You gasp with relief as air once again reaches your lungs."); - - _end_water_hold(); - - } - else if (you.res_water_drowning()) - { - // Reset so damage doesn't ramp up while able to breathe - you.duration[DUR_WATER_HOLD] = 10; - } - else if (!you.res_water_drowning()) - { - you.duration[DUR_WATER_HOLD] += delay; - int dam = - div_rand_round((28 + stepdown((float)you.duration[DUR_WATER_HOLD], 28.0)) - * delay, - BASELINE_DELAY * 10); - ouch(dam, KILLED_BY_WATER, mons->mid); - mprf(MSGCH_WARN, "Your lungs strain for air!"); - } + you.duration[DUR_WATER_HOLD] += delay; + int dam = + div_rand_round((28 + stepdown((float)you.duration[DUR_WATER_HOLD], 28.0)) + * delay, + BASELINE_DELAY * 10); + ouch(dam, KILLED_BY_WATER, you.props["water_holder"].get_int()); + mprf(MSGCH_WARN, "Your lungs strain for air!"); } } @@ -5088,6 +4980,7 @@ player::player() equip.init(-1); melded.reset(); unrand_reacts.reset(); + last_unequip = -1; symbol = MONS_PLAYER; form = transformation::none; @@ -5106,6 +4999,7 @@ player::player() royal_jelly_dead = false; transform_uncancellable = false; fishtail = false; + vampire_alive = true; pet_target = MHITNOT; @@ -5189,6 +5083,8 @@ player::player() num_turns = 0; exploration = 0; + trapped = false; + last_view_update = 0; spell_letter_table.init(-1); @@ -5196,6 +5092,8 @@ player::player() uniq_map_tags.clear(); uniq_map_names.clear(); + uniq_map_tags_abyss.clear(); + uniq_map_names_abyss.clear(); vault_list.clear(); global_info = PlaceInfo(); @@ -5220,6 +5118,7 @@ player::player() // Non-saved UI state: prev_targ = MHITNOT; prev_grd_targ.reset(); + divine_exegesis = false; travel_x = 0; travel_y = 0; @@ -5264,7 +5163,8 @@ player::player() abyss_speed = 0; game_seed = 0; - game_is_seeded = true; + fully_seeded = true; + deterministic_levelgen = true; old_hunger = hunger; @@ -5297,7 +5197,7 @@ void player::init_skills() train.init(TRAINING_DISABLED); train_alt.init(TRAINING_DISABLED); training.init(0); - can_train.reset(); + can_currently_train.reset(); skill_points.init(0); ct_skill_points.init(0); skill_order.init(MAX_SKILL_ORDER); @@ -5331,6 +5231,14 @@ bool player_save_info::operator<(const player_save_info& rhs) const || (experience_level == rhs.experience_level && name < rhs.name); } +string player_save_info::really_short_desc() const +{ + ostringstream desc; + desc << name << " the " << species_name << ' ' << class_name; + + return desc.str(); +} + string player_save_info::short_desc() const { ostringstream desc; @@ -5528,17 +5436,10 @@ void player::banish(actor* /*agent*/, const string &who, const int power, banished_power = power; } -// For semi-undead species (Vampire!) reduce food cost for spells and abilities -// to 50% (hungry, very hungry, near starving) or zero (starving). +// Currently a no-op, previously was used for vampire hunger modifications. +// Perhaps in the fullness of time this can be removed. int calc_hunger(int food_cost) { - if (you.undead_state() == US_SEMI_UNDEAD && you.hunger_state < HS_SATIATED) - { - if (you.hunger_state <= HS_STARVING) - return 0; - - return food_cost/2; - } return food_cost; } @@ -5563,7 +5464,7 @@ int player::get_noise_perception(bool adjusted) const const int level = los_noise_last_turn; static const int BAR_MAX = 1000; // TODO: export to output.cc & webtiles if (!adjusted) - return div_rand_round(level, BAR_MAX); + return (level + 500) / BAR_MAX; static const vector NOISE_BREAKPOINTS = { 0, 6000, 13000, 29000 }; const int BAR_FRAC = BAR_MAX / (NOISE_BREAKPOINTS.size() - 1); @@ -5637,7 +5538,6 @@ bool player::shielded() const || duration[DUR_DIVINE_SHIELD] || get_mutation_level(MUT_LARGE_BONE_PLATES) > 0 || qazlal_sh_boost() > 0 - || attribute[ATTR_BONE_ARMOUR] > 0 || you.wearing(EQ_AMULET_PLUS, AMU_REFLECTION) > 0 || you.scan_artefacts(ARTP_SHIELDING); } @@ -6066,8 +5966,8 @@ int player::evasion(ev_ignore_type evit, const actor* act) const const int constrict_penalty = is_constricted() ? 3 : 0; const bool attacker_invis = act && !act->visible_to(this); - const int invis_penalty = attacker_invis && !(evit & EV_IGNORE_HELPLESS) ? - 10 : 0; + const int invis_penalty + = attacker_invis && !testbits(evit, ev_ignore::helpless) ? 10 : 0; return base_evasion - constrict_penalty - invis_penalty; } @@ -6092,7 +5992,8 @@ mon_holy_type player::holiness(bool temp) const mon_holy_type holi; // Lich form takes precedence over a species' base holiness - if (undead_state(temp)) + // Alive Vampires are MH_NATURAL + if (is_lifeless_undead(temp)) holi = MH_UNDEAD; else if (species == SP_GARGOYLE) holi = MH_NONLIVING; @@ -6110,8 +6011,11 @@ mon_holy_type player::holiness(bool temp) const if (is_good_god(religion)) holi |= MH_HOLY; - if (is_evil_god(religion) || species == SP_DEMONSPAWN) + if (is_evil_god(religion) + || species == SP_DEMONSPAWN || species == SP_VAMPIRE) + { holi |= MH_EVIL; + } // possible XXX: Monsters get evil/unholy bits set on spell selection // should players? @@ -6224,7 +6128,7 @@ int player::res_rotting(bool temp) const return 1; // rottable by Zin, not by necromancy case US_SEMI_UNDEAD: - if (temp && hunger_state < HS_SATIATED) + if (temp && !you.vampire_alive) return 1; return 0; // no permanent resistance @@ -7106,7 +7010,7 @@ bool player::can_safely_mutate(bool temp) const bool player::is_lifeless_undead(bool temp) const { if (undead_state() == US_SEMI_UNDEAD) - return temp ? hunger_state < HS_SATIATED : false; + return temp ? !you.vampire_alive : false; else return undead_state(temp) != US_ALIVE; } @@ -7319,6 +7223,10 @@ bool player::do_shaft() return false; } + // Ensure altars, items, and shops discovered at the moment + // the player gets shafted are correctly registered. + maybe_update_stashes(); + duration[DUR_SHAFT_IMMUNITY] = 1; down_stairs(DNGN_TRAP_SHAFT); @@ -7781,7 +7689,7 @@ void player_open_door(coord_def doorpos) set_exclude(doorpos, 0); } } - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); return; } ignore_exclude = true; @@ -7795,7 +7703,7 @@ void player_open_door(coord_def doorpos) if (!yesno(prompt.c_str(), true, 'n', true, false)) { canned_msg(MSG_OK); - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); return; } } diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index 28230bdb7b74..1b6092f09911 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -68,9 +68,6 @@ static const int MR_PIP = 40; /// The standard unit of stealth; one level in %/@ screens static const int STEALTH_PIP = 50; -/// The rough number of aut getting hit takes off your bone armour -static const int BONE_ARMOUR_HIT_RATIO = 50; - /// The minimum aut cost for a player move (before haste) static const int FASTEST_PLAYER_MOVE_SPEED = 6; // relevant for swiftness, etc @@ -187,6 +184,7 @@ class player : public actor bool royal_jelly_dead; bool transform_uncancellable; bool fishtail; // Merfolk fishtail transformation + bool vampire_alive; unsigned short pet_target; @@ -209,7 +207,7 @@ class player : public actor FixedVector train; ///< see enum def FixedVector train_alt; ///< config of other mode FixedVector training; ///< percentage of XP used - FixedBitVector can_train; ///< Is training this skill allowed? + FixedBitVector can_currently_train; ///< Is training this skill allowed? FixedVector skill_points; FixedVector training_targets; ///< Training targets, scaled by 10 (so [0,270]). 0 means no target. @@ -311,6 +309,8 @@ class player : public actor // Maps without allow_dup that have been already used. set uniq_map_tags; set uniq_map_names; + set uniq_map_tags_abyss; + set uniq_map_names_abyss; // All maps, by level. map > vault_list; @@ -361,13 +361,17 @@ class player : public actor // Hash seed for deterministic stuff. uint64_t game_seed; - bool game_is_seeded; + bool fully_seeded; // true on all games started since 0.23 seeding changes + bool deterministic_levelgen; // true if a game was started with incremental + // or full pregen. // ------------------- // Non-saved UI state: // ------------------- unsigned short prev_targ; coord_def prev_grd_targ; + // Examining spell library spells for Sif Muna's ability + bool divine_exegesis; // Coordinates of last travel target; note that this is never used by // travel itself, only by the level-map to remember the last travel target. @@ -396,7 +400,7 @@ class player : public actor // The last spell cast by the player. spell_type last_cast_spell; map last_pickup; - + int last_unequip; // --------------------------- // Volatile (same-turn) state: @@ -816,7 +820,7 @@ class player : public actor int base_ac(int scale) const; int armour_class(bool /*calc_unid*/ = true) const override; int gdr_perc() const override; - int evasion(ev_ignore_type evit = EV_IGNORE_NONE, + int evasion(ev_ignore_type evit = ev_ignore::none, const actor *attacker = nullptr) const override; int stat_hp() const override { return hp; } diff --git a/crawl-ref/source/potion-type.h b/crawl-ref/source/potion-type.h index 1b0eecc41d6b..fcf5e8b908a9 100644 --- a/crawl-ref/source/potion-type.h +++ b/crawl-ref/source/potion-type.h @@ -41,8 +41,8 @@ enum potion_type #endif POT_MUTATION, POT_RESISTANCE, - POT_BLOOD, #if TAG_MAJOR_VERSION == 34 + POT_BLOOD, POT_BLOOD_COAGULATED, #endif POT_LIGNIFY, diff --git a/crawl-ref/source/potion.cc b/crawl-ref/source/potion.cc index 4ef1749a74e1..2333c2abeee0 100644 --- a/crawl-ref/source/potion.cc +++ b/crawl-ref/source/potion.cc @@ -89,7 +89,7 @@ class PotionCuring : public PotionEffect if (you.duration[DUR_DEATHS_DOOR]) { if (reason) - *reason = "You can't heal while in Death's door."; + *reason = "You can't heal while in death's door."; return false; } if (!you.can_potion_heal() @@ -175,7 +175,7 @@ class PotionHealWounds : public PotionEffect if (you.duration[DUR_DEATHS_DOOR]) { if (reason) - *reason = "You cannot heal while in Death's door!"; + *reason = "You cannot heal while in death's door."; return false; } if (you.hp == you.hp_max && player_rotted() == 0) @@ -655,7 +655,7 @@ class PotionBerserk : public PotionEffect bool effect(bool was_known = true, int pow = 40, bool=true) const override { - if (you.species == SP_VAMPIRE && you.hunger_state < HS_SATIATED) + if (you.species == SP_VAMPIRE && !you.vampire_alive) { mpr("You feel slightly irritated."); return false; diff --git a/crawl-ref/source/prompt.cc b/crawl-ref/source/prompt.cc index b541e812a935..4c00815c49a6 100644 --- a/crawl-ref/source/prompt.cc +++ b/crawl-ref/source/prompt.cc @@ -50,11 +50,10 @@ bool yes_or_no(const char* fmt, ...) // -- idea borrowed from Nethack bool yesno(const char *str, bool allow_lowercase, int default_answer, bool clear_after, bool interrupt_delays, bool noprompt, - const explicit_keymap *map, GotoRegion region) + const explicit_keymap *map) { - bool message = (region == GOTO_MSG); if (interrupt_delays && !crawl_state.is_repeating_cmd()) - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); // Allow players to answer prompts via clua. maybe_bool res = clua.callmaybefn("c_answer_prompt", "s", str); @@ -104,12 +103,7 @@ bool yesno(const char *str, bool allow_lowercase, int default_answer, bool clear else { if (!noprompt) - { - if (message) - mprf(MSGCH_PROMPT, "%s", prompt.c_str()); - else - cprintf("%s", prompt.c_str()); - } + mprf(MSGCH_PROMPT, "%s", prompt.c_str()); tmp = ui::getch(KMC_CONFIRM); } @@ -132,14 +126,15 @@ bool yesno(const char *str, bool allow_lowercase, int default_answer, bool clear tmp = default_answer; } - if (Options.easy_confirm == CONFIRM_ALL_EASY + if (Options.easy_confirm == easy_confirm_type::all || tmp == default_answer - || Options.easy_confirm == CONFIRM_SAFE_EASY && allow_lowercase) + || Options.easy_confirm == easy_confirm_type::safe + && allow_lowercase) { - tmp = toupper(tmp); + tmp = toupper_safe(tmp); } - if (clear_after && message) + if (clear_after) clear_messages(); if (tmp == 'N') @@ -155,10 +150,8 @@ bool yesno(const char *str, bool allow_lowercase, int default_answer, bool clear upper ? "Uppercase " : ""); if (use_popup && status) // redundant, but will quiet a warning status->text = pr; - else if (message) - mpr(pr); else - cprintf("%s\n", pr.c_str()); + mpr(pr); } } } @@ -221,7 +214,7 @@ int yesnoquit(const char* str, bool allow_lowercase, int default_answer, bool al bool clear_after, char alt_yes, char alt_yes2) { if (!crawl_state.is_repeating_cmd()) - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); mouse_control mc(MOUSE_MODE_YESNO); @@ -244,11 +237,12 @@ int yesnoquit(const char* str, bool allow_lowercase, int default_answer, bool al if ((tmp == ' ' || tmp == '\r' || tmp == '\n') && default_answer) tmp = default_answer; - if (Options.easy_confirm == CONFIRM_ALL_EASY + if (Options.easy_confirm == easy_confirm_type::all || tmp == default_answer - || allow_lowercase && Options.easy_confirm == CONFIRM_SAFE_EASY) + || allow_lowercase + && Options.easy_confirm == easy_confirm_type::safe) { - tmp = toupper(tmp); + tmp = toupper_safe(tmp); } if (clear_after) diff --git a/crawl-ref/source/prompt.h b/crawl-ref/source/prompt.h index 77e1b1d97b9d..f8b49d96d968 100644 --- a/crawl-ref/source/prompt.h +++ b/crawl-ref/source/prompt.h @@ -10,8 +10,7 @@ typedef map explicit_keymap; bool yesno(const char * str, bool allow_lowercase, int default_answer, bool clear_after = true, bool interrupt_delays = true, bool noprompt = false, - const explicit_keymap *map = nullptr, - GotoRegion = GOTO_MSG); + const explicit_keymap *map = nullptr); int yesnoquit(const char* str, bool safe = true, int default_answer = 0, bool allow_all = false, bool clear_after = true, diff --git a/crawl-ref/source/quiver.cc b/crawl-ref/source/quiver.cc index 66e15b733836..d5ac1255c6bc 100644 --- a/crawl-ref/source/quiver.cc +++ b/crawl-ref/source/quiver.cc @@ -166,7 +166,6 @@ void quiver_item(int slot) #endif mprf("Quivering %s for %s.", you.inv[slot].name(DESC_INVENTORY).c_str(), t == AMMO_THROW ? "throwing" : - t == AMMO_BLOWGUN ? "blowguns" : t == AMMO_SLING ? "slings" : t == AMMO_BOW ? "bows" : "crossbows"); @@ -181,8 +180,8 @@ void choose_item_for_quiver() } int slot = prompt_invent_item("Quiver which item? (- for none, * to show all)", - MT_INVLIST, OSEL_THROWABLE, OPER_QUIVER, - invprompt_flag::hide_known, '-'); + menu_type::invlist, OSEL_THROWABLE, + OPER_QUIVER, invprompt_flag::hide_known, '-'); if (prompt_failed(slot)) return; @@ -194,7 +193,6 @@ void choose_item_for_quiver() mprf("Reset %s quiver to default.", t == AMMO_THROW ? "throwing" : - t == AMMO_BLOWGUN ? "blowgun" : t == AMMO_SLING ? "sling" : t == AMMO_BOW ? "bow" : "crossbow"); @@ -392,20 +390,9 @@ void player_quiver::_get_fire_order(vector& order, const int inv_start = (ignore_inscription_etc ? 0 : Options.fire_items_start); - // If in a net, cannot throw anything, and can only launch from blowgun. + // If in a net, cannot launch anything. if (you.attribute[ATTR_HELD]) - { - if (launcher && launcher->sub_type == WPN_BLOWGUN) - { - for (int i_inv = inv_start; i_inv < ENDOFPACK; i_inv++) - if (you.inv[i_inv].defined() - && you.inv[i_inv].launched_by(*launcher)) - { - order.push_back(i_inv); - } - } return; - } for (int i_inv = inv_start; i_inv < ENDOFPACK; i_inv++) { @@ -531,7 +518,9 @@ static bool _item_matches(const item_def &item, fire_type types, return true; if ((types & FIRE_NET) && item.sub_type == MI_THROWING_NET) return true; - if ((types & FIRE_TOMAHAWK) && item.sub_type == MI_TOMAHAWK) + if ((types & FIRE_BOOMERANG) && item.sub_type == MI_BOOMERANG) + return true; + if ((types & FIRE_DART) && item.sub_type == MI_DART) return true; if (types & FIRE_LAUNCHER) @@ -589,8 +578,6 @@ static ammo_t _get_weapon_ammo_type(const item_def* weapon) switch (weapon->sub_type) { - case WPN_BLOWGUN: - return AMMO_BLOWGUN; case WPN_HUNTING_SLING: case WPN_FUSTIBALUS: return AMMO_SLING; diff --git a/crawl-ref/source/quiver.h b/crawl-ref/source/quiver.h index cb506f0f5045..674341f75d69 100644 --- a/crawl-ref/source/quiver.h +++ b/crawl-ref/source/quiver.h @@ -18,7 +18,9 @@ enum ammo_t AMMO_SLING, // wielded sling -> stones, sling bullets AMMO_CROSSBOW, // wielded crossbow -> bolts // Used to be hand crossbows +#if TAG_MAJOR_VERSION == 34 AMMO_BLOWGUN, // wielded blowgun -> needles +#endif NUM_AMMO }; diff --git a/crawl-ref/source/random-pick.h b/crawl-ref/source/random-pick.h index 1add44c10853..9cbba26e1d21 100644 --- a/crawl-ref/source/random-pick.h +++ b/crawl-ref/source/random-pick.h @@ -33,6 +33,7 @@ class random_picker public: virtual ~random_picker(); T pick(const random_pick_entry *weights, int level, T none); + int probability_at(T entry, const random_pick_entry *weights, int level); int rarity_at(const random_pick_entry *pop, int depth); virtual bool veto(T val) { return false; } @@ -80,6 +81,35 @@ T random_picker::pick(const random_pick_entry *weights, int level, die("random_pick roll out of range"); } +template +int random_picker::probability_at(T entry, + const random_pick_entry *weights, int level) +{ + int totalrar = 0; + int entry_rarity = 0; + + for (const random_pick_entry *pop = weights; pop->rarity; pop++) + { + if (level < pop->minr || level > pop->maxr) + continue; + + if (veto(pop->value)) + continue; + + int rar = rarity_at(pop, level); + ASSERTM(rar > 0, "Rarity %d: %d at level %d", rar, pop->value, level); + + if (entry == pop->value) + entry_rarity = rar; + totalrar += rar; + } + + if (totalrar == 0) + return 0; + return entry_rarity * 100 / totalrar; +} + + template int random_picker::rarity_at(const random_pick_entry *pop, int depth) { diff --git a/crawl-ref/source/random.cc b/crawl-ref/source/random.cc index be64976e95c0..1fdd7a59a233 100644 --- a/crawl-ref/source/random.cc +++ b/crawl-ref/source/random.cc @@ -17,130 +17,180 @@ #include "store.h" #include "options.h" -static FixedVector rngs; -static rng_type _generator = RNG_GAMEPLAY; - -CrawlVector generators_to_vector() +namespace rng { - CrawlVector store; - for (PcgRNG& rng : rngs) - store.push_back(rng.to_vector()); // TODO is this ok memory-wise? - return store; -} + // global/persistent rng state that will be saved + static FixedVector _global_state; -vector get_rng_states() -{ - // this doesn't return the internal state per se, but it returns the count - // of 32 bit integers that have been drawn, which amounts to the same thing. - // This isn't saved, though, so it's mainly useful for debugging within a - // session. - vector result; - for (PcgRNG& rng : rngs) - result.push_back(rng.get_count()); - return result; -} + // temporary rng state + static rng_type _generator = rng::GAMEPLAY; + // TODO: once we have c++17, convert to type optional + static PcgRNG * _sub_generator = nullptr; -void load_generators(const CrawlVector &v) -{ - // as-is, decreasing the number of rngs (e.g. by removing a branch) will - // break save compatibility. - ASSERT(v.size() <= rngs.size()); - for (int i = 0; i < v.size(); i++) + CrawlVector generators_to_vector() { - CrawlVector state = v[i].get_vector(); - rngs[i] = PcgRNG(state); + CrawlVector store; + for (PcgRNG& rng : _global_state) + store.push_back(rng.to_vector()); // TODO is this ok memory-wise? + return store; } -} -rng_generator::rng_generator(rng_type g) : previous(_generator) -{ - _generator = g; -} + vector get_states() + { + // this doesn't return the internal state per se, but it returns the count + // of 32 bit integers that have been drawn, which amounts to the same thing. + // This isn't saved, though, so it's mainly useful for debugging within a + // session. + vector result; + for (PcgRNG& rng : _global_state) + result.push_back(rng.get_count()); + return result; + } -static rng_type _get_branch_generator(const branch_type b) -{ - return static_cast(RNG_LEVELGEN + static_cast(b)); -} + void load_generators(const CrawlVector &v) + { + // as-is, decreasing the number of rngs (e.g. by removing a branch) will + // break save compatibility. + ASSERT(v.size() <= _global_state.size()); + for (int i = 0; i < v.size(); i++) + { + CrawlVector state = v[i].get_vector(); + _global_state[i] = PcgRNG(state); + } + } -rng_generator::rng_generator(branch_type b) : previous(_generator) -{ - _generator = _get_branch_generator(b); -} + generator::generator(rng_type g) : previous(_generator) + { + ASSERT(g != rng::SUB_GENERATOR); + _generator = g; + } -rng_generator::~rng_generator() -{ - _generator = previous; -} + rng_type get_branch_generator(const branch_type b) + { + return static_cast(rng::LEVELGEN + static_cast(b)); + } -uint32_t get_uint32(rng_type generator) -{ - return rngs[generator].get_uint32(); -} + generator::generator(branch_type b) : previous(_generator) + { + _generator = get_branch_generator(b); + } -uint32_t get_uint32() -{ - return get_uint32(_generator); -} + generator::~generator() + { + _generator = previous; + } -uint32_t peek_uint32() -{ - PcgRNG tmp = rngs[_generator]; - return tmp.get_uint32(); -} + subgenerator::subgenerator(uint64_t seed, uint64_t sequence) + : current(seed, sequence), + previous(_sub_generator), + previous_main(_generator) + { + _generator = rng::SUB_GENERATOR; + _sub_generator = ¤t; + } -uint64_t get_uint64(rng_type generator) -{ - return rngs[generator].get_uint64(); -} + subgenerator::~subgenerator() + { + _generator = previous_main; + _sub_generator = previous; + } -uint64_t get_uint64() -{ - return get_uint64(_generator); -} + subgenerator::subgenerator(uint64_t seed) + : subgenerator(seed, get_uint64()) + { } -static void _seed_rng(uint64_t seed_array[], int seed_len) -{ - PcgRNG seeded(seed_array, seed_len); - // TODO: don't initialize gameplay/ui rng? - // Use the just seeded RNG to initialize the rest. - for (PcgRNG& rng : rngs) + // call the 1-arg constructor so as to ensure a sequence point between the + // two get_uint64 calls. + subgenerator::subgenerator() + : subgenerator(get_uint64()) + { } + + PcgRNG *get_generator(rng_type r) { - uint64_t key[2] = { seeded.get_uint64(), seeded.get_uint64() }; - rng = PcgRNG(key, ARRAYSZ(key)); + ASSERT(_generator != ASSERT_NO_RNG); + if (_generator == SUB_GENERATOR) + return _sub_generator; + else + return &_global_state[_generator]; } -} -void seed_rng(uint64_t seed) -{ - uint64_t sarg[1] = { seed }; - _seed_rng(sarg, ARRAYSZ(sarg)); -} + PcgRNG ¤t_generator() + { + PcgRNG *ret = get_generator(_generator); + ASSERT(ret); + return *ret; + } -void seed_rng() -{ - /* Use a 128-bit wide seed */ - uint64_t seed_key[2]; - bool seeded = read_urandom((char*)(&seed_key), sizeof(seed_key)); - ASSERT(seeded); - _seed_rng(seed_key, ARRAYSZ(seed_key)); -} + uint32_t get_uint32() + { + return current_generator().get_uint32(); + } -/** - * Reset RNG to Options seed, and if that seed is 0, generate a new one. - */ -void reset_rng() -{ - crawl_state.seed = Options.seed; - while (!crawl_state.seed) // 0 = random seed + uint32_t peek_uint32() + { + PcgRNG tmp = current_generator(); // make a copy + return tmp.get_uint32(); + } + + uint64_t get_uint64() + { + return current_generator().get_uint64(); + } + + uint64_t peek_uint64() + { + PcgRNG tmp = current_generator(); // make a copy + return tmp.get_uint64(); + } + + static void _do_seeding(PcgRNG &master) + { + // TODO: don't initialize gameplay/ui rng? + // Use the just seeded RNG to initialize the rest. + for (PcgRNG& rng : _global_state) + { + uint64_t init_state = master.get_uint64(); + uint64_t seq = master.get_uint64(); + rng = PcgRNG(init_state, seq); + } + } + + void seed(uint64_t seed) + { + // use the default stream + PcgRNG master = PcgRNG(seed); + _do_seeding(master); + } + + void seed() { - seed_rng(); // reset entirely via read_urandom - crawl_state.seed = get_uint64(); + // seed both state and sequence from system randomness. + uint64_t seed_key[2]; + bool seeded = read_urandom((char*)(&seed_key), sizeof(seed_key)); + ASSERT(seeded); + PcgRNG master = PcgRNG(seed_key[0], seed_key[1]); + _do_seeding(master); + } + + /** + * Reset RNG to Options seed, and if that seed is 0, generate a new one. + */ + void reset() + { + crawl_state.seed = Options.seed; + while (!crawl_state.seed) // 0 = random seed + { + rng::seed(); // reset entirely via read_urandom + crawl_state.seed = get_uint64(); + } + dprf("Setting game seed to %" PRIu64, crawl_state.seed); + you.game_seed = crawl_state.seed; + rng::seed(crawl_state.seed); } - dprf("Setting game seed to %" PRIu64, crawl_state.seed); - you.game_seed = crawl_state.seed; - seed_rng(crawl_state.seed); } +// TODO: probably this could all be in the rng namespace + // [low, high] int random_range(int low, int high) { @@ -156,33 +206,21 @@ int random_range(int low, int high, int nrolls) return low + roll; } -static int _random2(int max, rng_type rng) +// [0, max) +int random2(int max) { if (max <= 1) return 0; - uint32_t partn = PcgRNG::max() / max; - - while (true) - { - uint32_t bits = get_uint32(rng); - uint32_t val = bits / partn; - - if (val < (uint32_t)max) - return (int)val; - } -} - -// [0, max) -int random2(int max) -{ - return _random2(max, _generator); + return rng::current_generator().get_bounded_uint32(max); } // [0, max), separate RNG state int ui_random(int max) { - return _random2(max, RNG_UI); + rng::generator ui(rng::UI); + + return random2(max); } // [0, 1] @@ -367,7 +405,7 @@ double random_real() { static const uint64_t UPPER_BITS = 0x3FF0000000000000ULL; static const uint64_t LOWER_MASK = 0x000FFFFFFFFFFFFFULL; - const uint64_t value = UPPER_BITS | (get_uint64() & LOWER_MASK); + const uint64_t value = UPPER_BITS | (rng::get_uint64() & LOWER_MASK); double result; // Portable memory transmutation. The union trick almost always // works, but this is safer. @@ -427,7 +465,7 @@ bool defer_rand::x_chance_in_y_contd(int x, int y, int index) do { if (index == int(bits.size())) - bits.push_back(get_uint32()); + bits.push_back(rng::get_uint32()); uint64_t expn_rand_1 = uint64_t(bits[index++]) * y; uint64_t expn_rand_2 = expn_rand_1 + y; @@ -450,7 +488,7 @@ int defer_rand::random2(int maxp1) return 0; if (bits.empty()) - bits.push_back(get_uint32()); + bits.push_back(rng::get_uint32()); uint64_t expn_rand_1 = uint64_t(bits[0]) * maxp1; uint64_t expn_rand_2 = expn_rand_1 + maxp1; diff --git a/crawl-ref/source/random.h b/crawl-ref/source/random.h index 3342fa3d30b5..e417a37eb605 100644 --- a/crawl-ref/source/random.h +++ b/crawl-ref/source/random.h @@ -7,32 +7,68 @@ #include "hash.h" #include "rng-type.h" +#include "pcg.h" -class rng_generator +class CrawlVector; + +namespace rng { -public: - rng_generator(rng_type g); - rng_generator(branch_type b); - ~rng_generator(); -private: - rng_type previous; -}; + class generator + { + public: + generator(rng_type g); + generator(branch_type b); + ~generator(); + private: + rng_type previous; + }; + + class subgenerator + { + public: + subgenerator(); + subgenerator(uint64_t seed); + subgenerator(uint64_t seed, uint64_t sequence); + ~subgenerator(); + private: + PcgRNG current; + PcgRNG *previous; + rng_type previous_main; + }; + + rng_type get_branch_generator(const branch_type b); + CrawlVector generators_to_vector(); + void load_generators(const CrawlVector &v); + vector get_states(); + PcgRNG *get_generator(rng_type r); + PcgRNG ¤t_generator(); + + void seed(); + void seed(uint64_t seed); + void seed(uint64_t[], int); + void reset(); + + uint32_t get_uint32(rng_type generator); + uint64_t get_uint64(rng_type generator); + uint32_t get_uint32(); + uint64_t get_uint64(); + uint32_t peek_uint32(); + uint64_t peek_uint64(); + + class ASSERT_stable + { + public: + ASSERT_stable() : initial_peek(peek_uint64()) { } + ~ASSERT_stable() + { + ASSERT(peek_uint64() == initial_peek); + } + + private: + uint64_t initial_peek; + }; +} -class CrawlVector; -CrawlVector generators_to_vector(); -void load_generators(const CrawlVector &v); -vector get_rng_states(); - -void seed_rng(); -void seed_rng(uint64_t seed); -void seed_rng(uint64_t[], int); -void reset_rng(); - -uint32_t get_uint32(rng_type generator); -uint64_t get_uint64(rng_type generator); -uint32_t get_uint32(); -uint64_t get_uint64(); -uint32_t peek_uint32(); bool coinflip(); int div_rand_round(int num, int den); int rand_round(double x); diff --git a/crawl-ref/source/ranged-attack.cc b/crawl-ref/source/ranged-attack.cc index 7cbf3ad17bb1..a8d199298106 100644 --- a/crawl-ref/source/ranged-attack.cc +++ b/crawl-ref/source/ranged-attack.cc @@ -105,7 +105,7 @@ bool ranged_attack::attack() return true; } - const int ev = defender->evasion(EV_IGNORE_NONE, attacker); + const int ev = defender->evasion(ev_ignore::none, attacker); ev_margin = test_hit(to_hit, ev, !attacker->is_player()); bool shield_blocked = attack_shield_blocked(false); @@ -223,7 +223,7 @@ bool ranged_attack::handle_phase_dodged() { did_hit = false; - const int ev = defender->evasion(EV_IGNORE_NONE, attacker); + const int ev = defender->evasion(ev_ignore::none, attacker); const int orig_ev_margin = test_hit(orig_to_hit, ev, !attacker->is_player()); @@ -272,9 +272,9 @@ bool ranged_attack::handle_phase_hit() if (!is_penetrating_attack(*attacker, weapon, *projectile)) range_used = BEAM_STOP; - if (projectile->is_type(OBJ_MISSILES, MI_NEEDLE)) + if (projectile->is_type(OBJ_MISSILES, MI_DART)) { - damage_done = blowgun_duration_roll(get_ammo_brand(*projectile)); + damage_done = dart_duration_roll(get_ammo_brand(*projectile)); set_attack_verb(0); announce_hit(); } @@ -290,7 +290,7 @@ bool ranged_attack::handle_phase_hit() else { damage_done = calc_damage(); - if (damage_done > 0 || projectile->is_type(OBJ_MISSILES, MI_NEEDLE)) + if (damage_done > 0 || projectile->is_type(OBJ_MISSILES, MI_DART)) { if (!handle_phase_damaged()) return false; @@ -340,14 +340,6 @@ int ranged_attack::weapon_damage() return 0; int dam = property(*projectile, PWPN_DAMAGE); - if (projectile->base_type == OBJ_MISSILES - && get_ammo_brand(*projectile) == SPMSL_STEEL) - { - if (dam) - dam = div_rand_round(dam * 13, 10); - else - dam += 2; - } if (using_weapon()) dam += property(*weapon, PWPN_DAMAGE); else if (attacker->is_player()) @@ -417,7 +409,6 @@ bool ranged_attack::apply_damage_brand(const char *what) // No stacking elemental brands. if (projectile->base_type == OBJ_MISSILES && get_ammo_brand(*projectile) != SPMSL_NORMAL - && get_ammo_brand(*projectile) != SPMSL_PENETRATION && (brand == SPWPN_FLAMING || brand == SPWPN_FREEZING || brand == SPWPN_HOLY_WRATH @@ -445,11 +436,9 @@ special_missile_type ranged_attack::random_chaos_missile_brand() 10, SPMSL_FROST, 10, SPMSL_POISONED, 10, SPMSL_CHAOS, - 5, SPMSL_PARALYSIS, - 5, SPMSL_SLEEP, + 10, SPMSL_BLINDING, 5, SPMSL_FRENZY, 2, SPMSL_CURARE, - 2, SPMSL_CONFUSION, 2, SPMSL_DISPERSAL)); if (one_chance_in(3)) @@ -474,18 +463,6 @@ special_missile_type ranged_attack::random_chaos_missile_brand() if (defender->no_tele(true, false, true)) susceptible = false; break; - case SPMSL_CONFUSION: - if (defender->holiness() & MH_PLANT) - { - susceptible = false; - break; - } - // fall through - case SPMSL_SLEEP: - case SPMSL_PARALYSIS: - if (defender->holiness() & (MH_UNDEAD | MH_NONLIVING)) - susceptible = false; - break; case SPMSL_FRENZY: if (defender->holiness() & (MH_UNDEAD | MH_NONLIVING) || defender->is_player() @@ -514,9 +491,8 @@ special_missile_type ranged_attack::random_chaos_missile_brand() case SPMSL_CURARE: brand_name += "curare"; break; case SPMSL_CHAOS: brand_name += "chaos"; break; case SPMSL_DISPERSAL: brand_name += "dispersal"; break; - case SPMSL_SLEEP: brand_name += "sleep"; break; - case SPMSL_CONFUSION: brand_name += "confusion"; break; case SPMSL_FRENZY: brand_name += "frenzy"; break; + case SPMSL_BLINDING: brand_name += "blinding"; break; default: brand_name += "(other)"; break; } @@ -528,7 +504,7 @@ special_missile_type ranged_attack::random_chaos_missile_brand() return brand; } -bool ranged_attack::blowgun_check(special_missile_type type) +bool ranged_attack::dart_check(special_missile_type type) { if (defender->holiness() & (MH_UNDEAD | MH_NONLIVING)) { @@ -545,31 +521,26 @@ bool ranged_attack::blowgun_check(special_missile_type type) return false; } - const int enchantment = using_weapon() ? weapon->plus : 0; - if (attacker->is_monster()) { int chance = 85 - ((defender->get_hit_dice() - attacker->get_hit_dice()) * 5 / 2); - chance += enchantment * 4; chance = min(95, chance); if (type == SPMSL_FRENZY) chance = chance / 2; - else if (type == SPMSL_PARALYSIS || type == SPMSL_SLEEP) - chance = chance * 4 / 5; return x_chance_in_y(chance, 100); } - const int skill = you.skill_rdiv(SK_THROWING); + const int pow = (2 * (you.skill_rdiv(SK_THROWING) + + you.skill_rdiv(SK_STEALTH))) / 3; - // You have a really minor chance of hitting with no skills or good - // enchants. + // You have a really minor chance of hitting weak things no matter what if (defender->get_hit_dice() < 15 && random2(100) <= 2) return true; - const int resist_roll = 2 + random2(4 + skill + enchantment); + const int resist_roll = 2 + random2(4 + pow); dprf(DIAG_COMBAT, "Brand rolled %d against defender HD: %d.", resist_roll, defender->get_hit_dice()); @@ -590,7 +561,7 @@ bool ranged_attack::blowgun_check(special_missile_type type) } -int ranged_attack::blowgun_duration_roll(special_missile_type type) +int ranged_attack::dart_duration_roll(special_missile_type type) { // Leaving monster poison the same by separating it from player poison if (type == SPMSL_POISONED && attacker->is_monster()) @@ -603,30 +574,16 @@ int ranged_attack::blowgun_duration_roll(special_missile_type type) ? attacker->get_hit_dice() : attacker->skill_rdiv(SK_THROWING); - const int plus = using_weapon() ? weapon->plus : 0; - - // Scale down nastier needle effects against players. + // Scale down nastier dart effects against players. // Fixed duration regardless of power, since power already affects success // chance considerably, and this helps avoid effects being too nasty from // high HD shooters and too ignorable from low ones. if (defender->is_player()) - { - switch (type) - { - case SPMSL_PARALYSIS: - return 3 + random2(4); - case SPMSL_SLEEP: - return 5 + random2(5); - case SPMSL_CONFUSION: - return 2 + random2(4); - default: - return 5 + random2(5); - } - } + return 5 + random2(5); else if (type == SPMSL_POISONED) // Player poison needles - return random2(3 + base_power * 2 + plus); + return random2(3 + base_power * 2); else - return 5 + random2(base_power + plus); + return 5 + random2(base_power); } bool ranged_attack::apply_missile_brand() @@ -658,8 +615,7 @@ bool ranged_attack::apply_missile_brand() defender->expose_to_element(BEAM_COLD, 2); break; case SPMSL_POISONED: - if (projectile->is_type(OBJ_MISSILES, MI_NEEDLE) - && using_weapon() + if (projectile->is_type(OBJ_MISSILES, MI_DART) && damage_done > 0 || !one_chance_in(4)) { @@ -674,7 +630,7 @@ bool ranged_attack::apply_missile_brand() } defender->poison(attacker, - projectile->is_type(OBJ_MISSILES, MI_NEEDLE) + projectile->is_type(OBJ_MISSILES, MI_DART) ? damage_done : 6 + random2(8) + random2(damage_done * 3 / 2)); @@ -728,27 +684,26 @@ bool ranged_attack::apply_missile_brand() } break; case SPMSL_SILVER: - special_damage = silver_damages_victim(defender, damage_done, - special_damage_message); - break; - case SPMSL_PARALYSIS: - if (!blowgun_check(brand)) - break; - defender->paralyse(attacker, damage_done); - break; - case SPMSL_SLEEP: - if (!blowgun_check(brand)) - break; - defender->put_to_sleep(attacker, damage_done); - should_alert_defender = false; + special_damage = max(1 + random2(damage_done) / 3, + silver_damages_victim(defender, damage_done, + special_damage_message)); break; - case SPMSL_CONFUSION: - if (!blowgun_check(brand)) + case SPMSL_BLINDING: + if (!dart_check(brand)) break; - defender->confuse(attacker, damage_done); + if (defender->is_monster()) + { + monster* mon = defender->as_monster(); + if (mons_can_be_blinded(mon->type)) + { + mon->add_ench(mon_enchant(ENCH_BLIND, 1, attacker, + damage_done * BASELINE_DELAY)); + } + } + defender->confuse(attacker, damage_done / 3); break; case SPMSL_FRENZY: - if (!blowgun_check(brand)) + if (!dart_check(brand)) break; if (defender->is_monster()) { diff --git a/crawl-ref/source/ranged-attack.h b/crawl-ref/source/ranged-attack.h index bd6f37247064..b6fc740b3032 100644 --- a/crawl-ref/source/ranged-attack.h +++ b/crawl-ref/source/ranged-attack.h @@ -35,8 +35,8 @@ class ranged_attack : public attack int apply_damage_modifiers(int damage, int damage_max) override; bool apply_damage_brand(const char *what = nullptr) override; special_missile_type random_chaos_missile_brand(); - bool blowgun_check(special_missile_type type); - int blowgun_duration_roll(special_missile_type type); + bool dart_check(special_missile_type type); + int dart_duration_roll(special_missile_type type); bool apply_missile_brand(); /* Weapon Effects */ diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc index fa585ff66b64..535fa83d924b 100644 --- a/crawl-ref/source/religion.cc +++ b/crawl-ref/source/religion.cc @@ -179,17 +179,13 @@ const vector god_powers[NUM_GODS] = }, // Sif Muna - { { 1, ABIL_SIF_MUNA_DIVINE_ENERGY, - "request divine energy to cast spells with insufficient magic", - "request divine energy" }, - { 2, "Sif Muna is now protecting you from the effects of miscast magic.", - "Sif Muna will no longer protect you from the effects of miscast magic.", - "Sif Muna protects you from the effects of miscast magic." }, - { 3, ABIL_SIF_MUNA_CHANNEL_ENERGY, + { { 1, ABIL_SIF_MUNA_CHANNEL_ENERGY, "call upon Sif Muna for magical energy" }, - { 4, ABIL_SIF_MUNA_FORGET_SPELL, + { 3, ABIL_SIF_MUNA_FORGET_SPELL, "freely open your mind to new spells", "forget spells at will" }, + { 4, ABIL_SIF_MUNA_DIVINE_EXEGESIS, + "call upon Sif Muna to cast any spell from your library" }, { 5, "Sif Muna will now gift you books as you gain piety.", "Sif Muna will no longer gift you books.", "Sif Muna will gift you books as you gain piety." }, @@ -267,12 +263,10 @@ const vector god_powers[NUM_GODS] = // Fedhas { - { 0, ABIL_FEDHAS_FUNGAL_BLOOM, "turn corpses into toadstools" }, - { 1, ABIL_FEDHAS_SUNLIGHT, "call sunshine" }, - { 2, ABIL_FEDHAS_EVOLUTION, "induce evolution" }, - { 3, ABIL_FEDHAS_PLANT_RING, "cause a ring of plants to grow" }, - { 4, ABIL_FEDHAS_SPAWN_SPORES, "spawn explosive spores" }, - { 5, ABIL_FEDHAS_RAIN, "control the weather" }, + { 2, ABIL_FEDHAS_WALL_OF_BRIARS, "encircle yourself with summoned briar patches"}, + { 3, ABIL_FEDHAS_GROW_BALLISTOMYCETE, "grow a ballistomycete" }, + { 4, ABIL_FEDHAS_OVERGROW, "transform dungeon walls and trees into plant allies"}, + { 5, ABIL_FEDHAS_GROW_OKLOB, "grow an oklob plant" }, }, // Cheibriados @@ -353,12 +347,14 @@ const vector god_powers[NUM_GODS] = { 5, ABIL_RU_APOCALYPSE, "wreak a terrible wrath on your foes" }, }, +#if TAG_MAJOR_VERSION == 34 // Pakellas { { 0, "gain magical power from killing" }, { 3, ABIL_PAKELLAS_DEVICE_SURGE, "spend magic to empower your devices" }, }, +#endif // Uskayaw { @@ -472,9 +468,11 @@ static bool _is_disabled_god(god_type god) { switch (god) { +#if TAG_MAJOR_VERSION == 34 // Disabled, pending a rework. case GOD_PAKELLAS: return true; +#endif default: return false; @@ -555,7 +553,9 @@ bool active_penance(god_type god) && god != GOD_GOZAG && god != GOD_RU && god != GOD_HEPLIAKLQANA +#if TAG_MAJOR_VERSION == 34 && god != GOD_PAKELLAS +#endif && god != GOD_ELYVILON && (god == you.religion && !is_good_god(god) || god_hates_your_god(god, you.religion)); @@ -569,7 +569,9 @@ bool xp_penance(god_type god) && (god == GOD_ASHENZARI || god == GOD_GOZAG || god == GOD_HEPLIAKLQANA +#if TAG_MAJOR_VERSION == 34 || god == GOD_PAKELLAS +#endif || god == GOD_ELYVILON) && god_hates_your_god(god, you.religion); } @@ -654,13 +656,16 @@ void dec_penance(god_type god, int val) } else { +#if TAG_MAJOR_VERSION == 34 if (god == GOD_PAKELLAS) { // Penance just ended w/o worshipping Pakellas; // notify the player that MP regeneration will start again. mprf(MSGCH_GOD, god, "You begin regenerating magic."); } - else if (god == GOD_HEPLIAKLQANA) + else +#endif + if (god == GOD_HEPLIAKLQANA) { calc_hp(); // frailty ends mprf(MSGCH_GOD, god, "Your full life essence returns."); @@ -831,17 +836,21 @@ static void _inc_penance(god_type god, int val) you.redraw_armour_class = true; } } +#if TAG_MAJOR_VERSION == 34 else if (god == GOD_PAKELLAS) { if (you.duration[DUR_DEVICE_SURGE]) you.duration[DUR_DEVICE_SURGE] = 0; } +#endif else if (god == GOD_SIF_MUNA) { if (you.duration[DUR_CHANNEL_ENERGY]) you.duration[DUR_CHANNEL_ENERGY] = 0; +#if TAG_MAJOR_VERSION == 34 if (you.attribute[ATTR_DIVINE_ENERGY]) you.attribute[ATTR_DIVINE_ENERGY] = 0; +#endif } if (you_worship(god)) @@ -1015,6 +1024,7 @@ static bool _give_nemelex_gift(bool forced = false) return false; } +#if TAG_MAJOR_VERSION == 34 /** * From the given list of items, return a random unseen item, if there are any. * Otherwise, just return any of them at random. @@ -1039,6 +1049,7 @@ static int _preferably_unseen_item(const vector &item_types, return unseen[random2(unseen.size())]; return item_types[random2(item_types.size())]; } +#endif static void _delayed_gift_callback(const mgen_data &mg, monster *&mon, int placed) @@ -1202,6 +1213,7 @@ static set _vehumet_get_spell_gifts() return offers; } +#if TAG_MAJOR_VERSION == 34 /// Has the player ID'd the given type of wand? static bool _seen_wand(int wand) { @@ -1340,6 +1352,7 @@ static bool _give_pakellas_gift() return false; } +#endif static bool _give_trog_oka_gift(bool forced) { @@ -1490,8 +1503,8 @@ static bool _gift_sif_kiku_gift(bool forced) gift = BOOK_DEATH; } } - else if (forced || you.piety >= piety_breakpoint(4) - && random2(you.piety) > 100) + else if (forced + || you.piety >= piety_breakpoint(4) && random2(you.piety) > 100) { // Sif Muna special: Keep quiet if acquirement fails // because the player already has seen all spells. @@ -2016,9 +2029,11 @@ bool do_god_gift(bool forced) success = _give_nemelex_gift(forced); break; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: success = _give_pakellas_gift(); break; +#endif case GOD_OKAWARU: case GOD_TROG: @@ -2107,7 +2122,9 @@ string god_name(god_type which_god, bool long_name) case GOD_GOZAG: return "Gozag"; case GOD_QAZLAL: return "Qazlal"; case GOD_RU: return "Ru"; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: return "Pakellas"; +#endif case GOD_USKAYAW: return "Uskayaw"; case GOD_HEPLIAKLQANA: return "Hepliaklqana"; case GOD_WU_JIAN: return "Wu Jian"; @@ -2548,9 +2565,11 @@ void lose_piety(int pgn) end(you.ability_letter_table), ABIL_YRED_ANIMATE_DEAD, ABIL_YRED_ANIMATE_REMAINS); } +#if TAG_MAJOR_VERSION == 34 // Deactivate the toggle if (power.abil == ABIL_SIF_MUNA_DIVINE_ENERGY) you.attribute[ATTR_DIVINE_ENERGY] = 0; +#endif } } #ifdef USE_TILE_LOCAL @@ -2671,7 +2690,9 @@ int initial_wrath_penance_for(god_type god) case GOD_CHEIBRIADOS: case GOD_DITHMENOS: case GOD_MAKHLEB: +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: +#endif case GOD_QAZLAL: case GOD_VEHUMET: case GOD_ZIN: @@ -2817,8 +2838,10 @@ void excommunication(bool voluntary, god_type new_god) case GOD_SIF_MUNA: if (you.duration[DUR_CHANNEL_ENERGY]) you.duration[DUR_CHANNEL_ENERGY] = 0; +#if TAG_MAJOR_VERSION == 34 if (you.attribute[ATTR_DIVINE_ENERGY]) you.attribute[ATTR_DIVINE_ENERGY] = 0; +#endif break; case GOD_NEMELEX_XOBEH: @@ -2934,6 +2957,7 @@ void excommunication(bool voluntary, god_type new_god) } break; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: simple_god_message(" continues to block your magic from regenerating.", old_god); @@ -2943,6 +2967,7 @@ void excommunication(bool voluntary, god_type new_god) - exp_needed(min(you.max_level, 27)); you.exp_docked_total[old_god] = you.exp_docked[old_god]; break; +#endif case GOD_CHEIBRIADOS: simple_god_message(" continues to slow your movements.", old_god); @@ -2960,7 +2985,6 @@ void excommunication(bool voluntary, god_type new_god) case GOD_WU_JIAN: you.attribute[ATTR_SERPENTS_LASH] = 0; you.attribute[ATTR_HEAVENLY_STORM] = 0; - _set_penance(old_god, 25); break; default: @@ -2981,8 +3005,8 @@ void excommunication(bool voluntary, god_type new_god) for (ability_type abil : get_god_abilities()) you.stop_train.insert(abil_skill(abil)); - update_can_train(); - you.can_train.set(SK_INVOCATIONS, false); + update_can_currently_train(); + you.can_currently_train.set(SK_INVOCATIONS, false); reset_training(); // Perhaps we abandoned Trog with everything but Spellcasting maxed out. @@ -3046,7 +3070,7 @@ static bool _transformed_player_can_join_god(god_type which_god) switch (you.form) { case transformation::lich: - return !(is_good_god(which_god) || which_god == GOD_FEDHAS); + return !is_good_god(which_god); case transformation::statue: case transformation::wisp: return !(which_god == GOD_YREDELEMNUL); @@ -3099,21 +3123,19 @@ bool player_can_join_god(god_type which_god) if (which_god == GOD_BEOGH && !species_is_orcish(you.species)) return false; - // Fedhas hates undead, but will accept demonspawn. - if (which_god == GOD_FEDHAS && you.holiness() & MH_UNDEAD) - return false; - if (which_god == GOD_GOZAG && you.gold < gozag_service_fee()) return false; if (you.get_mutation_level(MUT_NO_LOVE) && _god_rejects_loveless(which_god)) return false; +#if TAG_MAJOR_VERSION == 34 if (you.get_mutation_level(MUT_NO_ARTIFICE) && which_god == GOD_PAKELLAS) { return false; } +#endif return _transformed_player_can_join_god(which_god); } @@ -3371,12 +3393,12 @@ static void _set_initial_god_piety() // monk bonus... you.props[RU_SACRIFICE_PROGRESS_KEY] = 0; // offer the first sacrifice faster than normal - { - int delay = 50; - if (crawl_state.game_is_sprint()) - delay /= SPRINT_MULTIPLIER; - you.props[RU_SACRIFICE_DELAY_KEY] = delay; - } + { + int delay = 50; + if (crawl_state.game_is_sprint()) + delay /= SPRINT_MULTIPLIER; + you.props[RU_SACRIFICE_DELAY_KEY] = delay; + } you.props[RU_SACRIFICE_PENALTY_KEY] = 0; break; @@ -3440,7 +3462,7 @@ static void _join_gozag() #endif } - // Move gold to top of piles. + // Move gold to top of piles & detect it. add_daction(DACT_GOLD_ON_TOP); } @@ -3547,12 +3569,14 @@ static void _join_zin() } } +#if TAG_MAJOR_VERSION == 34 // Setup when becoming an overworked assistant to Pakellas. static void _join_pakellas() { mprf(MSGCH_GOD, "You stop regenerating magic."); you.attribute[ATTR_PAKELLAS_EXTRA_MP] = POT_MAGIC_MP; } +#endif // Setup for joining the easygoing followers of Cheibriados. static void _join_cheibriados() @@ -3581,7 +3605,9 @@ static const map> on_join = { if (you.worshipped[GOD_LUGONU] == 0) gain_piety(20, 1, false); // allow instant access to first power }}, +#if TAG_MAJOR_VERSION == 34 { GOD_PAKELLAS, _join_pakellas }, +#endif { GOD_RU, _join_ru }, { GOD_TROG, _join_trog }, { GOD_ZIN, _join_zin }, @@ -3614,12 +3640,10 @@ void join_religion(god_type which_god) mark_milestone("god.worship", "became a worshipper of " + god_name(you.religion) + "."); take_note(Note(NOTE_GET_GOD, you.religion)); - const bool returning = you.worshipped[which_god] - || is_good_god(which_god) - && you.species == SP_BARACHI; - simple_god_message( - make_stringf(" welcomes you%s!", - returning ? " back" : "").c_str()); + + simple_god_message(make_stringf(" welcomes you%s!", + you.worshipped[which_god] ? " back" + : "").c_str()); // included in default force_more_message #ifdef DGL_WHEREIS whereis_record(); @@ -3638,10 +3662,6 @@ void join_religion(god_type which_god) mprf(MSGCH_MONSTER_ENCHANT, "Your unholy and evil allies forsake you."); } - // Move gold to top of piles with Gozag. - if (have_passive(passive_t::detect_gold)) - add_daction(DACT_GOLD_ON_TOP); - const function *join_effect = map_find(on_join, you.religion); if (join_effect != nullptr) (*join_effect)(); @@ -3666,7 +3686,7 @@ void join_religion(god_type which_god) vector abilities = get_god_abilities(); for (ability_type abil : abilities) you.start_train.insert(abil_skill(abil)); - update_can_train(); + update_can_currently_train(); // now that you have a god, you can't save any piety from your prev god you.previous_good_god = GOD_NO_GOD; @@ -3724,12 +3744,14 @@ void god_pitch(god_type which_god) simple_god_message(" does not accept worship from the loveless!", which_god); } +#if TAG_MAJOR_VERSION == 34 else if (you.get_mutation_level(MUT_NO_ARTIFICE) && which_god == GOD_PAKELLAS) { simple_god_message(" does not accept worship from those who are " "unable to use magical devices!", which_god); } +#endif else if (!_transformed_player_can_join_god(which_god)) { simple_god_message(" says: How dare you approach in such a " @@ -3918,10 +3940,12 @@ bool god_hates_spell(spell_type spell, god_type god, bool fake_spell) if (is_hasty_spell(spell)) return true; break; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: if (spell == SPELL_SUBLIMATION_OF_BLOOD) return true; break; +#endif default: break; } @@ -4056,9 +4080,12 @@ void handle_god_time(int /*time_delta*/) case GOD_KIKUBAAQUDGHA: case GOD_VEHUMET: case GOD_ZIN: +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: +#endif case GOD_JIYVA: case GOD_WU_JIAN: + case GOD_SIF_MUNA: if (one_chance_in(17)) lose_piety(1); break; @@ -4068,7 +4095,6 @@ void handle_god_time(int /*time_delta*/) case GOD_HEPLIAKLQANA: case GOD_FEDHAS: case GOD_CHEIBRIADOS: - case GOD_SIF_MUNA: case GOD_SHINING_ONE: case GOD_NEMELEX_XOBEH: if (one_chance_in(35)) @@ -4161,8 +4187,10 @@ int god_colour(god_type god) // mv - added case GOD_RU: return BROWN; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: return LIGHTGREEN; +#endif case GOD_NO_GOD: case NUM_GODS: @@ -4249,8 +4277,10 @@ colour_t god_message_altar_colour(god_type god) case GOD_RU: return BROWN; +#if TAG_MAJOR_VERSION == 34 case GOD_PAKELLAS: return random_choose(LIGHTMAGENTA, LIGHTGREEN, LIGHTCYAN); +#endif case GOD_USKAYAW: return random_choose(RED, MAGENTA); diff --git a/crawl-ref/source/rltiles/effect/tomahawk0.png b/crawl-ref/source/rltiles/UNUSED/tomahawk0.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk0.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk0.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk1.png b/crawl-ref/source/rltiles/UNUSED/tomahawk1.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk1.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk1.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk2.png b/crawl-ref/source/rltiles/UNUSED/tomahawk2.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk2.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk2.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk3.png b/crawl-ref/source/rltiles/UNUSED/tomahawk3.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk3.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk3.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk4.png b/crawl-ref/source/rltiles/UNUSED/tomahawk4.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk4.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk4.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk5.png b/crawl-ref/source/rltiles/UNUSED/tomahawk5.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk5.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk5.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk6.png b/crawl-ref/source/rltiles/UNUSED/tomahawk6.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk6.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk6.png diff --git a/crawl-ref/source/rltiles/effect/tomahawk7.png b/crawl-ref/source/rltiles/UNUSED/tomahawk7.png similarity index 100% rename from crawl-ref/source/rltiles/effect/tomahawk7.png rename to crawl-ref/source/rltiles/UNUSED/tomahawk7.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/needle-c.png b/crawl-ref/source/rltiles/UNUSED/weapons/needle-c.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/needle-c.png rename to crawl-ref/source/rltiles/UNUSED/weapons/needle-c.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/needle-p.png b/crawl-ref/source/rltiles/UNUSED/weapons/needle-p.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/needle-p.png rename to crawl-ref/source/rltiles/UNUSED/weapons/needle-p.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/needle1.png b/crawl-ref/source/rltiles/UNUSED/weapons/needle1.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/needle1.png rename to crawl-ref/source/rltiles/UNUSED/weapons/needle1.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/needle2.png b/crawl-ref/source/rltiles/UNUSED/weapons/needle2.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/needle2.png rename to crawl-ref/source/rltiles/UNUSED/weapons/needle2.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/silver_tomahawk.png b/crawl-ref/source/rltiles/UNUSED/weapons/silver_tomahawk.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/silver_tomahawk.png rename to crawl-ref/source/rltiles/UNUSED/weapons/silver_tomahawk.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/steel_tomahawk.png b/crawl-ref/source/rltiles/UNUSED/weapons/steel_tomahawk.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/steel_tomahawk.png rename to crawl-ref/source/rltiles/UNUSED/weapons/steel_tomahawk.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/tomahawk1.png b/crawl-ref/source/rltiles/UNUSED/weapons/tomahawk1.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/tomahawk1.png rename to crawl-ref/source/rltiles/UNUSED/weapons/tomahawk1.png diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/tomahawk2.png b/crawl-ref/source/rltiles/UNUSED/weapons/tomahawk2.png similarity index 100% rename from crawl-ref/source/rltiles/item/weapon/ranged/tomahawk2.png rename to crawl-ref/source/rltiles/UNUSED/weapons/tomahawk2.png diff --git a/crawl-ref/source/rltiles/dc-abilities.txt b/crawl-ref/source/rltiles/dc-abilities.txt index a40d5f233148..781125faadf1 100644 --- a/crawl-ref/source/rltiles/dc-abilities.txt +++ b/crawl-ref/source/rltiles/dc-abilities.txt @@ -18,12 +18,14 @@ evoke_ratskin ABILITY_EVOKE_RATSKIN evoke_thunder ABILITY_EVOKE_THUNDER evoke_invisibility_end ABILITY_EVOKE_INVISIBILITY_END evoke_invisibility ABILITY_EVOKE_INVISIBILITY +exsanguinate ABILITY_EXSANGUINATE flight ABILITY_EVOKE_FLIGHT flight_end ABILITY_FLIGHT_END flight ABILITY_FLIGHT heal_wounds ABILITY_HEAL_WOUNDS hop ABILITY_HOP hurl_damnation ABILITY_HURL_DAMNATION +revivify ABILITY_REVIVIFY shaft_self ABILITY_SHAFT_SELF spit_poison ABILITY_SPIT_POISON stop_recall ABILITY_STOP_RECALL diff --git a/crawl-ref/source/rltiles/dc-feat.txt b/crawl-ref/source/rltiles/dc-feat.txt index 78699694be68..e39b36300ea5 100644 --- a/crawl-ref/source/rltiles/dc-feat.txt +++ b/crawl-ref/source/rltiles/dc-feat.txt @@ -461,7 +461,10 @@ makhleb_flame5 makhleb_flame6 makhleb_flame7 makhleb_flame8 -sif_muna DNGN_ALTAR_SIF_MUNA +sif_muna1 DNGN_ALTAR_SIF_MUNA +sif_muna2 +sif_muna3 +sif_muna4 trog DNGN_ALTAR_TROG nemelex1 DNGN_ALTAR_NEMELEX_XOBEH nemelex2 @@ -669,6 +672,7 @@ effect/heataura2 halo_friendly HALO_FRIENDLY halo_gd_neutral HALO_GD_NEUTRAL halo_neutral HALO_NEUTRAL +halo_summoner HALO_SUMMONER ray RAY ray_multi RAY_MULTI ray_out_of_range RAY_OUT_OF_RANGE diff --git a/crawl-ref/source/rltiles/dc-gui.txt b/crawl-ref/source/rltiles/dc-gui.txt index c589e17768ff..6d35521a2abc 100644 --- a/crawl-ref/source/rltiles/dc-gui.txt +++ b/crawl-ref/source/rltiles/dc-gui.txt @@ -164,3 +164,214 @@ misc/error ERROR prompt_yes PROMPT_YES prompt_no PROMPT_NO + +%sdir gui/tutorial +movement TUT_MOVEMENT +combat TUT_COMBAT + +%sdir gui/backgrounds +%texture enabled-fg +Fi JOB_RECOMMENDED_FIGHTER +Gl JOB_RECOMMENDED_GLADIATOR +Mo JOB_RECOMMENDED_MONK +Hu JOB_RECOMMENDED_HUNTER +As JOB_RECOMMENDED_ASSASSIN +Ar JOB_RECOMMENDED_ARTIFICER +Wn JOB_RECOMMENDED_WANDERER +Be JOB_RECOMMENDED_BERSERKER +AK JOB_RECOMMENDED_ABYSSAL_KNIGHT +CK JOB_RECOMMENDED_CHAOS_KNIGHT +Sk JOB_RECOMMENDED_SKALD +Tm JOB_RECOMMENDED_TRANSMUTER +AM JOB_RECOMMENDED_ARCANE_MARKSMAN +Wr JOB_RECOMMENDED_WARPER +En JOB_RECOMMENDED_ENCHANTER +Wz JOB_RECOMMENDED_WIZARD +Cj JOB_RECOMMENDED_CONJURER +Su JOB_RECOMMENDED_SUMMONER +Ne JOB_RECOMMENDED_NECROMANCER +FE JOB_RECOMMENDED_FIRE_ELEMENTALIST +IE JOB_RECOMMENDED_ICE_ELEMENTALIST +AE JOB_RECOMMENDED_AIR_ELEMENTALIST +EE JOB_RECOMMENDED_EARTH_ELEMENTALIST +VM JOB_RECOMMENDED_VENOM_MAGE +%syn LAST_RECOMMENDED_JOB + +%texture disabled-fg +Fi JOB_FIGHTER +Gl JOB_GLADIATOR +Mo JOB_MONK +Hu JOB_HUNTER +As JOB_ASSASSIN +Ar JOB_ARTIFICER +Wn JOB_WANDERER +Be JOB_BERSERKER +AK JOB_ABYSSAL_KNIGHT +CK JOB_CHAOS_KNIGHT +Sk JOB_SKALD +Tm JOB_TRANSMUTER +AM JOB_ARCANE_MARKSMAN +Wr JOB_WARPER +En JOB_ENCHANTER +Wz JOB_WIZARD +Cj JOB_CONJURER +Su JOB_SUMMONER +Ne JOB_NECROMANCER +FE JOB_FIRE_ELEMENTALIST +IE JOB_ICE_ELEMENTALIST +AE JOB_AIR_ELEMENTALIST +EE JOB_EARTH_ELEMENTALIST +VM JOB_VENOM_MAGE +%syn LAST_JOB + +%sdir player +%texture none +felids/cat3 SP_RECOMMENDED_FELID +%start +%compose base/centaur_lightgrey_f +%compose body/animal_skin +%compose hair/short_black +%compose boots/hooves +%finish SP_RECOMMENDED_CENTAUR +%start +%compose base/deep_dwarf_m +%compose body/animal_skin +%compose hair/short_white +%compose beard/garibaldi_white +%finish SP_RECOMMENDED_DEEP_DWARF +%start +%compose base/deep_elf_f +%compose body/animal_skin +%compose hair/elf_white +%finish SP_RECOMMENDED_DEEP_ELF +%start +%compose base/demigod_f +%compose body/animal_skin +%compose hair/short_black +%finish SP_RECOMMENDED_DEMIGOD +%start +%compose base/demonspawn_pink +%compose body/animal_skin +%finish SP_RECOMMENDED_DEMONSPAWN +%start +%compose base/draconian_f +%compose body/animal_skin +%compose drchead/drchead_brown +%finish SP_RECOMMENDED_BASE_DRACONIAN +%start +%compose base/formicid +%compose body/animal_skin +%finish SP_RECOMMENDED_FORMICID +%start +%compose base/frog_m +%compose body/animal_skin +%finish SP_RECOMMENDED_BARACHI +%start +%compose base/gargoyle_m +%compose body/animal_skin +%finish SP_RECOMMENDED_GARGOYLE +%start +%compose base/ghoul_m +%compose body/animal_skin +%finish SP_RECOMMENDED_GHOUL +%start +%compose base/gnoll_m +%compose body/animal_skin +%finish SP_RECOMMENDED_GNOLL +%start +%compose base/halfling_m +%compose body/animal_skin +%compose hair/short_black +%finish SP_RECOMMENDED_HALFLING +%start +%compose base/human_f +%compose body/animal_skin +%compose hair/short_black +%finish SP_RECOMMENDED_HUMAN +%start +%compose base/kobold_m +%compose body/animal_skin +%finish SP_RECOMMENDED_KOBOLD +%start +%compose base/merfolk_water_f +%compose body/animal_skin +%compose hair/green +%finish SP_RECOMMENDED_MERFOLK +%start +%compose base/minotaur_m +%compose body/animal_skin +%compose head/horns2 +%finish SP_RECOMMENDED_MINOTAUR +base/mummy_m SP_RECOMMENDED_MUMMY +%start +%compose base/naga_f +%compose body/animal_skin +%compose hair/part2_red +%finish SP_RECOMMENDED_NAGA +base/octopode1 SP_RECOMMENDED_OCTOPODE +%start +%compose base/ogre_m +%compose body/animal_skin +%compose hair/short_black +%finish SP_RECOMMENDED_OGRE +%start +%compose base/orc_f +%compose body/animal_skin +%finish SP_RECOMMENDED_HILL_ORC +%start +%compose base/spriggan_m +%compose body/animal_skin +%compose beard/medium_green +%finish SP_RECOMMENDED_SPRIGGAN +%start +%compose base/tengu_winged_f +%compose body/animal_skin +%compose hair/short_black +%finish SP_RECOMMENDED_TENGU +%start +%compose base/troll_f +%compose body/animal_skin +%compose hair/troll +%compose gloves/claws +%finish SP_RECOMMENDED_TROLL +%start +%compose base/vampire_f +%compose body/animal_skin +%compose hair/arwen +%finish SP_RECOMMENDED_VAMPIRE +%start +%compose base/vine_stalker_f +%compose body/animal_skin +%finish SP_RECOMMENDED_VINE_STALKER +%syn LAST_RECOMMENDED_SPECIES + +%sdir gui/backgrounds +%texture disabled-fg +enum:SP_RECOMMENDED_FELID SP_FELID +enum:SP_RECOMMENDED_CENTAUR SP_CENTAUR +enum:SP_RECOMMENDED_DEEP_DWARF SP_DEEP_DWARF +enum:SP_RECOMMENDED_DEEP_ELF SP_DEEP_ELF +enum:SP_RECOMMENDED_DEMIGOD SP_DEMIGOD +enum:SP_RECOMMENDED_DEMONSPAWN SP_DEMONSPAWN +enum:SP_RECOMMENDED_BASE_DRACONIAN SP_DRACONIAN +enum:SP_RECOMMENDED_FORMICID SP_FORMICID +enum:SP_RECOMMENDED_BARACHI SP_BARACHI +enum:SP_RECOMMENDED_GARGOYLE SP_GARGOYLE +enum:SP_RECOMMENDED_GHOUL SP_GHOUL +enum:SP_RECOMMENDED_GNOLL SP_GNOLL +enum:SP_RECOMMENDED_HALFLING SP_HALFLING +enum:SP_RECOMMENDED_HUMAN SP_HUMAN +enum:SP_RECOMMENDED_KOBOLD SP_KOBOLD +enum:SP_RECOMMENDED_MERFOLK SP_MERFOLK +enum:SP_RECOMMENDED_MINOTAUR SP_MINOTAUR +enum:SP_RECOMMENDED_MUMMY SP_MUMMY +enum:SP_RECOMMENDED_NAGA SP_NAGA +enum:SP_RECOMMENDED_OCTOPODE SP_OCTOPODE +enum:SP_RECOMMENDED_OGRE SP_OGRE +enum:SP_RECOMMENDED_HILL_ORC SP_HILL_ORC +enum:SP_RECOMMENDED_SPRIGGAN SP_SPRIGGAN +enum:SP_RECOMMENDED_TENGU SP_TENGU +enum:SP_RECOMMENDED_TROLL SP_TROLL +enum:SP_RECOMMENDED_VAMPIRE SP_VAMPIRE +enum:SP_RECOMMENDED_VINE_STALKER SP_VINE_STALKER +%syn LAST_SPECIES diff --git a/crawl-ref/source/rltiles/dc-invocations.txt b/crawl-ref/source/rltiles/dc-invocations.txt index 1e9e81342d69..e3beb26903a6 100644 --- a/crawl-ref/source/rltiles/dc-invocations.txt +++ b/crawl-ref/source/rltiles/dc-invocations.txt @@ -20,12 +20,10 @@ elyvilon_greater_healing ABILITY_ELYVILON_GREATER_HEALING elyvilon_lesser_healing ABILITY_ELYVILON_LESSER_HEALING elyvilon_purification ABILITY_ELYVILON_PURIFICATION elyvilon_heal_other ABILITY_ELYVILON_HEAL_OTHER -fedhas_fungal_bloom ABILITY_FEDHAS_FUNGAL_BLOOM -fedhas_evolution ABILITY_FEDHAS_EVOLUTION -fedhas_plant_ring ABILITY_FEDHAS_PLANT_RING -fedhas_rain ABILITY_FEDHAS_RAIN -fedhas_spawn_spores ABILITY_FEDHAS_SPAWN_SPORES -fedhas_sunlight ABILITY_FEDHAS_SUNLIGHT +fedhas_wall_of_briars ABILITY_FEDHAS_WALL_OF_BRIARS +fedhas_grow_ballistomycete ABILITY_FEDHAS_GROW_BALLISTOMYCETE +fedhas_overgrow ABILITY_FEDHAS_OVERGROW +fedhas_grow_oklob ABILITY_FEDHAS_GROW_OKLOB gozag_potion_petition ABILITY_GOZAG_POTION_PETITION gozag_call_merchant ABILITY_GOZAG_CALL_MERCHANT gozag_bribe_branch ABILITY_GOZAG_BRIBE_BRANCH @@ -113,8 +111,7 @@ ru_sacrifice_resistance ABILITY_RU_SACRIFICE_RESISTANCE ru_reject_sacrifices ABILITY_RU_REJECT_SACRIFICES sif_muna_amnesia ABILITY_SIF_MUNA_AMNESIA sif_muna_channel ABILITY_SIF_MUNA_CHANNEL -sif_muna_divine_energy ABILITY_SIF_MUNA_DIVINE_ENERGY -sif_muna_stop_divine_energy ABILITY_SIF_MUNA_STOP_DIVINE_ENERGY +sif_muna_exegesis ABILITY_SIF_MUNA_EXEGESIS trog_berserk ABILITY_TROG_BERSERK trog_brothers_in_arms ABILITY_TROG_BROTHERS_IN_ARMS trog_hand ABILITY_TROG_HAND diff --git a/crawl-ref/source/rltiles/dc-item.txt b/crawl-ref/source/rltiles/dc-item.txt index 0e5b38dbe382..c48d333d6320 100644 --- a/crawl-ref/source/rltiles/dc-item.txt +++ b/crawl-ref/source/rltiles/dc-item.txt @@ -14,6 +14,11 @@ ##### Artefacts (fixed and unrandart) %include dc-unrand.txt +urand_wyrmbane1 UNRAND_WYRMBANE1 +urand_wyrmbane2 UNRAND_WYRMBANE2 +urand_wyrmbane3 UNRAND_WYRMBANE3 +urand_wyrmbane4 UNRAND_WYRMBANE4 + #####NORMAL %sdir item/weapon %rim 0 @@ -190,7 +195,7 @@ i-protection BRAND_PROTECTION i-draining BRAND_DRAINING i-speed BRAND_SPEED i-vorpal BRAND_VORPAL -# Remove the BRAND_FLAME and BRAND_FROST line s(but not the tiles, which are +# Remove the BRAND_FLAME and BRAND_FROST lines (but not the tiles, which are # still used for melee weapons) when TAG_MAJOR_VERSION > 34 i-flaming BRAND_FLAME i-freezing BRAND_FROST @@ -200,13 +205,14 @@ i-pain BRAND_PAIN i-antimagic BRAND_ANTIMAGIC %rim 0 i-distortion BRAND_DISTORTION -# Remove when TAG_MAJOR_VERSION > 34 +# Remove confusion when TAG_MAJOR_VERSION > 34 i-confusion BRAND_REACHING -# Move i-returning down to missile-only brands when TAG_MAJOR_VERSION > 34 +# Remove returning when TAG_MAJOR_VERSION > 34 i-returning BRAND_RETURNING i-chaos BRAND_CHAOS +# Remove evasion when TAG_MAJOR_VERSION > 34 i-evasion BRAND_EVASION -# Move i-confusion down to missile-only brands when TAG_MAJOR_VERSION > 34 +# Remove confusion down when TAG_MAJOR_VERSION > 34 i-confusion BRAND_CONFUSION i-penetration BRAND_PENETRATION i-reaping BRAND_REAPING BRAND_WEP_LAST @@ -216,12 +222,13 @@ i-explosion BRAND_EXPLOSION i-venom BRAND_POISONED i-curare BRAND_CURARE i-electrocution BRAND_ELECTRIC -# Remove sickness and slowing when TAG_MAJOR_VERSION > 34 +# Remove sickness, slowingr, paralysis, and sleep when TAG_MAJOR_VERSION > 34 i-sickness BRAND_SICKNESS i-slowing BRAND_SLOWING i-paralysis BRAND_PARALYSIS i-frenzy BRAND_FRENZY i-sleep BRAND_SLEEP +i-blinding BRAND_BLINDING %rim 1 %sdir item/weapon/ranged @@ -277,34 +284,29 @@ effect/bolt6 MI_BOLT6 effect/bolt7 MI_BOLT7 %rim 0 -tomahawk1 MI_TOMAHAWK -tomahawk2 -steel_tomahawk MI_TOMAHAWK_STEEL -silver_tomahawk MI_TOMAHAWK_SILVER -# flying tomahawks -effect/tomahawk0 MI_TOMAHAWK0 -effect/tomahawk1 MI_TOMAHAWK1 -effect/tomahawk2 MI_TOMAHAWK2 -effect/tomahawk3 MI_TOMAHAWK3 -effect/tomahawk4 MI_TOMAHAWK4 -effect/tomahawk5 MI_TOMAHAWK5 -effect/tomahawk6 MI_TOMAHAWK6 -effect/tomahawk7 MI_TOMAHAWK7 +boomerang1 MI_BOOMERANG +boomerang2 +silver_boomerang MI_BOOMERANG_SILVER +# flying boomerangs +effect/boomerang0 MI_BOOMERANG0 +effect/boomerang1 MI_BOOMERANG1 +effect/boomerang2 MI_BOOMERANG2 +effect/boomerang3 MI_BOOMERANG3 %rim 1 -needle1 MI_NEEDLE -needle2 -needle-p MI_NEEDLE_P -needle-c MI_NEEDLE_CURARE +dart1 MI_DART +dart2 +dart-p MI_DART_P +dart-c MI_DART_CURARE # flying needles -effect/needle0 MI_NEEDLE0 -effect/needle1 MI_NEEDLE1 -effect/needle2 MI_NEEDLE2 -effect/needle3 MI_NEEDLE3 -effect/needle4 MI_NEEDLE4 -effect/needle5 MI_NEEDLE5 -effect/needle6 MI_NEEDLE6 -effect/needle7 MI_NEEDLE7 +effect/needle0 MI_DART0 +effect/needle1 MI_DART1 +effect/needle2 MI_DART2 +effect/needle3 MI_DART3 +effect/needle4 MI_DART4 +effect/needle5 MI_DART5 +effect/needle6 MI_DART6 +effect/needle7 MI_DART7 %rim 0 javelin1 MI_JAVELIN @@ -500,10 +502,12 @@ i-stealth BRAND_ARM_STEALTH i-resistance BRAND_ARM_RESISTANCE i-positive-energy BRAND_ARM_POSITIVE_ENERGY i-archmagi BRAND_ARM_ARCHMAGI +# Remove preservation when TAG_MAJOR_VERSION > 34 i-preservation BRAND_ARM_PRESERVATION i-reflection BRAND_ARM_REFLECTION i-spirit BRAND_ARM_SPIRIT_SHIELD i-archery BRAND_ARM_ARCHERY +# Remove jumping when TAG_MAJOR_VERSION > 34 i-jumping BRAND_ARM_JUMPING i-repulsion BRAND_ARM_REPULSION i-cloud-immune BRAND_ARM_CLOUD_IMMUNE BRAND_ARM_LAST @@ -609,6 +613,7 @@ i-fear SCR_FEAR i-noise SCR_NOISE %back scroll-yellow i-remove_curse SCR_REMOVE_CURSE +# aka scroll of summoning %back scroll-brown i-unholy_creation SCR_UNHOLY_CREATION %back scroll-green @@ -648,6 +653,7 @@ i-silence SCR_SILENCE i-amnesia SCR_AMNESIA %back scroll-grey #### Remove curse foo when TAG_MAJOR_VERSION > 34 +# Don't forget to update SCR_ID_LAST i-curse-jewellery SCR_CURSE_JEWELLERY SCR_ID_LAST %back none @@ -841,6 +847,7 @@ i-slowing POT_SLOWING i-cancel POT_CANCELLATION i-ambrosia POT_AMBROSIA i-invisibility POT_INVISIBILITY +# Remove porridge when TAG_MAJOR_VERSION > 34 i-porridge POT_PORRIDGE i-degeneration POT_DEGENERATION # please remove these 2 when TAG_MAJOR_VERSION is bumped @@ -856,10 +863,11 @@ i-berserk-rage POT_BERSERK_RAGE i-cure-mutation POT_REMOVEME8 i-mutation POT_MUTATION i-resistance POT_RESISTANCE +# Remove all blood blood when TAG_MAJOR_VERSION > 34 i-blood POT_BLOOD i-coagulated-blood POT_BLOOD_COAGULATED i-lignify POT_LIGNIFY -# please remove POT_BENEFICIAL_MUTATION when TAG_MAJOR_VERSION is bumped +# Remove beneficial mutation and update POT_ID_LAST when TAG_MAJOR_VERSION > 34 i-good-mutation POT_REMOVEME9 POT_ID_LAST %back none %rim 1 @@ -936,6 +944,7 @@ i-staff_conjuration STAFF_CONJURATION i-staff_enchantment STAFF_ENCHANTMENT i-staff_summoning STAFF_SUMMONING i-staff_air STAFF_AIR +# Remove channeling and update STAFF_ID_LAST when TAG_MAJOR_VERSION > 34 i-staff_earth STAFF_EARTH %rim 0 i-staff_channeling STAFF_CHANNELING STAFF_ID_LAST diff --git a/crawl-ref/source/rltiles/dc-mon.txt b/crawl-ref/source/rltiles/dc-mon.txt index bf843f9827a9..434e12efe5e0 100644 --- a/crawl-ref/source/rltiles/dc-mon.txt +++ b/crawl-ref/source/rltiles/dc-mon.txt @@ -938,9 +938,7 @@ fungus6 fungus7 fungus8 fungus9 -ballistomycete MONS_BALLISTOMYCETE_INACTIVE -active_ballistomycete -hyperactive_ballistomycete MONS_HYPERACTIVE_BALLISTOMYCETE +ballistomycete MONS_BALLISTOMYCETE wandering_mushroom MONS_WANDERING_MUSHROOM deathcap MONS_DEATHCAP %rim 1 @@ -1172,3 +1170,4 @@ old-serpent-geh MONS_OLD_SERPENT_GEH old-serpent-tar MONS_OLD_SERPENT_TAR wiglaf MONS_WIGLAF orb_guardian_fetus MONS_ORB_GUARDIAN_FETUS +polymoth MONS_POLYMOTH diff --git a/crawl-ref/source/rltiles/dc-player.txt b/crawl-ref/source/rltiles/dc-player.txt index bf755171022f..471025037e8e 100644 --- a/crawl-ref/source/rltiles/dc-player.txt +++ b/crawl-ref/source/rltiles/dc-player.txt @@ -546,6 +546,7 @@ asmodeus ASMODEUS dispater DISPATER olgreb OLGREB order ORDER +battle_staff BATTLE_STAFF ## axes axe_trog AXE_TROG arga ARGA @@ -1069,6 +1070,7 @@ cat7 cat8 cat9 cat10 +draconian1 HORNS_DRAC %sdir player/head ## Doll editor tiles, but not randomly picked diff --git a/crawl-ref/source/rltiles/dc-spells.txt b/crawl-ref/source/rltiles/dc-spells.txt index 1b816f747581..cf3e1190dd4b 100644 --- a/crawl-ref/source/rltiles/dc-spells.txt +++ b/crawl-ref/source/rltiles/dc-spells.txt @@ -172,10 +172,13 @@ bolt_energy ENERGY_BOLT %sdir gui/spells/monster generic GENERIC_MONSTER_SPELL air_elementals AIR_ELEMENTALS +awaken_vines AWAKEN_VINES blink_other BLINK_OTHER brain_feed BRAIN_FEED +call_down_damnation CALL_DOWN_DAMNATION cantrip CANTRIP cold_breath COLD_BREATH +create_tentacles CREATE_TENTACLES earth_elementals EARTH_ELEMENTALS fake_mara_summon FAKE_MARA_SUMMON fake_rakshasa_summon FAKE_RAKSHASA_SUMMON @@ -184,7 +187,6 @@ fire_elementals FIRE_ELEMENTALS ghostly_fireball GHOSTLY_FIREBALL grasping_roots GRASPING_ROOTS haste_other HASTE_OTHER -call_down_damnation CALL_DOWN_DAMNATION ink_cloud INK_CLOUD iron_elementals IRON_ELEMENTALS malmutate MALMUTATE @@ -198,23 +200,29 @@ phantom_mirror PHANTOM_MIRROR polymorph POLYMORPH porkalator PORKALATOR quicksilver_bolt QUICKSILVER_BOLT -# Remove when TAG_MAJOR_VERSION > 34 +seal_doors SEAL_DOORS +sentinel_mark SENTINEL_MARK sleep SLEEP spectral_cloud SPECTRAL_CLOUD spit_acid SPIT_ACID spit_poison SPIT_POISON +sporulate SPORULATE +sprint SPRINT steam_ball STEAM_BALL sticky_flame_range STICKY_FLAME_RANGE +strip_resistance STRIP_RESISTANCE summon_drakes SUMMON_DRAKES summon_eyeballs SUMMON_EYEBALLS summon_hell_beast SUMMON_HELL_BEAST summon_minor_demon SUMMON_MINOR_DEMON summon_mushrooms SUMMON_MUSHROOMS +summon_spectral_orcs SUMMON_SPECTRAL_ORCS summon_ufetubus SUMMON_UFETUBUS summon_undead SUMMON_UNDEAD summon_vermin SUMMON_VERMIN teleport TELEPORT twisted_resurrection TWISTED_RESURRECTION +wall_of_brambles WALL_OF_BRAMBLES water_elementals WATER_ELEMENTALS %back none diff --git a/crawl-ref/source/rltiles/dngn/altars/sif_muna.png b/crawl-ref/source/rltiles/dngn/altars/sif_muna.png deleted file mode 100644 index 82e462f73a56..000000000000 Binary files a/crawl-ref/source/rltiles/dngn/altars/sif_muna.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/dngn/altars/sif_muna1.png b/crawl-ref/source/rltiles/dngn/altars/sif_muna1.png new file mode 100644 index 000000000000..7009336f93fe Binary files /dev/null and b/crawl-ref/source/rltiles/dngn/altars/sif_muna1.png differ diff --git a/crawl-ref/source/rltiles/dngn/altars/sif_muna2.png b/crawl-ref/source/rltiles/dngn/altars/sif_muna2.png new file mode 100644 index 000000000000..b62f45e6a12c Binary files /dev/null and b/crawl-ref/source/rltiles/dngn/altars/sif_muna2.png differ diff --git a/crawl-ref/source/rltiles/dngn/altars/sif_muna3.png b/crawl-ref/source/rltiles/dngn/altars/sif_muna3.png new file mode 100644 index 000000000000..0072dbf565a1 Binary files /dev/null and b/crawl-ref/source/rltiles/dngn/altars/sif_muna3.png differ diff --git a/crawl-ref/source/rltiles/dngn/altars/sif_muna4.png b/crawl-ref/source/rltiles/dngn/altars/sif_muna4.png new file mode 100644 index 000000000000..e66e2ba768ca Binary files /dev/null and b/crawl-ref/source/rltiles/dngn/altars/sif_muna4.png differ diff --git a/crawl-ref/source/rltiles/effect/boomerang0.png b/crawl-ref/source/rltiles/effect/boomerang0.png new file mode 100644 index 000000000000..8a447e9feb27 Binary files /dev/null and b/crawl-ref/source/rltiles/effect/boomerang0.png differ diff --git a/crawl-ref/source/rltiles/effect/boomerang1.png b/crawl-ref/source/rltiles/effect/boomerang1.png new file mode 100644 index 000000000000..5fd303bbe52e Binary files /dev/null and b/crawl-ref/source/rltiles/effect/boomerang1.png differ diff --git a/crawl-ref/source/rltiles/effect/boomerang2.png b/crawl-ref/source/rltiles/effect/boomerang2.png new file mode 100644 index 000000000000..6a7692d09c3d Binary files /dev/null and b/crawl-ref/source/rltiles/effect/boomerang2.png differ diff --git a/crawl-ref/source/rltiles/effect/boomerang3.png b/crawl-ref/source/rltiles/effect/boomerang3.png new file mode 100644 index 000000000000..c15c2787345c Binary files /dev/null and b/crawl-ref/source/rltiles/effect/boomerang3.png differ diff --git a/crawl-ref/source/rltiles/gui/abilities/exsanguinate.png b/crawl-ref/source/rltiles/gui/abilities/exsanguinate.png new file mode 100644 index 000000000000..9ad0945488f2 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/abilities/exsanguinate.png differ diff --git a/crawl-ref/source/rltiles/gui/abilities/revivify.png b/crawl-ref/source/rltiles/gui/abilities/revivify.png new file mode 100644 index 000000000000..8fa27334ebc5 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/abilities/revivify.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/AE.png b/crawl-ref/source/rltiles/gui/backgrounds/AE.png new file mode 100644 index 000000000000..8dbc6c30f11a Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/AE.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/AK.png b/crawl-ref/source/rltiles/gui/backgrounds/AK.png new file mode 100644 index 000000000000..9e4fc57e6213 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/AK.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/AM.png b/crawl-ref/source/rltiles/gui/backgrounds/AM.png new file mode 100644 index 000000000000..536ce18e3a96 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/AM.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Ar.png b/crawl-ref/source/rltiles/gui/backgrounds/Ar.png new file mode 100644 index 000000000000..69a3106c4474 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Ar.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/As.png b/crawl-ref/source/rltiles/gui/backgrounds/As.png new file mode 100644 index 000000000000..0aa9d37a499a Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/As.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Be.png b/crawl-ref/source/rltiles/gui/backgrounds/Be.png new file mode 100644 index 000000000000..7533f54023e3 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Be.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/CK.png b/crawl-ref/source/rltiles/gui/backgrounds/CK.png new file mode 100644 index 000000000000..085fc6b751f3 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/CK.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Cj.png b/crawl-ref/source/rltiles/gui/backgrounds/Cj.png new file mode 100644 index 000000000000..4da2a3655138 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Cj.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/EE.png b/crawl-ref/source/rltiles/gui/backgrounds/EE.png new file mode 100644 index 000000000000..e29f23a631f7 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/EE.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/En.png b/crawl-ref/source/rltiles/gui/backgrounds/En.png new file mode 100644 index 000000000000..47253b6318e1 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/En.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/FE.png b/crawl-ref/source/rltiles/gui/backgrounds/FE.png new file mode 100644 index 000000000000..1714917f081f Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/FE.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Fi.png b/crawl-ref/source/rltiles/gui/backgrounds/Fi.png new file mode 100644 index 000000000000..a92a8e78902c Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Fi.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Gl.png b/crawl-ref/source/rltiles/gui/backgrounds/Gl.png new file mode 100644 index 000000000000..df82851d6922 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Gl.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Hu.png b/crawl-ref/source/rltiles/gui/backgrounds/Hu.png new file mode 100644 index 000000000000..32dcf3b96ffe Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Hu.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/IE.png b/crawl-ref/source/rltiles/gui/backgrounds/IE.png new file mode 100644 index 000000000000..d3639dfb8a7e Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/IE.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Mo.png b/crawl-ref/source/rltiles/gui/backgrounds/Mo.png new file mode 100644 index 000000000000..726b4cec6344 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Mo.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Ne.png b/crawl-ref/source/rltiles/gui/backgrounds/Ne.png new file mode 100644 index 000000000000..527e8c62bb10 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Ne.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Sk.png b/crawl-ref/source/rltiles/gui/backgrounds/Sk.png new file mode 100644 index 000000000000..1212f7c570aa Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Sk.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Su.png b/crawl-ref/source/rltiles/gui/backgrounds/Su.png new file mode 100644 index 000000000000..7eae07a58565 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Su.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Tm.png b/crawl-ref/source/rltiles/gui/backgrounds/Tm.png new file mode 100644 index 000000000000..247b4023056e Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Tm.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/VM.png b/crawl-ref/source/rltiles/gui/backgrounds/VM.png new file mode 100644 index 000000000000..acb5a8fd2099 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/VM.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Wn.png b/crawl-ref/source/rltiles/gui/backgrounds/Wn.png new file mode 100644 index 000000000000..2a4d1177d5ca Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Wn.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Wr.png b/crawl-ref/source/rltiles/gui/backgrounds/Wr.png new file mode 100644 index 000000000000..f37f6d2eb51a Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Wr.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/Wz.png b/crawl-ref/source/rltiles/gui/backgrounds/Wz.png new file mode 100644 index 000000000000..13d5c17fc642 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/Wz.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/disabled-fg.png b/crawl-ref/source/rltiles/gui/backgrounds/disabled-fg.png new file mode 100644 index 000000000000..42679d2bb46b Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/disabled-fg.png differ diff --git a/crawl-ref/source/rltiles/gui/backgrounds/enabled-fg.png b/crawl-ref/source/rltiles/gui/backgrounds/enabled-fg.png new file mode 100644 index 000000000000..6a9af475ef41 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/backgrounds/enabled-fg.png differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_evolution.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_evolution.png deleted file mode 100644 index ada89e16ec5d..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/fedhas_evolution.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_fungal_bloom.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_fungal_bloom.png deleted file mode 100644 index 7d911410a44a..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/fedhas_fungal_bloom.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_spawn_spores.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_grow_ballistomycete.png similarity index 100% rename from crawl-ref/source/rltiles/gui/invocations/fedhas_spawn_spores.png rename to crawl-ref/source/rltiles/gui/invocations/fedhas_grow_ballistomycete.png diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_grow_oklob.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_grow_oklob.png new file mode 100644 index 000000000000..3b919ebb9917 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/invocations/fedhas_grow_oklob.png differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_overgrow.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_overgrow.png new file mode 100644 index 000000000000..a711e69862f2 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/invocations/fedhas_overgrow.png differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_plant_ring.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_plant_ring.png deleted file mode 100644 index ef367f50a411..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/fedhas_plant_ring.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_rain.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_rain.png deleted file mode 100644 index 83c0a45eba3b..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/fedhas_rain.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_sunlight.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_sunlight.png deleted file mode 100644 index cb7cab270477..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/fedhas_sunlight.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/invocations/fedhas_wall_of_briars.png b/crawl-ref/source/rltiles/gui/invocations/fedhas_wall_of_briars.png new file mode 100644 index 000000000000..0fda4bd1820f Binary files /dev/null and b/crawl-ref/source/rltiles/gui/invocations/fedhas_wall_of_briars.png differ diff --git a/crawl-ref/source/rltiles/gui/invocations/sif_muna_divine_energy.png b/crawl-ref/source/rltiles/gui/invocations/sif_muna_divine_energy.png deleted file mode 100644 index 75d6f75ed54d..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/sif_muna_divine_energy.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/invocations/sif_muna_exegesis.png b/crawl-ref/source/rltiles/gui/invocations/sif_muna_exegesis.png new file mode 100644 index 000000000000..beca61fe36df Binary files /dev/null and b/crawl-ref/source/rltiles/gui/invocations/sif_muna_exegesis.png differ diff --git a/crawl-ref/source/rltiles/gui/invocations/sif_muna_stop_divine_energy.png b/crawl-ref/source/rltiles/gui/invocations/sif_muna_stop_divine_energy.png deleted file mode 100644 index 0b190915b23e..000000000000 Binary files a/crawl-ref/source/rltiles/gui/invocations/sif_muna_stop_divine_energy.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/gui/spells/conjuration/fulminant_prism.png b/crawl-ref/source/rltiles/gui/spells/conjuration/fulminant_prism.png index bf1e8083b448..51bde8a5729e 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/conjuration/fulminant_prism.png and b/crawl-ref/source/rltiles/gui/spells/conjuration/fulminant_prism.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/fire/bolt_of_fire.png b/crawl-ref/source/rltiles/gui/spells/fire/bolt_of_fire.png index d18bd4a0f7eb..5329068cfd5c 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/fire/bolt_of_fire.png and b/crawl-ref/source/rltiles/gui/spells/fire/bolt_of_fire.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/ice/bolt_of_cold.png b/crawl-ref/source/rltiles/gui/spells/ice/bolt_of_cold.png index 1aee250f13a9..6c569a9583fe 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/ice/bolt_of_cold.png and b/crawl-ref/source/rltiles/gui/spells/ice/bolt_of_cold.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/ice/freeze.png b/crawl-ref/source/rltiles/gui/spells/ice/freeze.png index 60813965481d..1474bd300e6b 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/ice/freeze.png and b/crawl-ref/source/rltiles/gui/spells/ice/freeze.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_armour.png b/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_armour.png index a05336acd964..e33dc5ba5caa 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_armour.png and b/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_armour.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_refrigeration.png b/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_refrigeration.png index dbf09b176c88..1e33c4b82a46 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_refrigeration.png and b/crawl-ref/source/rltiles/gui/spells/ice/ozocubus_refrigeration.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/ice/throw_icicle.png b/crawl-ref/source/rltiles/gui/spells/ice/throw_icicle.png index dd30bf1f9ebf..e9b867fe0cfb 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/ice/throw_icicle.png and b/crawl-ref/source/rltiles/gui/spells/ice/throw_icicle.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/awaken_vines.png b/crawl-ref/source/rltiles/gui/spells/monster/awaken_vines.png new file mode 100644 index 000000000000..602abc953ed1 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/awaken_vines.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/create_tentacles.png b/crawl-ref/source/rltiles/gui/spells/monster/create_tentacles.png new file mode 100644 index 000000000000..24b0f3340c26 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/create_tentacles.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/seal_doors.png b/crawl-ref/source/rltiles/gui/spells/monster/seal_doors.png new file mode 100644 index 000000000000..db14ea969226 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/seal_doors.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/sentinel_mark.png b/crawl-ref/source/rltiles/gui/spells/monster/sentinel_mark.png new file mode 100644 index 000000000000..6efd0a7c3704 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/sentinel_mark.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/sporulate.png b/crawl-ref/source/rltiles/gui/spells/monster/sporulate.png new file mode 100644 index 000000000000..6b2463512b28 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/sporulate.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/sprint.png b/crawl-ref/source/rltiles/gui/spells/monster/sprint.png new file mode 100644 index 000000000000..f534a185167f Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/sprint.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/strip_resistance.png b/crawl-ref/source/rltiles/gui/spells/monster/strip_resistance.png new file mode 100644 index 000000000000..368d6d9bcfa4 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/strip_resistance.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/summon_spectral_orcs.png b/crawl-ref/source/rltiles/gui/spells/monster/summon_spectral_orcs.png new file mode 100644 index 000000000000..457cea80902d Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/summon_spectral_orcs.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/monster/wall_of_brambles.png b/crawl-ref/source/rltiles/gui/spells/monster/wall_of_brambles.png new file mode 100644 index 000000000000..123eb0a5348b Binary files /dev/null and b/crawl-ref/source/rltiles/gui/spells/monster/wall_of_brambles.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/necromancy/animate_dead.png b/crawl-ref/source/rltiles/gui/spells/necromancy/animate_dead.png index b27843acd503..0236221c2a1f 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/necromancy/animate_dead.png and b/crawl-ref/source/rltiles/gui/spells/necromancy/animate_dead.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/necromancy/animate_skeleton.png b/crawl-ref/source/rltiles/gui/spells/necromancy/animate_skeleton.png index 59e35444279b..3a3eb6febf0f 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/necromancy/animate_skeleton.png and b/crawl-ref/source/rltiles/gui/spells/necromancy/animate_skeleton.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/poison/olgrebs_toxic_radiance.png b/crawl-ref/source/rltiles/gui/spells/poison/olgrebs_toxic_radiance.png index bd9897144d90..252d4113a766 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/poison/olgrebs_toxic_radiance.png and b/crawl-ref/source/rltiles/gui/spells/poison/olgrebs_toxic_radiance.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/translocation/beckoning.png b/crawl-ref/source/rltiles/gui/spells/translocation/beckoning.png index 9985cf1a6bfc..60383ebc57f5 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/translocation/beckoning.png and b/crawl-ref/source/rltiles/gui/spells/translocation/beckoning.png differ diff --git a/crawl-ref/source/rltiles/gui/spells/translocation/shroud_of_golubria.png b/crawl-ref/source/rltiles/gui/spells/translocation/shroud_of_golubria.png index 3161765cdb28..614f76ab7b8b 100644 Binary files a/crawl-ref/source/rltiles/gui/spells/translocation/shroud_of_golubria.png and b/crawl-ref/source/rltiles/gui/spells/translocation/shroud_of_golubria.png differ diff --git a/crawl-ref/source/rltiles/gui/startup/high_scores.png b/crawl-ref/source/rltiles/gui/startup/high_scores.png index 4ceef49fc7a4..b0422b105e0e 100644 Binary files a/crawl-ref/source/rltiles/gui/startup/high_scores.png and b/crawl-ref/source/rltiles/gui/startup/high_scores.png differ diff --git a/crawl-ref/source/rltiles/gui/tutorial/combat.png b/crawl-ref/source/rltiles/gui/tutorial/combat.png new file mode 100644 index 000000000000..7831a14912e8 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/tutorial/combat.png differ diff --git a/crawl-ref/source/rltiles/gui/tutorial/movement.png b/crawl-ref/source/rltiles/gui/tutorial/movement.png new file mode 100644 index 000000000000..d4ced7e5c647 Binary files /dev/null and b/crawl-ref/source/rltiles/gui/tutorial/movement.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/artefact/urand_staff_of_battle.png b/crawl-ref/source/rltiles/item/weapon/artefact/urand_staff_of_battle.png new file mode 100644 index 000000000000..7d1397441169 Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/artefact/urand_staff_of_battle.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/brands/i-blinding.png b/crawl-ref/source/rltiles/item/weapon/brands/i-blinding.png new file mode 100644 index 000000000000..e2299989653c Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/brands/i-blinding.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/boomerang1.png b/crawl-ref/source/rltiles/item/weapon/ranged/boomerang1.png new file mode 100644 index 000000000000..8a447e9feb27 Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/boomerang1.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/boomerang2.png b/crawl-ref/source/rltiles/item/weapon/ranged/boomerang2.png new file mode 100644 index 000000000000..e19c9237d318 Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/boomerang2.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/dart-c.png b/crawl-ref/source/rltiles/item/weapon/ranged/dart-c.png new file mode 100644 index 000000000000..8756b842164e Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/dart-c.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/dart-p.png b/crawl-ref/source/rltiles/item/weapon/ranged/dart-p.png new file mode 100644 index 000000000000..d11baecb4a5f Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/dart-p.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/dart1.png b/crawl-ref/source/rltiles/item/weapon/ranged/dart1.png new file mode 100644 index 000000000000..55e4889babfb Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/dart1.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/dart2.png b/crawl-ref/source/rltiles/item/weapon/ranged/dart2.png new file mode 100644 index 000000000000..6914eee0215d Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/dart2.png differ diff --git a/crawl-ref/source/rltiles/item/weapon/ranged/silver_boomerang.png b/crawl-ref/source/rltiles/item/weapon/ranged/silver_boomerang.png new file mode 100644 index 000000000000..28da51f4c55e Binary files /dev/null and b/crawl-ref/source/rltiles/item/weapon/ranged/silver_boomerang.png differ diff --git a/crawl-ref/source/rltiles/misc/halo_summoner.png b/crawl-ref/source/rltiles/misc/halo_summoner.png new file mode 100644 index 000000000000..daa31f0116ea Binary files /dev/null and b/crawl-ref/source/rltiles/misc/halo_summoner.png differ diff --git a/crawl-ref/source/rltiles/mon/fungi_plants/active_ballistomycete.png b/crawl-ref/source/rltiles/mon/fungi_plants/active_ballistomycete.png deleted file mode 100644 index fcd533178416..000000000000 Binary files a/crawl-ref/source/rltiles/mon/fungi_plants/active_ballistomycete.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/mon/fungi_plants/ballistomycete.png b/crawl-ref/source/rltiles/mon/fungi_plants/ballistomycete.png index 65046accdef3..94dfbf16dc4d 100644 Binary files a/crawl-ref/source/rltiles/mon/fungi_plants/ballistomycete.png and b/crawl-ref/source/rltiles/mon/fungi_plants/ballistomycete.png differ diff --git a/crawl-ref/source/rltiles/mon/fungi_plants/hyperactive_ballistomycete.png b/crawl-ref/source/rltiles/mon/fungi_plants/hyperactive_ballistomycete.png deleted file mode 100644 index 94dfbf16dc4d..000000000000 Binary files a/crawl-ref/source/rltiles/mon/fungi_plants/hyperactive_ballistomycete.png and /dev/null differ diff --git a/crawl-ref/source/rltiles/mon/player/polymoth.png b/crawl-ref/source/rltiles/mon/player/polymoth.png new file mode 100644 index 000000000000..45824904663b Binary files /dev/null and b/crawl-ref/source/rltiles/mon/player/polymoth.png differ diff --git a/crawl-ref/source/rltiles/player/hand1/artefact/battle_staff.png b/crawl-ref/source/rltiles/player/hand1/artefact/battle_staff.png new file mode 100644 index 000000000000..de2af128dbb5 Binary files /dev/null and b/crawl-ref/source/rltiles/player/hand1/artefact/battle_staff.png differ diff --git a/crawl-ref/source/rltiles/player/mutations/draconian1.png b/crawl-ref/source/rltiles/player/mutations/draconian1.png new file mode 100644 index 000000000000..68aa70479839 Binary files /dev/null and b/crawl-ref/source/rltiles/player/mutations/draconian1.png differ diff --git a/crawl-ref/source/rltiles/tool/tile.h b/crawl-ref/source/rltiles/tool/tile.h index 175bad30262d..aa83bb006904 100644 --- a/crawl-ref/source/rltiles/tool/tile.h +++ b/crawl-ref/source/rltiles/tool/tile.h @@ -6,6 +6,39 @@ #include using namespace std; +// copied from libutil.h +static inline bool isalower(int c) +{ + return c >= 'a' && c <= 'z'; +} + +static inline bool isaupper(int c) +{ + return c >= 'A' && c <= 'Z'; +} + +static inline char32_t toalower(char32_t c) +{ + return isaupper(c) ? c + 'a' - 'A' : c; +} + +// Same thing with signed int, so we can pass though -1 undisturbed. +static inline int toalower(int c) +{ + return isaupper(c) ? c + 'a' - 'A' : c; +} + +static inline char32_t toaupper(char32_t c) +{ + return isalower(c) ? c + 'A' - 'a' : c; +} + +// Same thing with signed int, so we can pass though -1 undisturbed. +static inline int toaupper(int c) +{ + return isalower(c) ? c + 'A' - 'a' : c; +} + class tile { public: diff --git a/crawl-ref/source/rltiles/tool/tile_list_processor.cc b/crawl-ref/source/rltiles/tool/tile_list_processor.cc index e08bfdd0b2fb..7a12fda94ff8 100644 --- a/crawl-ref/source/rltiles/tool/tile_list_processor.cc +++ b/crawl-ref/source/rltiles/tool/tile_list_processor.cc @@ -39,11 +39,30 @@ tile_list_processor::~tile_list_processor() delete m_texture; } +bool tile_list_processor::load_image_from_tile(tile &img, string filename) +{ + if (filename.substr(0, 5) != "enum:") + return false; + const string enumname = filename.substr(5); + + int idx = m_page.find(enumname); + if (idx == -1) + return false; + + img.copy(*m_page.m_tiles[idx]); + for (int i = 0; i < MAX_COLOUR; ++i) + img.add_variation(i, -1); + return true; +} + bool tile_list_processor::load_image(tile &img, const char *filename, bool background) { assert(filename); + if (load_image_from_tile(img, filename)) + return true; + char temp[1024]; const unsigned int num_ext = 3; @@ -190,7 +209,7 @@ static int str_to_colour(string colour) return 0; for (unsigned int c = 0; c < colour.size(); c++) - colour[c] = tolower(colour[c]); + colour[c] = toalower(colour[c]); for (int i = 0; i < 16; ++i) { @@ -894,8 +913,8 @@ bool tile_list_processor::write_data(bool image, bool code) string ucname = m_name; for (unsigned int i = 0; i < m_name.size(); i++) { - lcname[i] = tolower(m_name[i]); - ucname[i] = toupper(m_name[i]); + lcname[i] = toalower(m_name[i]); + ucname[i] = toaupper(m_name[i]); } string max = m_prefix; max += "_"; @@ -1055,7 +1074,7 @@ bool tile_list_processor::write_data(bool image, bool code) max_enum += "_MAX"; for (char& c : max_enum) - c = toupper(c); + c = toaupper(c); fprintf(fp, " %s_%s_MAX = %s\n};\n\n", m_prefix.c_str(), ucname.c_str(), max_enum.c_str()); } @@ -1112,6 +1131,7 @@ bool tile_list_processor::write_data(bool image, bool code) fprintf(fp, "// This file has been automatically generated.\n\n"); fprintf(fp, "#include \"AppHdr.h\"\n"); + fprintf(fp, "#include \"libutil.h\"\n"); fprintf(fp, "#include \"tiledef-%s.h\"\n\n", lcname.c_str()); fprintf(fp, "static unsigned int _tile_%s_count[%s - %s] =\n{\n", @@ -1258,7 +1278,7 @@ bool tile_list_processor::write_data(bool image, bool code) string lcenum = enumname; for (unsigned int c = 0; c < enumname.size(); c++) - lcenum[c] = tolower(enumname[c]); + lcenum[c] = toalower(enumname[c]); table.insert(sort_map::value_type(lcenum, i)); } @@ -1282,7 +1302,7 @@ bool tile_list_processor::write_data(bool image, bool code) "\n" " string lc = str;\n" " for (unsigned int i = 0; i < lc.size(); i++)\n" - " lc[i] = tolower(lc[i]);\n" + " lc[i] = toalower(lc[i]);\n" "\n" " int num_pairs = sizeof(%s_name_pairs) / sizeof(%s_name_pairs[0]);\n" " bool result = binary_search(\n" @@ -1371,7 +1391,7 @@ bool tile_list_processor::write_data(bool image, bool code) max_enum += "_MAX"; for (char& c : max_enum) - c = toupper(c); + c = toaupper(c); uc_max_enum.push_back(max_enum); @@ -1472,7 +1492,7 @@ bool tile_list_processor::write_data(bool image, bool code) { string lcenum = m_page.m_tiles[i]->enumname(0); for (unsigned int c = 0; c < lcenum.size(); c++) - lcenum[c] = tolower(lcenum[c]); + lcenum[c] = toalower(lcenum[c]); if (i == 0 || m_page.m_counts[i] == 1) fprintf(fp, "%s", lcenum.c_str()); @@ -1704,7 +1724,7 @@ bool tile_list_processor::write_data(bool image, bool code) max_enum += "_MAX"; for (char& c : max_enum) - c = toupper(c); + c = toaupper(c); fprintf(fp, "exports.%s_MAX = window.%s_%s_MAX = %s.%s;\n\n", ucname.c_str(), m_prefix.c_str(), ucname.c_str() @@ -1719,7 +1739,7 @@ bool tile_list_processor::write_data(bool image, bool code) string max_enum = abstract.first; max_enum += "_MAX"; for (char& c : max_enum) - c = toupper(c); + c = toaupper(c); max_enum = abstract.first + "." + max_enum; diff --git a/crawl-ref/source/rltiles/tool/tile_list_processor.h b/crawl-ref/source/rltiles/tool/tile_list_processor.h index 4d937d7b0a39..fdb900738140 100644 --- a/crawl-ref/source/rltiles/tool/tile_list_processor.h +++ b/crawl-ref/source/rltiles/tool/tile_list_processor.h @@ -16,6 +16,7 @@ class tile_list_processor bool process_list(const char *list_file); bool write_data(bool image, bool code); protected: + bool load_image_from_tile(tile &img, string filename); bool load_image(tile &img, const char *filename, bool background = false); bool process_line(char *read_line, const char *list_file, int line); void add_image(tile &img, const char *enumname); diff --git a/crawl-ref/source/rng-type.h b/crawl-ref/source/rng-type.h index a72eca156b72..ce3b87a2da0d 100644 --- a/crawl-ref/source/rng-type.h +++ b/crawl-ref/source/rng-type.h @@ -1,12 +1,17 @@ #pragma once #include "branch-type.h" -enum rng_type { - RNG_GAMEPLAY, // gameplay effects in general - RNG_UI, // UI randomization - RNG_SYSTEM_SPECIFIC, // anything that may vary from system to system, e.g. ghosts - RNG_SPARE2, - RNG_SPARE3, - RNG_LEVELGEN, // branch 0, i.e. the dungeon - NUM_RNGS = RNG_LEVELGEN + NUM_BRANCHES, // and then one for each other branch -}; +namespace rng +{ + enum rng_type { + GAMEPLAY, // gameplay effects in general + UI, // UI randomization + SYSTEM_SPECIFIC, // anything that may vary from system to system, e.g. ghosts + SPARE2, + SPARE3, + LEVELGEN, // branch 0, i.e. the dungeon + NUM_RNGS = LEVELGEN + NUM_BRANCHES, // and then one for each other branch + SUB_GENERATOR, // unsaved -- past NUM_RNGS + ASSERT_NO_RNG, // debugging tool + }; +} diff --git a/crawl-ref/source/rot.cc b/crawl-ref/source/rot.cc index 1a3b5ed31889..7eb79b163531 100644 --- a/crawl-ref/source/rot.cc +++ b/crawl-ref/source/rot.cc @@ -204,11 +204,7 @@ static void _rot_corpse(item_def &it, int mitm_index, int rot_time) destroy_item(mitm_index); } else - { turn_corpse_into_skeleton(it); - const int piety = x_chance_in_y(2, 5) ? 2 : 1; // match fungal_bloom() - did_god_conduct(DID_ROT_CARRION, piety); - } } /** diff --git a/crawl-ref/source/scripts/seed_explorer.lua b/crawl-ref/source/scripts/seed_explorer.lua new file mode 100644 index 000000000000..ba5bb7a02df2 --- /dev/null +++ b/crawl-ref/source/scripts/seed_explorer.lua @@ -0,0 +1,205 @@ +-- script interface to the explorer.lua package + +crawl_require('dlua/explorer.lua') + +-- This requires a debug build to run, as well as fake_pty, which may need to +-- be built manually. It also needs a pty-based system, i.e. linux or mac. +-- make debug OR (this will be a lot faster): make profile +-- make util/fake_pty +-- +-- examples. +-- full catalog for a seed: +-- util/fake_pty ./crawl -script seed_explorer.lua -seed 1 +-- +-- find all artefacts in a seed: +-- util/fake_pty ./crawl -script seed_explorer.lua -seed 1 -cats monsters items -mon-items -artefacts +-- +-- look at D:1 for 10 random seeds: +-- util/fake_pty ./crawl -script seed_explorer.lua -seed random -count 10 -depth 1 +-- +-- find all artefacts in shops (and print all shop names): +-- util/fake_pty ./crawl -script seed_explorer.lua -seed 1 -depth all -cats features items -shops -artefacts +-- +-- show temple altars for 10 random seeds: +-- util/fake_pty ./crawl -script seed_explorer.lua -seed random -count 10 -show Temple -cats features +-- (This will need to generate all of D before getting to the temple, so is +-- somewhat slow.) +-- +-- When running scripts with fake_pty, all output goes to stderr, so to +-- redirect this to a file you will need to do something like: +-- util/fake_pty ./crawl -script seed_explorer.lua -seed 1 > out.txt 2>&1 + +local basic_usage = [=[ +Usage: seed_explorer.lua -seed ([ ...]|[-count ]) ([-depth ]|[-show [ ...]]) [-cats [: either a number, or 'random'. Random values are 32 bits only. + : a number of times to iterate from . If seed is a number, this + will count up; if it is 'random' it will choose n random seeds. + Note that this converts seed values to doubles in lua, so limits + the range of possible values to some degree. + : A level or branch name in short form, e.g. `Zot:5`, `Hell`, or + `D`, a number, or 'all'. If this is a number, then this value is + depth relative to the level generation order. Defaults to Tomb:3, + i.e. excluding the hells. + : same format as , but will only show levels in the list. + Note that this doesn't affect which levels are generated, so + `-show Zot:5` will take as long to run as `-depth Zot:5`. + : a seed explorer category, drawn from: + {]=] .. + table.concat(explorer.available_categories, ", ") .. [[} + The '-cats' list defaults to all categories. + Category-specific flags (ignored if category is not shown): + -artefacts: show only artefact items (items, monsters). + -mon-items: show only monsters with items (monsters). + -shops: show only shops (items, features). + -all-mons: show all monsters (monsters). + -all-items: show all items on ground (items).]] + +function parse_args(args, err_fun) + accum_init = { } + accum_params = { } + cur = nil + for _,a in ipairs(args) do + if string.find(a, '-') == 1 then + cur = a + if accum_params[a] ~= nil then err_fun("Repeated argument '" .. a .."'") end + accum_params[a] = { } + else + if cur == nil then + accum_init[#accum_init + 1] = a + else + accum_params[cur][#accum_params[cur] + 1] = a + end + end + end + return accum_init, accum_params +end + +function one_arg(args, a) + if args[a] == nil or #(args[a]) ~= 1 then return nil end + return args[a][1] +end + +function usage_error(extra) + local err = basic_usage + if extra ~= nil then + err = err .. "\n" .. extra + end + script.usage(err) +end + +local arg_list = crawl.script_args() +local args_init, args = parse_args(arg_list, usage_error) + +if #arg_list == 0 or #args_init ~= 0 or args["-seed"] == nil or #(args["-seed"]) < 1 then + usage_error("\nNo seed(s) supplied!") +end + +-- let these be converted to numbers on the crawl side +local seed_seq = args["-seed"] + +local count = 1 +if args["-count"] ~= nil then + count = tonumber(one_arg(args, "-count")) + if count == nil then usage_error("\nInvalid argument to -count") end +end + +if count > 1 then + if seed_seq[1] == "random" then + for i = 2, count do seed_seq[#seed_seq+1] = "random" end + else + -- this has the caveats that come with forcing this to be a number... + local n = tonumber(seed_seq[1]) + for i = n+1, n+count-1 do + seed_seq[#seed_seq+1] = i + end + end +end + +math.randomseed(crawl.millis()) +for _, seed in ipairs(seed_seq) do + if seed == "random" then + -- intentional use of non-crawl random(). random doesn't seem to accept + -- anything bigger than 32 bits for the range. + seed_seq[_] = math.random(0x7FFFFFFF) + end +end + +local max_depth = nil +if args["-depth"] ~= nil then + max_depth = explorer.to_gendepth(one_arg(args, "-depth")) + if max_depth == nil then + usage_error("\n must be level name/branch, a number, or 'all'!") + end +end + +local categories = { } +if args["-cats"] == nil then + categories = explorer.available_categories +else + categories = args["-cats"] +end + +categories = util.filter(explorer.is_category, categories) +if #categories == 0 then + usage_error("\nNo valid categories specified!") +end + +local show_level_fun = nil + +if args["-show"] ~= nil then + local levels_to_show = util.map(explorer.to_gendepth, args["-show"]) + if #levels_to_show == 0 then + usage_error("\nNo valid levels or depths provided with -show!") + end + if max_depth == nil then + -- this doesn't handle portal-only lists correctly + max_depth = 0 + for i, depth in ipairs(levels_to_show) do + if depth > max_depth then max_depth = depth end + end + if max_depth == 0 then + -- portals only. + -- TODO: this is a heuristic; but no portals currently generate + -- later than this. (In fact, elf:2 is really the latest.) + max_depth = explorer.level_to_gendepth("Zot:4") + end + end + local levels_set = util.set(levels_to_show) + show_level_fun = function (l) return levels_set[l] end +end + +if max_depth == nil then + max_depth = explorer.level_to_gendepth("Tomb:3") +end + +-- TODO: these are kind of ad hoc, maybe some kind of more general interface? +local arts_only = (args["-artefacts"] ~= nil) +local all_items = (args["-all-items"] ~= nil) +local shops_only = (args["-shops"] ~= nil) +if arts_only and all_items then + usage_error("\n-artefacts and -all-items are not compatible.") +end +local mon_items_only = (args["-mon-items"] ~= nil) +local all_mons = (args["-all-mons"] ~= nil) +if mon_items_only and all_mons then + usage_error("\n-mon-items and -all-mons are not compatible.") +end + +explorer.reset_to_defaults() +if arts_only then explorer.item_notable = explorer.arts_only end +if all_items then explorer.item_notable = function (x) return true end end +if shops_only then -- TODO: generalize the technique here + local old_fun = explorer.item_notable + explorer.item_notable = function(i) return i.is_in_shop and old_fun(i) end + explorer.feat_notable = function(f) return f == "enter_shop" end +end +if mon_items_only then + explorer.mons_notable = function (m) return false end + explorer.mons_feat_filter = function (f) + return f:find("item:") == 1 and f or nil + end +end +if all_mons then explorer.mons_notable = function (x) return true end end + +explorer.catalog_seeds(seed_seq, max_depth, categories, show_level_fun) +explorer.reset_to_defaults() diff --git a/crawl-ref/source/scroller.cc b/crawl-ref/source/scroller.cc index 0f163ec536a1..fef408fb39fb 100644 --- a/crawl-ref/source/scroller.cc +++ b/crawl-ref/source/scroller.cc @@ -71,22 +71,25 @@ void formatted_scroller::scroll_to_end() int formatted_scroller::show() { auto vbox = make_shared(Widget::VERT); + vbox->align_cross = Widget::Align::STRETCH; if (!m_title.empty()) { shared_ptr title = make_shared(); title->set_text(m_title); - title->set_margin_for_crt({0, 0, 1, 0}); - title->set_margin_for_sdl({0, 0, 20, 0}); + title->set_margin_for_crt(0, 0, 1, 0); + title->set_margin_for_sdl(0, 0, 20, 0); + auto title_hbox = make_shared(Widget::HORZ); #ifdef USE_TILE_LOCAL - title->align_self = Widget::Align::CENTER; + title_hbox->align_main = Widget::Align::CENTER; #endif - vbox->add_child(move(title)); + title_hbox->add_child(move(title)); + vbox->add_child(move(title_hbox)); } #ifdef USE_TILE_LOCAL if (!(m_flags & FS_PREWRAPPED_TEXT)) - vbox->max_size()[0] = tiles.get_crt_font()->char_width()*80; + vbox->max_size().width = tiles.get_crt_font()->char_width()*80; #endif m_scroller = make_shared(*this); @@ -97,7 +100,7 @@ int formatted_scroller::show() formatted_string c = formatted_string::parse_string(contents.to_colour_string()); text->set_text(c); text->set_highlight_pattern(highlight, true); - text->wrap_text = !(m_flags & FS_PREWRAPPED_TEXT); + text->set_wrap_text(!(m_flags & FS_PREWRAPPED_TEXT)); m_scroller->set_child(text); vbox->add_child(m_scroller); @@ -106,8 +109,8 @@ int formatted_scroller::show() shared_ptr more = make_shared(); more = make_shared(); more->set_text(m_more); - more->set_margin_for_crt({1, 0, 0, 0}); - more->set_margin_for_sdl({20, 0, 0, 0}); + more->set_margin_for_crt(1, 0, 0, 0); + more->set_margin_for_sdl(20, 0, 0, 0); vbox->add_child(move(more)); } @@ -115,7 +118,7 @@ int formatted_scroller::show() m_contents_dirty = false; bool done = false; - popup->on(Widget::slots.event, [&done, &vbox, &text, this](wm_event ev) { + popup->on(Widget::slots.event, [&done, &text, this](wm_event ev) { if (ev.type != WME_KEYDOWN) return false; // allow default event handling m_lastch = ev.key.keysym.sym; @@ -138,11 +141,11 @@ int formatted_scroller::show() } if (done) return true; - if (vbox->on_event(ev)) + if (m_scroller->on_event(ev)) return true; if (m_flags & FS_EASY_EXIT) return done = true; - return false; + return true; }); #ifdef USE_TILE_WEB @@ -209,5 +212,5 @@ void recv_formatted_scroller_scroll(int line) // XXX: since the scroll event from webtiles is not delivered by the event // pumping loop in ui::pump_events, the UI system won't automatically draw // any changes for console spectators, so we need to force a redraw here. - ui_force_render(); + ui::force_render(); } diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index ff7ca565068a..e5ec0847da37 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -295,10 +295,13 @@ unsigned int item_value(item_def item, bool ident) break; case SPMSL_CURARE: + case SPMSL_BLINDING: + case SPMSL_SILVER: +#if TAG_MAJOR_VERSION == 34 case SPMSL_PARALYSIS: case SPMSL_PENETRATION: - case SPMSL_SILVER: case SPMSL_STEEL: +#endif case SPMSL_DISPERSAL: valued *= 30; break; @@ -306,16 +309,16 @@ unsigned int item_value(item_def item, bool ident) #if TAG_MAJOR_VERSION == 34 case SPMSL_FLAME: case SPMSL_FROST: -#endif case SPMSL_SLEEP: case SPMSL_CONFUSION: valued *= 25; break; +#endif - case SPMSL_EXPLODING: case SPMSL_POISONED: - case SPMSL_RETURNING: #if TAG_MAJOR_VERSION == 34 + case SPMSL_RETURNING: + case SPMSL_EXPLODING: case SPMSL_SLOW: case SPMSL_SICKNESS: #endif @@ -510,8 +513,8 @@ unsigned int item_value(item_def item, bool ident) case POT_PORRIDGE: case POT_SLOWING: case POT_DECAY: -#endif case POT_BLOOD: +#endif case POT_DEGENERATION: valued += 10; break; @@ -820,8 +823,8 @@ bool is_worthless_consumable(const item_def &item) switch (item.sub_type) { // Blood potions are worthless because they are easy to make. - case POT_BLOOD: #if TAG_MAJOR_VERSION == 34 + case POT_BLOOD: case POT_BLOOD_COAGULATED: case POT_SLOWING: case POT_DECAY: diff --git a/crawl-ref/source/shout.cc b/crawl-ref/source/shout.cc index b1aa8735c772..938ea77f55c2 100644 --- a/crawl-ref/source/shout.cc +++ b/crawl-ref/source/shout.cc @@ -337,8 +337,10 @@ bool check_awaken(monster* mons, int stealth) return false; } -void item_noise(const item_def &item, string msg, int loudness) +void item_noise(const item_def &item, actor &act, string msg, int loudness) { + // TODO: messaging for cases where act != you. (This doesn't come up right + // now.) if (is_unrandom_artefact(item)) { // "Your Singing Sword" sounds disrespectful @@ -412,7 +414,7 @@ void item_noise(const item_def &item, string msg, int loudness) mprf(channel, "%s", msg.c_str()); if (channel != MSGCH_TALK_VISUAL) - noisy(loudness, you.pos()); + noisy(loudness, act.pos()); } // TODO: Let artefacts besides weapons generate noise. @@ -439,7 +441,7 @@ void noisy_equipment() if (msg.empty()) msg = getSpeakString("noisy weapon"); - item_noise(*weapon, msg, 20); + item_noise(*weapon, you, msg, 20); } // Berserking monsters cannot be ordered around. @@ -521,7 +523,7 @@ static int _issue_orders_prompt() if (!you.cannot_speak()) { string cap_shout = you.shout_verb(false); - cap_shout[0] = toupper(cap_shout[0]); + cap_shout[0] = toupper_safe(cap_shout[0]); mprf(" t - %s!", cap_shout.c_str()); } diff --git a/crawl-ref/source/shout.h b/crawl-ref/source/shout.h index b23ffa09f0d0..8a6e04a8412e 100644 --- a/crawl-ref/source/shout.h +++ b/crawl-ref/source/shout.h @@ -11,7 +11,7 @@ bool fake_noisy(int loudness, const coord_def& where); void yell(const actor* mon = nullptr); void issue_orders(); -void item_noise(const item_def& item, string msg, int loudness = 25); +void item_noise(const item_def& item, actor &act, string msg, int loudness = 25); void noisy_equipment(); void check_monsters_sense(sense_type sense, int range, const coord_def& where); diff --git a/crawl-ref/source/show.cc b/crawl-ref/source/show.cc index 871f964b2a29..3dbe1241d84a 100644 --- a/crawl-ref/source/show.cc +++ b/crawl-ref/source/show.cc @@ -167,13 +167,6 @@ static void _update_feat_at(const coord_def &gp) if (is_bloodcovered(gp)) env.map_knowledge(gp).flags |= MAP_BLOODY; - if (is_moldy(gp)) - { - env.map_knowledge(gp).flags |= MAP_MOLDY; - if (glowing_mold(gp)) - env.map_knowledge(gp).flags |= MAP_GLOWING_MOLDY; - } - if (env.level_state & LSTATE_SLIMY_WALL && slime_wall_neighbour(gp)) env.map_knowledge(gp).flags |= MAP_CORRODING; @@ -441,7 +434,7 @@ static void _update_monster(monster* mons) if (you.attribute[ATTR_SEEN_INVIS_TURN] != you.num_turns) { you.attribute[ATTR_SEEN_INVIS_TURN] = you.num_turns; - you.attribute[ATTR_SEEN_INVIS_SEED] = get_uint32(); + you.attribute[ATTR_SEEN_INVIS_SEED] = rng::get_uint32(); } // After the player finishes this turn, the monster's unseen pos (and // its invis indicator due to going unseen) will be erased. diff --git a/crawl-ref/source/showsymb.cc b/crawl-ref/source/showsymb.cc index ede24efc2306..8fee4762eb28 100644 --- a/crawl-ref/source/showsymb.cc +++ b/crawl-ref/source/showsymb.cc @@ -78,8 +78,6 @@ static unsigned short _cell_feat_show_colour(const map_cell& cell, } else if (cell.flags & MAP_BLOODY && !norecolour) colour = RED; - else if (cell.flags & MAP_MOLDY && !norecolour) - colour = (cell.flags & MAP_GLOWING_MOLDY) ? LIGHTRED : LIGHTGREEN; else if (cell.flags & MAP_CORRODING && !norecolour && !feat_is_wall(feat) && !feat_is_lava(feat) && !feat_is_water(feat)) @@ -191,8 +189,10 @@ static int _get_mons_colour(const monster_info& mi) if (stype != mi.type && mi.type != MONS_SENSED) col = mons_class_colour(stype); +#if TAG_MAJOR_VERSION == 34 if (mi.is(MB_ROLLING)) col = ETC_BONE; +#endif if (mi.is(MB_BERSERK)) col = RED; diff --git a/crawl-ref/source/skill-menu.cc b/crawl-ref/source/skill-menu.cc index ddd05c2ec391..ca1c7ae0c644 100644 --- a/crawl-ref/source/skill-menu.cc +++ b/crawl-ref/source/skill-menu.cc @@ -111,7 +111,7 @@ static bool _show_skill(skill_type sk, skill_menu_state state) switch (state) { case SKM_SHOW_DEFAULT: - return you.can_train[sk] || you.skill(sk, 10, false, false) + return you.can_currently_train[sk] || you.skill(sk, 10, false, false) || sk == you.transfer_from_skill || sk == you.transfer_to_skill; case SKM_SHOW_ALL: return true; default: return false; @@ -149,7 +149,7 @@ bool SkillMenuEntry::is_selectable(bool keep_hotkey) return false; } - if (!you.can_train[m_sk] && !is_set(SKMF_RESKILL_TO) + if (!you.can_currently_train[m_sk] && !is_set(SKMF_RESKILL_TO) && !is_set(SKMF_RESKILL_FROM)) { return false; @@ -226,7 +226,7 @@ void SkillMenuEntry::set_name(bool keep_hotkey) if (!keep_hotkey) { m_name->add_hotkey(++m_letter); - m_name->add_hotkey(toupper(m_letter)); + m_name->add_hotkey(toupper_safe(m_letter)); } m_name->set_id(m_sk); m_name->allow_highlight(true); @@ -336,7 +336,7 @@ string SkillMenuEntry::get_prefix() else letter = ' '; - const int sign = (!you.can_train[m_sk] || mastered()) ? ' ' : + const int sign = (!you.can_currently_train[m_sk] || mastered()) ? ' ' : (you.train[m_sk] == TRAINING_FOCUSED) ? '*' : you.train[m_sk] ? '+' : '-'; @@ -791,9 +791,9 @@ void SkillMenu::init_experience() for (int i = 0; i < NUM_SKILLS; ++i) { const skill_type sk = skill_type(i); - if (!is_useless_skill(sk) && !you.can_train[sk]) + if (!is_useless_skill(sk) && !you.can_currently_train[sk]) { - you.can_train.set(sk); + you.can_currently_train.set(sk); you.train[sk] = TRAINING_DISABLED; } } @@ -1603,7 +1603,7 @@ void SkillMenu::set_skills() void SkillMenu::toggle_practise(skill_type sk, int keyn) { - ASSERT(you.can_train[sk]); + ASSERT(you.can_currently_train[sk]); if (keyn >= 'A' && keyn <= 'Z') you.train.init(TRAINING_DISABLED); if (get_state(SKM_DO) == SKM_DO_PRACTISE) @@ -1774,14 +1774,13 @@ SizeReq UISkillMenu::_get_preferred_size(Direction dim, int prosp_width) void UISkillMenu::_allocate_region() { skm.exit(true); - int height = m_region[3]; - skm.init(flag, height); + skm.init(flag, m_region.height); } void UISkillMenu::_render() { #ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {1, 1, 1}; + GLW_3VF t = {(float)m_region.x, (float)m_region.y, 0}, s = {1, 1, 1}; glmanager->set_transform(t, s); #endif skm.draw_menu(); @@ -1801,8 +1800,8 @@ bool UISkillMenu::on_event(const wm_event& ev) } MouseEvent mouse_ev = ev.mouse_event; - mouse_ev.px -= m_region[0]; - mouse_ev.py -= m_region[1]; + mouse_ev.px -= m_region.x; + mouse_ev.py -= m_region.y; int key = skm.handle_mouse(mouse_ev); if (key && key != CK_NO_KEY) diff --git a/crawl-ref/source/skills.cc b/crawl-ref/source/skills.cc index 313cc05aa24b..b1047f7fc421 100644 --- a/crawl-ref/source/skills.cc +++ b/crawl-ref/source/skills.cc @@ -488,9 +488,9 @@ static void _check_start_train() if (is_invalid_skill(sk) || is_useless_skill(sk)) continue; - if (!you.can_train[sk] && you.train[sk]) + if (!you.can_currently_train[sk] && you.train[sk]) skills.insert(sk); - you.can_train.set(sk); + you.can_currently_train.set(sk); } reset_training(); @@ -508,10 +508,15 @@ static void _check_start_train() you.start_train.clear(); } +static bool _player_is_gnoll() +{ + return you.species == SP_GNOLL; +} + static void _check_stop_train() { // Gnolls can't stop training skills. - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) return; _check_inventory_skills(); @@ -531,7 +536,7 @@ static void _check_stop_train() if (skill_trained(sk) && you.training[sk]) skills.insert(sk); - you.can_train.set(sk, false); + you.can_currently_train.set(sk, false); } if (!skills.empty()) @@ -544,7 +549,7 @@ static void _check_stop_train() you.stop_train.clear(); } -void update_can_train() +void update_can_currently_train() { if (!you.start_train.empty()) _check_start_train(); @@ -555,7 +560,7 @@ void update_can_train() bool training_restricted(skill_type sk) { - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) return false; switch (sk) @@ -575,15 +580,15 @@ bool training_restricted(skill_type sk) } /* - * Init the can_train array by examining inventory and spell list to see which - * skills can be trained. + * Init the can_currently_train array by examining inventory and spell list to + * see which skills can be trained. */ -void init_can_train() +void init_can_currently_train() { // Clear everything out, in case this isn't the first game. you.start_train.clear(); you.stop_train.clear(); - you.can_train.reset(); + you.can_currently_train.reset(); for (int i = 0; i < NUM_SKILLS; ++i) { @@ -592,7 +597,7 @@ void init_can_train() if (is_useless_skill(sk)) continue; - you.can_train.set(sk); + you.can_currently_train.set(sk); if (training_restricted(sk)) you.stop_train.insert(sk); } @@ -602,15 +607,13 @@ void init_can_train() void init_train() { - const bool is_gnoll = you.species == SP_GNOLL; - for (int i = 0; i < NUM_SKILLS; ++i) { - if (you.can_train[i] && you.skill_points[i]) + if (you.can_currently_train[i] && you.skill_points[i]) you.train[i] = you.train_alt[i] = TRAINING_ENABLED; else { - const bool gnoll_enable = is_gnoll && + const bool gnoll_enable = _player_is_gnoll() && !is_removed_skill((skill_type) i); // Skills are on by default in auto mode and off in manual. you.train[i] = (training_status) (gnoll_enable @@ -747,7 +750,7 @@ bool check_selected_skills() mpr("You need to enable at least one skill for training."); // Training will be fixed up on load if this ASSERT triggers. - ASSERT(you.species != SP_GNOLL); + ASSERT(!_player_is_gnoll()); more(); reset_training(); skill_menu(); @@ -763,10 +766,9 @@ bool check_selected_skills() */ void reset_training() { - const bool is_gnoll = you.species == SP_GNOLL; // Disable this here since we don't want any autotraining related skilling // changes for Gnolls. - if (is_gnoll) + if (_player_is_gnoll()) you.auto_training = false; // We clear the values in the training array. In auto mode they are set @@ -776,7 +778,7 @@ void reset_training() { // skill_trained doesn't work for gnolls, but all existent skills // will be set as enabled here. - if (!is_gnoll && (you.auto_training || !skill_trained(i))) + if (!_player_is_gnoll() && (you.auto_training || !skill_trained(i))) you.training[i] = 0; else you.training[i] = you.train[i]; @@ -822,12 +824,15 @@ void reset_training() // Focused skills get at least 20% training. for (int sk = 0; sk < NUM_SKILLS; ++sk) - if (you.train[sk] == 2 && you.training[sk] < 20 && you.can_train[sk]) + if (you.train[sk] == 2 && you.training[sk] < 20 + && you.can_currently_train[sk]) + { you.training[sk] += 5 * (5 - you.training[sk] / 4); + } } _scale_array(you.training, 100, you.auto_training); - if (is_gnoll) + if (_player_is_gnoll()) { // we use the full set of skills to calculate gnoll percentages, // but they don't actually get to train sacrificed skills. @@ -888,7 +893,7 @@ int _gnoll_total_skill_cost(); void train_skills(bool simu) { int cost, exp; - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) { do { @@ -960,7 +965,7 @@ static void _train_skills(int exp, const int cost, const bool simu) if (you.training[i] > 0) { sk_exp[i] = you.training[i] * exp / 100; - if (sk_exp[i] < cost && you.species != SP_GNOLL) + if (sk_exp[i] < cost && !_player_is_gnoll()) { // One skill has a too low training to be trained at all. // We skip the first phase and go directly to the random @@ -976,7 +981,7 @@ static void _train_skills(int exp, const int cost, const bool simu) { // We randomize the order, to avoid a slight bias to first skills. // Being trained first can make a difference if skill cost increases. - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) reverse(training_order.begin(), training_order.end()); else shuffle_array(training_order); @@ -984,7 +989,7 @@ static void _train_skills(int exp, const int cost, const bool simu) { int gain = 0; - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) sk_exp[sk] = exp; while (sk_exp[sk] >= cost && you.training[sk]) { @@ -994,7 +999,7 @@ static void _train_skills(int exp, const int cost, const bool simu) ASSERT(exp >= 0); if (_level_up_check(sk, simu)) sk_exp[sk] = 0; - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) break; } @@ -1010,7 +1015,7 @@ static void _train_skills(int exp, const int cost, const bool simu) // with random_choose_weighted. while (exp >= cost) { - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) break; int gain; skill_type sk = SK_NONE; @@ -1053,7 +1058,7 @@ static void _train_skills(int exp, const int cost, const bool simu) for (int i = 0; i < NUM_SKILLS; ++i) { skill_type sk = static_cast(i); - if (total_gain[sk] && !simu && you.species != SP_GNOLL) + if (total_gain[sk] && !simu && !_player_is_gnoll()) { dprf(DIAG_SKILLS, "Trained %s by %d.", skill_name(sk), total_gain[sk]); @@ -1080,7 +1085,7 @@ static void _train_skills(int exp, const int cost, const bool simu) bool skill_trained(int i) { - return you.can_train[i] && you.train[i]; + return you.can_currently_train[i] && you.train[i]; } /** @@ -1247,7 +1252,7 @@ static int _train(skill_type exsk, int &max_exp, bool simu) // This will be deducted from you.exp_available. int cost = calc_skill_cost(you.skill_cost_level); - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) { int useless_count = _useless_skill_count(); int total_count = _total_skill_count(); @@ -1529,7 +1534,7 @@ bool player::set_training_target(const skill_type sk, const int target, bool ann const int ranged_target = min(max((int) target, 0), 270); if (announce && ranged_target != (int) training_targets[sk]) { - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) mprf("Gnolls can't set training targets!"); else if (ranged_target == 0) mprf("Clearing the skill training target for %s.", skill_name(sk)); @@ -1701,10 +1706,12 @@ string skill_title_by_rank(skill_type best_skill, uint8_t skill_rank, result = god_title(god, species, piety); break; +#if TAG_MAJOR_VERSION == 34 case SK_EVOCATIONS: if (god == GOD_PAKELLAS) result = god_title(god, species, piety); break; +#endif default: break; @@ -1941,7 +1948,7 @@ float species_apt_factor(skill_type sk, species_type sp) vector get_crosstrain_skills(skill_type sk) { // Gnolls do not have crosstraining. - if (you.species == SP_GNOLL) + if (_player_is_gnoll()) return {}; switch (sk) @@ -2038,7 +2045,7 @@ void dump_skills(string &text) { text += make_stringf(" %c Level %.*f%s %s\n", real == 270 ? 'O' : - !you.can_train[i] ? ' ' : + !you.can_currently_train[i] ? ' ' : you.train[i] == 2 ? '*' : you.train[i] ? '+' : '-', @@ -2069,7 +2076,7 @@ int skill_transfer_amount(skill_type sk) int transfer_skill_points(skill_type fsk, skill_type tsk, int skp_max, bool simu, bool boost) { - ASSERT(you.species != SP_GNOLL); + ASSERT(!_player_is_gnoll()); ASSERT(!is_invalid_skill(fsk) && !is_invalid_skill(tsk)); const int penalty = 90; // 10% XP penalty @@ -2188,18 +2195,18 @@ skill_state::skill_state() : void skill_state::save() { - can_train = you.can_train; - skills = you.skills; - train = you.train; - training = you.training; - skill_points = you.skill_points; - training_targets = you.training_targets; - ct_skill_points = you.ct_skill_points; - skill_cost_level = you.skill_cost_level; - skill_order = you.skill_order; - auto_training = you.auto_training; - exp_available = you.exp_available; - total_experience = you.total_experience; + can_currently_train = you.can_currently_train; + skills = you.skills; + train = you.train; + training = you.training; + skill_points = you.skill_points; + training_targets = you.training_targets; + ct_skill_points = you.ct_skill_points; + skill_cost_level = you.skill_cost_level; + skill_order = you.skill_order; + auto_training = you.auto_training; + exp_available = you.exp_available; + total_experience = you.total_experience; get_all_manual_charges(manual_charges); for (int i = 0; i < NUM_SKILLS; i++) { @@ -2240,7 +2247,7 @@ void skill_state::restore_training() } } - you.can_train = can_train; + you.can_currently_train = can_currently_train; you.auto_training = auto_training; reset_training(); } @@ -2248,7 +2255,6 @@ void skill_state::restore_training() // Sanitize skills after an upgrade, racechange, etc. void fixup_skills() { - const bool is_gnoll = you.species == SP_GNOLL; for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk) { if (is_useless_skill(sk)) @@ -2257,21 +2263,21 @@ void fixup_skills() // gnolls have everything existent enabled, so that the // training percentage is calculated correctly. (Useless // skills still won't be trained for them.) - if (is_gnoll && !is_removed_skill(sk)) + if (_player_is_gnoll() && !is_removed_skill(sk)) you.train[sk] = TRAINING_ENABLED; else you.train[sk] = TRAINING_DISABLED; } - else if (is_gnoll) + else if (_player_is_gnoll()) you.train[sk] = TRAINING_ENABLED; you.skill_points[sk] = min(you.skill_points[sk], skill_exp_needed(MAX_SKILL_LEVEL, sk)); check_skill_level_change(sk); } - init_can_train(); + init_can_currently_train(); reset_training(); - if (you.exp_available >= 10 * calc_skill_cost(you.skill_cost_level) && !is_gnoll) + if (you.exp_available >= 10 * calc_skill_cost(you.skill_cost_level) && !_player_is_gnoll()) skill_menu(SKMF_EXPERIENCE); check_training_targets(); @@ -2287,8 +2293,8 @@ void fixup_skills() bool can_enable_skill(skill_type sk, bool override) { // TODO: should this check you.skill_points or you.skills? - return you.species != SP_GNOLL + return !_player_is_gnoll() && you.skills[sk] < MAX_SKILL_LEVEL && !is_useless_skill(sk) - && (override || (you.can_train[sk] && !is_harmful_skill(sk))); + && (override || (you.can_currently_train[sk] && !is_harmful_skill(sk))); } diff --git a/crawl-ref/source/skills.h b/crawl-ref/source/skills.h index 0bf7cfb67687..df2a6308ace6 100644 --- a/crawl-ref/source/skills.h +++ b/crawl-ref/source/skills.h @@ -12,7 +12,7 @@ struct skill_state { skill_state(); - FixedBitVector can_train; + FixedBitVector can_currently_train; FixedVector skills; FixedVector real_skills; // Those two are FixedVector changed_skills; // scaled by 10. @@ -62,9 +62,9 @@ bool training_restricted(skill_type sk); void reassess_starting_skills(); bool check_selected_skills(); void init_train(); -void init_can_train(); +void init_can_currently_train(); void init_training(); -void update_can_train(); +void update_can_currently_train(); void reset_training(); int calc_skill_level_change(skill_type sk, int starting_level, int sk_points); void check_skill_level_change(skill_type sk, bool do_level_up = true); diff --git a/crawl-ref/source/spell-type.h b/crawl-ref/source/spell-type.h index 3bba2aeed2f4..a3e6d3f7ffe9 100644 --- a/crawl-ref/source/spell-type.h +++ b/crawl-ref/source/spell-type.h @@ -463,5 +463,6 @@ enum spell_type : int SPELL_GRASPING_ROOTS, SPELL_SONIC_WAVE, SPELL_THROW_PIE, + SPELL_SPORULATE, NUM_SPELLS }; diff --git a/crawl-ref/source/spl-book.cc b/crawl-ref/source/spl-book.cc index a0a629bc0c1a..a1ad18a948d7 100644 --- a/crawl-ref/source/spl-book.cc +++ b/crawl-ref/source/spl-book.cc @@ -36,6 +36,7 @@ #include "output.h" #include "prompt.h" #include "religion.h" +#include "spl-cast.h" #include "spl-util.h" #include "state.h" #include "stringutil.h" @@ -412,7 +413,13 @@ static spell_list _get_spell_list(bool just_check = false, for (const spell_type spell : available_spells) { if (you.has_spell(spell)) + { num_known++; + + // Divine Exegesis includes spells the player already knows. + if (you.divine_exegesis) + mem_spells.emplace_back(spell); + } else if (!you_can_memorise(spell)) { if (!memorise_only) @@ -511,11 +518,30 @@ static bool _sort_mem_spells(const sortable_spell &a, const sortable_spell &b) return strcasecmp(spell_title(a.spell), spell_title(b.spell)) < 0; } +static bool _sort_divine_spells(const sortable_spell &a, const sortable_spell &b) +{ + // Put useless spells last + const bool useful_a = !spell_is_useless(a.spell, true, true); + const bool useful_b = !spell_is_useless(b.spell, true, true); + if (useful_a != useful_b) + return useful_a; + + // Put higher levels spells first, as they're more likely to be what we + // want. + if (a.difficulty != b.difficulty) + return a.difficulty > b.difficulty; + + return strcasecmp(spell_title(a.spell), spell_title(b.spell)) < 0; +} + vector get_sorted_spell_list(bool silent, bool memorise_only) { spell_list mem_spells(_get_spell_list(silent, memorise_only)); - sort(mem_spells.begin(), mem_spells.end(), _sort_mem_spells); + if (you.divine_exegesis) + sort(mem_spells.begin(), mem_spells.end(), _sort_divine_spells); + else + sort(mem_spells.begin(), mem_spells.end(), _sort_mem_spells); vector result; for (auto s : mem_spells) @@ -524,23 +550,22 @@ vector get_sorted_spell_list(bool silent, bool memorise_only) return result; } -class MemoriseMenu : public Menu +class SpellLibraryMenu : public Menu { public: - enum class action { memorise, describe, hide, unhide } current_action; + enum class action { cast, memorise, describe, hide, unhide } current_action; protected: virtual formatted_string calc_title() override { return formatted_string::parse_string( - make_stringf("Spells %s Type Failure Level ", - current_action == action::memorise ? - "(Memorise)" : - current_action == action::describe ? - "(Describe)" : - current_action == action::hide ? - "(Hide) " : - "(Show) ")); + make_stringf("Spells %s Type %sLevel ", + current_action == action::cast ? "(Cast)" : + current_action == action::memorise ? "(Memorise)" : + current_action == action::describe ? "(Describe)" : + current_action == action::hide ? "(Hide) " : + "(Show) ", + you.divine_exegesis ? "" : "Failure ")); } private: @@ -578,17 +603,20 @@ class MemoriseMenu : public Menu } desc << "\n"; + const string act = you.divine_exegesis ? "Cast" : "Memorise"; // line 2 desc << "[?] help " "[Ctrl-f] search " "[!] "; - desc << ( current_action == action::memorise + desc << ( current_action == action::cast + ? "Cast|Describe|Hide|Show" + : current_action == action::memorise ? "Memorise|Describe|Hide|Show" : current_action == action::describe - ? "Memorise|Describe|Hide|Show" + ? act + "|Describe|Hide|Show" : current_action == action::hide - ? "Memorise|Describe|Hide|Show" - : "Memorise|Describe|Hide|Show"); + ? act + "|Describe|Hide|Show" + : act + "|Describe|Hide|Show"); set_more(formatted_string::parse_string(desc.str())); } @@ -604,6 +632,7 @@ class MemoriseMenu : public Menu #endif switch (current_action) { + case action::cast: case action::memorise: current_action = action::describe; entries_changed = true; // need to add hotkeys @@ -616,7 +645,8 @@ class MemoriseMenu : public Menu entries_changed = true; break; case action::unhide: - current_action = action::memorise; + current_action = you.divine_exegesis ? action::cast + : action::memorise; entries_changed = true; break; } @@ -663,6 +693,19 @@ class MemoriseMenu : public Menu return true; } + colour_t entry_colour(const sortable_spell& entry) + { + if (vehumet_is_offering(entry.spell)) + return LIGHTBLUE; + else + { + bool transient = false; + bool memcheck = true; + return spell_highlight_by_utility(entry.spell, COL_UNKNOWN, + transient, memcheck); + } + } + // Update the list of spells. If show_hidden is true, show only hidden // ones; otherwise, show only non-hidden ones. void update_entries() @@ -689,20 +732,9 @@ class MemoriseMenu : public Menu if (spell_hidden != show_hidden) continue; - ostringstream desc; - - int colour = LIGHTGRAY; - if (vehumet_is_offering(spell.spell)) - colour = LIGHTBLUE; - else - { - bool transient = false; - bool memcheck = true; - colour = spell_highlight_by_utility(spell.spell, COL_UNKNOWN, - transient, memcheck); - } - + const int colour = entry_colour(spell); + ostringstream desc; desc << "<" << colour_to_str(colour) << ">"; desc << left; @@ -714,20 +746,26 @@ class MemoriseMenu : public Menu desc << string(60 - so_far, ' '); desc << ""; - colour = spell.fail_rate_colour; - desc << "<" << colour_to_str(colour) << ">"; - desc << chop_string(failure_rate_to_string(spell.raw_fail), 12); - desc << ""; + if (!you.divine_exegesis) + { + desc << "<" << colour_to_str(spell.fail_rate_colour) << ">"; + desc << chop_string(failure_rate_to_string(spell.raw_fail), 12); + desc << ""; + } + desc << spell.difficulty; MenuEntry* me = new MenuEntry(desc.str(), MEL_ITEM, 1, - // don't add a hotkey if you can't memorise it + // don't add a hotkey if you can't memorise/cast it (current_action == action::memorise && - !you_can_memorise(spell.spell)) ? ' ' : char(hotkey)); - // But do increment hotkeys anyway, to keep the memorise and - // describe hotkeys consistent. + !you_can_memorise(spell.spell)) + || current_action == action::cast && + spell_is_useless(spell.spell, true, true) + ? ' ' : char(hotkey)); + // But do increment hotkeys anyway, to keep the hotkeys consistent. ++hotkey; + me->colour = colour; #ifdef USE_TILE me->add_tile(tile_def(tileidx_spell(spell.spell), TEX_GUI)); #endif @@ -739,30 +777,35 @@ class MemoriseMenu : public Menu } public: - MemoriseMenu(spell_list& list) + SpellLibraryMenu(spell_list& list) : Menu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALWAYS_SHOW_MORE | MF_ALLOW_FORMATTING // To have the ctrl-f menu show up in webtiles | MF_ALLOW_FILTER, "spell"), - current_action(action::memorise), + current_action(you.divine_exegesis ? action::cast : action::memorise), spells(list), hidden_count(0) { set_highlighter(nullptr); - set_title(new MenuEntry(""), true, true); // Actual text handled by calc_title + // Actual text handled by calc_title + set_title(new MenuEntry(""), true, true); - spell_levels_str = make_stringf("%d spell level%s" - "", player_spell_levels(), - (player_spell_levels() > 1 || player_spell_levels() == 0) - ? "s left" : " left "); - if (player_spell_levels() < 9) - spell_levels_str += " "; + if (!you.divine_exegesis) + { + spell_levels_str = make_stringf("%d spell level%s" + "", player_spell_levels(), + (player_spell_levels() > 1 || player_spell_levels() == 0) + ? "s left" : " left "); + if (player_spell_levels() < 9) + spell_levels_str += " "; + + set_more(formatted_string::parse_string(spell_levels_str + "\n")); + } - set_more(formatted_string::parse_string(spell_levels_str + "\n")); #ifdef USE_TILE_LOCAL FontWrapper *font = tiles.get_crt_font(); int title_width = font->string_width(calc_title()); - m_ui.vbox->min_size() = {38 + title_width + 10, 0}; + m_ui.vbox->min_size().width = 38 + title_width + 10; #endif m_ui.scroller->expand_v = true; // TODO: doesn't work on webtiles @@ -776,6 +819,7 @@ class MemoriseMenu : public Menu switch (current_action) { case action::memorise: + case action::cast: return false; case action::describe: describe_spell(spell, nullptr); @@ -798,7 +842,7 @@ static spell_type _choose_mem_spell(spell_list &spells) // If we've gotten this far, we know that at least one spell here is // memorisable, which is enough. - MemoriseMenu spell_menu(spells); + SpellLibraryMenu spell_menu(spells); const vector sel = spell_menu.show(); if (!crawl_state.doing_prev_cmd_again) @@ -838,7 +882,7 @@ bool can_learn_spell(bool silent) bool learn_spell() { - spell_list spells(_get_spell_list(false, true)); + spell_list spells(_get_spell_list()); if (spells.empty()) return false; @@ -998,3 +1042,41 @@ bool book_has_title(const item_def &book) return book.props.exists(BOOK_TITLED_KEY) && book.props[BOOK_TITLED_KEY].get_bool() == true; } + +spret divine_exegesis(bool fail) +{ + unwind_var dk(you.divine_exegesis, true); + + spell_list spells(_get_spell_list(true, true)); + if (spells.empty()) + { + mpr("You don't know of any spells!"); + return spret::abort; + } + + sort(spells.begin(), spells.end(), _sort_divine_spells); + // If we've gotten this far, we know at least one useful spell. + + SpellLibraryMenu spell_menu(spells); + + const vector sel = spell_menu.show(); + if (!crawl_state.doing_prev_cmd_again) + redraw_screen(); + + if (sel.empty()) + return spret::abort; + + const spell_type spell = *static_cast(sel[0]->data); + if (spell == SPELL_NO_SPELL) + return spret::abort; + + ASSERT(is_valid_spell(spell)); + + if (fail) + return spret::fail; + + if (cast_a_spell(false, spell)) + return spret::success; + + return spret::abort; +} diff --git a/crawl-ref/source/spl-book.h b/crawl-ref/source/spl-book.h index 427454cfe301..d69184def1e4 100644 --- a/crawl-ref/source/spl-book.h +++ b/crawl-ref/source/spl-book.h @@ -42,3 +42,4 @@ bool you_can_memorise(spell_type spell) PURE; bool has_spells_to_memorise(bool silent = true); vector get_sorted_spell_list(bool silent = false, bool memorise_only = true); +spret divine_exegesis(bool fail); diff --git a/crawl-ref/source/spl-cast.cc b/crawl-ref/source/spl-cast.cc index d6bb7ee3609b..e8b8d25257ee 100644 --- a/crawl-ref/source/spl-cast.cc +++ b/crawl-ref/source/spl-cast.cc @@ -85,6 +85,7 @@ static int _spell_enhancement(spell_type spell); static string _spell_failure_rate_description(spell_type spell); +#if TAG_MAJOR_VERSION == 34 void surge_power(const int enhanced) { if (enhanced) // one way or the other {dlb} @@ -112,6 +113,7 @@ void surge_power_wand(const int mp_cost) slight ? "." : "!"); } } +#endif static string _spell_base_description(spell_type spell, bool viewing) { @@ -157,7 +159,7 @@ static string _spell_extra_description(spell_type spell, bool viewing) const string rangestring = spell_range_string(spell); desc << chop_string(spell_power_string(spell), 13) - << chop_string(rangestring, 9 + tagged_string_tag_length(rangestring)) + << chop_string(rangestring, 9) << chop_string(spell_hunger_string(spell), 8) << chop_string(spell_noise_string(spell, 10), 14); @@ -184,9 +186,6 @@ int list_spells(bool toggle_with_I, bool viewing, bool allow_preselect, titlestring + " Type Failure Level ", titlestring + " Power Range Hunger Noise ", MEL_TITLE); -#ifdef USE_TILE_LOCAL - me->colour = BLUE; -#endif spell_menu.set_title(me, true, true); } spell_menu.set_highlighter(nullptr); @@ -440,6 +439,9 @@ int calc_spell_power(spell_type spell, bool apply_intel, bool fail_rate_check, power += you.skill(SK_SPELLCASTING, 50); + if (you.divine_exegesis) + power += you.skill(SK_INVOCATIONS, 300); + if (fail_rate_check) { // Scale appropriately. @@ -637,13 +639,6 @@ bool can_cast_spells(bool quiet) return false; } - if (you.duration[DUR_NO_CAST]) - { - if (!quiet) - mpr("You are unable to access your magic!"); - return false; - } - if (apply_starvation_penalties()) { if (!quiet) @@ -786,20 +781,11 @@ bool cast_a_spell(bool check_range, spell_type spell) } int cost = spell_mana(spell); - int sifcast_amount = 0; if (!enough_mp(cost, true)) { - if (you.attribute[ATTR_DIVINE_ENERGY]) - { - sifcast_amount = cost - you.magic_points; - cost = you.magic_points; - } - else - { - mpr("You don't have enough magic to cast that spell."); - crawl_state.zero_turns_taken(); - return false; - } + mpr("You don't have enough magic to cast that spell."); + crawl_state.zero_turns_taken(); + return false; } if (check_range && spell_no_hostile_in_range(spell)) @@ -853,7 +839,8 @@ bool cast_a_spell(bool check_range, spell_type spell) // Silently take MP before the spell. dec_mp(cost, true); - const spret cast_result = your_spells(spell, 0, true); + const spret cast_result = your_spells(spell, 0, !you.divine_exegesis, + nullptr); if (cast_result == spret::abort) { crawl_state.zero_turns_taken(); @@ -882,13 +869,6 @@ bool cast_a_spell(bool check_range, spell_type spell) } } - if (sifcast_amount) - { - simple_god_message(" grants you divine energy."); - mpr("You briefly lose access to your magic!"); - you.set_duration(DUR_NO_CAST, 3 + random2avg(sifcast_amount * 2, 2)); - } - you.turn_is_over = true; alert_nearby_monsters(); @@ -918,9 +898,6 @@ static void _spellcasting_god_conduct(spell_type spell) if (is_chaotic_spell(spell)) did_god_conduct(DID_CHAOS, conduct_level); - if (is_corpse_violating_spell(spell)) - did_god_conduct(DID_CORPSE_VIOLATION, conduct_level); - // not is_hasty_spell since the other ones handle the conduct themselves. if (spell == SPELL_SWIFTNESS) did_god_conduct(DID_HASTY, conduct_level); @@ -1220,16 +1197,6 @@ static unique_ptr _spell_targeter(spell_type spell, int pow, return nullptr; } -static double _chance_miscast_prot() -{ - double miscast_prot = 0; - - if (have_passive(passive_t::miscast_protection)) - miscast_prot = (double) you.piety/piety_breakpoint(5); - - return min(1.0, miscast_prot); -} - // Returns the nth triangular number. static int _triangular_number(int n) { @@ -1286,8 +1253,12 @@ vector desc_success_chance(const monster_info& mi, int pow, bool evoked, } else { +#if TAG_MAJOR_VERSION == 34 const int adj_pow = evoked ? pakellas_effective_hex_power(pow) : pow; +#else + const int adj_pow = pow; +#endif const int success = hex_success_chance(mr, adj_pow, 100); descs.push_back(make_stringf("chance to defeat MR: %d%%", success)); } @@ -1403,9 +1374,9 @@ spret your_spells(spell_type spell, int powc, bool allow_fail, args.show_boring_feats = false; // don't show "The floor." } if (testbits(flags, spflag::not_self)) - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; else - args.self = CONFIRM_NONE; + args.self = confirm_prompt_type::none; args.get_desc_func = additional_desc; if (!spell_direction(spd, beam, &args)) return spret::abort; @@ -1428,14 +1399,22 @@ spret your_spells(spell_type spell, int powc, bool allow_fail, if (evoked_item) { +#if TAG_MAJOR_VERSION == 34 const int surge = pakellas_surge_devices(); +#else + const int surge = 0; +#endif powc = player_adjust_evoc_power(powc, surge); +#if TAG_MAJOR_VERSION == 34 int mp_cost_of_wand = evoked_item->base_type == OBJ_WANDS ? wand_mp_cost() : 0; surge_power_wand(mp_cost_of_wand + surge * 3); +#endif } +#if TAG_MAJOR_VERSION == 34 else if (allow_fail) surge_power(_spell_enhancement(spell)); +#endif // Enhancers only matter for calc_spell_power() and raw_spell_fail(). // Not sure about this: is it flavour or misleading? (jpeg) @@ -1549,12 +1528,6 @@ spret your_spells(spell_type spell, int powc, bool allow_fail, flush_input_buffer(FLUSH_ON_FAILURE); learned_something_new(HINT_SPELL_MISCAST); - if (decimal_chance(_chance_miscast_prot())) - { - simple_god_message(" protects you from the effects of your miscast!"); - return spret::fail; - } - // All spell failures give a bit of magical radiation. // Failure is a function of power squared multiplied by how // badly you missed the spell. High power spells can be @@ -2033,15 +2006,6 @@ double get_miscast_chance(spell_type spell, int severity) return chance; } -static double _get_miscast_chance_with_miscast_prot(spell_type spell) -{ - double raw_chance = get_miscast_chance(spell); - double miscast_prot = _chance_miscast_prot(); - double chance = raw_chance * (1 - miscast_prot); - - return chance; -} - const char *fail_severity_adjs[] = { "safe", @@ -2053,7 +2017,7 @@ COMPILE_CHECK(ARRAYSZ(fail_severity_adjs) > 3); int fail_severity(spell_type spell) { - const double chance = _get_miscast_chance_with_miscast_prot(spell); + const double chance = get_miscast_chance(spell); return (chance < 0.001) ? 0 : (chance < 0.005) ? 1 : diff --git a/crawl-ref/source/spl-cast.h b/crawl-ref/source/spl-cast.h index 5b9c4a866569..054fefa4a1d6 100644 --- a/crawl-ref/source/spl-cast.h +++ b/crawl-ref/source/spl-cast.h @@ -35,7 +35,7 @@ enum class spflag needs_tracer = 0x00080000, // monster casting needs tracer noisy = 0x00100000, // makes noise, even if innate testing = 0x00200000, // a testing/debugging spell - corpse_violating = 0x00400000, // Conduct violation for Fedhas + // 0x00400000, // was spflag::corpse_violating // 0x00800000, // was SPFLAG_ALLOW_SELF utility = 0x01000000, // usable no matter what foe is no_ghost = 0x02000000, // ghosts can't get this spell @@ -103,7 +103,7 @@ class targeter; vector desc_success_chance(const monster_info& mi, int pow, bool evoked, targeter* hitfunc); spret your_spells(spell_type spell, int powc = 0, bool allow_fail = true, - const item_def* const evoked_item = nullptr); + const item_def* const evoked_item = nullptr); extern const char *fail_severity_adjs[]; diff --git a/crawl-ref/source/spl-damage.cc b/crawl-ref/source/spl-damage.cc index 23cc1378b799..1a18f7fac873 100644 --- a/crawl-ref/source/spl-damage.cc +++ b/crawl-ref/source/spl-damage.cc @@ -30,6 +30,7 @@ #include "invent.h" #include "item-name.h" #include "items.h" +#include "los.h" #include "losglobal.h" #include "macro.h" #include "message.h" @@ -226,6 +227,18 @@ spret cast_chain_spell(spell_type spell_cast, int pow, continue; } + // check for actors along the arc path + ray_def ray; + if (!find_ray(source, mi->pos(), ray, opc_solid)) + continue; + + while (ray.advance()) + if (actor_at(ray.pos())) + break; + + if (ray.pos() != mi->pos()) + continue; + count++; if (dist < min_dist) @@ -608,10 +621,7 @@ static spret _cast_los_attack_spell(spell_type spell, int pow, } mpr(player_msg); - flash_view(UA_PLAYER, beam.colour, &hitfunc); - more(); - clear_messages(); - flash_view(UA_PLAYER, 0); + flash_view_delay(UA_PLAYER, beam.colour, 300, &hitfunc); } else if (actual) { @@ -1447,7 +1457,13 @@ static int _ignite_poison_monsters(coord_def where, int pow, actor *agent) return mons_aligned(mon, agent) ? -1 : 1; return mons_aligned(mon, agent) ? -1 * damage : damage; } - simple_monster_message(*mon, " seems to burn from within!"); + + if (you.see_cell(mon->pos())) + { + mprf("%s seems to burn from within%s", + mon->name(DESC_THE).c_str(), + attack_strength_punctuation(damage).c_str()); + } dprf("Dice: %dd%d; Damage: %d", dam_dice.num, dam_dice.size, damage); @@ -2306,7 +2322,7 @@ spret cast_thunderbolt(actor *caster, int pow, coord_def aim, bool fail) beam.colour = LIGHTCYAN; beam.range = 1; beam.hit = AUTOMATIC_HIT; - beam.ac_rule = AC_PROPORTIONAL; + beam.ac_rule = ac_type::proportional; beam.set_agent(caster); #ifdef USE_TILE beam.tile_beam = -1; @@ -2415,7 +2431,7 @@ void forest_damage(const actor *mon) int dmg = 0; string msg; - if (!apply_chunked_AC(1, foe->evasion(EV_IGNORE_NONE, mon))) + if (!apply_chunked_AC(1, foe->evasion(ev_ignore::none, mon))) { msg = random_choose( "@foe@ @is@ waved at by a branch", @@ -2423,7 +2439,7 @@ void forest_damage(const actor *mon) "A root lunges up near @foe@"); } else if (!(dmg = foe->apply_ac(hd + random2(hd), hd * 2 - 1, - AC_PROPORTIONAL))) + ac_type::proportional))) { msg = random_choose( "@foe@ @is@ scraped by a branch", @@ -2620,7 +2636,8 @@ spret cast_toxic_radiance(actor *agent, int pow, bool fail, bool mon_tracer) else mpr("Your toxic radiance grows in intensity."); - you.increase_duration(DUR_TOXIC_RADIANCE, 3 + random2(pow/20), 15); + you.increase_duration(DUR_TOXIC_RADIANCE, 2 + random2(pow/20), 15); + toxic_radiance_effect(&you, 10, true); flash_view_delay(UA_PLAYER, GREEN, 300, &hitfunc); @@ -2646,7 +2663,8 @@ spret cast_toxic_radiance(actor *agent, int pow, bool fail, bool mon_tracer) " begins to radiate toxic energy."); mon_agent->add_ench(mon_enchant(ENCH_TOXIC_RADIANCE, 1, mon_agent, - (5 + random2avg(pow/15, 2)) * BASELINE_DELAY)); + (4 + random2avg(pow/15, 2)) * BASELINE_DELAY)); + toxic_radiance_effect(agent, 10); targeter_los hitfunc(mon_agent, LOS_NO_TRANS); flash_view_delay(UA_MONSTER, GREEN, 300, &hitfunc); @@ -2655,7 +2673,20 @@ spret cast_toxic_radiance(actor *agent, int pow, bool fail, bool mon_tracer) } } -void toxic_radiance_effect(actor* agent, int mult) +/* + * Attempt to poison all monsters in line of sight of the caster. + * + * @param agent The caster. + * @param mult A number to multiply the damage by. + * This is the time taken for the player's action in auts, + * or 10 if the spell was cast this turn. + * @param on_cast Whether the spell was cast this turn. This only matters + * if the player cast the spell. If true, we trigger conducts + * if the player hurts allies; if false, we don't, to avoid + * the player being accidentally put under penance. + * Defaults to false. + */ +void toxic_radiance_effect(actor* agent, int mult, bool on_cast) { int pow; if (agent->is_player()) @@ -2674,7 +2705,7 @@ void toxic_radiance_effect(actor* agent, int mult) if (agent->is_monster() && mons_aligned(agent, *ai)) continue; - int dam = roll_dice(1, 1 + pow / 20) * mult; + int dam = roll_dice(1, 1 + pow / 20) * div_rand_round(mult, BASELINE_DELAY); dam = resist_adjust_damage(*ai, BEAM_POISON, dam); if (ai->is_player()) @@ -2692,17 +2723,30 @@ void toxic_radiance_effect(actor* agent, int mult) } else { + // We need to deal with conducts before damaging the monster, + // because otherwise friendly monsters that are one-shot won't + // trigger conducts. Only trigger conducts on the turn the player + // casts the spell (see PR #999). + if (on_cast && agent->is_player()) + { + god_conduct_trigger conducts[3]; + set_attack_conducts(conducts, *ai->as_monster()); + if (is_sanctuary(ai->pos())) + break_sanctuary = true; + } + ai->hurt(agent, dam, BEAM_POISON); + if (ai->alive()) { behaviour_event(ai->as_monster(), ME_ANNOY, agent, agent->pos()); - if (coinflip() || !ai->as_monster()->has_ench(ENCH_POISON)) - poison_monster(ai->as_monster(), agent, 1); + int q = mult / BASELINE_DELAY; + int levels = roll_dice(q, 2) - q + (roll_dice(1, 20) <= (mult % BASELINE_DELAY)); + if (!ai->as_monster()->has_ench(ENCH_POISON)) // Always apply poison to an unpoisoned enemy + levels = max(levels, 1); + poison_monster(ai->as_monster(), agent, levels); } - - if (agent->is_player() && is_sanctuary(ai->pos())) - break_sanctuary = true; } } diff --git a/crawl-ref/source/spl-damage.h b/crawl-ref/source/spl-damage.h index 3e61bc727078..cc7153f2c5b7 100644 --- a/crawl-ref/source/spl-damage.h +++ b/crawl-ref/source/spl-damage.h @@ -57,7 +57,7 @@ spret cast_dazzling_spray(int pow, coord_def aim, bool fail); spret cast_toxic_radiance(actor *caster, int pow, bool fail = false, bool mon_tracer = false); -void toxic_radiance_effect(actor* agent, int mult); +void toxic_radiance_effect(actor* agent, int mult, bool on_cast = false); spret cast_searing_ray(int pow, bolt &beam, bool fail); void handle_searing_ray(); diff --git a/crawl-ref/source/spl-data.h b/crawl-ref/source/spl-data.h index 78b87bf0e4ce..539d45fad2bf 100644 --- a/crawl-ref/source/spl-data.h +++ b/crawl-ref/source/spl-data.h @@ -626,7 +626,7 @@ static const struct spell_desc spelldata[] = { SPELL_ANIMATE_DEAD, "Animate Dead", spschool::necromancy, - spflag::area | spflag::neutral | spflag::corpse_violating | spflag::utility, + spflag::area | spflag::neutral | spflag::utility, 4, 0, -1, -1, @@ -661,7 +661,7 @@ static const struct spell_desc spelldata[] = { SPELL_ANIMATE_SKELETON, "Animate Skeleton", spschool::necromancy, - spflag::corpse_violating | spflag::utility, + spflag::utility, 1, 0, -1, -1, @@ -832,7 +832,7 @@ static const struct spell_desc spelldata[] = { SPELL_FULSOME_DISTILLATION, "Fulsome Distillation", spschool::transmutation | spschool::necromancy, - spflag::corpse_violating, + spflag::none, 1, 0, -1, -1, @@ -855,8 +855,7 @@ static const struct spell_desc spelldata[] = { SPELL_TWISTED_RESURRECTION, "Twisted Resurrection", spschool::necromancy, - spflag::chaotic | spflag::corpse_violating | spflag::utility - | spflag::monster, + spflag::chaotic | spflag::utility | spflag::monster, 5, 200, -1, -1, @@ -1236,7 +1235,7 @@ static const struct spell_desc spelldata[] = { SPELL_NECROMUTATION, "Necromutation", spschool::transmutation | spschool::necromancy, - spflag::helpful | spflag::corpse_violating | spflag::chaotic, + spflag::helpful | spflag::chaotic, 8, 200, -1, -1, @@ -1600,7 +1599,7 @@ static const struct spell_desc spelldata[] = { SPELL_SIMULACRUM, "Simulacrum", spschool::ice | spschool::necromancy, - spflag::corpse_violating, + spflag::none, 6, 200, -1, -1, @@ -2092,7 +2091,7 @@ static const struct spell_desc spelldata[] = 0, -1, -1, 4, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_CREATE_TENTACLES, }, { @@ -2365,7 +2364,7 @@ static const struct spell_desc spelldata[] = 0, LOS_RADIUS, LOS_RADIUS, 4, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_SUMMON_SPECTRAL_ORCS, }, #if TAG_MAJOR_VERSION == 34 @@ -2536,7 +2535,7 @@ static const struct spell_desc spelldata[] = spschool::charms | spschool::translocation, spflag::selfench, 2, - 200, + 50, -1, -1, 2, 0, TILEG_SHROUD_OF_GOLUBRIA, @@ -2677,7 +2676,7 @@ static const struct spell_desc spelldata[] = 200, LOS_RADIUS, LOS_RADIUS, 4, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_SENTINEL_MARK, }, // Ironbrand Convoker version (delayed activation, recalls only humanoids) @@ -2689,7 +2688,7 @@ static const struct spell_desc spelldata[] = 0, -1, -1, 3, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_RECALL, }, { @@ -2780,7 +2779,7 @@ static const struct spell_desc spelldata[] = 200, LOS_RADIUS, LOS_RADIUS, 5, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_AWAKEN_VINES, }, #if TAG_MAJOR_VERSION == 34 @@ -2815,7 +2814,7 @@ static const struct spell_desc spelldata[] = 100, LOS_RADIUS, LOS_RADIUS, 5, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_WALL_OF_BRAMBLES, }, { @@ -2861,7 +2860,7 @@ static const struct spell_desc spelldata[] = 200, LOS_RADIUS, LOS_RADIUS, 4, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_STRIP_RESISTANCE, }, { @@ -2962,7 +2961,7 @@ static const struct spell_desc spelldata[] = 200, LOS_RADIUS, LOS_RADIUS, 5, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_INVISIBILITY, }, { @@ -3515,7 +3514,7 @@ static const struct spell_desc spelldata[] = 0, -1, -1, 5, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_SEAL_DOORS, }, { @@ -3691,8 +3690,7 @@ static const struct spell_desc spelldata[] = { SPELL_CIGOTUVIS_EMBRACE, "Cigotuvi's Embrace", spschool::necromancy, - spflag::chaotic | spflag::corpse_violating | spflag::utility - | spflag::no_ghost, + spflag::chaotic | spflag::utility | spflag::no_ghost, 5, 200, -1, -1, @@ -3822,7 +3820,7 @@ static const struct spell_desc spelldata[] = 100, -1, -1, 2, 0, - TILEG_GENERIC_MONSTER_SPELL, + TILEG_SPRINT, }, { @@ -4013,6 +4011,17 @@ static const struct spell_desc spelldata[] = TILEG_GENERIC_MONSTER_SPELL, }, +{ + SPELL_SPORULATE, "Sporulate", + spschool::conjuration | spschool::earth, + spflag::monster | spflag::selfench, + 5, + 200, + -1, -1, + 5, 0, + TILEG_SPORULATE, +}, + { SPELL_NO_SPELL, "nonexistent spell", spschool::none, diff --git a/crawl-ref/source/spl-goditem.cc b/crawl-ref/source/spl-goditem.cc index afc82ec2f558..d8250b3130b7 100644 --- a/crawl-ref/source/spl-goditem.cc +++ b/crawl-ref/source/spl-goditem.cc @@ -12,6 +12,7 @@ #include "coordit.h" #include "database.h" #include "directn.h" +#include "english.h" #include "env.h" #include "fight.h" #include "god-conduct.h" @@ -101,9 +102,11 @@ string unpacifiable_reason(const monster_info& mi) if (mi.is(MB_SLEEPING)) // not aware of what is happening { - return make_stringf("You cannot pacify this monster while %s is " + return make_stringf("You cannot pacify this monster while %s %s " "sleeping!", - mi.pronoun(PRONOUN_SUBJECTIVE)); + mi.pronoun(PRONOUN_SUBJECTIVE), + conjugate_verb("are", + mi.pronoun_plurality()).c_str()); } // pacifiable, maybe! @@ -196,7 +199,7 @@ static spret _try_to_pacify(monster &mon, int healed, int pow, if (_pacification_sides(mon.type, pow) < mon_hp) { // monster avg hp too high to ever be pacified with your invo skill. - mprf("%s would be completely unfazed by your meager offer of peace.", + mprf("%s would be completely unfazed by your meagre offer of peace.", mon.name(DESC_THE).c_str()); return spret::abort; } @@ -327,7 +330,7 @@ spret cast_healing(int pow, bool fail) args.restricts = DIR_TARGET; args.mode = TARG_INJURED_FRIEND; args.needs_path = false; - args.self = CONFIRM_CANCEL; + args.self = confirm_prompt_type::cancel; args.target_prefix = "Heal"; args.get_desc_func = bind(_desc_pacify_chance, placeholders::_1, pow); direction(spd, args); @@ -660,7 +663,8 @@ static bool _selectively_remove_curse(const string &pre_msg) return used; } - int item_slot = prompt_invent_item("Uncurse which item?", MT_INVLIST, + int item_slot = prompt_invent_item("Uncurse which item?", + menu_type::invlist, OSEL_CURSED_WORN, OPER_ANY, invprompt_flag::escape_only); if (prompt_failed(item_slot)) @@ -738,7 +742,7 @@ static bool _selectively_curse_item(bool armour, const string &pre_msg) { while (1) { - int item_slot = prompt_invent_item("Curse which item?", MT_INVLIST, + int item_slot = prompt_invent_item("Curse which item?", menu_type::invlist, armour ? OSEL_UNCURSED_WORN_ARMOUR : OSEL_UNCURSED_WORN_JEWELLERY, OPER_ANY, invprompt_flag::escape_only); @@ -1255,7 +1259,7 @@ void torment_cell(coord_def where, actor *attacker, torment_source_type taux) { if (mons->observable()) simple_monster_message(*mons, " convulses!"); - else + else if (you.see_cell(mons->pos())) mpr("Something is bathed in an unholy light!"); // Currently, torment doesn't annoy the monsters it affects @@ -1302,7 +1306,8 @@ void torment(actor *attacker, torment_source_type taux, const coord_def& where) torment_cell(*ri, attacker, taux); } -void setup_cleansing_flame_beam(bolt &beam, int pow, int caster, +void setup_cleansing_flame_beam(bolt &beam, int pow, + cleansing_flame_source caster, coord_def where, actor *attacker) { beam.flavour = BEAM_HOLY; @@ -1311,13 +1316,14 @@ void setup_cleansing_flame_beam(bolt &beam, int pow, int caster, beam.target = where; beam.name = "golden flame"; beam.colour = YELLOW; - beam.aux_source = (caster == CLEANSING_FLAME_TSO) + beam.aux_source = (caster == cleansing_flame_source::tso) ? "the Shining One's cleansing flame" : "cleansing flame"; beam.ex_size = 2; beam.is_explosion = true; - if (caster == CLEANSING_FLAME_GENERIC || caster == CLEANSING_FLAME_TSO) + if (caster == cleansing_flame_source::generic + || caster == cleansing_flame_source::tso) { beam.thrower = KILL_MISC; beam.source_id = MID_NOBODY; @@ -1338,7 +1344,7 @@ void setup_cleansing_flame_beam(bolt &beam, int pow, int caster, } } -void cleansing_flame(int pow, int caster, coord_def where, +void cleansing_flame(int pow, cleansing_flame_source caster, coord_def where, actor *attacker) { bolt beam; diff --git a/crawl-ref/source/spl-goditem.h b/crawl-ref/source/spl-goditem.h index ecf60da05d55..ab95a10b2d78 100644 --- a/crawl-ref/source/spl-goditem.h +++ b/crawl-ref/source/spl-goditem.h @@ -1,5 +1,6 @@ #pragma once +#include "cleansing-flame-source-type.h" #include "enchant-type.h" #include "holy-word-source-type.h" #include "spell-type.h" @@ -79,9 +80,10 @@ void torment(actor *attacker, torment_source_type taux, const coord_def& where); void torment_cell(coord_def where, actor *attacker, torment_source_type taux); void torment_player(actor *attacker, torment_source_type taux); -void setup_cleansing_flame_beam(bolt &beam, int pow, int caster, +void setup_cleansing_flame_beam(bolt &beam, int pow, + cleansing_flame_source caster, coord_def where, actor *attacker = nullptr); -void cleansing_flame(int pow, int caster, coord_def where, +void cleansing_flame(int pow, cleansing_flame_source caster, coord_def where, actor *attacker = nullptr); spret cast_random_effects(int pow, bolt& beam, bool fail); diff --git a/crawl-ref/source/spl-miscast.cc b/crawl-ref/source/spl-miscast.cc index dfdb2beed323..dcd72efd809c 100644 --- a/crawl-ref/source/spl-miscast.cc +++ b/crawl-ref/source/spl-miscast.cc @@ -495,7 +495,7 @@ bool MiscastEffect::_explosion() // wild magic card if (special_source.source == miscast_source::deck) - beam.thrower = KILL_MISCAST; + beam.thrower = KILL_YOU; int max_dam = beam.damage.num * beam.damage.size; max_dam = check_your_resists(max_dam, beam.flavour, cause); diff --git a/crawl-ref/source/spl-other.cc b/crawl-ref/source/spl-other.cc index 1bd39265ea28..4179fb33d7df 100644 --- a/crawl-ref/source/spl-other.cc +++ b/crawl-ref/source/spl-other.cc @@ -30,7 +30,7 @@ spret cast_sublimation_of_blood(int pow, bool fail) bool success = false; if (you.duration[DUR_DEATHS_DOOR]) - mpr("You can't draw power from your own body while in Death's door."); + mpr("You can't draw power from your own body while in death's door."); else if (!you.can_bleed()) { if (you.species == SP_VAMPIRE) diff --git a/crawl-ref/source/spl-selfench.cc b/crawl-ref/source/spl-selfench.cc index 71510e36d627..33b5e8d0307c 100644 --- a/crawl-ref/source/spl-selfench.cc +++ b/crawl-ref/source/spl-selfench.cc @@ -31,13 +31,6 @@ #include "view.h" #include "viewchar.h" -int allowed_deaths_door_hp() -{ - int hp = calc_spell_power(SPELL_DEATHS_DOOR, true) / 10; - - return max(hp, 1); -} - spret cast_deaths_door(int pow, bool fail) { fail_check(); @@ -47,7 +40,9 @@ spret cast_deaths_door(int pow, bool fail) you.set_duration(DUR_DEATHS_DOOR, 10 + random2avg(13, 3) + (random2(pow) / 10)); - calc_hp(false, true); + const int hp = max(calc_spell_power(SPELL_DEATHS_DOOR, true) / 10, 1); + you.attribute[ATTR_DEATHS_DOOR_HP] = hp; + set_hp(hp); if (you.duration[DUR_DEATHS_DOOR] > 25 * BASELINE_DELAY) you.duration[DUR_DEATHS_DOOR] = (23 + random2(5)) * BASELINE_DELAY; @@ -72,12 +67,6 @@ spret ice_armour(int pow, bool fail) else mpr("A film of ice covers your body!"); - if (you.attribute[ATTR_BONE_ARMOUR] > 0) - { - you.attribute[ATTR_BONE_ARMOUR] = 0; - mpr("Your corpse armour falls away."); - } - you.increase_duration(DUR_ICY_ARMOUR, random_range(40, 50), 50); you.props[ICY_ARMOUR_KEY] = pow; you.redraw_armour_class = true; @@ -181,7 +170,6 @@ int cast_selective_amnesia(const string &pre_msg) } } - canned_msg(MSG_OK); return -1; } diff --git a/crawl-ref/source/spl-selfench.h b/crawl-ref/source/spl-selfench.h index 6e3ce67e7fa7..4a656d882ee6 100644 --- a/crawl-ref/source/spl-selfench.h +++ b/crawl-ref/source/spl-selfench.h @@ -4,7 +4,6 @@ #include "spl-cast.h" #include "transformation.h" -int allowed_deaths_door_hp(); spret cast_deaths_door(int pow, bool fail); void remove_ice_armour(); spret ice_armour(int pow, bool fail); diff --git a/crawl-ref/source/spl-summoning.cc b/crawl-ref/source/spl-summoning.cc index 9355afa8837a..2346ca21f0ec 100644 --- a/crawl-ref/source/spl-summoning.cc +++ b/crawl-ref/source/spl-summoning.cc @@ -92,6 +92,9 @@ static mgen_data _pal_data(monster_type pal, int dur, god_type god, spret cast_summon_butterflies(int pow, god_type god, bool fail) { + if (otr_stop_summoning_prompt()) + return spret::abort; + fail_check(); bool success = false; @@ -114,6 +117,9 @@ spret cast_summon_butterflies(int pow, god_type god, bool fail) spret cast_summon_small_mammal(int pow, god_type god, bool fail) { + if (otr_stop_summoning_prompt()) + return spret::abort; + fail_check(); monster_type mon = MONS_PROGRAM_BUG; @@ -157,6 +163,10 @@ spret cast_sticks_to_snakes(int pow, god_type god, bool fail) mpr("You don't have anything to turn into a snake."); return spret::abort; } + + if (otr_stop_summoning_prompt("create snakes")) + return spret::abort; + // Sort by the quantity if the player has no bow skill; this will // put arrows with the smallest quantity first in line // If the player has bow skill, we will already have plain arrows @@ -227,6 +237,9 @@ spret cast_sticks_to_snakes(int pow, god_type god, bool fail) spret cast_call_canine_familiar(int pow, god_type god, bool fail) { + if (otr_stop_summoning_prompt()) + return spret::abort; + fail_check(); monster_type mon = MONS_PROGRAM_BUG; @@ -266,6 +279,9 @@ spret cast_summon_ice_beast(int pow, god_type god, bool fail) spret cast_monstrous_menagerie(actor* caster, int pow, god_type god, bool fail) { + if (caster->is_player() && otr_stop_summoning_prompt()) + return spret::abort; + fail_check(); monster_type type = MONS_PROGRAM_BUG; @@ -382,6 +398,9 @@ spret cast_dragon_call(int pow, bool fail) return spret::abort; } + if (otr_stop_summoning_prompt("call dragons")) + return spret::abort; + fail_check(); mpr("You call out to the draconic realm, and the dragon horde roars back!"); @@ -1148,6 +1167,10 @@ bool summon_demon_type(monster_type mon, int pow, god_type god, spret cast_summon_demon(int pow, god_type god, bool fail) { + // Chaos spawn, orange demons and sixfirhies are not rPois + if (otr_stop_summoning_prompt()) + return spret::abort; + fail_check(); mpr("You open a gate to Pandemonium!"); @@ -1159,6 +1182,9 @@ spret cast_summon_demon(int pow, god_type god, bool fail) spret cast_summon_greater_demon(int pow, god_type god, bool fail) { + if (otr_stop_summoning_prompt()) + return spret::abort; + fail_check(); mpr("You open a gate to Pandemonium!"); @@ -1171,6 +1197,9 @@ spret cast_summon_greater_demon(int pow, god_type god, bool fail) spret cast_shadow_creatures(int st, god_type god, level_id place, bool fail) { + if (otr_stop_summoning_prompt("summon")) + return spret::abort; + fail_check(); const bool scroll = (st == MON_SUMM_SCROLL); mpr("Wisps of shadow whirl around you..."); @@ -1409,6 +1438,9 @@ spret cast_summon_forest(actor* caster, int pow, god_type god, bool fail) if (success) { + if (otr_stop_summoning_prompt("summon a forest")) + return spret::abort; + fail_check(); // Replace some rock walls with trees, then scatter a smaller number // of trees on unoccupied floor (such that they do not break connectivity) @@ -1701,7 +1733,9 @@ static bool _raise_remains(const coord_def &pos, int corps, beh_type beha, *motions_r |= DEAD_ARE_HOPPING; } else if (mons_genus(zombie_type) == MONS_WORKER_ANT +#if TAG_MAJOR_VERSION == 34 || mons_genus(zombie_type) == MONS_BEETLE +#endif || mons_base_char(zombie_type) == 's') // many genera { *motions_r |= DEAD_ARE_CRAWLING; @@ -3439,3 +3473,224 @@ int count_summons(const actor *summoner, spell_type spell) return count; } + +static bool _create_briar_patch(coord_def& target) +{ + mgen_data mgen = mgen_data(MONS_BRIAR_PATCH, BEH_FRIENDLY, target, + MHITNOT, MG_FORCE_PLACE, GOD_FEDHAS); + mgen.hd = mons_class_hit_dice(MONS_BRIAR_PATCH) + + you.skill_rdiv(SK_INVOCATIONS); + mgen.set_summoned(&you, 3 + you.skill_rdiv(SK_INVOCATIONS, 1, 6), + SPELL_NO_SPELL); + + if (create_monster(mgen)) + { + mpr("A briar patch grows up from the ground."); + return true; + } + + return false; +} + +bool fedhas_wall_of_briars() +{ + // How many adjacent open spaces are there? + vector adjacent; + for (adjacent_iterator adj_it(you.pos()); adj_it; ++adj_it) + { + if (monster_habitable_grid(MONS_BRIAR_PATCH, env.grid(*adj_it)) + && !actor_at(*adj_it)) + { + adjacent.push_back(*adj_it); + } + } + + // Don't prompt if we can't do anything. + if (adjacent.empty()) + { + mpr("No empty adjacent squares."); + return false; + } + + int created_count = 0; + for (auto p : adjacent) + { + if (_create_briar_patch(p)) + created_count++; + } + + if (!created_count) + canned_msg(MSG_NOTHING_HAPPENS); + + return created_count; +} + +static void _overgrow_wall(const coord_def &pos) +{ + const dungeon_feature_type feat = grd(pos); + const string what = feature_description(feat, NUM_TRAPS, "", DESC_THE, + false); + + if (monster_at(pos)) + { + mprf("Something unseen blocks growth in %s.", what.c_str()); + return; + } + + destroy_wall(pos); + + const monster_type mon = random_choose_weighted(4, MONS_OKLOB_SAPLING, + 4, MONS_BURNING_BUSH, + 4, MONS_WANDERING_MUSHROOM, + 1, MONS_BALLISTOMYCETE, + 1, MONS_OKLOB_PLANT); + mgen_data mgen(mon, BEH_FRIENDLY, pos, MHITYOU, MG_FORCE_PLACE); + mgen.hd = mons_class_hit_dice(mon) + you.skill_rdiv(SK_INVOCATIONS); + mgen.set_summoned(&you, 3 + you.skill_rdiv(SK_INVOCATIONS, 1, 5), + SPELL_NO_SPELL); + if (const monster* const plant = create_monster(mgen)) + { + mprf("%s is torn apart as %s grows in its place.", what.c_str(), + plant->name(DESC_A).c_str()); + } + // XXX: Maybe try to make this revert the terrain if a monster isn't placed. + else + mprf("%s falls apart, but nothing grows.", what.c_str()); +} + +bool fedhas_overgrow() +{ + targeter_overgrow tgt; + direction_chooser_args args; + args.hitfunc = &tgt; + args.restricts = DIR_TARGET; + args.mode = TARG_ANY; + args.range = LOS_RADIUS; + args.just_looking = false; + args.needs_path = false; + args.top_prompt = "Aiming: Overgrow"; + dist sdirect; + direction(sdirect, args); + if (!sdirect.isValid) + return false; + + for (auto site : tgt.affected_positions) + _overgrow_wall(site); + + return true; +} + +spret fedhas_grow_ballistomycete(bool fail) +{ + dist spd; + bolt beam; + beam.range = 2; + direction_chooser_args args; + args.restricts = DIR_TARGET; + args.mode = TARG_HOSTILE; + args.needs_path = false; + if (!spell_direction(spd, beam, &args)) + return spret::abort; + + if (grid_distance(beam.target, you.pos()) > 2 || !in_bounds(beam.target)) + { + mpr("That's too far away."); + return spret::abort; + } + + if (!monster_habitable_grid(MONS_BALLISTOMYCETE, grd(beam.target))) + { + mpr("You can't grow a ballistomycete there."); + return spret::abort; + } + + monster* mons = monster_at(beam.target); + if (mons) + { + if (you.can_see(*mons)) + { + mpr("That space is already occupied."); + return spret::abort; + } + + fail_check(); + + // invisible monster + mpr("Something you can't see occupies that space!"); + return spret::success; + } + + fail_check(); + + mgen_data mgen(MONS_BALLISTOMYCETE, BEH_FRIENDLY, beam.target, MHITYOU, + MG_FORCE_BEH | MG_FORCE_PLACE | MG_AUTOFOE); + mgen.hd = mons_class_hit_dice(MONS_BALLISTOMYCETE) + + you.skill_rdiv(SK_INVOCATIONS); + mgen.set_summoned(&you, 3 + you.skill_rdiv(SK_INVOCATIONS, 1, 5), + SPELL_NO_SPELL); + + if (create_monster(mgen)) + mpr("A ballistomycete grows from the ground."); + else + canned_msg(MSG_NOTHING_HAPPENS); + + return spret::success; +} + +spret fedhas_grow_oklob(bool fail) +{ + dist spd; + bolt beam; + beam.range = 2; + direction_chooser_args args; + args.restricts = DIR_TARGET; + args.mode = TARG_HOSTILE; + args.needs_path = false; + if (!spell_direction(spd, beam, &args)) + return spret::abort; + + if (grid_distance(beam.target, you.pos()) > 2 || !in_bounds(beam.target)) + { + mpr("That's too far away."); + return spret::abort; + } + + if (!monster_habitable_grid(MONS_OKLOB_PLANT, grd(beam.target))) + { + mpr("You can't grow an oklob plant there."); + return spret::abort; + } + + monster* mons = monster_at(beam.target); + if (mons) + { + if (you.can_see(*mons)) + { + mpr("That space is already occupied."); + return spret::abort; + } + + fail_check(); + + // invisible monster + mpr("Something you can't see is occupying that space!"); + return spret::success; + } + + fail_check(); + + mgen_data mgen(MONS_OKLOB_PLANT, BEH_FRIENDLY, beam.target, MHITYOU, + MG_FORCE_BEH | MG_FORCE_PLACE | MG_AUTOFOE); + mgen.hd = mons_class_hit_dice(MONS_OKLOB_PLANT) + + you.skill_rdiv(SK_INVOCATIONS); + mgen.set_summoned(&you, 3 + you.skill_rdiv(SK_INVOCATIONS, 1, 5), + SPELL_NO_SPELL); + + if (create_monster(mgen)) + mpr("An oklob plant grows from the ground."); + else + canned_msg(MSG_NOTHING_HAPPENS); + + return spret::success; + +} diff --git a/crawl-ref/source/spl-summoning.h b/crawl-ref/source/spl-summoning.h index 4adf0329a209..3169317a39ae 100644 --- a/crawl-ref/source/spl-summoning.h +++ b/crawl-ref/source/spl-summoning.h @@ -125,3 +125,8 @@ void summoned_monster(const monster* mons, const actor* caster, bool summons_are_capped(spell_type spell); int summons_limit(spell_type spell); int count_summons(const actor *summoner, spell_type spell); + +bool fedhas_wall_of_briars(); +spret fedhas_grow_ballistomycete(bool fail); +bool fedhas_overgrow(); +spret fedhas_grow_oklob(bool fail); diff --git a/crawl-ref/source/spl-tornado.cc b/crawl-ref/source/spl-tornado.cc index b906c7892df9..2f23b8b1d185 100644 --- a/crawl-ref/source/spl-tornado.cc +++ b/crawl-ref/source/spl-tornado.cc @@ -386,7 +386,7 @@ void tornado_damage(actor *caster, int dur, bool is_vortex) { int dmg = victim->apply_ac( div_rand_round(roll_dice(9, rpow), 15), - 0, AC_PROPORTIONAL); + 0, ac_type::proportional); dprf("damage done: %d", dmg); victim->hurt(caster, dmg, BEAM_AIR, KILLED_BY_BEAM, "", "tornado"); diff --git a/crawl-ref/source/spl-transloc.cc b/crawl-ref/source/spl-transloc.cc index e8d14b2e61a4..581dd8b00504 100644 --- a/crawl-ref/source/spl-transloc.cc +++ b/crawl-ref/source/spl-transloc.cc @@ -569,7 +569,7 @@ static bool _teleport_player(bool wizard_tele, bool teleportitis, // After this point, we're guaranteed to teleport. Kill the appropriate // delays. Teleportitis needs to check the target square first, though. if (!teleportitis) - interrupt_activity(AI_TELEPORT); + interrupt_activity(activity_interrupt::teleport); // Update what we can see at the current location as well as its stash, // in case something happened in the exact turn that we teleported @@ -670,7 +670,7 @@ static bool _teleport_player(bool wizard_tele, bool teleportitis, } else { - interrupt_activity(AI_TELEPORT); + interrupt_activity(activity_interrupt::teleport); if (!reason.empty()) mpr(reason); mprf("You are suddenly yanked towards %s nearby monster%s!", diff --git a/crawl-ref/source/spl-util.cc b/crawl-ref/source/spl-util.cc index 97a54fe76275..cdf8f6690a17 100644 --- a/crawl-ref/source/spl-util.cc +++ b/crawl-ref/source/spl-util.cc @@ -957,7 +957,8 @@ int spell_range(spell_type spell, int pow, bool allow_bonus) && vehumet_supports_spell(spell) && have_passive(passive_t::spells_range) && maxrange > 1 - && spell != SPELL_GLACIATE) + && spell != SPELL_GLACIATE + && spell != SPELL_THUNDERBOLT) // lightning rod only { maxrange++; minrange++; @@ -1218,7 +1219,7 @@ string spell_uselessness_reason(spell_type spell, bool temp, bool prevent, return "you're too dead."; break; case SPELL_NECROMUTATION: - // only prohibted to actual undead, not lichformed players + // only prohibited to actual undead, not lichformed players if (you.undead_state(false)) return "you're too dead."; break; diff --git a/crawl-ref/source/stairs.cc b/crawl-ref/source/stairs.cc index 8ee150b38f68..bdb96726067b 100644 --- a/crawl-ref/source/stairs.cc +++ b/crawl-ref/source/stairs.cc @@ -7,6 +7,7 @@ #include "abyss.h" #include "act-iter.h" #include "areas.h" +#include "artefact.h" #include "bloodspatter.h" #include "branch.h" #include "chardump.h" @@ -96,7 +97,7 @@ bool check_annotation_exclusion_warning() && !yesno("Enter next level anyway?", true, 'n', true, false)) { canned_msg(MSG_OK); - interrupt_activity(AI_FORCE_INTERRUPT); + interrupt_activity(activity_interrupt::force); crawl_state.level_annotation_shown = false; return false; } @@ -221,8 +222,6 @@ static void _clear_prisms() void leaving_level_now(dungeon_feature_type stair_used) { - process_sunlights(true); - if (stair_used == DNGN_EXIT_ZIGGURAT) { if (you.depth == 27) @@ -231,6 +230,14 @@ void leaving_level_now(dungeon_feature_type stair_used) you.depth)); } + if (stair_used == DNGN_EXIT_ABYSS) + { +#ifdef DEBUG + auto &vault_list = you.vault_list[level_id::current()]; + vault_list.push_back("[exit]"); +#endif + } + dungeon_events.fire_position_event(DET_PLAYER_CLIMBS, you.pos()); dungeon_events.fire_event(DET_LEAVING_LEVEL); @@ -399,6 +406,22 @@ static void _rune_effect(dungeon_feature_type ftype) } } +static void _gauntlet_effect() +{ + // already doomed + if (you.species == SP_FORMICID) + return; + + mprf(MSGCH_WARN, "The nature of this place prevents you from teleporting."); + + if (you.has_mutation(MUT_TELEPORT, true) + || you.wearing(EQ_RINGS, RING_TELEPORTATION, true) + || you.scan_artefacts(ARTP_CAUSE_TELEPORTATION, true)) + { + mpr("You feel stable on this floor."); + } +} + static void _new_level_amuses_xom(dungeon_feature_type feat, dungeon_feature_type old_feat, bool shaft, int shaft_depth, bool voluntary) @@ -523,13 +546,10 @@ static level_id _travel_destination(const dungeon_feature_type how, + shaft_dest.describe() + "."); } - string howfar; - if (shaft_depth > 1) - howfar = make_stringf(" for %d floors", shaft_depth); - - mprf("You %s a shaft%s!", you.airborne() ? "are sucked into" - : "fall through", - howfar.c_str()); + mprf("You %s into a shaft and drop %d floor%s!", + you.airborne() ? "are sucked" : "fall", + shaft_depth, + shaft_depth > 1 ? "s" : ""); // Shafts are one-time-use. mpr("The shaft crumbles and collapses."); @@ -771,6 +791,9 @@ void floor_transition(dungeon_feature_type how, mprf("Welcome to %s!", branches[branch].longname); } + if (branch == BRANCH_GAUNTLET) + _gauntlet_effect(); + const set boring_branch_exits = { BRANCH_TEMPLE, BRANCH_BAZAAR, @@ -783,7 +806,7 @@ void floor_transition(dungeon_feature_type how, { string old_branch_string = branches[old_level.branch].longname; if (starts_with(old_branch_string, "The ")) - old_branch_string[0] = tolower(old_branch_string[0]); + old_branch_string[0] = tolower_safe(old_branch_string[0]); mark_milestone("br.exit", "left " + old_branch_string + ".", old_level.describe()); you.branches_left.set(old_level.branch); @@ -799,8 +822,8 @@ void floor_transition(dungeon_feature_type how, mpr(rune_msg); } - // Entered a regular (non-portal) branch from above. - if (!going_up && parent_branch(branch) == old_level.branch) + // Entered a branch from its parent. + if (parent_branch(branch) == old_level.branch) enter_branch(branch, old_level); } @@ -1048,18 +1071,6 @@ void down_stairs(dungeon_feature_type force_stair, bool force_known_shaft, bool take_stairs(force_stair, false, force_known_shaft, update_travel_cache); } -static bool _any_glowing_mold() -{ - for (rectangle_iterator ri(0); ri; ++ri) - if (glowing_mold(*ri)) - return true; - for (monster_iterator mon_it; mon_it; ++mon_it) - if (mon_it->type == MONS_HYPERACTIVE_BALLISTOMYCETE) - return true; - - return false; -} - static void _update_level_state() { env.level_state = 0; @@ -1068,8 +1079,6 @@ static void _update_level_state() if (!golub.empty()) env.level_state |= LSTATE_GOLUBRIA; - if (_any_glowing_mold()) - env.level_state |= LSTATE_GLOW_MOLD; for (monster_iterator mon_it; mon_it; ++mon_it) { if (mons_allows_beogh(**mon_it)) diff --git a/crawl-ref/source/startup.cc b/crawl-ref/source/startup.cc index d856f48a7262..ba6304ae8dbb 100644 --- a/crawl-ref/source/startup.cc +++ b/crawl-ref/source/startup.cc @@ -34,6 +34,7 @@ #include "macro.h" #include "maps.h" #include "menu.h" +#include "outer-menu.h" #include "message.h" #include "misc.h" #include "mon-cast.h" @@ -85,7 +86,7 @@ static void _initialize() you.symbol = MONS_PLAYER; msg::initialise_mpr_streams(); - seed_rng(); // don't use any chosen seed yet + rng::seed(); // don't use any chosen seed yet init_char_table(Options.char_set); init_show_table(); @@ -198,7 +199,8 @@ static void _initialize() #endif } - mpr(opening_screen().c_str()); + mpr(opening_screen().tostring().c_str()); + mpr(options_read_status().tostring().c_str()); } /** KILL_RESETs all monsters in LOS. @@ -237,112 +239,6 @@ static void _zap_los_monsters(bool items_also) } } -/** - * Ensure that the level given by `pos` is generated. This does not do much in - * the way of cleanup, and the caller must ensure the player ends up somewhere - * sensible (this will not place the player). - */ -static bool _ensure_level_generated(const level_pos &pos) -{ - // TODO: how important is it to get stair_taken right? bel just used stone 1 - dungeon_feature_type stair_taken = - absdungeon_depth(pos.id.branch, pos.id.depth) > env.absdepth0 ? - DNGN_STONE_STAIRS_DOWN_I : DNGN_STONE_STAIRS_UP_I; - - if (pos.id.depth == brdepth[pos.id.branch]) - stair_taken = DNGN_STONE_STAIRS_DOWN_I; - - if (pos.id.depth == 1 && pos.id.branch != BRANCH_DUNGEON) - stair_taken = branches[pos.id.branch].entry_stairs; - - const level_id old_level = level_id::current(); - - you.where_are_you = static_cast(pos.id.branch); - you.depth = pos.id.depth; - - return load_level(stair_taken, LOAD_GENERATE, old_level); -} - -static void _pregen_levels(const branch_type branch, progress_popup &progress) -{ - for (int i = 1; i <= branches[branch].numlevels; i++) - { - level_id new_level = level_id(branch, i); - level_pos pos = level_pos(new_level); - dprf("Pregenerating %s:%d", branches[pos.id.branch].abbrevname, - pos.id.depth); - progress.advance_progress(); - _ensure_level_generated(pos); - } -} - -static void _pregen_dungeon() -{ - // be sure that AK start doesn't interfere with the builder - unwind_var chapter(you.chapter, CHAPTER_ORB_HUNTING); - - // bel's original proposal generated D to lair depth, then lair, then D - // to orc depth, then orc, then the rest of D. I have simplified this to - // just generate whole branches at a time -- I am not sure how much real - // impact this has. One idea might be to shuffle this slightly based on - // the seed. - // TODO: probably need to do portal vaults too? - // Should this use something like logical_branch_order? - const vector generation_order = - { - BRANCH_DUNGEON, - BRANCH_TEMPLE, - BRANCH_LAIR, - BRANCH_ORC, - BRANCH_SPIDER, - BRANCH_SNAKE, - BRANCH_SHOALS, - BRANCH_SWAMP, - BRANCH_VAULTS, - BRANCH_CRYPT, - BRANCH_DEPTHS, - BRANCH_VESTIBULE, - BRANCH_ELF, - BRANCH_ZOT, - BRANCH_SLIME, - BRANCH_TOMB, - BRANCH_TARTARUS, - BRANCH_COCYTUS, - BRANCH_DIS, - BRANCH_GEHENNA, - }; - - progress_popup progress("Generating dungeon...\n\n", 35); - progress.advance_progress(); - // TODO: why is dungeon invalid? it's not set up properly in - // `initialise_branch_depths` for some reason. The vestibule is invalid - // because its depth isn't set until the player actually enters a portal. - for (auto br : generation_order) - if (brentry[br].is_valid() - || br == BRANCH_DUNGEON || br == BRANCH_VESTIBULE) - { - string status = "\nbuilding "; - - switch (br) - { - case BRANCH_SPIDER: - case BRANCH_SNAKE: - status += "a lair branch"; - break; - case BRANCH_SHOALS: - case BRANCH_SWAMP: - status += "another lair branch"; - break; - default: - status += branches[br].longname; - break; - } - progress.set_status_text(status); - _pregen_levels(br, progress); - progress.advance_progress(); - } -} - static void _post_init(bool newc) { ASSERT(strwidth(you.your_name) <= MAX_NAME_LENGTH); @@ -369,7 +265,7 @@ static void _post_init(bool newc) if (newc) { if (Options.pregen_dungeon && crawl_state.game_standard_levelgen()) - _pregen_dungeon(); + pregen_dungeon(level_id(NUM_BRANCHES, -1)); you.entering_level = false; you.transit_stair = DNGN_UNSEEN; @@ -385,9 +281,6 @@ static void _post_init(bool newc) level_id old_level; old_level.branch = NUM_BRANCHES; - handle_terminal_resize(false); // resize HUD now that we know player - // species and game mode - load_level(you.entering_level ? you.transit_stair : DNGN_STONE_STAIRS_DOWN_I, you.entering_level ? LOAD_ENTER_LEVEL : newc ? LOAD_START_GAME : LOAD_RESTART_GAME, @@ -448,7 +341,6 @@ static void _post_init(bool newc) ash_check_bondage(false); trackers_init_new_level(false); - tile_new_level(newc); if (newc) // start a new game { @@ -459,7 +351,6 @@ static void _post_init(bool newc) // This just puts the view up for the first turn. you.redraw_title = true; - textcolour(LIGHTGREY); you.redraw_status_lights = true; print_stats(); viewwindow(); @@ -471,225 +362,147 @@ static void _post_init(bool newc) if (newc) run_map_epilogues(); - // Sanitize skills, init can_train[]. + // Sanitize skills, init can_currently_train[]. fixup_skills(); } #ifndef DGAMELAUNCH -/** - * Helper for show_startup_menu() - * constructs the game modes section - */ -static void _construct_game_modes_menu(MenuScroller* menu) -{ -#ifdef USE_TILE_LOCAL - TextTileItem* tmp = nullptr; -#else - TextItem* tmp = nullptr; -#endif - string text; -#ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_NORMAL), TEX_GUI)); -#else - tmp = new TextItem(); -#endif - text = "Dungeon Crawl"; - if (Options.seed_from_rc) - text += make_stringf(" (custom seed %" PRIu64 ")", Options.seed_from_rc); - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_NORMAL); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - if (Options.seed_from_rc) - { - tmp->set_description_text( - "Dungeon Crawl: The main game. " - "(Your options file has selected a custom seed.)"); - } - else - { - tmp->set_description_text( - "Dungeon Crawl: The main game: full of monsters, " - "items, gods and danger!"); - } - menu->attach_item(tmp); - tmp->set_visible(true); +struct game_modes_menu_item +{ + game_type id; + const char *label; + const char *description; +}; -#ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_NORMAL), TEX_GUI)); -#else - tmp = new TextItem(); -#endif - text = "Choose game seed"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_CUSTOM_SEED); - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("Play with a chosen custom dungeon seed."); - menu->attach_item(tmp); - tmp->set_visible(true); +static const game_modes_menu_item entries[] = +{ + {GAME_TYPE_NORMAL, "Dungeon Crawl", + "Dungeon Crawl: The main game: full of monsters, items, " + "gods and danger!" }, + {GAME_TYPE_CUSTOM_SEED, "Choose Game Seed", + "Play with a chosen custom dungeon seed." }, + {GAME_TYPE_TUTORIAL, "Tutorial for Dungeon Crawl", + "Tutorial that covers the basics of Dungeon Crawl survival." }, + {GAME_TYPE_HINTS, "Hints Mode for Dungeon Crawl", + "A mostly normal game that provides more advanced hints " + "than the tutorial."}, + {GAME_TYPE_SPRINT, "Dungeon Sprint", + "Hard, fixed single level game mode." }, + {GAME_TYPE_INSTRUCTIONS, "Instructions", "Help menu." }, + {GAME_TYPE_ARENA, "The Arena", + "Pit computer controlled teams versus each other!" }, + {GAME_TYPE_HIGH_SCORES, "High Scores", + "View the high score list." }, +}; -#ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_TUTORIAL), TEX_GUI)); -#else - tmp = new TextItem(); -#endif - text = "Tutorial for Dungeon Crawl"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_TUTORIAL); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("Tutorial that covers the basics of " - "Dungeon Crawl survival."); - menu->attach_item(tmp); - tmp->set_visible(true); +static void _construct_game_modes_menu(shared_ptr& container) +{ + for (unsigned int i = 0; i < ARRAYSZ(entries); ++i) + { + const auto& entry = entries[i]; + auto label = make_shared(); #ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_HINTS), TEX_GUI)); -#else - tmp = new TextItem(); + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + auto tile = make_shared(); + tile->set_tile(tile_def(tileidx_gametype(entry.id), TEX_GUI)); + tile->set_margin_for_sdl(0, 6, 0, 0); + hbox->add_child(move(tile)); + hbox->add_child(label); #endif - text = "Hints Mode for Dungeon Crawl"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_HINTS); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("A mostly normal game that provides more " - "advanced hints than the tutorial."); - menu->attach_item(tmp); - tmp->set_visible(true); -#ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_SPRINT), TEX_GUI)); -#else - tmp = new TextItem(); -#endif - text = "Dungeon Sprint"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_SPRINT); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("Hard, fixed single level game mode."); - menu->attach_item(tmp); - tmp->set_visible(true); + label->set_text(formatted_string(entry.label, WHITE)); + auto btn = make_shared(); #ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_INSTRUCTIONS), TEX_GUI)); + hbox->set_margin_for_sdl(2, 10, 2, 2); + btn->set_child(move(hbox)); #else - tmp = new TextItem(); + btn->set_child(move(label)); #endif - text = "Instructions"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_INSTRUCTIONS); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("Help menu."); - menu->attach_item(tmp); - tmp->set_visible(true); + btn->id = entry.id; + btn->description = entry.description; + btn->highlight_colour = LIGHTGREY; + container->add_button(move(btn), 0, i); + } +} -#ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_ARENA), TEX_GUI)); -#else - tmp = new TextItem(); -#endif - text = "The Arena"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_ARENA); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("Pit computer controlled teams versus each other!"); - menu->attach_item(tmp); - tmp->set_visible(true); +static shared_ptr _make_newgame_button(shared_ptr& container, int num_chars) +{ + auto label = make_shared(formatted_string("New Game", WHITE)); #ifdef USE_TILE_LOCAL - tmp = new TextTileItem(); - tmp->add_tile(tile_def(tileidx_gametype(GAME_TYPE_HIGH_SCORES), TEX_GUI)); -#else - tmp = new TextItem(); + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + hbox->add_child(label); + label->set_margin_for_sdl(0,0,0,TILE_Y+6); + hbox->min_size().height = TILE_Y; #endif - text = "High Scores"; - tmp->set_text(text); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - tmp->set_id(GAME_TYPE_HIGH_SCORES); - // Scroller does not care about x-coordinates and only cares about - // item height obtained from max.y - min.y - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_description_text("View the high score list."); - menu->attach_item(tmp); - tmp->set_visible(true); -} -static void _add_newgame_button(MenuScroller* menu, int num_chars) -{ - // XXX: duplicates a lot of _construct_save_games_menu code. not ideal. + auto btn = make_shared(); #ifdef USE_TILE_LOCAL - SaveMenuItem* tmp = new SaveMenuItem(); + btn->set_child(move(hbox)); #else - TextItem* tmp = new TextItem(); + btn->set_child(move(label)); #endif - tmp->set_text("New Game"); - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_fg_colour(WHITE); - tmp->set_highlight_colour(LIGHTGREY); - // unique id - tmp->set_id(NUM_GAME_TYPE + num_chars); - menu->attach_item(tmp); - tmp->set_visible(true); + btn->get_child()->set_margin_for_sdl(2, 10, 2, 2); + btn->id = NUM_GAME_TYPE + num_chars; + btn->highlight_colour = LIGHTGREY; + return btn; } -static void _construct_save_games_menu(MenuScroller* menu, +static void _construct_save_games_menu(shared_ptr& container, const vector& chars) { for (unsigned int i = 0; i < chars.size(); ++i) { + auto hbox = make_shared(Box::HORZ); + hbox->align_cross = Widget::Align::CENTER; + +#ifdef USE_TILE_LOCAL + auto tile = make_shared(chars.at(i).doll); + tile->set_margin_for_sdl(0, 6, 0, 0); + hbox->add_child(move(tile)); +#endif + + const COLOURS fg = chars.at(i).save_loadable ? WHITE : RED; + auto text = chars.at(i).short_desc(); + bool wiz = strip_suffix(text, " (WIZ)"); + auto label = make_shared(formatted_string(text, fg)); + label->set_ellipsize(true); #ifdef USE_TILE_LOCAL - SaveMenuItem* tmp = new SaveMenuItem(); + label->max_size().height = tiles.get_crt_font()->char_height(); #else - TextItem* tmp = new TextItem(); + label->max_size().height = 1; #endif - tmp->set_text(chars.at(i).short_desc()); - tmp->set_bounds(coord_def(1, 1), coord_def(1, 2)); - tmp->set_fg_colour(chars.at(i).save_loadable ? WHITE : RED); - tmp->set_highlight_colour(LIGHTGREY); - // unique id - tmp->set_id(NUM_GAME_TYPE + i); + hbox->add_child(label); + + if (wiz) + { + const COLOURS wiz_bg = chars.at(i).save_loadable ? LIGHTMAGENTA : RED; + auto wiz_text = formatted_string(" (WIZ)", wiz_bg); + hbox->add_child(make_shared(wiz_text)); + } + + auto btn = make_shared(); #ifdef USE_TILE_LOCAL - tmp->set_doll(chars.at(i).doll); + btn->set_child(move(hbox)); +#else + btn->set_child(move(label)); #endif - menu->attach_item(tmp); - tmp->set_visible(true); + btn->get_child()->set_margin_for_sdl(2, 10, 2, 2); + btn->id = NUM_GAME_TYPE + i; + btn->highlight_colour = LIGHTGREY; + container->add_button(move(btn), 0, i); } if (!chars.empty()) - _add_newgame_button(menu, chars.size()); + { + auto btn = _make_newgame_button(container, chars.size()); + container->add_button(move(btn), 0, (int)chars.size()); + } } // Should probably use some find invocation instead. @@ -708,12 +521,6 @@ static bool _game_defined(const newgame_def& ng) && ng.job != JOB_UNKNOWN; } -static const int SCROLLER_MARGIN_X = 18; -static const int NAME_START_Y = 5; -static const int GAME_MODES_START_Y = 7; -static const int NUM_HELP_LINES = 3; -static const int NUM_MISC_LINES = 5; - // TODO: should be game_type. Also, does this really need to be static? // maybe part of crawl_state? static int startup_menu_game_type = GAME_TYPE_UNSPECIFIED; @@ -725,326 +532,291 @@ class UIStartupMenu : public Widget chars = find_all_saved_characters(); num_saves = chars.size(); input_string = crawl_state.default_startup_name; + + m_root = make_shared(Box::VERT); + m_root->_set_parent(this); + m_root->align_cross = Widget::Align::STRETCH; + + auto about = make_shared(opening_screen()); + about->set_margin_for_crt(0, 0, 1, 0); + about->set_margin_for_sdl(0, 0, 10, 0); + + m_root->add_child(move(about)); + + auto grid = make_shared(); + grid->set_margin_for_crt(0, 0, 1, 0); + + auto name_prompt = make_shared("Enter your name:"); + name_prompt->set_margin_for_crt(0, 1, 1, 0); + name_prompt->set_margin_for_sdl(0, 0, 10, 0); + + // If the game filled in a complete name, the user will + // usually want to enter a new name instead of adding + // to the current one. + input_text = make_shared(formatted_string(input_string, WHITE)); + input_text->set_margin_for_crt(0, 0, 1, 0); + input_text->set_margin_for_sdl(0, 0, 10, 10); + + grid->add_child(move(name_prompt), 0, 0); + grid->add_child(input_text, 1, 0); + + descriptions = make_shared(); + + auto mode_prompt = make_shared("Choices:"); + mode_prompt->set_margin_for_crt(0, 1, 1, 0); + mode_prompt->set_margin_for_sdl(0, 0, 10, 0); + game_modes_menu = make_shared(true, 1, ARRAYSZ(entries)); + game_modes_menu->set_margin_for_sdl(0, 0, 10, 10); + game_modes_menu->set_margin_for_crt(0, 0, 1, 0); + game_modes_menu->descriptions = descriptions; + _construct_game_modes_menu(game_modes_menu); + +#ifdef USE_TILE_LOCAL + game_modes_menu->min_size().height = TILE_Y*3; +#else + game_modes_menu->min_size().height = 2; +#endif + + grid->add_child(move(mode_prompt), 0, 1); + grid->add_child(game_modes_menu, 1, 1); + + save_games_menu = make_shared(num_saves > 1, 1, num_saves + 1); + if (num_saves > 0) + { + auto save_prompt = make_shared("Saved games:"); + save_prompt->set_margin_for_crt(0, 1, 1, 0); + save_prompt->set_margin_for_sdl(0, 0, 10, 0); + save_games_menu->set_margin_for_sdl(0, 0, 10, 10); +#ifdef USE_TILE_LOCAL + save_prompt->min_size().height = TILE_Y + 2; + save_games_menu->min_size().height = TILE_Y * min(num_saves, 3); +#else + save_games_menu->min_size().height = 2; +#endif + save_games_menu->descriptions = descriptions; + + _construct_save_games_menu(save_games_menu, chars); + grid->add_child(move(save_prompt), 0, 2); + grid->add_child(save_games_menu, 1, 2); + + game_modes_menu->linked_menus[2] = save_games_menu; + save_games_menu->linked_menus[0] = game_modes_menu; + + grid->row_flex_grow(1) = 1000; + grid->row_flex_grow(2) = 1; + } + + game_modes_menu->on_button_activated = + save_games_menu->on_button_activated = + [this](int id) { this->menu_item_activated(id); }; + + for (auto &w : game_modes_menu->get_buttons()) + { + w->on(Widget::slots.event, [w, this](wm_event ev) { + return this->button_event_hook(ev, w); + }); + } + for (auto &w : save_games_menu->get_buttons()) + { + w->on(Widget::slots.event, [w, this](wm_event ev) { + return this->button_event_hook(ev, w); + }); + } + + grid->column_flex_grow(0) = 1; + grid->column_flex_grow(1) = 10; + + m_root->add_child(move(grid)); + + string instructions_text; + // TODO: these can overflow on console 80x24 and won't line-wrap, is + // there any good solution to this? e.g. + // `long name long name the Vine Stalker Earth Elementalist` + if (defaults.name.size() > 0 && _find_save(chars, defaults.name) != -1) + { + auto save = _find_save(chars, defaults.name); + instructions_text += + "[tab] quick-load last game: " + + chars[save].really_short_desc() + "\n"; + } + else if (_game_defined(defaults)) + { + instructions_text += + "[tab] quick-start last combo: " + + defaults.name + " the " + + newgame_char_description(defaults) + "\n"; + } + instructions_text += + "[ctrl-p] view rc file information and log"; + if (recent_error_messages()) + instructions_text += " (Errors during initialization!)"; + + m_root->add_child(make_shared( + formatted_string::parse_string(instructions_text))); + + descriptions->set_margin_for_crt(1, 0, 0, 0); + descriptions->set_margin_for_sdl(10, 0, 0, 0); + m_root->add_child(descriptions); }; virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; - virtual bool on_event(const wm_event& event) override; - PrecisionMenu menu; + bool has_allocated = false; + bool done; bool end_game; + virtual shared_ptr get_child_at_offset(int x, int y) override { + return m_root; + } + private: newgame_def& ng_choice; const newgame_def &defaults; string input_string; - bool full_name; - TextItem *input_text; vector chars; int num_saves; - MenuScroller *game_modes, *save_games; - MenuDescriptor *descriptor; -}; -SizeReq UIStartupMenu::_get_preferred_size(Direction dim, int prosp_width) -{ -#ifdef USE_TILE_LOCAL - SizeReq ret; - if (!dim) - ret = { 80, 90 }; // seems to work well empirically - else + bool button_event_hook(const wm_event& ev, MenuButton* btn) { - // duplicate of calculations below in _allocate_region() - const int save_lines = num_saves + (num_saves ? 1 : 0); // add 1 for New Game - const int help_start = GAME_MODES_START_Y + num_to_lines(save_lines + NUM_GAME_TYPE) + 2; - const int help_end = help_start + NUM_HELP_LINES + 1; - ret = { 20, help_end }; + if (ev.type == WME_FOCUSIN) + { + startup_menu_game_type = btn->id; + switch (startup_menu_game_type) + { + case GAME_TYPE_ARENA: + break; + case GAME_TYPE_NORMAL: + case GAME_TYPE_CUSTOM_SEED: + case GAME_TYPE_TUTORIAL: + case GAME_TYPE_SPRINT: + case GAME_TYPE_HINTS: + // If a game type is chosen, the user expects + // to start a new game. Just blanking the name + // it it clashes for now. + if (_find_save(chars, input_string) != -1) + input_string = ""; + break; + case GAME_TYPE_HIGH_SCORES: + break; + + case GAME_TYPE_INSTRUCTIONS: + break; + + default: + int save_number = startup_menu_game_type - NUM_GAME_TYPE; + if (save_number < num_saves) + input_string = chars.at(save_number).name; + else // new game + input_string = ""; + break; + } + input_text->set_text(formatted_string(input_string, WHITE)); + } + return false; } - const FontWrapper* font = tiles.get_crt_font(); - const int f = !dim ? font->char_width() : font->char_height(); - ret.min *= f; - ret.nat *= f; + void on_show(); + void menu_item_activated(int id); - return ret; -#else - if (!dim) - return { 80, 80 }; - else - return { 24, 24 }; -#endif + shared_ptr m_root; + shared_ptr input_text; + shared_ptr descriptions; + shared_ptr game_modes_menu; + shared_ptr save_games_menu; +}; + +SizeReq UIStartupMenu::_get_preferred_size(Direction dim, int prosp_width) +{ + return m_root->get_preferred_size(dim, prosp_width); } void UIStartupMenu::_render() { -#ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {1, 1, 1}; - glmanager->set_transform(t, s); -#endif - ui::push_scissor(m_region); - menu.draw_menu(); - ui::pop_scissor(); -#ifdef USE_TILE_LOCAL - glmanager->reset_transform(); -#endif + m_root->render(); } void UIStartupMenu::_allocate_region() { - menu.clear(); + m_root->allocate_region(m_region); -#ifdef USE_TILE_LOCAL - const FontWrapper* font = tiles.get_crt_font(); - const int max_col = m_region[2]/font->char_width(); - const int max_line = m_region[3]/font->char_height(); -#else - const int max_col = m_region[2], max_line = m_region[3]; -#endif - - const int num_modes = NUM_GAME_TYPE; - - int save_lines = num_saves + (num_saves ? 1 : 0); // add 1 for New Game - const int help_start = min(GAME_MODES_START_Y + num_to_lines(save_lines + num_modes) + 2, - max_line - NUM_MISC_LINES + 1); - const int help_end = help_start + NUM_HELP_LINES + 1; - - const int game_mode_bottom = GAME_MODES_START_Y + num_to_lines(num_modes); - const int game_save_top = help_start - 2 - num_to_lines(min(2, save_lines)); - const int save_games_start_y = min(game_mode_bottom, game_save_top); - - menu.set_select_type(PrecisionMenu::PRECISION_SINGLESELECT); - MenuFreeform* freeform = new MenuFreeform(); - freeform->init(coord_def(1, 1), coord_def(max_col, max_line), "freeform"); - // This freeform will only containt unfocusable texts - freeform->allow_focus(false); - game_modes = new MenuScroller(); - game_modes->init(coord_def(SCROLLER_MARGIN_X, GAME_MODES_START_Y), - coord_def(max_col, save_games_start_y), - "game modes"); - - save_games = new MenuScroller(); - save_games->init(coord_def(SCROLLER_MARGIN_X, save_games_start_y), - coord_def(max_col, help_start - 1), - "save games"); - _construct_game_modes_menu(game_modes); - _construct_save_games_menu(save_games, chars); - - NoSelectTextItem* tmp = new NoSelectTextItem(); - tmp->set_text("Enter your name:"); - tmp->set_bounds(coord_def(1, NAME_START_Y), - coord_def(SCROLLER_MARGIN_X, NAME_START_Y + 1)); - freeform->attach_item(tmp); - tmp->set_visible(true); - - tmp = new NoSelectTextItem(); - tmp->set_text("Choices:"); - tmp->set_bounds(coord_def(1, GAME_MODES_START_Y), - coord_def(SCROLLER_MARGIN_X, GAME_MODES_START_Y + 1)); - freeform->attach_item(tmp); - tmp->set_visible(true); - - if (num_saves) + if (!has_allocated) { - tmp = new NoSelectTextItem(); - tmp->set_text("Saved games:"); - tmp->set_bounds(coord_def(1, save_games_start_y), - coord_def(SCROLLER_MARGIN_X, save_games_start_y + 1)); - freeform->attach_item(tmp); - tmp->set_visible(true); + has_allocated = true; + on_show(); } +} - tmp = new NoSelectTextItem(); - - string text = "Use the up/down keys to select the type of game or load a " - "character."; -#ifdef USE_TILE_LOCAL - if (tiles.is_using_small_layout()) - text += " "; - else -#endif - text += "\n"; - text += "You can type your name; if you leave it blank you will be " - "asked later.\n" - "Press Enter to start"; - // TODO: this should include a description of that character. - if (_game_defined(defaults)) - text += ", Tab to repeat the last game's choice"; - text += ".\n"; - tmp->set_text(text); - tmp->set_bounds(coord_def(1, help_start), coord_def(max_col - 1, help_end)); - freeform->attach_item(tmp); - tmp->set_visible(true); - - menu.attach_object(freeform); - menu.attach_object(game_modes); - menu.attach_object(save_games); - - descriptor = new MenuDescriptor(&menu); - descriptor->init(coord_def(1, help_end), coord_def(max_col, help_end + 1), - "descriptor"); - menu.attach_object(descriptor); - -#ifdef USE_TILE_LOCAL - // Black and White highlighter looks kinda bad on tiles - BoxMenuHighlighter* highlighter = new BoxMenuHighlighter(&menu); -#else - BlackWhiteHighlighter* highlighter = new BlackWhiteHighlighter(&menu); -#endif - highlighter->init(coord_def(-1, -1), coord_def(-1, -1), "highlighter"); - menu.attach_object(highlighter); - - freeform->set_visible(true); - game_modes->set_visible(true); - save_games->set_visible(true); - descriptor->set_visible(true); - highlighter->set_visible(true); - - // Draw legal info etc - auto intro_text = new FormattedTextItem(); - intro_text->set_text(opening_screen()); - intro_text->set_bounds(coord_def(1, 1), coord_def(max_col, 5)); - intro_text->set_visible(true); - freeform->attach_item(intro_text); - - input_text = new TextItem(); - input_text->set_text(input_string); - input_text->set_fg_colour(WHITE); - input_text->set_bounds(coord_def(SCROLLER_MARGIN_X, NAME_START_Y), - coord_def(max_col, NAME_START_Y+1)); - input_text->set_visible(true); - freeform->attach_item(input_text); - - // If the game filled in a complete name, the user will - // usually want to enter a new name instead of adding - // to the current one. - full_name = !input_string.empty(); - +void UIStartupMenu::on_show() +{ int save = _find_save(chars, input_string); // don't use non-enum game_type values across restarts, as the list of // saves may have changed on restart. if (startup_menu_game_type >= NUM_GAME_TYPE) startup_menu_game_type = GAME_TYPE_UNSPECIFIED; + int id; if (startup_menu_game_type != GAME_TYPE_UNSPECIFIED) - { - menu.set_active_object(game_modes); - game_modes->set_active_item(startup_menu_game_type); - } + id = startup_menu_game_type; else if (save != -1) { - menu.set_active_object(save_games); // save game id is offset by NUM_GAME_TYPE - save_games->set_active_item(NUM_GAME_TYPE + save); + id = NUM_GAME_TYPE + save; } else if (defaults.type != NUM_GAME_TYPE) - { - menu.set_active_object(game_modes); - game_modes->set_active_item(defaults.type); - } + id = defaults.type; else if (!chars.empty()) - { - menu.set_active_object(save_games); - save_games->activate_first_item(); - } - else - { - menu.set_active_object(game_modes); - game_modes->activate_first_item(); - } - - descriptor->render(); - if (recent_error_messages()) - { - descriptor->override_description( - "Errors during initialization; press ctrl-p to view the full log."); - } + id = NUM_GAME_TYPE + 0; else - descriptor->override_description(crawl_state.last_game_exit.message); - -} - -bool UIStartupMenu::on_event(const wm_event& ev) -{ -#ifdef USE_TILE_LOCAL - if (ev.type == WME_MOUSEMOTION - || ev.type == WME_MOUSEBUTTONDOWN - || ev.type == WME_MOUSEWHEEL) - { - MouseEvent mouse_ev = ev.mouse_event; - mouse_ev.px -= m_region[0]; - mouse_ev.py -= m_region[1]; + id = 0; + + if (auto focus = game_modes_menu->get_button_by_id(id)) + game_modes_menu->scroll_button_into_view(focus); + else if (auto focus2 = save_games_menu->get_button_by_id(id)) + save_games_menu->scroll_button_into_view(focus2); + + Layout *layout = nullptr; + for (Widget *w = _get_parent(); w && !layout; w = w->_get_parent()) + layout = dynamic_cast(w); + ASSERT(layout); + layout->add_event_filter([this](wm_event ev) { + if (ev.type != WME_KEYDOWN) + return false; + const int keyn = ev.key.keysym.sym; + bool changed_name = false; - int key = menu.handle_mouse(mouse_ev); - if (key && key != CK_NO_KEY) + if (key_is_escape(keyn) || keyn == CK_MOUSE_CMD) { - wm_event fake_key = {0}; - fake_key.type = WME_KEYDOWN; - fake_key.key.keysym.sym = key; - on_event(fake_key); + // End the game + return done = end_game = true; + } + else if (keyn == '\t' && _game_defined(defaults)) + { + ng_choice = defaults; + return done = true; + } + else if (keyn == '?') + { + menu_item_activated(GAME_TYPE_INSTRUCTIONS); + return true; + } + else if (keyn == CONTROL('P')) + { + replay_messages_during_startup(); + return true; + } + else if (keyn == CONTROL('U')) + { + input_string = ""; + changed_name = true; } - if (ev.type == WME_MOUSEMOTION) - _expose(); - return true; - } -#endif - - if (ev.type != WME_KEYDOWN) - return false; - int keyn = ev.key.keysym.sym; - - // XXX: these keys are effectively broken on the main menu; there's a new - // menu implementation on the way, but until it's ready, disable them - if (keyn == CK_PGUP || keyn == CK_PGDN) - return true; - - _expose(); - - if (key_is_escape(keyn) || keyn == CK_MOUSE_CMD) - { - // End the game - return done = end_game = true; - } - else if (keyn == '\t' && _game_defined(defaults)) - { - ng_choice = defaults; - return done = true; - } - else if (keyn == '?') - { - show_help(); - - // If we had a save selected, reset type so that the save - // will continue to be selected when we restart the menu. - MenuItem *active = menu.get_active_item(); - if (active && active->get_id() >= NUM_GAME_TYPE) - startup_menu_game_type = GAME_TYPE_UNSPECIFIED; - - return true; - } - else if (keyn == CONTROL('P')) - { - replay_messages_during_startup(); - MenuItem *active = menu.get_active_item(); - if (active && active->get_id() >= NUM_GAME_TYPE) - startup_menu_game_type = GAME_TYPE_UNSPECIFIED; - return true; - } - - if (!menu.process_key(keyn)) - { // handle the non-action keys by hand to poll input // Only consider alphanumeric keys and -_ . - bool changed_name = false; if (iswalnum(keyn) || keyn == '-' || keyn == '.' || keyn == '_' || keyn == ' ') { - if (full_name) - { - full_name = false; - input_string = ""; - } if (strwidth(input_string) < MAX_NAME_LENGTH) { input_string += stringize_glyph(keyn); @@ -1055,82 +827,31 @@ bool UIStartupMenu::on_event(const wm_event& ev) { if (!input_string.empty()) { - if (full_name) - input_string = ""; - else - input_string.erase(input_string.size() - 1); + input_string.erase(input_string.size() - 1); changed_name = true; - full_name = false; } } + if (!changed_name) + return false; + + input_text->set_text(formatted_string(input_string, WHITE)); + // Depending on whether the current name occurs // in the saved games, update the active object. // We want enter to start a new game if no character // with the given name exists, or load the corresponding // game. - if (changed_name) - { - int i = _find_save(chars, input_string); - if (i == -1) - menu.set_active_object(game_modes); - else - { - menu.set_active_object(save_games); - // save game ID is offset by NUM_GAME_TYPE - save_games->set_active_item(NUM_GAME_TYPE + i); - } - } - else - { - // Menu might have changed selection -- sync name. - // TODO: the mapping from menu item to game_type is alarmingly - // brittle here - startup_menu_game_type = menu.get_active_item()->get_id(); - switch (startup_menu_game_type) - { - case GAME_TYPE_ARENA: - input_string = ""; - break; - case GAME_TYPE_NORMAL: - case GAME_TYPE_CUSTOM_SEED: - case GAME_TYPE_TUTORIAL: - case GAME_TYPE_SPRINT: - case GAME_TYPE_HINTS: - // If a game type is chosen, the user expects - // to start a new game. Just blanking the name - // it it clashes for now. - if (_find_save(chars, input_string) != -1) - input_string = ""; - break; - case GAME_TYPE_HIGH_SCORES: - break; - - case GAME_TYPE_INSTRUCTIONS: - break; - - default: - int save_number = startup_menu_game_type - NUM_GAME_TYPE; - if (save_number < num_saves) - input_string = chars.at(save_number).name; - else // new game - input_string = ""; - full_name = true; - break; - } - } - input_text->set_text(input_string); - } - // we had a significant action! - vector selected = menu.get_selected_items(); - menu.clear_selections(); - if (selected.empty()) - { - // Uninteresting action, poll a new key + int i = _find_save(chars, input_string); + auto menu = i == -1 ? game_modes_menu : save_games_menu; + auto btn = menu->get_button(0, i == -1 ? 0 : i); + menu->scroll_button_into_view(btn); return true; - } + }); +} - int id = selected.at(0)->get_id(); +void UIStartupMenu::menu_item_activated(int id) +{ switch (id) { case GAME_TYPE_NORMAL: @@ -1139,32 +860,29 @@ bool UIStartupMenu::on_event(const wm_event& ev) case GAME_TYPE_SPRINT: case GAME_TYPE_HINTS: trim_string(input_string); + // XXX: is it ever not a good name? if (is_good_name(input_string, true)) { ng_choice.type = static_cast(id); ng_choice.name = input_string; - return done = true; - } - else - { - // bad name - descriptor->override_description("That's a silly name."); - // Don't make the next key re-enter the game. - menu.clear_selections(); + done = true; } - return true; + return; case GAME_TYPE_ARENA: ng_choice.type = GAME_TYPE_ARENA; - return done = true; + done = true; + return; case GAME_TYPE_INSTRUCTIONS: - show_help(); - return true; - case GAME_TYPE_HIGH_SCORES: - show_hiscore_table(); - return true; + { + if (id == GAME_TYPE_INSTRUCTIONS) + show_help(); + else + show_hiscore_table(); + } + return; default: // It was a savegame instead @@ -1182,7 +900,8 @@ bool UIStartupMenu::on_event(const wm_event& ev) ng_choice.type = GAME_TYPE_NORMAL; ng_choice.filename = ""; // ? } - return done = true; + done = true; + return; } } @@ -1206,7 +925,7 @@ static void _show_startup_menu(newgame_def& ng_choice, ui::run_layout(move(popup), startup_ui->done); - if (startup_ui->end_game) + if (startup_ui->end_game || crawl_state.seen_hups) { #ifdef USE_TILE_WEB tiles.send_exit_reason("cancel"); @@ -1216,66 +935,6 @@ static void _show_startup_menu(newgame_def& ng_choice, } #endif -static void _choose_arena_teams(newgame_def& choice, - const newgame_def& defaults) -{ -#ifdef USE_TILE_WEB - tiles_crt_popup show_as_popup; -#endif - - if (!choice.arena_teams.empty()) - return; - clear_message_store(); - - char buf[80]; - resumable_line_reader reader(buf, sizeof(buf)); - bool done = false, cancel; - auto prompt_ui = make_shared(); - - prompt_ui->on(Widget::slots.event, [&](wm_event ev) { - if (ev.type != WME_KEYDOWN) - return false; - int key = ev.key.keysym.sym; - key = reader.putkey(key); - if (key == -1) - return true; - cancel = !!key; - return done = true; - }); - - auto popup = make_shared(prompt_ui); - ui::push_layout(move(popup)); - while (!done && !crawl_state.seen_hups) - { - string hlbuf = formatted_string(buf).to_colour_string(); - if (hlbuf.find(" v ") != string::npos) - hlbuf = "" + replace_all(hlbuf, " v ", " v ") + ""; - - formatted_string prompt; - prompt.cprintf("Enter your choice of teams:\n\n "); - prompt += formatted_string::parse_string(hlbuf); - - prompt.cprintf("\n\n"); - if (!defaults.arena_teams.empty()) - prompt.cprintf("Enter - %s\n", defaults.arena_teams.c_str()); - prompt.cprintf("\n"); - prompt.cprintf("Examples:\n"); - prompt.cprintf(" Sigmund v Jessica\n"); - prompt.cprintf(" 99 orc v the Royal Jelly\n"); - prompt.cprintf(" 20-headed hydra v 10 kobold ; scimitar ego:flaming"); - prompt_ui->set_text(prompt); - - ui::pump_events(); - } - ui::pop_layout(); - - if (cancel || crawl_state.seen_hups) - game_ended(game_exit::abort); - choice.arena_teams = buf; - if (choice.arena_teams.empty()) - choice.arena_teams = defaults.arena_teams; -} - #ifndef DGAMELAUNCH static bool _exit_type_allows_menu_bypass(game_exit exit) { @@ -1362,10 +1021,8 @@ bool startup_step() // choose_game and setup_game if (choice.type == GAME_TYPE_ARENA) { - _choose_arena_teams(choice, defaults); - write_newgame_options_file(choice); crawl_state.last_type = GAME_TYPE_ARENA; - run_arena(choice.arena_teams); // this is NORETURN + run_arena(choice, defaults.arena_teams); // this is NORETURN } bool newchar = false; diff --git a/crawl-ref/source/stash.cc b/crawl-ref/source/stash.cc index b5efca0d05a2..d3bf2af35e57 100644 --- a/crawl-ref/source/stash.cc +++ b/crawl-ref/source/stash.cc @@ -225,7 +225,8 @@ bool Stash::needs_stop() const bool Stash::is_boring_feature(dungeon_feature_type feature) { // Count shops as boring features, because they are handled separately. - return !is_notable_terrain(feature) || feature == DNGN_ENTER_SHOP; + return !is_notable_terrain(feature) && !feat_is_trap(feature) + || feature == DNGN_ENTER_SHOP; } static bool _grid_has_perceived_item(const coord_def& pos) @@ -460,6 +461,7 @@ vector Stash::matches_search( || is_dumpable_artefact(item) && search.matches(chardump_desc(item))) { stash_search_result res; + res.match_type = MATCH_ITEM; res.match = s; res.primary_sort = item.name(DESC_QUALNAME); res.item = item; @@ -467,14 +469,17 @@ vector Stash::matches_search( } } - if (results.empty() && feat != DNGN_FLOOR) + if (feat != DNGN_FLOOR) { const string fdesc = feature_description(); - if (!fdesc.empty() && search.matches(fdesc)) + if (!fdesc.empty() && search.matches(prefix + " " + fdesc)) { stash_search_result res; + res.match_type = MATCH_FEATURE; res.match = fdesc; res.primary_sort = fdesc; + res.feat = feat; + res.trap = trap; results.push_back(res); } } @@ -707,6 +712,7 @@ vector ShopInfo::matches_search( stash_search_result res; res.match = shoptitle; res.primary_sort = shoptitle; + res.match_type = MATCH_SHOP; res.shop = this; res.pos.pos = shop.pos; results.push_back(res); @@ -729,6 +735,7 @@ vector ShopInfo::matches_search( || search.matches(shop_item_desc(item))) { stash_search_result res; + res.match_type = MATCH_ITEM; res.match = sname; res.primary_sort = item.name(DESC_QUALNAME); res.item = item; @@ -1244,14 +1251,18 @@ class stash_search_reader : public line_reader static bool _is_potentially_boring(stash_search_result res) { - return res.item.defined() && !res.in_inventory && !res.shop - && (res.item.base_type == OBJ_WEAPONS - || res.item.base_type == OBJ_ARMOUR - || res.item.base_type == OBJ_MISSILES) - && (item_type_known(res.item) || !item_is_branded(res.item)); + return res.match_type == MATCH_ITEM && res.item.defined() + && !res.in_inventory + && (res.item.base_type == OBJ_WEAPONS + || res.item.base_type == OBJ_ARMOUR + || res.item.base_type == OBJ_MISSILES) + && (item_type_known(res.item) || !item_is_branded(res.item)) + || res.match_type == MATCH_FEATURE && feat_is_trap(res.feat); } -static bool _is_duplicate_for_search(stash_search_result l, stash_search_result r, bool ignore_missile_stacks=true) +static bool _is_duplicate_for_search(stash_search_result l, + stash_search_result r, + bool ignore_missile_stacks=true) { if (l.in_inventory || r.in_inventory) return false; @@ -1387,11 +1398,31 @@ static vector _stash_filter_duplicates(vector _stash_filter_duplicates(vector 0) { - matchtitle << " (" << res.duplicates << " further duplicate" << (res.duplicates == 1 ? "" : "s"); - if (res.duplicates != res.duplicate_piles) + matchtitle << " (" << res.duplicates << " further duplicate" << + (res.duplicates == 1 ? "" : "s"); + if (res.duplicates != res.duplicate_piles // piles are only + // meaningful for items + && res.match_type == MATCH_ITEM) { matchtitle << " in " << res.duplicate_piles << " pile" << (res.duplicate_piles == 1 ? "" : "s"); @@ -1722,6 +1760,17 @@ bool StashTracker::display_search_results( } else if (res.shop) me->add_tile(tile_def(tileidx_shop(&res.shop->shop), TEX_FEAT)); + else if (feat_is_trap(res.feat)) + { + const tileidx_t idx = tileidx_trap(res.trap); + me->add_tile(tile_def(idx, get_dngn_tex(idx))); + } + else if (feat_is_runed(res.feat)) + { + // Handle large doors and huge gates + const tileidx_t idx = tileidx_feature_base(res.feat); + me->add_tile(tile_def(idx, get_dngn_tex(idx))); + } else { const dungeon_feature_type feat = feat_by_desc(res.match); diff --git a/crawl-ref/source/stash.h b/crawl-ref/source/stash.h index b0a06e1c9f8d..4cebd2d3435a 100644 --- a/crawl-ref/source/stash.h +++ b/crawl-ref/source/stash.h @@ -110,6 +110,15 @@ class ShopInfo friend class ST_ItemIterator; }; +enum stash_match_type +{ + MATCH_UNKNOWN, + MATCH_ITEM, + MATCH_SHOP, + MATCH_FEATURE, + MATCH_NUM_MATCH_TYPE +}; + struct stash_search_result { // Where's this thingummy of interest. @@ -119,6 +128,9 @@ struct stash_search_result // is on. int player_distance; + // Type of thing found. + stash_match_type match_type; + // Text that describes this search result - usually the name of // the first matching item in the stash or the name of the shop. string match; @@ -129,9 +141,15 @@ struct stash_search_result // Item that was matched. item_def item; - // The shop in question, if this is result is for a shop name. + // The shop in question, if this result is for a shop name. const ShopInfo *shop; + // Type of feature, if this result is for a feature. + dungeon_feature_type feat; + + // Type of trap, if this result is for a trap. + trap_type trap; + // Whether the found items are in the player's inventory. bool in_inventory; @@ -139,8 +157,10 @@ struct stash_search_result int duplicates; int duplicate_piles; - stash_search_result() : pos(), player_distance(0), match(), primary_sort(), item(), - shop(nullptr), in_inventory(false), duplicates(0), duplicate_piles(0) + stash_search_result() : pos(), player_distance(0), match_type(), match(), + primary_sort(), item(), shop(nullptr), feat(), + trap(TRAP_UNASSIGNED), in_inventory(false), + duplicates(0), duplicate_piles(0) { } @@ -223,7 +243,7 @@ class StashTracker { } - void search_stashes(); + void search_stashes(string search_term = ""); LevelStashes &get_current_level(); LevelStashes *find_current_level(); @@ -249,7 +269,7 @@ class StashTracker // Mark nets at (x,y) on current level as no longer trapping an actor. bool unmark_trapping_nets(const coord_def &c); - void add_stash(coord_def p); + void add_stash(coord_def p); void save(writer&) const; void load(reader&); diff --git a/crawl-ref/source/state.cc b/crawl-ref/source/state.cc index 63ed878f0a9e..211ee0181b35 100644 --- a/crawl-ref/source/state.cc +++ b/crawl-ref/source/state.cc @@ -84,6 +84,7 @@ void game_state::reset_game() need_save = false; type = GAME_TYPE_UNSPECIFIED; updating_scores = false; + clear_mon_acting(); reset_cmd_repeat(); reset_cmd_again(); } @@ -192,7 +193,7 @@ void game_state::zero_turns_taken() cancel_cmd_repeat(); } -bool interrupt_cmd_repeat(activity_interrupt_type ai, +bool interrupt_cmd_repeat(activity_interrupt ai, const activity_interrupt_data &at) { if (crawl_state.cmd_repeat_start) @@ -203,12 +204,12 @@ bool interrupt_cmd_repeat(activity_interrupt_type ai, switch (ai) { - case AI_HUNGRY: - case AI_TELEPORT: - case AI_FORCE_INTERRUPT: - case AI_HP_LOSS: - case AI_MONSTER_ATTACKS: - case AI_MIMIC: + case activity_interrupt::hungry: + case activity_interrupt::teleport: + case activity_interrupt::force: + case activity_interrupt::hp_loss: + case activity_interrupt::monster_attacks: + case activity_interrupt::mimic: crawl_state.cancel_cmd_repeat("Command repetition interrupted."); return true; @@ -216,7 +217,7 @@ bool interrupt_cmd_repeat(activity_interrupt_type ai, break; } - if (ai == AI_SEE_MONSTER) + if (ai == activity_interrupt::see_monster) { const monster* mon = at.mons_data; ASSERT(mon); @@ -235,7 +236,6 @@ bool interrupt_cmd_repeat(activity_interrupt_type ai, if (at.context == SC_NEWLY_SEEN) { monster_info mi(mon); - set_auto_exclude(mon); mprf(MSGCH_WARN, "%s comes into view.", get_monster_equipment_desc(mi, DESC_WEAPON).c_str()); @@ -261,9 +261,9 @@ bool interrupt_cmd_repeat(activity_interrupt_type ai, // then everything interrupts it. if (crawl_state.repeat_cmd == CMD_WAIT) { - if (ai == AI_FULL_MP) + if (ai == activity_interrupt::full_mp) crawl_state.cancel_cmd_repeat("Magic restored."); - else if (ai == AI_FULL_HP) + else if (ai == activity_interrupt::full_hp) crawl_state.cancel_cmd_repeat("HP restored"); else crawl_state.cancel_cmd_repeat("Command repetition interrupted."); @@ -274,7 +274,7 @@ bool interrupt_cmd_repeat(activity_interrupt_type ai, if (crawl_state.cmd_repeat_started_unsafe) return false; - if (ai == AI_HIT_MONSTER) + if (ai == activity_interrupt::hit_monster) { // This check is for when command repetition is used to // whack away at a 0xp monster, since the player feels safe diff --git a/crawl-ref/source/state.h b/crawl-ref/source/state.h index 4b1844afaf84..59b51961c7ba 100644 --- a/crawl-ref/source/state.h +++ b/crawl-ref/source/state.h @@ -266,5 +266,5 @@ class mon_acting monster* mon; }; -bool interrupt_cmd_repeat(activity_interrupt_type ai, +bool interrupt_cmd_repeat(activity_interrupt ai, const activity_interrupt_data &at); diff --git a/crawl-ref/source/status.cc b/crawl-ref/source/status.cc index 95a06861743d..ddb928fa9ce3 100644 --- a/crawl-ref/source/status.cc +++ b/crawl-ref/source/status.cc @@ -188,29 +188,15 @@ bool fill_status_info(int status, status_info& inf) // completing or overriding the defaults set above. switch (status) { - case STATUS_DIVINE_ENERGY: - if (you.duration[DUR_NO_CAST]) - { - inf.light_colour = RED; - inf.light_text = "-Cast"; - inf.short_text = "no casting"; - inf.long_text = "You are unable to cast spells."; - } - else if (you.attribute[ATTR_DIVINE_ENERGY]) - { - inf.light_colour = WHITE; - inf.light_text = "+Cast"; - inf.short_text = "divine energy"; - inf.long_text = "You are calling on Sif Muna for divine " - "energy."; - } - break; - case DUR_CORROSION: inf.light_text = make_stringf("Corr (%d)", (-4 * you.props["corrosion_amount"].get_int())); break; + case DUR_FLAYED: + inf.light_text = make_stringf("Flay (%d)", + (-1 * you.props["flay_damage"].get_int())); + case DUR_NO_POTIONS: if (you_foodless()) inf.light_colour = DARKGREY; @@ -387,14 +373,6 @@ bool fill_status_info(int status, status_info& inf) _describe_stat_zero(inf, STAT_DEX); break; - case STATUS_BONE_ARMOUR: - if (you.attribute[ATTR_BONE_ARMOUR] > 0) - { - inf.short_text = "corpse armour"; - inf.long_text = "You are enveloped in carrion and bones."; - } - break; - case STATUS_CONSTRICTED: if (you.is_constricted()) { @@ -740,13 +718,28 @@ bool fill_status_info(int status, status_info& inf) static void _describe_hunger(status_info& inf) { - const bool vamp = (you.species == SP_VAMPIRE); + + if (you.species == SP_VAMPIRE) + { + if (!you.vampire_alive) + { + inf.light_colour = LIGHTRED; + inf.light_text = "Bloodless"; + inf.short_text = "bloodless"; + } + else + { + inf.light_colour = GREEN; + inf.light_text = "Alive"; + } + return; + } switch (you.hunger_state) { case HS_ENGORGED: - inf.light_colour = (vamp ? GREEN : LIGHTGREEN); - inf.light_text = (vamp ? "Alive" : "Engorged"); + inf.light_colour = LIGHTGREEN; + inf.light_text = "Engorged"; break; case HS_VERY_FULL: inf.light_colour = GREEN; @@ -758,25 +751,25 @@ static void _describe_hunger(status_info& inf) break; case HS_HUNGRY: inf.light_colour = YELLOW; - inf.light_text = (vamp ? "Thirsty" : "Hungry"); + inf.light_text = "Hungry"; break; case HS_VERY_HUNGRY: inf.light_colour = YELLOW; - inf.light_text = (vamp ? "Very Thirsty" : "Very Hungry"); + inf.light_text = "Very Hungry"; break; case HS_NEAR_STARVING: inf.light_colour = YELLOW; - inf.light_text = (vamp ? "Near Bloodless" : "Near Starving"); + inf.light_text = "Near Starving"; break; case HS_STARVING: inf.light_colour = LIGHTRED; - inf.light_text = (vamp ? "Bloodless" : "Starving"); - inf.short_text = (vamp ? "bloodless" : "starving"); + inf.light_text = "Starving"; + inf.short_text = "starving"; break; case HS_FAINTING: inf.light_colour = RED; - inf.light_text = (vamp ? "Bloodless" : "Fainting"); - inf.short_text = (vamp ? "bloodless" : "fainting"); + inf.light_text = "Fainting"; + inf.short_text = "fainting"; break; case HS_SATIATED: // no status light default: @@ -827,8 +820,7 @@ static void _describe_regen(status_info& inf) || you.duration[DUR_TROGS_HAND] > 0); const bool no_heal = !player_regenerates_hp(); // Does vampire hunger level affect regeneration rate significantly? - const bool vampmod = !no_heal && !regen && you.species == SP_VAMPIRE - && you.hunger_state != HS_SATIATED; + const bool vampmod = !no_heal && !regen && you.species == SP_VAMPIRE; if (regen) { @@ -861,15 +853,8 @@ static void _describe_regen(status_info& inf) } else if (vampmod) { - if (you.disease) - inf.short_text = "recuperating"; - else - inf.short_text = "regenerating"; - - if (you.hunger_state < HS_SATIATED) - inf.short_text += " slowly"; - else - inf.short_text += " quickly"; + inf.short_text = you.disease ? "recuperating" : "regenerating"; + inf.short_text += " quickly"; } } diff --git a/crawl-ref/source/status.h b/crawl-ref/source/status.h index 2a7c40036f83..6393bd68fc1f 100644 --- a/crawl-ref/source/status.h +++ b/crawl-ref/source/status.h @@ -49,9 +49,10 @@ enum status_type #endif STATUS_BRIBE, STATUS_CLOUD, - STATUS_BONE_ARMOUR, STATUS_ORB, +#if TAG_MAJOR_VERSION == 34 STATUS_DIVINE_ENERGY, +#endif STATUS_STILL_WINDS, STATUS_MISSILES, STATUS_SERPENTS_LASH, diff --git a/crawl-ref/source/store.cc b/crawl-ref/source/store.cc index bbcec5df264b..74cedb32e23b 100644 --- a/crawl-ref/source/store.cc +++ b/crawl-ref/source/store.cc @@ -429,13 +429,18 @@ store_val_type CrawlStoreValue::get_type() const // Read/write from/to savefile void CrawlStoreValue::write(writer &th) const { - ASSERT(type != SV_NONE || (flags & SFLAG_UNSET)); - ASSERT(!(flags & SFLAG_UNSET) || type == SV_NONE); - - marshallByte(th, (char) type); + auto _type = type; + ASSERT(_type != SV_NONE || (flags & SFLAG_UNSET)); + ASSERT(!(flags & SFLAG_UNSET) || _type == SV_NONE); + // if the string is too long to save using the regular way, write it with + // a 4 byte length. SV_STR_LONG is for saving/loading only. + if (_type == SV_STR && static_cast(val.ptr)->length() > SHRT_MAX) + _type = SV_STR_LONG; + + marshallByte(th, (char) _type); marshallByte(th, (char) flags); - switch (type) + switch (_type) { case SV_BOOL: marshallBoolean(th, val.boolean); @@ -468,6 +473,13 @@ void CrawlStoreValue::write(writer &th) const break; } + case SV_STR_LONG: + { + string* str = static_cast(val.ptr); + marshallString4(th, *str); + break; + } + case SV_COORD: { coord_def* coord = static_cast(val.ptr); @@ -575,6 +587,15 @@ void CrawlStoreValue::read(reader &th) break; } + case SV_STR_LONG: + { + val.ptr = (void*) new string(); + unmarshallString4(th, *static_cast(val.ptr)); + // SV_STR_LONG is used only for saving + type = SV_STR; + break; + } + case SV_COORD: { const coord_def coord = unmarshallCoord(th); @@ -650,6 +671,7 @@ void CrawlStoreValue::read(reader &th) break; case NUM_STORE_VAL_TYPES: + default: die("unknown stored value type"); } } @@ -1264,6 +1286,7 @@ void CrawlHashTable::assert_validity() const ASSERT(key == trimmed); ASSERT(val.type != SV_NONE); + ASSERT(val.type != SV_STR_LONG); ASSERT(!(val.flags & SFLAG_UNSET)); switch (val.type) diff --git a/crawl-ref/source/store.h b/crawl-ref/source/store.h index 6daba94f70ea..db21b940494e 100644 --- a/crawl-ref/source/store.h +++ b/crawl-ref/source/store.h @@ -49,7 +49,8 @@ enum store_val_type SV_MONST, SV_LUA, SV_INT64, - NUM_STORE_VAL_TYPES + NUM_STORE_VAL_TYPES, + SV_STR_LONG, // this is a save-only type }; enum store_flag_type diff --git a/crawl-ref/source/stringutil.cc b/crawl-ref/source/stringutil.cc index 37c76f3f18e1..a707ff65ea24 100644 --- a/crawl-ref/source/stringutil.cc +++ b/crawl-ref/source/stringutil.cc @@ -44,7 +44,16 @@ string lowercase_string(const string &s) char32_t c; char buf[4]; for (const char *tp = s.c_str(); int len = utf8towc(&c, tp); tp += len) - res.append(buf, wctoutf8(buf, towlower(c))); + { + // crawl breaks horribly if this is allowed to affect ascii chars, + // so override locale-specific casing for ascii. (For example, in + // Turkish; tr_TR.utf8 lowercase I is a dotless i that is not + // ascii, which breaks many things.) + if (isaalpha(tp[0])) + res.append(1, toalower(tp[0])); + else + res.append(buf, wctoutf8(buf, towlower(c))); + } return res; } @@ -57,8 +66,7 @@ string &lowercase(string &s) string &uppercase(string &s) { for (char &ch : s) - ch = toupper(ch); - + ch = toupper_safe(ch); return s; } diff --git a/crawl-ref/source/stringutil.h b/crawl-ref/source/stringutil.h index 9bbb1e290ae8..a7f09b6cdfcf 100644 --- a/crawl-ref/source/stringutil.h +++ b/crawl-ref/source/stringutil.h @@ -219,12 +219,12 @@ string comma_separated_line(Z start, Z end, const string &andc = " and ", template string join_strings(Z start, Z end, const string &sep = " ") { - return comma_separated_line(start, end, " ", " "); + return comma_separated_line(start, end, sep, sep); } static inline bool starts_with(const string &s, const string &prefix) { - return s.rfind(prefix, 0) != string::npos; + return s.compare(0, prefix.size(), prefix) == 0; } static inline bool ends_with(const string &s, const string &suffix) diff --git a/crawl-ref/source/tag-version.h b/crawl-ref/source/tag-version.h index 74bbdb96e7cb..6142b8c038a0 100644 --- a/crawl-ref/source/tag-version.h +++ b/crawl-ref/source/tag-version.h @@ -230,6 +230,12 @@ enum tag_minor_version TAG_MINOR_REMOVE_DECKS, // Decks are no more TAG_MINOR_GAMESEEDS, // Game seeds + rng state saved TAG_MINOR_YELLOW_DRACONIAN_RACID, // Change yellow draconians' rAcid fake mutation to a true mutation. + TAG_MINOR_THROW_CONSOLIDATION, // Throwing brands consolidated + TAG_MINOR_VAMPIRE_NO_EAT, // Decouple Vampires from the food system + TAG_MINOR_SINGULAR_THEY, // Add singular they pronouns + TAG_MINOR_ABYSS_UNIQUE_VAULTS, // Separate abyss vault tracking from main dungeon + TAG_MINOR_INCREMENTAL_PREGEN, // save tracks whether the game is an incremental pregen game + TAG_MINOR_NO_SUNLIGHT, // Removal of Fedhas' Sunlight #endif NUM_TAG_MINORS, TAG_MINOR_VERSION = NUM_TAG_MINORS - 1 diff --git a/crawl-ref/source/tags.cc b/crawl-ref/source/tags.cc index 670cb4457002..c87c1d23a82b 100644 --- a/crawl-ref/source/tags.cc +++ b/crawl-ref/source/tags.cc @@ -37,6 +37,8 @@ #if TAG_MAJOR_VERSION == 34 #include "cloud.h" #include "decks.h" + #include "food.h" + #include "hunger-state-t.h" #endif #include "colour.h" #include "coordit.h" @@ -304,7 +306,9 @@ static void tag_read_you(reader &th); static void tag_read_you_items(reader &th); static void tag_read_you_dungeon(reader &th); static void tag_read_lost_monsters(reader &th); +#if TAG_MAJOR_VERSION == 34 static void tag_read_lost_items(reader &th); +#endif static void tag_read_companions(reader &th); static void tag_construct_level(writer &th); @@ -372,6 +376,7 @@ uint8_t unmarshallUByte(reader &th) // Marshall 2 byte short in network order. void marshallShort(writer &th, short data) { + // TODO: why does this use `short` and `char` when unmarshall uses int16_t?? CHECK_INITIALIZED(data); const char b2 = (char)(data & 0x00FF); const char b1 = (char)((data & 0xFF00) >> 8); @@ -826,7 +831,7 @@ float unmarshallFloat(reader &th) void marshallString(writer &th, const string &data) { size_t len = data.length(); - // A limit of 32K. + // A limit of 32K. TODO: why doesn't this use int16_t? if (len > SHRT_MAX) die("trying to marshall too long a string (len=%ld)", (long int)len); marshallShort(th, len); @@ -836,7 +841,7 @@ void marshallString(writer &th, const string &data) string unmarshallString(reader &th) { - char buffer[SHRT_MAX]; + char buffer[SHRT_MAX]; // TODO: why doesn't this use int16_t? short len = unmarshallShort(th); ASSERT(len >= 0); @@ -862,14 +867,20 @@ static string unmarshallString2(reader &th) // string -- 4 byte length, non-terminated string data. void marshallString4(writer &th, const string &data) { - marshallInt(th, data.length()); - th.write(data.c_str(), data.length()); + const size_t len = data.length(); + if (len > static_cast(numeric_limits::max())) + die("trying to marshall too long a string (len=%ld)", (long int) len); + marshallInt(th, len); + th.write(data.c_str(), len); } + void unmarshallString4(reader &th, string& s) { const int len = unmarshallInt(th); + ASSERT(len >= 0); s.resize(len); - if (len) th.read(&s.at(0), len); + if (len) + th.read(&s.at(0), len); } // boolean (to avoid system-dependent bool implementations) @@ -1256,10 +1267,10 @@ void tag_read(reader &inf, tag_type tag_id) #endif tag_read_companions(th); - // If somebody SIGHUP'ed out of the skill menu with all skills disabled. - // Doing this here rather in tag_read_you() because you.can_train() - // requires the player's equipment be loaded. - init_can_train(); + // If somebody SIGHUP'ed out of the skill menu with every skill + // disabled. Doing this here rather in tag_read_you() because + // you.can_currently_train() requires the player's equipment be loaded. + init_can_currently_train(); check_selected_skills(); break; case TAG_LEVEL: @@ -1392,6 +1403,7 @@ static void tag_construct_you(writer &th) marshallShort(th, you.hunger); marshallBoolean(th, you.fishtail); + marshallBoolean(th, you.vampire_alive); _marshall_as_int(th, you.form); CANARY; @@ -1658,8 +1670,9 @@ static void tag_construct_you(writer &th) marshallUByte(th, 1); // number of seeds, for historical reasons: always 1 marshallUnsigned(th, you.game_seed); - marshallBoolean(th, you.game_is_seeded); - CrawlVector rng_states = generators_to_vector(); + marshallBoolean(th, you.fully_seeded); // TODO: remove on major version inc? + marshallBoolean(th, you.deterministic_levelgen); + CrawlVector rng_states = rng::generators_to_vector(); rng_states.write(th); CANARY; @@ -1826,6 +1839,10 @@ static void tag_construct_you_dungeon(writer &th) marshallString); marshall_iterator(th, you.uniq_map_names.begin(), you.uniq_map_names.end(), marshallString); + marshall_iterator(th, you.uniq_map_tags_abyss.begin(), + you.uniq_map_tags_abyss.end(), marshallString); + marshall_iterator(th, you.uniq_map_names_abyss.begin(), + you.uniq_map_names_abyss.end(), marshallString); marshallMap(th, you.vault_list, marshall_level_id, marshallStringVector); write_level_connectivity(th); @@ -2272,6 +2289,7 @@ void tag_read_char(reader &th, uint8_t format, uint8_t major, uint8_t minor) you.explore = unmarshallBoolean(th); } +#if TAG_MAJOR_VERSION == 34 static void _cap_mutation_at(mutation_type mut, int cap) { if (you.mutation[mut] > cap) @@ -2285,6 +2303,7 @@ static void _cap_mutation_at(mutation_type mut, int cap) if (you.innate_mutation[mut] > cap) you.innate_mutation[mut] = cap; } +#endif static void tag_read_you(reader &th) { @@ -2346,6 +2365,14 @@ static void tag_read_you(reader &th) you.hp = unmarshallShort(th); you.hunger = unmarshallShort(th); you.fishtail = unmarshallBoolean(th); +#if TAG_MAJOR_VERSION == 34 + if (th.getMinorVersion() >= TAG_MINOR_VAMPIRE_NO_EAT) + you.vampire_alive = unmarshallBoolean(th); + else + you.vampire_alive = calc_hunger_state() > HS_STARVING; +#else + you.vampire_alive = unmarshallBoolean(th); +#endif #if TAG_MAJOR_VERSION == 34 if (th.getMinorVersion() < TAG_MINOR_NOME_NO_MORE) unmarshallInt(th); @@ -3587,7 +3614,7 @@ static void tag_read_you(reader &th) if (th.getMinorVersion() >= TAG_MINOR_REMOVE_ABYSS_SEED && th.getMinorVersion() < TAG_MINOR_ADD_ABYSS_SEED) { - abyssal_state.seed = get_uint32(); + abyssal_state.seed = rng::get_uint32(); } else #endif @@ -3601,7 +3628,7 @@ static void tag_read_you(reader &th) unmarshallFloat(th); // converted abyssal_state.depth to int. abyssal_state.depth = 0; abyssal_state.destroy_all_terrain = true; - abyssal_state.seed = get_uint32(); + abyssal_state.seed = rng::get_uint32(); } #endif abyssal_state.phase = unmarshallFloat(th); @@ -3666,10 +3693,10 @@ static void tag_read_you(reader &th) ASSERT(th.getMinorVersion() < TAG_MINOR_GAMESEEDS || count == 1); if (th.getMinorVersion() < TAG_MINOR_GAMESEEDS) { - you.game_seed = count > 0 ? unmarshallInt(th) : get_uint64(); + you.game_seed = count > 0 ? unmarshallInt(th) : rng::get_uint64(); dprf("Upgrading from unseeded game."); crawl_state.seed = you.game_seed; - you.game_is_seeded = false; + you.fully_seeded = false; for (int i = 1; i < count; i++) unmarshallInt(th); } @@ -3682,10 +3709,19 @@ static void tag_read_you(reader &th) you.game_seed = unmarshallUnsigned(th); dprf("Unmarshalling seed %" PRIu64, you.game_seed); crawl_state.seed = you.game_seed; - you.game_is_seeded = unmarshallBoolean(th); + you.fully_seeded = unmarshallBoolean(th); +#if TAG_MAJOR_VERSION == 34 + // there is no way to tell the levelgen method for games before this + // tag, unfortunately. Though if there are unvisited generated levels, + // that guarantees some form of deterministic pregen. + if (th.getMinorVersion() < TAG_MINOR_INCREMENTAL_PREGEN) + you.deterministic_levelgen = false; + else +#endif + you.deterministic_levelgen = unmarshallBoolean(th); CrawlVector rng_states; rng_states.read(th); - load_generators(rng_states); + rng::load_generators(rng_states); #if TAG_MAJOR_VERSION == 34 } #endif @@ -3735,6 +3771,12 @@ static void tag_read_you(reader &th) for (int i = 0; i < you.props[THUNDERBOLT_CHARGES_KEY].get_int(); i++) expend_xp_evoker(MISC_LIGHTNING_ROD); } + if (th.getMinorVersion() < TAG_MINOR_SINGULAR_THEY + && you.props.exists(HEPLIAKLQANA_ALLY_GENDER_KEY)) + { + if (you.props[HEPLIAKLQANA_ALLY_GENDER_KEY].get_int() == GENDER_NEUTER) + you.props[HEPLIAKLQANA_ALLY_GENDER_KEY] = GENDER_NEUTRAL; + } #endif } @@ -3954,7 +3996,7 @@ static void tag_read_you_items(reader &th) // If fruit pickup was not set explicitly during the time between // FOOD_PURGE and FOOD_PURGE_AP_FIX, copy the old exemplar FOOD_PEAR. - if (food_pickups[FOOD_FRUIT] == 0) + if (food_pickups[FOOD_FRUIT] == AP_FORCE_NONE) food_pickups[FOOD_FRUIT] = food_pickups[FOOD_PEAR]; } if (you.species == SP_FORMICID) @@ -4269,6 +4311,19 @@ static void tag_read_you_dungeon(reader &th) &string_set::insert, unmarshallString); #if TAG_MAJOR_VERSION == 34 + if (th.getMinorVersion() >= TAG_MINOR_ABYSS_UNIQUE_VAULTS) + { +#endif + unmarshall_container(th, you.uniq_map_tags_abyss, + (ssipair (string_set::*)(const string &)) + &string_set::insert, + unmarshallString); + unmarshall_container(th, you.uniq_map_names_abyss, + (ssipair (string_set::*)(const string &)) + &string_set::insert, + unmarshallString); +#if TAG_MAJOR_VERSION == 34 + } if (th.getMinorVersion() >= TAG_MINOR_VAULT_LIST) // 33:17 has it #endif unmarshallMap(th, you.vault_list, unmarshall_level_id, @@ -4402,13 +4457,6 @@ static void tag_construct_level(writer &th) marshallInt(th, env.forest_awoken_until); marshall_level_vault_data(th); marshallInt(th, env.density); - - marshallShort(th, env.sunlight.size()); - for (const auto &sunspot : env.sunlight) - { - marshallCoord(th, sunspot.first); - marshallInt(th, sunspot.second); - } } void marshallItem(writer &th, const item_def &item, bool iinfo) @@ -4507,13 +4555,13 @@ void unmarshallItem(reader &th, item_def &item) item.quantity = unmarshallShort(th); #if TAG_MAJOR_VERSION == 34 // These used to come in stacks in monster inventory as throwing weapons. - // Replace said stacks (but not single items) with tomahawks. + // Replace said stacks (but not single items) with boomerangs. if (item.quantity > 1 && item.base_type == OBJ_WEAPONS && (item.sub_type == WPN_CLUB || item.sub_type == WPN_HAND_AXE || item.sub_type == WPN_DAGGER || item.sub_type == WPN_SPEAR)) { item.base_type = OBJ_MISSILES; - item.sub_type = MI_TOMAHAWK; + item.sub_type = MI_BOOMERANG; item.plus = item.plus2 = 0; item.brand = SPMSL_NORMAL; } @@ -4997,6 +5045,42 @@ void unmarshallItem(reader &th, item_def &item) bind_item_tile(item); } + if (th.getMinorVersion() < TAG_MINOR_THROW_CONSOLIDATION + && item.base_type == OBJ_MISSILES) + { + if (item.sub_type == MI_NEEDLE) + { + item.sub_type = MI_DART; + + switch (item.brand) + { + case SPMSL_PARALYSIS: + case SPMSL_SLOW: + case SPMSL_SLEEP: + case SPMSL_CONFUSION: + case SPMSL_SICKNESS: + item.brand = SPMSL_BLINDING; + break; + default: break; + } + } + else if (item.sub_type == MI_BOOMERANG || item.sub_type == MI_JAVELIN) + { + switch (item.brand) + { + case SPMSL_RETURNING: + case SPMSL_EXPLODING: + case SPMSL_POISONED: + case SPMSL_PENETRATION: + item.brand = SPMSL_NORMAL; + break; + case SPMSL_STEEL: + item.brand = SPMSL_SILVER; + break; + } + } + } + #endif if (is_unrandom_artefact(item)) @@ -5964,15 +6048,19 @@ static void tag_read_level(reader &th) env.forest_awoken_until = unmarshallInt(th); unmarshall_level_vault_data(th); env.density = unmarshallInt(th); +#if TAG_MAJOR_VERSION == 34 - int num_lights = unmarshallShort(th); - ASSERT(num_lights >= 0); - env.sunlight.clear(); - while (num_lights-- > 0) + if (th.getMinorVersion() < TAG_MINOR_NO_SUNLIGHT) { - coord_def c = unmarshallCoord(th); - env.sunlight.emplace_back(c, unmarshallInt(th)); + int num_lights = unmarshallShort(th); + ASSERT(num_lights >= 0); + while (num_lights-- > 0) + { + unmarshallCoord(th); + unmarshallInt(th); + } } +#endif } #if TAG_MAJOR_VERSION == 34 diff --git a/crawl-ref/source/targ-mode-type.h b/crawl-ref/source/targ-mode-type.h index 95ecaf5caf72..6c6767860c38 100644 --- a/crawl-ref/source/targ-mode-type.h +++ b/crawl-ref/source/targ-mode-type.h @@ -7,7 +7,6 @@ enum targ_mode_type TARG_INJURED_FRIEND, // for healing TARG_HOSTILE, TARG_HOSTILE_SUBMERGED, // Target hostiles including submerged ones - TARG_EVOLVABLE_PLANTS, // Targeting mode for Fedhas' evolution TARG_BEOGH_GIFTABLE, // For Beogh followers who can be given gifts TARG_MOVABLE_OBJECT, // Movable objects only TARG_MOBILE_MONSTER, // Non-stationary monsters diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index c4529c555c72..aed12a20b8fd 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -1181,15 +1181,15 @@ bool targeter_shadow_step::valid_aim(coord_def a) { switch (no_landing_reason) { - case BLOCKED_MOVE: - case BLOCKED_OCCUPIED: + case shadow_step_blocked::move: + case shadow_step_blocked::occupied: return notify_fail("There is no safe place near that" " location."); - case BLOCKED_PATH: + case shadow_step_blocked::path: return notify_fail("There's something in the way."); - case BLOCKED_NO_TARGET: + case shadow_step_blocked::no_target: return notify_fail("There isn't a shadow there."); - case BLOCKED_NONE: + case shadow_step_blocked::none: die("buggy no_landing_reason"); } } @@ -1203,7 +1203,7 @@ bool targeter_shadow_step::valid_landing(coord_def a, bool check_invis) if (!agent->is_habitable(a)) { - blocked_landing_reason = BLOCKED_MOVE; + blocked_landing_reason = shadow_step_blocked::move; return false; } if (agent->is_player()) @@ -1211,20 +1211,20 @@ bool targeter_shadow_step::valid_landing(coord_def a, bool check_invis) monster* beholder = you.get_beholder(a); if (beholder) { - blocked_landing_reason = BLOCKED_MOVE; + blocked_landing_reason = shadow_step_blocked::move; return false; } monster* fearmonger = you.get_fearmonger(a); if (fearmonger) { - blocked_landing_reason = BLOCKED_MOVE; + blocked_landing_reason = shadow_step_blocked::move; return false; } } if (!find_ray(agent->pos(), a, ray, opc_solid_see)) { - blocked_landing_reason = BLOCKED_PATH; + blocked_landing_reason = shadow_step_blocked::path; return false; } @@ -1238,7 +1238,7 @@ bool targeter_shadow_step::valid_landing(coord_def a, bool check_invis) { if (act && (!check_invis || agent->can_see(*act))) { - blocked_landing_reason = BLOCKED_OCCUPIED; + blocked_landing_reason = shadow_step_blocked::occupied; return false; } break; @@ -1309,11 +1309,11 @@ void targeter_shadow_step::get_additional_sites(coord_def a) || !agent->can_see(*victim) || !victim->umbraed()) { - no_landing_reason = BLOCKED_NO_TARGET; + no_landing_reason = shadow_step_blocked::no_target; return; } - no_landing_reason = BLOCKED_NONE; + no_landing_reason = shadow_step_blocked::none; for (adjacent_iterator ai(a, false); ai; ++ai) { // See if site is valid, record a putative reason for why no sites were @@ -1323,7 +1323,7 @@ void targeter_shadow_step::get_additional_sites(coord_def a) if (valid_landing(*ai)) { temp_sites.insert(*ai); - no_landing_reason = BLOCKED_NONE; + no_landing_reason = shadow_step_blocked::none; } else no_landing_reason = blocked_landing_reason; @@ -1604,3 +1604,76 @@ aff_type targeter_monster_sequence::is_affected(coord_def loc) return on_path ? AFF_TRACER : AFF_NO; } + +targeter_overgrow::targeter_overgrow() +{ + agent = &you; + origin = you.pos(); +} + +bool targeter_overgrow::overgrow_affects_pos(const coord_def &p) +{ + if (!in_bounds(p)) + return false; + if (env.markers.property_at(p, MAT_ANY, "veto_shatter") == "veto") + return false; + + const dungeon_feature_type feat = grd(p); + if (feat_is_open_door(feat)) + { + const monster* const mons = monster_at(p); + if (mons && agent && agent->can_see(*mons)) + return false; + + return true; + } + + return feat_is_diggable(feat) + || feat_is_closed_door(feat) + || feat_is_tree(feat) + || (feat_is_wall(feat) && !feat_is_permarock(feat)); +} + +aff_type targeter_overgrow::is_affected(coord_def loc) +{ + if (affected_positions.count(loc)) + return AFF_YES; + + return AFF_NO; +} + +bool targeter_overgrow::valid_aim(coord_def a) +{ + if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS)) + { + if (agent && agent->see_cell(a)) + return notify_fail("There's something in the way."); + return notify_fail("You cannot see that place."); + } + + if (!overgrow_affects_pos(a)) + return notify_fail("You cannot grow anything here."); + + return true; +} + +bool targeter_overgrow::set_aim(coord_def a) +{ + affected_positions.clear(); + + if (!targeter::set_aim(a)) + return false; + + if (overgrow_affects_pos(a)) + affected_positions.insert(a); + else + return false; + + for (adjacent_iterator ai(a, true); ai; ++ai) + { + if (overgrow_affects_pos(*ai) && you.see_cell_no_trans(*ai)) + affected_positions.insert(*ai); + } + + return affected_positions.size(); +} diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h index b9f8bb0a4850..6c0f6acd172f 100644 --- a/crawl-ref/source/target.h +++ b/crawl-ref/source/target.h @@ -225,13 +225,13 @@ class targeter_spray : public targeter int _range; }; -enum shadow_step_block_reason +enum class shadow_step_blocked { - BLOCKED_NONE, - BLOCKED_OCCUPIED, - BLOCKED_MOVE, - BLOCKED_PATH, - BLOCKED_NO_TARGET, + none, + occupied, + move, + path, + no_target, }; class targeter_shadow_step : public targeter @@ -250,8 +250,8 @@ class targeter_shadow_step : public targeter void set_additional_sites(coord_def a); void get_additional_sites(coord_def a); bool valid_landing(coord_def a, bool check_invis = true); - shadow_step_block_reason no_landing_reason; - shadow_step_block_reason blocked_landing_reason; + shadow_step_blocked no_landing_reason; + shadow_step_blocked blocked_landing_reason; set temp_sites; int range; }; @@ -324,3 +324,15 @@ class targeter_dig : public targeter_beam private: map aim_test_cache; }; + +class targeter_overgrow: public targeter +{ +public: + targeter_overgrow(); + bool valid_aim(coord_def a) override; + aff_type is_affected(coord_def loc) override; + bool set_aim(coord_def a) override; + set affected_positions; +private: + bool overgrow_affects_pos(const coord_def &p); +}; diff --git a/crawl-ref/source/teleport.cc b/crawl-ref/source/teleport.cc index 00c5390448a6..a1f2be658393 100644 --- a/crawl-ref/source/teleport.cc +++ b/crawl-ref/source/teleport.cc @@ -277,7 +277,7 @@ void monster_teleport(monster* mons, bool instan, bool silent) // delayed, the monster isn't hostile) we still want to give // a message. activity_interrupt_data ai(mons, SC_TELEPORT_IN); - if (!interrupt_activity(AI_SEE_MONSTER, ai)) + if (!interrupt_activity(activity_interrupt::see_monster, ai)) simple_monster_message(*mons, " appears out of thin air!"); } } diff --git a/crawl-ref/source/terrain.cc b/crawl-ref/source/terrain.cc index cedb4ca8fc30..b5741e714b2d 100644 --- a/crawl-ref/source/terrain.cc +++ b/crawl-ref/source/terrain.cc @@ -541,7 +541,9 @@ static const pair _god_altars[] = { GOD_GOZAG, DNGN_ALTAR_GOZAG }, { GOD_QAZLAL, DNGN_ALTAR_QAZLAL }, { GOD_RU, DNGN_ALTAR_RU }, +#if TAG_MAJOR_VERSION == 34 { GOD_PAKELLAS, DNGN_ALTAR_PAKELLAS }, +#endif { GOD_USKAYAW, DNGN_ALTAR_USKAYAW }, { GOD_HEPLIAKLQANA, DNGN_ALTAR_HEPLIAKLQANA }, { GOD_WU_JIAN, DNGN_ALTAR_WU_JIAN }, @@ -1075,7 +1077,10 @@ void dgn_move_entities_at(coord_def src, coord_def dst, move_item_stack_to_grid(src, dst); if (cell_is_solid(dst)) + { delete_cloud(src); + delete_cloud(dst); // in case there was already a clear there + } else move_cloud(src, dst); @@ -1142,34 +1147,33 @@ static void _dgn_check_terrain_items(const coord_def &pos, bool preserve_items) } } -static void _dgn_check_terrain_monsters(const coord_def &pos) +static bool _dgn_check_terrain_monsters(const coord_def &pos) { if (monster* m = monster_at(pos)) + { m->apply_location_effects(pos); + return true; + } + else + return false; } -// Clear blood or mold off of terrain that shouldn't have it. Also clear -// of blood if a bloody wall has been dug out and replaced by a floor, -// or if a bloody floor has been replaced by a wall. +// Clear blood or off of terrain that shouldn't have it. Also clear of blood if +// a bloody wall has been dug out and replaced by a floor, or if a bloody floor +// has been replaced by a wall. static void _dgn_check_terrain_covering(const coord_def &pos, dungeon_feature_type old_feat, dungeon_feature_type new_feat) { - if (!testbits(env.pgrid(pos), FPROP_BLOODY) - && !is_moldy(pos)) - { + if (!testbits(env.pgrid(pos), FPROP_BLOODY)) return; - } if (new_feat == DNGN_UNSEEN) { // Caller has already changed the grid, and old_feat is actually // the new feat. if (old_feat != DNGN_FLOOR && !feat_is_solid(old_feat)) - { env.pgrid(pos) &= ~(FPROP_BLOODY); - remove_mold(pos); - } } else { @@ -1178,7 +1182,6 @@ static void _dgn_check_terrain_covering(const coord_def &pos, || feat_is_critical(new_feat)) { env.pgrid(pos) &= ~(FPROP_BLOODY); - remove_mold(pos); } } } @@ -1221,6 +1224,8 @@ void dungeon_terrain_changed(const coord_def &pos, { if (grd(pos) == nfeat) return; + if (_dgn_check_terrain_monsters(pos) && feat_is_wall(nfeat)) + return; _dgn_check_terrain_covering(pos, grd(pos), nfeat); @@ -1246,7 +1251,6 @@ void dungeon_terrain_changed(const coord_def &pos, } _dgn_check_terrain_items(pos, preserve_items); - _dgn_check_terrain_monsters(pos); if (!wizmode) _dgn_check_terrain_player(pos); if (!temporary && feature_mimic_at(pos)) @@ -1847,8 +1851,6 @@ void destroy_wall(const coord_def& p) if (is_bloodcovered(p)) env.pgrid(p) &= ~(FPROP_BLOODY); - remove_mold(p); - _revert_terrain_to_floor(p); env.level_map_mask(p) |= MMT_TURNED_TO_FLOOR; } @@ -2103,12 +2105,10 @@ static dungeon_feature_type _destroyed_feat_type() static bool _revert_terrain_to_floor(coord_def pos) { dungeon_feature_type newfeat = _destroyed_feat_type(); - bool found_marker = false; for (map_marker *marker : env.markers.get_markers_at(pos)) { if (marker->get_type() == MAT_TERRAIN_CHANGE) { - found_marker = true; map_terrain_change_marker* tmarker = dynamic_cast(marker); @@ -2140,11 +2140,8 @@ static bool _revert_terrain_to_floor(coord_def pos) grd(pos) = newfeat; set_terrain_changed(pos); - if (found_marker) - { - tile_clear_flavour(pos); - tile_init_flavour(pos); - } + tile_clear_flavour(pos); + tile_init_flavour(pos); return true; } diff --git a/crawl-ref/source/test/d1_vaults.lua b/crawl-ref/source/test/d1_vaults.lua deleted file mode 100644 index c6cba3fb0897..000000000000 --- a/crawl-ref/source/test/d1_vaults.lua +++ /dev/null @@ -1,40 +0,0 @@ --- this test prints out the D:1-D:4 vaults for some test seeds. This can be used --- to diagnose when seeding changes or isn't working right. - --- this test messes with RNG state! It leaves the RNG with a clean seed 1, --- but if you are expecting deterministic test behavior, take that into --- account. - -local eol = string.char(13) - -for seed = 1,10 do - crawl.stderr("Seed: " .. seed .. eol) - debug.reset_rng(seed) - -- debug.enter_dungeon() crashes, for some reason - -- the following runs through RNG state in the same way that starting a - -- regular game would -- if it produces different results, that's a bug. - debug.flush_map_memory() - debug.dungeon_setup() - debug.goto_place("D:1") - debug.generate_level() - local vault_names = debug.vault_names() - - crawl.stderr(" D:1 vaults for seed " .. seed .. " are: " .. vault_names .. eol) - debug.goto_place("D:2") - debug.generate_level() - local vault_names = debug.vault_names() - - crawl.stderr(" D:2 vaults for seed " .. seed .. " are: " .. vault_names .. eol) - debug.goto_place("D:3") - debug.generate_level() - local vault_names = debug.vault_names() - - crawl.stderr(" D:3 vaults for seed " .. seed .. " are: " .. vault_names .. eol) - debug.goto_place("D:4") - debug.generate_level() - local vault_names = debug.vault_names() - - crawl.stderr(" D:4 vaults for seed " .. seed .. " are: " .. vault_names .. eol) -end - -debug.reset_rng(1) diff --git a/crawl-ref/source/test/rng_test.lua b/crawl-ref/source/test/rng_test.lua index 151257c7cbb8..b449a65c98d0 100644 --- a/crawl-ref/source/test/rng_test.lua +++ b/crawl-ref/source/test/rng_test.lua @@ -3,7 +3,16 @@ local eol = string.char(13) -local results = {49, 97, 51, 75, 3, 38, 66, 84, 28, 76} +function big_rng_test() + crawl.stderr("Running big rng test, this may take a while...") + for i = 1, 0xffffffff do + crawl.random2(i) + end + debug.reset_rng(1) + +end + +local results = {51, 11, 78, 21, 19, 95, 21, 43, 33, 75} debug.reset_rng(1) crawl.stderr("Here are 10 random numbers from seed 1:" .. eol) @@ -15,3 +24,5 @@ end crawl.stderr(eol) debug.reset_rng(1) + +--big_rng_test() diff --git a/crawl-ref/source/test/seed_explorer.lua b/crawl-ref/source/test/seed_explorer.lua new file mode 100644 index 000000000000..5ddbf744ed3c --- /dev/null +++ b/crawl-ref/source/test/seed_explorer.lua @@ -0,0 +1,38 @@ +-- test to exercise seed explorer: look at the first 5 levels of 5 seeds. + +crawl_require('dlua/explorer.lua') + +-- to use a >32bit seed, you will need to use a string here. If you use a +-- string, `fixed_seed` is maxed at 1. +local starting_seed = 1 -- fixed seed to start with, number or string +local fixed_seeds = 5 -- how many fixed seeds to run? Can be 0. +local rand_seeds = 0 -- how many random seeds to run + + +-- this variable determines how deep in the generation order to go +--local max_depth = #explorer.generation_order +local max_depth = 5 + +if fixed_seeds > 0 then + local seed_seq = { starting_seed } + + if (type(starting_seed) ~= "string") then + for i=starting_seed + 1, starting_seed + fixed_seeds - 1 do + seed_seq[#seed_seq + 1] = i + end + end + explorer.catalog_seeds(seed_seq, max_depth, explorer.available_categories) +end + +if rand_seeds > 0 then + local rand_seq = { } + math.randomseed(crawl.millis()) + for i=1,rand_seeds do + -- intentional use of non-crawl random(). random doesn't seem to accept + -- anything bigger than 32 bits for the range. + rand_seed = math.random(0x7FFFFFFF) + rand_seq[#rand_seq + 1] = rand_seed + end + crawl.stderr("Exploring " .. rand_seeds .. " random seed(s).") + explorer.catalog_seeds(rand_seq, max_depth, explorer.available_categories) +end diff --git a/crawl-ref/source/test/stress/fireworks.rc b/crawl-ref/source/test/stress/fireworks.rc index 34dbd33ad849..f2449ebf8b75 100644 --- a/crawl-ref/source/test/stress/fireworks.rc +++ b/crawl-ref/source/test/stress/fireworks.rc @@ -7,6 +7,7 @@ species = mu background = ar restart_after_game = false show_more = false +pregen_dungeon = false #message_colour = mute:.* : bot_start = true diff --git a/crawl-ref/source/test/vault_catalog.lua b/crawl-ref/source/test/vault_catalog.lua index a9503bc56764..4769f5ac1baa 100644 --- a/crawl-ref/source/test/vault_catalog.lua +++ b/crawl-ref/source/test/vault_catalog.lua @@ -9,68 +9,35 @@ -- but if you are expecting deterministic test behavior, take that into -- account. +crawl_require('dlua/explorer.lua') + local starting_seed = 1 -- fixed seed to start with local fixed_seeds = 1 -- how many fixed seeds to run? local per_seed_iters = 1 -- how many stability tests to run per seed local rand_seeds = 1 -- how many random seeds to run --- matches pregeneration order -local generation_order = { - "D:1", - "D:2", "D:3", "D:4", "D:5", "D:6", "D:7", "D:8", "D:9", - "D:10", "D:11", "D:12", "D:13", "D:14", "D:15", - "Temple", - "Lair:1", "Lair:2", "Lair:3", "Lair:4", "Lair:5", "Lair:6", - "Orc:1", "Orc:2", - "Spider:1", "Spider:2", "Spider:3", "Spider:4", - "Snake:1", "Snake:2", "Snake:3", "Snake:4", - "Shoals:1", "Shoals:2", "Shoals:3", "Shoals:4", - "Swamp:1", "Swamp:2", "Swamp:3", "Swamp:4", - "Vaults:1", "Vaults:2", "Vaults:3", "Vaults:4", "Vaults:5", - "Crypt:1", "Crypt:2", "Crypt:3", - "Depths:1", "Depths:2", "Depths:3", "Depths:4", "Depths:5", - "Hell", - "Elf:1", "Elf:2", "Elf:3", - "Zot:1", "Zot:2", "Zot:3", "Zot:4", "Zot:5", - "Slime:1", "Slime:2", "Slime:3", "Slime:4", "Slime:5", - "Tomb:1", "Tomb:2", "Tomb:3", - } - -- pregen continues: tartarus, cocytus, dis, geh - --- want to exercise these a bit -local generation_order_extended = { - "D:1", - "Tar:1", "Tar:2", "Tar:3", "Tar:4", "Tar:5", "Tar:6", "Tar:7", - "Coc:1", "Coc:2", "Coc:3", "Coc:4", "Coc:5", "Coc:6", "Coc:7", - "Dis:1", "Dis:2", "Dis:3", "Dis:4", "Dis:5", "Dis:6", "Dis:7", - "Pan", "Pan", "Pan", "Pan", "Pan", "Pan", "Pan" -} +local max_depth = #explorer.generation_order --- a bit redundant with mapstat? but simpler... -function catalog_dungeon_vaults(level_order, silent) - local result = {} - dgn.reset_level() - debug.flush_map_memory() - debug.dungeon_setup() - for i,lvl in ipairs(level_order) do - if (dgn.br_exists(string.match(lvl, "[^:]+"))) then - debug.goto_place(lvl) - debug.generate_level() - result[lvl] = debug.vault_names() - if (not silent) then - crawl.stderr(lvl .. " vaults are: ") - crawl.stderr(" " .. result[lvl]) - end - end +function catalog_dungeon_vaults(silent) + -- TODO: this doesn't do any pan levels, but the previous version did + local old_quiet = explorer.quiet + explorer.quiet = silent + local catalog = explorer.catalog_dungeon(max_depth, + { "vaults_raw" }) + explorer.quiet = old_quiet + local result = { } + for lvl, cats in pairs(catalog) do + -- eliminate the extra catalog structure we aren't using here + result[lvl] = catalog[lvl]["vaults_raw"][1] end return result end -function compare_catalogs(order, run1, run2, seed) +function compare_catalogs(run1, run2, seed) local diverge = false -- stop at the first divergence by generation order. Everything after that -- is likely to be a mess with very little signal. - for i,lvl in ipairs(order) do + for i,lvl in ipairs(explorer.generation_order) do if run1[lvl] == nil and run2[lvl] ~= nil then crawl.stderr("Run 1 is missing " .. lvl .. "!") elseif run2[lvl] == nil and run1[lvl] ~= nil then @@ -84,14 +51,14 @@ function compare_catalogs(order, run1, run2, seed) end end -function test_seed(seed, iters, order, quiet) +function test_seed(seed, iters, quiet) seed_used = debug.reset_rng(seed) if quiet then crawl.stderr(".") else crawl.stderr("Vault catalog for seed " .. seed .. ":") end - local run1 = catalog_dungeon_vaults(order, quiet) + local run1 = catalog_dungeon_vaults(quiet) if not quiet then crawl.stderr("....now testing vault generation stability for seed " ..seed.. ".") @@ -100,31 +67,42 @@ function test_seed(seed, iters, order, quiet) for i = 1,iters do crawl.stderr(".") debug.reset_rng(seed) - local run2 = catalog_dungeon_vaults(order, true) - compare_catalogs(order, run1, run2, seed) + local run2 = catalog_dungeon_vaults(true) + compare_catalogs(run1, run2, seed) end end -test_seed(starting_seed, per_seed_iters, generation_order, false) --- just do a quick check of hell / pan stability -crawl.stderr("Checking some extended branches for seed " .. starting_seed .. "...") -test_seed(starting_seed, per_seed_iters, generation_order_extended, true) +function test_seed_sequence(seq, iters, quiet, quiet_first_only) + for _,s in ipairs(seq) do + crawl.stderr("Testing seed " .. s .. ".") + test_seed(s, iters, quiet or quiet_first_only and s ~= seq[1]) + end +end -if (type(starting_seed) ~= "string") then - for i=starting_seed + 1, starting_seed + fixed_seeds - 1 do - crawl.stderr("Testing seed " .. i .. ".") - test_seed(i, per_seed_iters, generation_order, true) +if fixed_seeds > 0 then + local seeds_to_test = { starting_seed } + if (type(starting_seed) ~= "string") then + for i=starting_seed + 1, starting_seed + fixed_seeds - 1 do + seeds_to_test[#seeds_to_test + 1] = i + end end + test_seed_sequence(seeds_to_test, per_seed_iters, quiet) end +-- shorten the depth here for the sake of travis: +max_depth = explorer.zot_depth + if rand_seeds > 0 then crawl.stderr("Testing " .. rand_seeds .. " random seed(s).") + local seeds_to_test = { } + for i=1,rand_seeds do + -- intentional use of non-crawl random(). random doesn't seem to accept + -- anything bigger than 32 bits for the range. + math.randomseed(crawl.millis()) + rand_seed = math.random(0x7FFFFFFF) + seeds_to_test[#seeds_to_test + 1] = rand_seed + end + test_seed_sequence(seeds_to_test, per_seed_iters, true) end -for i=1,rand_seeds do - -- intentional use of non-crawl random(). random doesn't seem to accept - -- anything bigger than 32 bits for the range. - math.randomseed(crawl.millis()) - rand_seed = math.random(0x7FFFFFFF) - crawl.stderr("Testing random seed " .. rand_seed .. ".") - test_seed(rand_seed, per_seed_iters, generation_order, true) -end + +debug.reset_rng(1) diff --git a/crawl-ref/source/throw.cc b/crawl-ref/source/throw.cc index 77144bd1f0ca..33f1464aec8e 100644 --- a/crawl-ref/source/throw.cc +++ b/crawl-ref/source/throw.cc @@ -10,6 +10,7 @@ #include #include +#include "art-enum.h" #include "artefact.h" #include "chardump.h" #include "command.h" @@ -50,17 +51,18 @@ static int _fire_prompt_for_item(); static bool _fire_validate_item(int selected, string& err); -static int _get_blowgun_chance(const int hd); +static int _get_dart_chance(const int hd); bool is_penetrating_attack(const actor& attacker, const item_def* weapon, const item_def& projectile) { return is_launched(&attacker, weapon, projectile) != launch_retval::FUMBLED && projectile.base_type == OBJ_MISSILES - && get_ammo_brand(projectile) == SPMSL_PENETRATION + && projectile.sub_type == MI_JAVELIN || weapon && is_launched(&attacker, weapon, projectile) == launch_retval::LAUNCHED - && get_weapon_brand(*weapon) == SPWPN_PENETRATION; + && (get_weapon_brand(*weapon) == SPWPN_PENETRATION + || is_unrandom_artefact(*weapon, UNRAND_STORM_BOW)); } bool item_is_quivered(const item_def &item) @@ -283,25 +285,21 @@ vector fire_target_behaviour::get_monster_desc(const monster_info& mi) descs.emplace_back("immune to nets"); } - // Display the chance for a needle of para/confuse/sleep/frenzy + // Display the chance for a dart of para/confuse/sleep/frenzy // to affect monster - if (you.weapon() && you.weapon()->is_type(OBJ_WEAPONS, WPN_BLOWGUN)) + if (item->is_type(OBJ_MISSILES, MI_DART)) { special_missile_type brand = get_ammo_brand(*item); - if (brand == SPMSL_PARALYSIS || brand == SPMSL_CONFUSION - || brand == SPMSL_FRENZY || brand == SPMSL_SLEEP) + if (brand == SPMSL_FRENZY || brand == SPMSL_BLINDING) { - int chance = _get_blowgun_chance(mi.hd); + int chance = _get_dart_chance(mi.hd); bool immune = false; if (mi.holi & (MH_UNDEAD | MH_NONLIVING)) immune = true; - string verb = brand == SPMSL_PARALYSIS ? "paralyse" : - brand == SPMSL_CONFUSION ? "confuse" : - brand == SPMSL_FRENZY ? "frenzy" - /* SPMSL_SLEEP */ : "sleep"; + string verb = brand == SPMSL_FRENZY ? "frenzy" : "blind"; - string chance_string = immune ? "immune to needles" : + string chance_string = immune ? "immune to darts" : make_stringf("chance to %s on hit: %d%%", verb.c_str(), chance); descs.emplace_back(chance_string); @@ -312,26 +310,25 @@ vector fire_target_behaviour::get_monster_desc(const monster_info& mi) } /** - * Chance for a needle fired by the player to affect a monster of a particular - * hit dice, given the player's throwing skill and blowgun enchantment. + * Chance for a dart fired by the player to affect a monster of a particular + * hit dice, given the player's throwing skill. * * @param hd The monster's hit dice. * @return The percentage chance for the player to affect the monster, * rounded down. * - * This chance is rolled in ranged_attack::blowgun_check using this formula for + * This chance is rolled in ranged_attack::dart_check using this formula for * success: * if hd < 15, fixed 3% chance to succeed regardless of roll * else, or if the 3% chance fails, - * succeed if 2 + random2(4 + skill + enchantment) >= hd + * succeed if 2 + random2(4 + (2/3)*(throwing + stealth) ) >= hd */ -static int _get_blowgun_chance(const int hd) +static int _get_dart_chance(const int hd) { - ASSERT(you.weapon()->is_type(OBJ_WEAPONS, WPN_BLOWGUN)); - const int plus = you.weapon()->plus; - const int skill = you.skill_rdiv(SK_THROWING); + const int pow = (2 * (you.skill_rdiv(SK_THROWING) + + you.skill_rdiv(SK_STEALTH))) / 3; - int chance = 10000 - 10000 * (hd - 2) / (4 + skill + plus); + int chance = 10000 - 10000 * (hd - 2) / (4 + pow); chance = min(max(chance, 0), 10000); if (hd < 15) { @@ -420,7 +417,7 @@ static int _fire_prompt_for_item() return -1; int slot = prompt_invent_item("Fire/throw which item? (* to show all)", - MT_INVLIST, + menu_type::invlist, OSEL_THROWABLE, OPER_FIRE); if (slot == PROMPT_ABORT || slot == PROMPT_NOTHING) @@ -474,7 +471,10 @@ bool fire_warn_if_impossible(bool silent) mprf("You cannot throw anything while %s.", held_status()); return true; } - else if (weapon->sub_type != WPN_BLOWGUN) + else +#if TAG_MAJOR_VERSION == 34 + if (weapon->sub_type != WPN_BLOWGUN) +#endif { if (!silent) { @@ -674,7 +674,7 @@ static bool _setup_missile_beam(const actor *agent, bolt &beam, item_def &item, } returning = item.base_type == OBJ_MISSILES - && get_ammo_brand(item) == SPMSL_RETURNING; + && item.sub_type == MI_BOOMERANG; if (item.base_type == OBJ_MISSILES && get_ammo_brand(item) == SPMSL_EXPLODING) @@ -721,15 +721,11 @@ static void _throw_noise(actor* act, const bolt &pbolt, const item_def &ammo) if (is_launched(act, launcher, ammo) != launch_retval::LAUNCHED) return; - // Throwing and blowguns are silent... int level = 0; const char* msg = nullptr; switch (launcher->sub_type) { - case WPN_BLOWGUN: - return; - case WPN_HUNTING_SLING: level = 1; msg = "You hear a whirring sound."; @@ -858,6 +854,11 @@ bool throw_it(bolt &pbolt, int throw_2, dist *target) monster *am = monster_at(*ai); if (am) cancelled = stop_attack_prompt(am, false, *ai); + else if (*ai == you.pos()) + { + cancelled = !yesno("That is likely to hit you. Continue anyway?", + false, 'n'); + } } } } @@ -958,15 +959,6 @@ bool throw_it(bolt &pbolt, int throw_2, dist *target) if (teleport) returning = false; - if (returning && projected != launch_retval::FUMBLED) - { - const skill_type sk = - projected == launch_retval::THROWN ? SK_THROWING - : item_attack_skill(*you.weapon()); - if (!one_chance_in(1 + skill_bump(sk))) - did_return = true; - } - you.time_taken = you.attack_delay(&item).roll(); // Create message. @@ -1010,14 +1002,14 @@ bool throw_it(bolt &pbolt, int throw_2, dist *target) Hints.hints_throw_counter++; // Dropping item copy, since the launched item might be different. - pbolt.drop_item = !did_return; + pbolt.drop_item = !returning; pbolt.fire(); hit = !pbolt.hit_verb.empty(); // The item can be destroyed before returning. - if (did_return && thrown_object_destroyed(&item, pbolt.target)) - did_return = false; + if (returning && thrown_object_destroyed(&item, pbolt.target)) + returning = false; } if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) @@ -1029,28 +1021,15 @@ bool throw_it(bolt &pbolt, int throw_2, dist *target) if (ammo_brand == SPMSL_FRENZY) did_god_conduct(DID_HASTY, 6 + random2(3), true); - if (did_return) + if (returning) { // Fire beam in reverse. pbolt.setup_retrace(); viewwindow(); pbolt.fire(); - - msg::stream << item.name(DESC_THE) << " returns to your pack!" - << endl; - - // Player saw the item return. - if (!is_artefact(you.inv[throw_2])) - set_ident_flags(you.inv[throw_2], ISFLAG_KNOW_TYPE); } else { - // Should have returned but didn't. - if (returning && item_type_known(you.inv[throw_2])) - { - msg::stream << item.name(DESC_THE) - << " fails to return to your pack!" << endl; - } dec_inv_item_quantity(throw_2, 1); if (unwielded) canned_msg(MSG_EMPTY_HANDED_NOW); @@ -1071,7 +1050,7 @@ bool throw_it(bolt &pbolt, int throw_2, dist *target) && projected != launch_retval::FUMBLED && will_have_passive(passive_t::shadow_attacks) && thrown.base_type == OBJ_MISSILES - && thrown.sub_type != MI_NEEDLE) + && thrown.sub_type != MI_DART) { dithmenos_shadow_throw(thr, item); } @@ -1174,14 +1153,7 @@ bool mons_throw(monster* mons, bolt &beam, int msl, bool teleport) _throw_noise(mons, beam, item); - // decrease inventory - bool really_returns; - if (returning && !one_chance_in(mons_power(mons->type) + 3)) - really_returns = true; - else - really_returns = false; - - beam.drop_item = !really_returns; + beam.drop_item = !returning; // Redraw the screen before firing, in case the monster just // came into view and the screen hasn't been updated yet. @@ -1191,7 +1163,7 @@ bool mons_throw(monster* mons, bolt &beam, int msl, bool teleport) beam.use_target_as_pos = true; beam.affect_cell(); beam.affect_endpoint(); - if (!really_returns) + if (!returning) beam.drop_object(); } else @@ -1199,11 +1171,11 @@ bool mons_throw(monster* mons, bolt &beam, int msl, bool teleport) beam.fire(); // The item can be destroyed before returning. - if (really_returns && thrown_object_destroyed(&item, beam.target)) - really_returns = false; + if (returning && thrown_object_destroyed(&item, beam.target)) + returning = false; } - if (really_returns) + if (returning) { // Fire beam in reverse. beam.setup_retrace(); diff --git a/crawl-ref/source/throw.h b/crawl-ref/source/throw.h index bf2f3db00fd4..530f416746df 100644 --- a/crawl-ref/source/throw.h +++ b/crawl-ref/source/throw.h @@ -13,15 +13,12 @@ enum fire_type { FIRE_NONE = 0x0000, FIRE_LAUNCHER = 0x0001, -#if TAG_MAJOR_VERSION == 34 FIRE_DART = 0x0002, -#endif FIRE_STONE = 0x0004, FIRE_JAVELIN = 0x0010, FIRE_ROCK = 0x0100, FIRE_NET = 0x0200, - FIRE_RETURNING = 0x0400, - FIRE_TOMAHAWK = 0x0800, + FIRE_BOOMERANG = 0x0800, FIRE_INSCRIBED = 0x1000, // Only used for _get_fire_order }; diff --git a/crawl-ref/source/tilebuf.cc b/crawl-ref/source/tilebuf.cc index 30b75f24b6e1..343ad31174fd 100644 --- a/crawl-ref/source/tilebuf.cc +++ b/crawl-ref/source/tilebuf.cc @@ -302,7 +302,7 @@ void ShapeBuffer::add(float pos_sx, float pos_sy, float pos_ex, float pos_ey, ///////////////////////////////////////////////////////////////////////////// // LineBuffer -LineBuffer::LineBuffer() : VertBuffer(false, true, nullptr, GLW_LINES) +LineBuffer::LineBuffer() : VertBuffer(false, true, nullptr) { m_state.array_colour = true; } @@ -318,13 +318,13 @@ void LineBuffer::add(float pos_sx, float pos_sy, float pos_ex, float pos_ey, void LineBuffer::add_square(float sx, float sy, float ex, float ey, const VColour &col) { - GLW_3VF scale; - glmanager->get_transform(nullptr, &scale); - float dx = 1.0/scale.x, dy = 1.0/scale.y; - add(sx-dx, sy, ex-dx, sy, col); - add(ex, sy-dy, ex, ey-dy, col); - add(ex, ey, sx, ey, col); - add(sx, ey, sx, sy, col); + const float dx = 1, dy = 1; // line thickness + const float tx = 1, ty = 1; // shift used to prevent corner overlap + + add(sx-dx, sy-ty, ex-dx, sy, col); + add(ex-tx, sy-dy, ex, ey-dy, col); + add(ex, ey-ty, sx, ey, col); + add(sx-tx, ey, sx, sy, col); } #endif diff --git a/crawl-ref/source/tilebuf.h b/crawl-ref/source/tilebuf.h index 6b78e04027b6..f4a256613c2e 100644 --- a/crawl-ref/source/tilebuf.h +++ b/crawl-ref/source/tilebuf.h @@ -114,6 +114,7 @@ class LineBuffer : public VertBuffer { public: LineBuffer(); - void add(float sx, float sy, float ex, float ey, const VColour &c); void add_square(float sx, float sy, float ex, float ey, const VColour &c); +private: + void add(float sx, float sy, float ex, float ey, const VColour &c); }; diff --git a/crawl-ref/source/tilecell.cc b/crawl-ref/source/tilecell.cc index fa800b316734..c494c3a1033c 100644 --- a/crawl-ref/source/tilecell.cc +++ b/crawl-ref/source/tilecell.cc @@ -2,12 +2,14 @@ #ifdef USE_TILE #include "tilecell.h" -#include "cloud.h" +#include "colour.h" #include "coord.h" #include "coordit.h" -#include "env.h" #include "terrain.h" +#include "tile-flags.h" #include "tiledef-dngn.h" +#include "tiledef-main.h" +#include "viewgeom.h" void packed_cell::clear() { @@ -15,6 +17,7 @@ void packed_cell::clear() fg = 0; bg = 0; cloud = 0; + map_knowledge.clear(); flv.floor_idx = 0; flv.wall_idx = 0; @@ -24,11 +27,10 @@ void packed_cell::clear() flv.feat = 0; flv.special = 0; + is_highlighted_summoner = false; is_bloody = false; is_silenced = false; halo = HALO_NONE; - is_moldy = false; - glowing_mold = false; is_sanctuary = false; is_liquefied = false; mangrove_water = false; @@ -48,12 +50,11 @@ bool packed_cell::operator ==(const packed_cell &other) const if (fg != other.fg) return false; if (bg != other.bg) return false; if (cloud != other.cloud) return false; + if (map_knowledge != other.map_knowledge) return false; if (is_bloody != other.is_bloody) return false; if (is_silenced != other.is_silenced) return false; if (halo != other.halo) return false; - if (is_moldy != other.is_moldy) return false; - if (glowing_mold != other.glowing_mold) return false; if (is_sanctuary != other.is_sanctuary) return false; if (is_liquefied != other.is_liquefied) return false; if (mangrove_water != other.mangrove_water) return false; @@ -81,12 +82,12 @@ enum wave_type WV_DEEP, }; -static void _add_overlay(int tileidx, packed_cell *cell) +static void _add_overlay(int tileidx, packed_cell& cell) { - cell->dngn_overlay[cell->num_dngn_overlay++] = tileidx; + cell.dngn_overlay[cell.num_dngn_overlay++] = tileidx; } -typedef bool (*map_predicate) (const coord_def&); +typedef bool (*map_predicate) (const coord_def&, crawl_view_buffer& vbuf); static coord_def overlay_directions[] = { @@ -100,15 +101,17 @@ static coord_def overlay_directions[] = coord_def(-1, 1) }; -static void _add_directional_overlays(const coord_def& gc, packed_cell* cell, +static void _add_directional_overlays(const coord_def& gc, crawl_view_buffer& vbuf, tileidx_t tile, map_predicate pred, uint8_t tile_mask = 0xFF) { + auto& cell = vbuf(gc).tile; + uint8_t dir_mask = 0; for (int i = 0; i < 8; ++i) { - if (!pred(gc + overlay_directions[i])) + if (!pred(gc + overlay_directions[i], vbuf)) continue; if (i > 3) @@ -137,11 +140,18 @@ static void _add_directional_overlays(const coord_def& gc, packed_cell* cell, } } -static void _pack_shoal_waves(const coord_def &gc, packed_cell *cell) +static bool _feat_has_ink(coord_def gc, crawl_view_buffer& vbuf) +{ + return vbuf(gc).tile.map_knowledge.cloud() == CLOUD_INK; +} + +static void _pack_shoal_waves(const coord_def &gc, crawl_view_buffer& vbuf) { + auto& cell = vbuf(gc).tile; + // Add wave tiles on floor adjacent to shallow water. - const dungeon_feature_type feat = env.map_knowledge(gc).feat(); - const bool feat_has_ink = (cloud_type_at(coord_def(gc)) == CLOUD_INK); + const auto feat = cell.map_knowledge.feat(); + const bool feat_has_ink = _feat_has_ink(gc, vbuf); if (feat == DNGN_DEEP_WATER && feat_has_ink) { @@ -164,13 +174,17 @@ static void _pack_shoal_waves(const coord_def &gc, packed_cell *cell) for (adjacent_iterator ri(gc, true); ri; ++ri) { - if (!env.map_knowledge(*ri).seen() && !env.map_knowledge(*ri).mapped()) + if (ri->x < 0 || ri->x >= vbuf.size().x || ri->y < 0 || ri->y >= vbuf.size().y) + continue; + const map_cell& adj_knowledge = vbuf(*ri).tile.map_knowledge; + if (!adj_knowledge.seen() && !adj_knowledge.mapped()) continue; - const bool ink = (cloud_type_at(coord_def(*ri)) == CLOUD_INK); + const bool ink = _feat_has_ink(*ri, vbuf); + const auto adj_feat = adj_knowledge.feat(); wave_type wt = WV_NONE; - if (env.map_knowledge(*ri).feat() == DNGN_SHALLOW_WATER) + if (adj_feat == DNGN_SHALLOW_WATER) { // Adjacent shallow water is only interesting for // floor cells. @@ -180,7 +194,7 @@ static void _pack_shoal_waves(const coord_def &gc, packed_cell *cell) if (feat != DNGN_SHALLOW_WATER) wt = WV_SHALLOW; } - else if (env.map_knowledge(*ri).feat() == DNGN_DEEP_WATER) + else if (adj_feat == DNGN_DEEP_WATER) wt = WV_DEEP; else continue; @@ -325,12 +339,12 @@ static void _pack_shoal_waves(const coord_def &gc, packed_cell *cell) } } -static dungeon_feature_type _safe_feat(coord_def gc) +static dungeon_feature_type _safe_feat(coord_def gc, crawl_view_buffer& vbuf) { - if (!map_bounds(gc)) + if (gc.x < 0 || gc.x >= vbuf.size().x || gc.y < 0 || gc.y >= vbuf.size().y) return DNGN_UNSEEN; - return env.map_knowledge(gc).feat(); + return vbuf(gc).tile.map_knowledge.feat(); } static bool _feat_is_mangrove(dungeon_feature_type feat) @@ -338,80 +352,67 @@ static bool _feat_is_mangrove(dungeon_feature_type feat) return feat == DNGN_TREE && player_in_branch(BRANCH_SWAMP); } -static bool _is_seen_land(coord_def gc) +static bool _is_seen_land(coord_def gc, crawl_view_buffer& vbuf) { - const dungeon_feature_type feat = _safe_feat(gc); + const auto feat = _safe_feat(gc, vbuf); return feat != DNGN_UNSEEN && !feat_is_water(feat) && !feat_is_lava(feat) && !_feat_is_mangrove(feat); } -static bool _is_seen_shallow(coord_def gc) +static bool _is_seen_shallow(coord_def gc, crawl_view_buffer& vbuf) { - const dungeon_feature_type feat = _safe_feat(gc); + const auto feat = _safe_feat(gc, vbuf); + + if (!vbuf(gc).tile.map_knowledge.seen()) + return false; return feat == DNGN_SHALLOW_WATER || _feat_is_mangrove(feat); } -static void _pack_default_waves(const coord_def &gc, packed_cell *cell) +static tileidx_t _base_wave_tile(colour_t colour) { + switch (colour) + { + case BLACK: return TILE_DNGN_WAVE_N; + case GREEN: return TILE_MURKY_WAVE_N; + default: die("no %s deep water wave tiles", colour_to_str(colour).c_str()); + } +} + +static void _pack_default_waves(const coord_def &gc, crawl_view_buffer& vbuf) +{ + auto& cell = vbuf(gc).tile; // Any tile on water with an adjacent solid tile will get an extra // bit of shoreline. - dungeon_feature_type feat = env.map_knowledge(gc).feat(); + auto feat = cell.map_knowledge.feat(); + auto colour = cell.map_knowledge.feat_colour(); // Treat trees in Swamp as though they were shallow water. - if (cell->mangrove_water && feat == DNGN_TREE) + if (cell.mangrove_water && feat == DNGN_TREE) feat = DNGN_SHALLOW_WATER; if (!feat_is_water(feat) && !feat_is_lava(feat)) return; - if (feat == DNGN_DEEP_WATER && !env.grid_colours(gc)) + if (feat == DNGN_DEEP_WATER && (colour == BLACK || colour == GREEN)) { - if (_is_seen_shallow(coord_def(gc.x, gc.y - 1))) - _add_overlay(TILE_DNGN_WAVE_N, cell); - if (_is_seen_shallow(coord_def(gc.x + 1, gc.y - 1))) - _add_overlay(TILE_DNGN_WAVE_NE, cell); - if (_is_seen_shallow(coord_def(gc.x + 1, gc.y))) - _add_overlay(TILE_DNGN_WAVE_E, cell); - if (_is_seen_shallow(coord_def(gc.x + 1, gc.y + 1))) - _add_overlay(TILE_DNGN_WAVE_SE, cell); - if (_is_seen_shallow(coord_def(gc.x, gc.y + 1))) - _add_overlay(TILE_DNGN_WAVE_S, cell); - if (_is_seen_shallow(coord_def(gc.x - 1, gc.y + 1))) - _add_overlay(TILE_DNGN_WAVE_SW, cell); - if (_is_seen_shallow(coord_def(gc.x - 1, gc.y))) - _add_overlay(TILE_DNGN_WAVE_W, cell); - if (_is_seen_shallow(coord_def(gc.x - 1, gc.y - 1))) - _add_overlay(TILE_DNGN_WAVE_NW, cell); - } - - // Sewer water - if (feat == DNGN_DEEP_WATER && env.grid_colours(gc) == GREEN) - { - if (_is_seen_shallow(coord_def(gc.x, gc.y - 1))) - _add_overlay(TILE_MURKY_WAVE_N, cell); - if (_is_seen_shallow(coord_def(gc.x + 1, gc.y - 1))) - _add_overlay(TILE_MURKY_WAVE_NE, cell); - if (_is_seen_shallow(coord_def(gc.x + 1, gc.y))) - _add_overlay(TILE_MURKY_WAVE_E, cell); - if (_is_seen_shallow(coord_def(gc.x + 1, gc.y + 1))) - _add_overlay(TILE_MURKY_WAVE_SE, cell); - if (_is_seen_shallow(coord_def(gc.x, gc.y + 1))) - _add_overlay(TILE_MURKY_WAVE_S, cell); - if (_is_seen_shallow(coord_def(gc.x - 1, gc.y + 1))) - _add_overlay(TILE_MURKY_WAVE_SW, cell); - if (_is_seen_shallow(coord_def(gc.x - 1, gc.y))) - _add_overlay(TILE_MURKY_WAVE_W, cell); - if (_is_seen_shallow(coord_def(gc.x - 1, gc.y - 1))) - _add_overlay(TILE_MURKY_WAVE_NW, cell); + // +7 and -- reverse the iteration order + int tile = _base_wave_tile(colour) + 7; + for (adjacent_iterator ai(gc); ai; ++ai, --tile) + { + if (ai->x < 0 || ai->x >= vbuf.size().x || ai->y < 0 || ai->y >= vbuf.size().y) + continue; + if (_is_seen_shallow(*ai, vbuf)) + _add_overlay(tile, cell); + } } - bool north = _is_seen_land(coord_def(gc.x, gc.y - 1)); - bool west = _is_seen_land(coord_def(gc.x - 1, gc.y)); - bool east = _is_seen_land(coord_def(gc.x + 1, gc.y)); + bool north = _is_seen_land(coord_def(gc.x, gc.y - 1), vbuf); + bool west = _is_seen_land(coord_def(gc.x - 1, gc.y), vbuf); + bool east = _is_seen_land(coord_def(gc.x + 1, gc.y), vbuf); - if (north || west || east && (!env.grid_colours(gc) || env.grid_colours(gc) == LIGHTGREEN)) + if (north || west || east && (colour == BLACK || colour == LIGHTGREEN)) { if (north) _add_overlay(TILE_SHORE_N, cell); @@ -426,71 +427,77 @@ static void _pack_default_waves(const coord_def &gc, packed_cell *cell) } } -static bool _is_seen_wall(coord_def gc) +static bool _is_seen_wall(coord_def gc, crawl_view_buffer& vbuf) { - const dungeon_feature_type feat = _safe_feat(gc); + const auto feat = _safe_feat(gc, vbuf); return (feat_is_opaque(feat) || feat_is_wall(feat)) && feat != DNGN_TREE && feat != DNGN_UNSEEN; } -static void _pack_wall_shadows(const coord_def &gc, packed_cell *cell, +static void _pack_wall_shadows(const coord_def &gc, crawl_view_buffer& vbuf, tileidx_t tile) { - if (_is_seen_wall(gc) || _safe_feat(gc) == DNGN_GRATE) + if (_is_seen_wall(gc, vbuf) || _safe_feat(gc, vbuf) == DNGN_GRATE) return; + auto& cell = vbuf(gc).tile; + bool ne = 0; bool nw = 0; int offset; // orthogonals - if (_is_seen_wall(coord_def(gc.x - 1, gc.y))) + if (_is_seen_wall(coord_def(gc.x - 1, gc.y), vbuf)) { - offset = _is_seen_wall(coord_def(gc.x - 1, gc.y - 1)) ? 0 : 5; + offset = _is_seen_wall(coord_def(gc.x - 1, gc.y - 1), vbuf) ? 0 : 5; _add_overlay(tile + offset, cell); nw = 1; } - if (_is_seen_wall(coord_def(gc.x, gc.y - 1))) + if (_is_seen_wall(coord_def(gc.x, gc.y - 1), vbuf)) { _add_overlay(tile + 2, cell); ne = 1; nw = 1; } - if (_is_seen_wall(coord_def(gc.x + 1, gc.y))) + if (_is_seen_wall(coord_def(gc.x + 1, gc.y), vbuf)) { - offset = _is_seen_wall(coord_def(gc.x + 1, gc.y - 1)) ? 4 : 6; + offset = _is_seen_wall(coord_def(gc.x + 1, gc.y - 1), vbuf) ? 4 : 6; _add_overlay(tile + offset, cell); ne = 1; } // corners - if (nw == 0 && _is_seen_wall(coord_def(gc.x - 1, gc.y - 1))) + if (nw == 0 && _is_seen_wall(coord_def(gc.x - 1, gc.y - 1), vbuf)) _add_overlay(tile + 1, cell); - if (ne == 0 && _is_seen_wall(coord_def(gc.x + 1, gc.y - 1))) + if (ne == 0 && _is_seen_wall(coord_def(gc.x + 1, gc.y - 1), vbuf)) _add_overlay(tile + 3, cell); } -static bool _is_seen_slimy_wall(const coord_def& gc) +static bool _is_seen_slimy_wall(const coord_def& gc, crawl_view_buffer &vbuf) { - const dungeon_feature_type feat = _safe_feat(gc); + const auto feat = _safe_feat(gc, vbuf); return feat == DNGN_SLIMY_WALL; } -void pack_cell_overlays(const coord_def &gc, packed_cell *cell) +void pack_cell_overlays(const coord_def &gc, crawl_view_buffer &vbuf) { - if (env.map_knowledge(gc).feat() == DNGN_UNSEEN) + auto& cell = vbuf(gc).tile; + + cell.num_dngn_overlay = 0; + + if (cell.map_knowledge.feat() == DNGN_UNSEEN) return; // Don't put overlays on unseen tiles if (player_in_branch(BRANCH_SHOALS)) - _pack_shoal_waves(gc, cell); + _pack_shoal_waves(gc, vbuf); else - _pack_default_waves(gc, cell); + _pack_default_waves(gc, vbuf); if (player_in_branch(BRANCH_SLIME) && - env.map_knowledge(gc).feat() != DNGN_SLIMY_WALL) + cell.map_knowledge.feat() != DNGN_SLIMY_WALL) { - _add_directional_overlays(gc, cell, TILE_SLIME_OVERLAY, + _add_directional_overlays(gc, vbuf, TILE_SLIME_OVERLAY, _is_seen_slimy_wall); } else @@ -498,7 +505,7 @@ void pack_cell_overlays(const coord_def &gc, packed_cell *cell) tileidx_t shadow_tile = TILE_DNGN_WALL_SHADOW; if (player_in_branch(BRANCH_CRYPT) || player_in_branch(BRANCH_DEPTHS)) shadow_tile = TILE_DNGN_WALL_SHADOW_DARK; - _pack_wall_shadows(gc, cell, shadow_tile); + _pack_wall_shadows(gc, vbuf, shadow_tile); } } #endif //TILECELL.CC diff --git a/crawl-ref/source/tilecell.h b/crawl-ref/source/tilecell.h index a21a9440ea4f..4009d15041b7 100644 --- a/crawl-ref/source/tilecell.h +++ b/crawl-ref/source/tilecell.h @@ -1,6 +1,8 @@ #ifdef USE_TILE #pragma once +#include "map-cell.h" + enum halo_type { HALO_NONE = 0, @@ -21,11 +23,13 @@ struct packed_cell tile_flavour flv; tileidx_t cloud; + // This is directly copied from env.map_knowledge by viewwindow() + map_cell map_knowledge; + + bool is_highlighted_summoner; bool is_bloody; bool is_silenced; char halo; - bool is_moldy; - bool glowing_mold; bool is_sanctuary; bool is_liquefied; bool mangrove_water; @@ -43,11 +47,12 @@ struct packed_cell bool operator ==(const packed_cell &other) const; bool operator !=(const packed_cell &other) const { return !(*this == other); } - packed_cell() : num_dngn_overlay(0), fg(0), bg(0), cloud(0), is_bloody(false), - is_silenced(false), halo(HALO_NONE), is_moldy(false), - glowing_mold(false), is_sanctuary(false), is_liquefied(false), - mangrove_water(false), awakened_forest(false), orb_glow(0), - blood_rotation(0), old_blood(false), travel_trail(0), + packed_cell() : num_dngn_overlay(0), fg(0), bg(0), cloud(0), + is_highlighted_summoner(false), is_bloody(false), + is_silenced(false), halo(HALO_NONE), is_sanctuary(false), + is_liquefied(false), mangrove_water(false), + awakened_forest(false), orb_glow(0), blood_rotation(0), + old_blood(false), travel_trail(0), quad_glow(false), disjunct(false) #if TAG_MAJOR_VERSION == 34 , heat_aura(false) @@ -57,11 +62,11 @@ struct packed_cell packed_cell(const packed_cell* c) : num_dngn_overlay(c->num_dngn_overlay), fg(c->fg), bg(c->bg), flv(c->flv), cloud(c->cloud), + map_knowledge(c->map_knowledge), + is_highlighted_summoner(c->is_highlighted_summoner), is_bloody(c->is_bloody), is_silenced(c->is_silenced), halo(c->halo), - is_moldy(c->is_moldy), - glowing_mold(c->glowing_mold), is_sanctuary(c->is_sanctuary), is_liquefied(c->is_liquefied), mangrove_water(c->mangrove_water), @@ -80,8 +85,10 @@ struct packed_cell void clear(); }; +class crawl_view_buffer; + // For a given location, pack any waves/ink/wall shadow tiles // that require knowledge of the surrounding env cells. -void pack_cell_overlays(const coord_def &gc, packed_cell *cell); +void pack_cell_overlays(const coord_def &gc, crawl_view_buffer &vbuf); #endif // TILECELL_H diff --git a/crawl-ref/source/tiledgnbuf.cc b/crawl-ref/source/tiledgnbuf.cc index 7766b2b78cab..8a3a35a8f019 100644 --- a/crawl-ref/source/tiledgnbuf.cc +++ b/crawl-ref/source/tiledgnbuf.cc @@ -2,8 +2,6 @@ #ifdef USE_TILE_LOCAL #include "tiledgnbuf.h" -#include "env.h" -#include "player.h" #include "tile-flags.h" #include "tiledef-dngn.h" #include "tiledef-icons.h" @@ -40,7 +38,11 @@ void DungeonCellBuffer::add(const packed_cell &cell, int x, int y) const tileidx_t fg_idx = cell.fg & TILE_FLAG_MASK; const bool in_water = _in_water(cell); - const tileidx_t cloud_idx = cell.cloud & TILE_FLAG_MASK; + tileidx_t cloud_idx = cell.cloud & TILE_FLAG_MASK; + + // in the shoals, ink is handled in pack_cell_overlays(): don't overdraw + if (cloud_idx == TILE_CLOUD_INK && player_in_branch(BRANCH_SHOALS)) + cloud_idx = 0; if (fg_idx >= TILEP_MCACHE_START) { @@ -228,16 +230,6 @@ void DungeonCellBuffer::add_blood_overlay(int x, int y, const packed_cell &cell, const int offset = cell.flv.special % tile_dngn_count(basetile); m_buf_feat.add(basetile + offset, x, y); } - else if (cell.is_moldy) - { - int offset = cell.flv.special % tile_dngn_count(TILE_MOLD); - m_buf_feat.add(TILE_MOLD + offset, x, y); - } - else if (cell.glowing_mold) - { - int offset = cell.flv.special % tile_dngn_count(TILE_GLOWING_MOLD); - m_buf_feat.add(TILE_GLOWING_MOLD + offset, x, y); - } } void DungeonCellBuffer::pack_background(int x, int y, const packed_cell &cell) @@ -369,6 +361,9 @@ void DungeonCellBuffer::pack_background(int x, int y, const packed_cell &cell) m_buf_feat.add(TILE_HALO_GD_NEUTRAL, x, y); else if (att_flag == TILE_FLAG_NEUTRAL) m_buf_feat.add(TILE_HALO_NEUTRAL, x, y); + + if (cell.is_highlighted_summoner) + m_buf_feat.add(TILE_HALO_SUMMONER, x, y); } // Apply the travel exclusion under the foreground if the cell is diff --git a/crawl-ref/source/tiledoll.cc b/crawl-ref/source/tiledoll.cc index a18dd316ca28..25516d2451a3 100644 --- a/crawl-ref/source/tiledoll.cc +++ b/crawl-ref/source/tiledoll.cc @@ -7,7 +7,11 @@ #include #include "files.h" +#include "jobs.h" +#include "makeitem.h" #include "mon-info.h" +#include "newgame.h" +#include "ng-setup.h" #include "options.h" #include "syscalls.h" #include "tile-player-flag-cut.h" @@ -19,6 +23,7 @@ #include "tilepick.h" #include "tilepick-p.h" #include "transform.h" +#include "unwind.h" dolls_data::dolls_data() { @@ -349,7 +354,8 @@ void fill_doll_equipment(dolls_data &result) { if (is_player_tile(result.parts[TILEP_PART_BASE], TILEP_BASE_OCTOPODE)) result.parts[TILEP_PART_HAND1] = TILEP_HAND1_BLADEHAND_OP; - else if (is_player_tile(result.parts[TILEP_PART_BASE], TILEP_BASE_FELID)) + else if (is_player_tile(result.parts[TILEP_PART_BASE], TILEP_BASE_FELID) + || Options.tile_use_monster == MONS_NATASHA) result.parts[TILEP_PART_HAND1] = TILEP_HAND1_BLADEHAND_FE; else result.parts[TILEP_PART_HAND1] = TILEP_HAND1_BLADEHAND; } @@ -366,7 +372,8 @@ void fill_doll_equipment(dolls_data &result) { if (is_player_tile(result.parts[TILEP_PART_BASE], TILEP_BASE_OCTOPODE)) result.parts[TILEP_PART_HAND2] = TILEP_HAND1_BLADEHAND_OP; - else if (is_player_tile(result.parts[TILEP_PART_BASE], TILEP_BASE_FELID)) + else if (is_player_tile(result.parts[TILEP_PART_BASE], TILEP_BASE_FELID) + || Options.tile_use_monster == MONS_NATASHA) result.parts[TILEP_PART_HAND2] = TILEP_HAND1_BLADEHAND_FE; else result.parts[TILEP_PART_HAND2] = TILEP_HAND1_BLADEHAND; } @@ -416,6 +423,8 @@ void fill_doll_equipment(dolls_data &result) result.parts[TILEP_PART_HELM] = TILEP_HELM_HORNS_CAT; } } + else if (species_is_draconian(you.species)) + result.parts[TILEP_PART_HELM] = TILEP_HELM_HORNS_DRAC; else switch (you.get_mutation_level(MUT_HORNS)) { @@ -497,6 +506,36 @@ void fill_doll_equipment(dolls_data &result) result.parts[i] = 0; } +void fill_doll_for_newgame(dolls_data &result, const newgame_def& ng) +{ + for (int j = 0; j < TILEP_PART_MAX; j++) + result.parts[j] = TILEP_SHOW_EQUIP; + + unwind_var unwind_you(you); + you = player(); + + // The following is part of the new game setup code from _setup_generic() + you.your_name = ng.name; + you.species = ng.species; + you.char_class = ng.job; + you.chr_class_name = get_job_name(you.char_class); + + species_stat_init(you.species); + update_vision_range(); + job_stat_init(you.char_class); + give_basic_mutations(you.species); + give_items_skills(ng); + + for (int i = 0; i < ENDOFPACK; ++i) + { + auto &item = you.inv[i]; + if (item.defined()) + item_colour(item); + } + + fill_doll_equipment(result); +} + // Writes equipment information into per-character doll file. void save_doll_file(writer &dollf) { @@ -546,6 +585,17 @@ void pack_doll_buf(SubmergedTileBuffer& buf, const dolls_data &doll, p_order[6] = TILEP_PART_LEG; } + // Draw scarves above other clothing. + if (doll.parts[TILEP_PART_CLOAK] >= TILEP_CLOAK_SCARF_FIRST_NORM) + { + p_order[4] = p_order[5]; + p_order[5] = p_order[6]; + p_order[6] = p_order[7]; + p_order[7] = p_order[8]; + p_order[8] = p_order[9]; + p_order[9] = TILEP_PART_CLOAK; + } + // Special case bardings from being cut off. const bool is_naga = is_player_tile(doll.parts[TILEP_PART_BASE], TILEP_BASE_NAGA); diff --git a/crawl-ref/source/tiledoll.h b/crawl-ref/source/tiledoll.h index 309215f91315..9a64d01ee61c 100644 --- a/crawl-ref/source/tiledoll.h +++ b/crawl-ref/source/tiledoll.h @@ -3,6 +3,8 @@ #include "tile-player-flags.h" +struct newgame_def; + struct dolls_data { dolls_data(); @@ -32,6 +34,8 @@ void fill_doll_equipment(dolls_data &result); void create_random_doll(dolls_data &result); void save_doll_file(writer &dollf); +void fill_doll_for_newgame(dolls_data &result, const newgame_def& ng); + // Saves player doll definitions into dolls.txt. // Returns true if successful, else false. bool save_doll_data(int mode, int num, const dolls_data* dolls); diff --git a/crawl-ref/source/tilefont.h b/crawl-ref/source/tilefont.h index a80b7b171ea3..1c6a22e5acba 100644 --- a/crawl-ref/source/tilefont.h +++ b/crawl-ref/source/tilefont.h @@ -30,15 +30,16 @@ class FontWrapper bool drop_shadow = false) = 0; // render text + background box + virtual void render_tooltip(unsigned int x, unsigned int y, + const formatted_string &text, + const coord_def &min_pos, + const coord_def &max_pos) = 0; + virtual void render_string(unsigned int x, unsigned int y, - const char *text, const coord_def &min_pos, - const coord_def &max_pos, - unsigned char font_colour, - bool drop_shadow = false, - unsigned char box_alpha = 0, - unsigned char box_colour = 0, - unsigned int outline = 0, - bool tooltip = false) = 0; + const formatted_string &text) = 0; + + virtual void render_hover_string(unsigned int x, unsigned int y, + const formatted_string &text) = 0; // FontBuffer helper functions virtual void store(FontBuffer &buf, float &x, float &y, diff --git a/crawl-ref/source/tilemcache.cc b/crawl-ref/source/tilemcache.cc index 1600775f83e6..bad1f6b69ef7 100644 --- a/crawl-ref/source/tilemcache.cc +++ b/crawl-ref/source/tilemcache.cc @@ -824,6 +824,7 @@ bool mcache_monster::get_shield_offset(tileidx_t mon_tile, case TILEP_MONS_ORC_WARRIOR: case TILEP_MONS_ORC_KNIGHT: case TILEP_MONS_ORC_WARLORD: + case TILEP_MONS_DEEP_ELF_KNIGHT: case TILEP_MONS_KIRKE: case TILEP_MONS_DIMME: *ofs_x = 1; diff --git a/crawl-ref/source/tilepick-p.cc b/crawl-ref/source/tilepick-p.cc index 88ec83cdfb3b..d48fc8fbdb74 100644 --- a/crawl-ref/source/tilepick-p.cc +++ b/crawl-ref/source/tilepick-p.cc @@ -248,9 +248,11 @@ tileidx_t tilep_equ_weapon(const item_def &item) case WPN_TRIPLE_CROSSBOW: tile = TILEP_HAND1_TRIPLE_CROSSBOW; break; +#if TAG_MAJOR_VERSION == 34 case WPN_BLOWGUN: tile = TILEP_HAND1_BLOWGUN; break; +#endif case WPN_LONGBOW: tile = TILEP_HAND1_BOW3; break; @@ -344,7 +346,7 @@ tileidx_t tilep_equ_armour(const item_def &item) tileidx_t tilep_equ_cloak(const item_def &item) { - if (item.base_type != OBJ_ARMOUR || item.sub_type != ARM_CLOAK) + if (item.base_type != OBJ_ARMOUR) return 0; if (item.props.exists("worn_tile")) @@ -357,7 +359,18 @@ tileidx_t tilep_equ_cloak(const item_def &item) return tile; } - return _modrng(item.rnd, TILEP_CLOAK_FIRST_NORM, TILEP_CLOAK_LAST_NORM); + switch (item.sub_type) + { + case ARM_CLOAK: + return _modrng(item.rnd, TILEP_CLOAK_FIRST_NORM, + TILEP_CLOAK_LAST_NORM); + + case ARM_SCARF: + return _modrng(item.rnd, TILEP_CLOAK_SCARF_FIRST_NORM, + TILEP_CLOAK_SCARF_LAST_NORM); + } + + return 0; } tileidx_t tilep_equ_helm(const item_def &item) @@ -965,6 +978,9 @@ void tilep_calc_flags(const dolls_data &doll, int flag[]) if (doll.parts[TILEP_PART_HELM] >= TILEP_HELM_FHELM_OFS) flag[TILEP_PART_BEARD] = TILEP_FLAG_HIDE; + if (doll.parts[TILEP_PART_HELM] >= TILEP_HELM_HELM_OFS) + flag[TILEP_PART_DRCHEAD] = TILEP_FLAG_HIDE; + if (is_player_tile(doll.parts[TILEP_PART_BASE], TILEP_BASE_NAGA)) { flag[TILEP_PART_BOOTS] = flag[TILEP_PART_LEG] = TILEP_FLAG_HIDE; diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 3056dd613822..870975d2f250 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -75,7 +75,7 @@ TextureID get_dngn_tex(tileidx_t idx) return TEX_FEAT; } -static tileidx_t _tileidx_trap(trap_type type) +tileidx_t tileidx_trap(trap_type type) { switch (type) { @@ -99,8 +99,8 @@ static tileidx_t _tileidx_trap(trap_type type) return TILE_DNGN_TRAP_NET; case TRAP_ZOT: return TILE_DNGN_TRAP_ZOT; - case TRAP_NEEDLE: - return TILE_DNGN_TRAP_NEEDLE; + case TRAP_DART: + return TILE_DNGN_TRAP_DART; case TRAP_SHAFT: return TILE_DNGN_TRAP_SHAFT; case TRAP_GOLUBRIA: @@ -558,7 +558,7 @@ tileidx_t tileidx_feature(const coord_def &gc) case DNGN_TRAP_MECHANICAL: case DNGN_TRAP_TELEPORT: - return _tileidx_trap(env.map_knowledge(gc).trap()); + return tileidx_trap(env.map_knowledge(gc).trap()); case DNGN_TRAP_WEB: { @@ -1462,7 +1462,7 @@ tileidx_t tileidx_monster_base(int type, bool in_water, int colour, int number, case LIGHTMAGENTA: offset = 4; break; case CYAN: offset = 5; break; case MAGENTA: offset = 6; break; - case RED: offset = 7; break; + case LIGHTRED: offset = 7; break; case WHITE: offset = 8; break; } @@ -2073,6 +2073,20 @@ static tileidx_t _tileidx_unrand_artefact(int idx) return tile ? tile : TILE_TODO; } +static tileidx_t _tileidx_wyrmbane(int plus) +{ + if (plus < 10) + return TILE_UNRAND_WYRMBANE; + else if (plus < 12) + return TILE_UNRAND_WYRMBANE1; + else if (plus < 15) + return TILE_UNRAND_WYRMBANE2; + else if (plus < 18) + return TILE_UNRAND_WYRMBANE3; + else + return TILE_UNRAND_WYRMBANE4; +} + static tileidx_t _tileidx_weapon_base(const item_def &item) { switch (item.sub_type) @@ -2092,7 +2106,9 @@ static tileidx_t _tileidx_weapon_base(const item_def &item) case WPN_BROAD_AXE: return TILE_WPN_BROAD_AXE; case WPN_BATTLEAXE: return TILE_WPN_BATTLEAXE; case WPN_EXECUTIONERS_AXE: return TILE_WPN_EXECUTIONERS_AXE; +#if TAG_MAJOR_VERSION == 34 case WPN_BLOWGUN: return TILE_WPN_BLOWGUN; +#endif case WPN_HUNTING_SLING: return TILE_WPN_HUNTING_SLING; case WPN_FUSTIBALUS: return TILE_WPN_FUSTIBALUS; case WPN_SHORTBOW: return TILE_WPN_SHORTBOW; @@ -2148,22 +2164,21 @@ static tileidx_t _tileidx_missile_base(const item_def &item) case MI_STONE: return TILE_MI_STONE; case MI_LARGE_ROCK: return TILE_MI_LARGE_ROCK; case MI_THROWING_NET: return TILE_MI_THROWING_NET; - case MI_TOMAHAWK: + case MI_BOOMERANG: switch (brand) { - default: return TILE_MI_TOMAHAWK + 1; - case 0: return TILE_MI_TOMAHAWK; - case SPMSL_STEEL: return TILE_MI_TOMAHAWK_STEEL; - case SPMSL_SILVER: return TILE_MI_TOMAHAWK_SILVER; + default: return TILE_MI_BOOMERANG + 1; + case 0: return TILE_MI_BOOMERANG; + case SPMSL_SILVER: return TILE_MI_BOOMERANG_SILVER; } - case MI_NEEDLE: + case MI_DART: switch (brand) { - default: return TILE_MI_NEEDLE + 1; - case 0: return TILE_MI_NEEDLE; - case SPMSL_POISONED: return TILE_MI_NEEDLE_P; - case SPMSL_CURARE: return TILE_MI_NEEDLE_CURARE; + default: return TILE_MI_DART + 1; + case 0: return TILE_MI_DART; + case SPMSL_POISONED: return TILE_MI_DART_P; + case SPMSL_CURARE: return TILE_MI_DART_CURARE; } case MI_ARROW: @@ -2573,7 +2588,9 @@ tileidx_t tileidx_item(const item_def &item) switch (clas) { case OBJ_WEAPONS: - if (is_unrandom_artefact(item) && !is_randapp_artefact(item)) + if (is_unrandom_artefact(item, UNRAND_WYRMBANE)) + return _tileidx_wyrmbane(item.plus); + else if (is_unrandom_artefact(item) && !is_randapp_artefact(item)) return _tileidx_unrand_artefact(find_unrandart_index(item)); else return _tileidx_weapon(item); @@ -2739,8 +2756,8 @@ tileidx_t tileidx_item_throw(const item_def &item, int dx, int dy) case MI_BOLT: ch = TILE_MI_BOLT0; break; - case MI_NEEDLE: - ch = TILE_MI_NEEDLE0; + case MI_DART: + ch = TILE_MI_DART0; break; case MI_JAVELIN: ch = TILE_MI_JAVELIN0; @@ -2748,8 +2765,6 @@ tileidx_t tileidx_item_throw(const item_def &item, int dx, int dy) case MI_THROWING_NET: ch = TILE_MI_THROWING_NET0; break; - case MI_TOMAHAWK: - ch = TILE_MI_TOMAHAWK0; default: break; } @@ -2782,6 +2797,8 @@ tileidx_t tileidx_item_throw(const item_def &item, int dx, int dy) case MI_THROWING_NET: ch = TILE_MI_THROWING_NET0; break; + case MI_BOOMERANG: + ch = TILE_MI_BOOMERANG0; default: break; } @@ -3022,6 +3039,8 @@ tileidx_t vary_bolt_tile(tileidx_t tile, int dist) case TILE_BOLT_FLAME: case TILE_BOLT_IRRADIATE: return tile + ui_random(tile_main_count(tile)); + case TILE_MI_BOOMERANG0: + return tile + ui_random(4); default: return tile; } @@ -3345,6 +3364,10 @@ tileidx_t tileidx_ability(const ability_type ability) // Vampires case ABIL_TRAN_BAT: return TILEG_ABILITY_BAT_FORM; + case ABIL_EXSANGUINATE: + return TILEG_ABILITY_EXSANGUINATE; + case ABIL_REVIVIFY: + return TILEG_ABILITY_REVIVIFY; // Deep Dwarves case ABIL_HEAL_WOUNDS: return TILEG_ABILITY_HEAL_WOUNDS; @@ -3433,14 +3456,12 @@ tileidx_t tileidx_ability(const ability_type ability) case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: return TILEG_ABILITY_MAKHLEB_GREATER_SERVANT; // Sif Muna - case ABIL_SIF_MUNA_DIVINE_ENERGY: - return TILEG_ABILITY_SIF_MUNA_DIVINE_ENERGY; - case ABIL_SIF_MUNA_STOP_DIVINE_ENERGY: - return TILEG_ABILITY_SIF_MUNA_STOP_DIVINE_ENERGY; case ABIL_SIF_MUNA_CHANNEL_ENERGY: return TILEG_ABILITY_SIF_MUNA_CHANNEL; case ABIL_SIF_MUNA_FORGET_SPELL: return TILEG_ABILITY_SIF_MUNA_AMNESIA; + case ABIL_SIF_MUNA_DIVINE_EXEGESIS: + return TILEG_ABILITY_SIF_MUNA_EXEGESIS; // Trog case ABIL_TROG_BERSERK: return TILEG_ABILITY_TROG_BERSERK; @@ -3508,18 +3529,14 @@ tileidx_t tileidx_ability(const ability_type ability) case ABIL_JIYVA_CURE_BAD_MUTATION: return TILEG_ABILITY_JIYVA_CURE_BAD_MUTATIONS; // Fedhas - case ABIL_FEDHAS_FUNGAL_BLOOM: - return TILEG_ABILITY_FEDHAS_FUNGAL_BLOOM; - case ABIL_FEDHAS_SUNLIGHT: - return TILEG_ABILITY_FEDHAS_SUNLIGHT; - case ABIL_FEDHAS_RAIN: - return TILEG_ABILITY_FEDHAS_RAIN; - case ABIL_FEDHAS_PLANT_RING: - return TILEG_ABILITY_FEDHAS_PLANT_RING; - case ABIL_FEDHAS_SPAWN_SPORES: - return TILEG_ABILITY_FEDHAS_SPAWN_SPORES; - case ABIL_FEDHAS_EVOLUTION: - return TILEG_ABILITY_FEDHAS_EVOLUTION; + case ABIL_FEDHAS_WALL_OF_BRIARS: + return TILEG_ABILITY_FEDHAS_WALL_OF_BRIARS; + case ABIL_FEDHAS_GROW_BALLISTOMYCETE: + return TILEG_ABILITY_FEDHAS_GROW_BALLISTOMYCETE; + case ABIL_FEDHAS_OVERGROW: + return TILEG_ABILITY_FEDHAS_OVERGROW; + case ABIL_FEDHAS_GROW_OKLOB: + return TILEG_ABILITY_FEDHAS_GROW_OKLOB; // Cheibriados case ABIL_CHEIBRIADOS_TIME_STEP: return TILEG_ABILITY_CHEIBRIADOS_TIME_STEP; @@ -3723,6 +3740,138 @@ tileidx_t tileidx_branch(const branch_type br) } } +static tileidx_t _tileidx_player_job_base(const job_type job) +{ + switch (job) + { + case JOB_FIGHTER: + return TILEG_JOB_FIGHTER; + case JOB_WIZARD: + return TILEG_JOB_WIZARD; + case JOB_GLADIATOR: + return TILEG_JOB_GLADIATOR; + case JOB_NECROMANCER: + return TILEG_JOB_NECROMANCER; + case JOB_ASSASSIN: + return TILEG_JOB_ASSASSIN; + case JOB_BERSERKER: + return TILEG_JOB_BERSERKER; + case JOB_HUNTER: + return TILEG_JOB_HUNTER; + case JOB_CONJURER: + return TILEG_JOB_CONJURER; + case JOB_ENCHANTER: + return TILEG_JOB_ENCHANTER; + case JOB_FIRE_ELEMENTALIST: + return TILEG_JOB_FIRE_ELEMENTALIST; + case JOB_ICE_ELEMENTALIST: + return TILEG_JOB_ICE_ELEMENTALIST; + case JOB_SUMMONER: + return TILEG_JOB_SUMMONER; + case JOB_AIR_ELEMENTALIST: + return TILEG_JOB_AIR_ELEMENTALIST; + case JOB_EARTH_ELEMENTALIST: + return TILEG_JOB_EARTH_ELEMENTALIST; + case JOB_SKALD: + return TILEG_JOB_SKALD; + case JOB_VENOM_MAGE: + return TILEG_JOB_VENOM_MAGE; + case JOB_CHAOS_KNIGHT: + return TILEG_JOB_CHAOS_KNIGHT; + case JOB_TRANSMUTER: + return TILEG_JOB_TRANSMUTER; + case JOB_MONK: + return TILEG_JOB_MONK; + case JOB_WARPER: + return TILEG_JOB_WARPER; + case JOB_WANDERER: + return TILEG_JOB_WANDERER; + case JOB_ARTIFICER: + return TILEG_JOB_ARTIFICER; + case JOB_ARCANE_MARKSMAN: + return TILEG_JOB_ARCANE_MARKSMAN; + case JOB_ABYSSAL_KNIGHT: + return TILEG_JOB_ABYSSAL_KNIGHT; + default: + return TILEG_ERROR; + } +} + +static tileidx_t _tileidx_player_species_base(const species_type species) +{ + switch (species) + { + case SP_HUMAN: + return TILEG_SP_HUMAN; + case SP_DEEP_ELF: + return TILEG_SP_DEEP_ELF; + case SP_HALFLING: + return TILEG_SP_HALFLING; + case SP_HILL_ORC: + return TILEG_SP_HILL_ORC; + case SP_KOBOLD: + return TILEG_SP_KOBOLD; + case SP_MUMMY: + return TILEG_SP_MUMMY; + case SP_NAGA: + return TILEG_SP_NAGA; + case SP_OGRE: + return TILEG_SP_OGRE; + case SP_TROLL: + return TILEG_SP_TROLL; + case SP_BASE_DRACONIAN: + return TILEG_SP_DRACONIAN; + case SP_CENTAUR: + return TILEG_SP_CENTAUR; + case SP_DEMIGOD: + return TILEG_SP_DEMIGOD; + case SP_SPRIGGAN: + return TILEG_SP_SPRIGGAN; + case SP_MINOTAUR: + return TILEG_SP_MINOTAUR; + case SP_DEMONSPAWN: + return TILEG_SP_DEMONSPAWN; + case SP_GHOUL: + return TILEG_SP_GHOUL; + case SP_TENGU: + return TILEG_SP_TENGU; + case SP_MERFOLK: + return TILEG_SP_MERFOLK; + case SP_VAMPIRE: + return TILEG_SP_VAMPIRE; + case SP_DEEP_DWARF: + return TILEG_SP_DEEP_DWARF; + case SP_FELID: + return TILEG_SP_FELID; + case SP_OCTOPODE: + return TILEG_SP_OCTOPODE; + case SP_GARGOYLE: + return TILEG_SP_GARGOYLE; + case SP_FORMICID: + return TILEG_SP_FORMICID; + case SP_VINE_STALKER: + return TILEG_SP_VINE_STALKER; + case SP_BARACHI: + return TILEG_SP_BARACHI; + case SP_GNOLL: + return TILEG_SP_GNOLL; + default: + return TILEP_ERROR; + } +} + +tileidx_t tileidx_player_species(const species_type species, bool recommended) +{ + tileidx_t off = recommended ? -TILEG_LAST_SPECIES+TILEG_LAST_RECOMMENDED_SPECIES: 0; + return _tileidx_player_species_base(species) + off; +} + +tileidx_t tileidx_player_job(const job_type job, bool recommended) +{ + tileidx_t off = recommended ? -TILEG_LAST_JOB+TILEG_LAST_RECOMMENDED_JOB: 0; + return _tileidx_player_job_base(job) + off; +} + tileidx_t tileidx_known_brand(const item_def &item) { if (!item_type_known(item)) @@ -3764,20 +3913,22 @@ tileidx_t tileidx_known_brand(const item_def &item) return TILE_BRAND_DISPERSAL; case SPMSL_EXPLODING: return TILE_BRAND_EXPLOSION; +#if TAG_MAJOR_VERSION == 34 case SPMSL_CONFUSION: return TILE_BRAND_CONFUSION; case SPMSL_PARALYSIS: return TILE_BRAND_PARALYSIS; -#if TAG_MAJOR_VERSION == 34 case SPMSL_SLOW: return TILE_BRAND_SLOWING; case SPMSL_SICKNESS: return TILE_BRAND_SICKNESS; + case SPMSL_SLEEP: + return TILE_BRAND_SLEEP; #endif case SPMSL_FRENZY: return TILE_BRAND_FRENZY; - case SPMSL_SLEEP: - return TILE_BRAND_SLEEP; + case SPMSL_BLINDING: + return TILE_BRAND_BLINDING; default: break; } @@ -3797,10 +3948,6 @@ tileidx_t tileidx_corpse_brand(const item_def &item) if (item.base_type != OBJ_CORPSES || item.sub_type != CORPSE_BODY) return 0; - // Vampires are only interested in fresh blood. - if (you.species == SP_VAMPIRE && !mons_has_blood(item.mon_type)) - return TILE_FOOD_INEDIBLE; - // Harmful chunk effects > religious rules > reduced nutrition. if (is_noxious(item)) return TILE_FOOD_ROTTING; diff --git a/crawl-ref/source/tilepick.h b/crawl-ref/source/tilepick.h index ae6996b29d95..72c14628caf7 100644 --- a/crawl-ref/source/tilepick.h +++ b/crawl-ref/source/tilepick.h @@ -8,7 +8,10 @@ #include "ability-type.h" #include "command-type.h" #include "game-type.h" +#include "trap-type.h" #include "tiledef_defines.h" +#include "job-type.h" +#include "species-type.h" #define TILE_NUM_KEY "tile_num" @@ -24,6 +27,7 @@ bool is_door_tile(tileidx_t tile); // Tile index lookup from Crawl data. tileidx_t tileidx_feature(const coord_def &gc); +tileidx_t tileidx_trap(trap_type type); tileidx_t tileidx_shop(const shop_struct *shop); tileidx_t tileidx_feature_base(dungeon_feature_type feat); tileidx_t tileidx_out_of_bounds(int branch); @@ -51,6 +55,8 @@ tileidx_t tileidx_command(const command_type cmd); tileidx_t tileidx_gametype(const game_type gtype); tileidx_t tileidx_ability(const ability_type ability); tileidx_t tileidx_branch(const branch_type br); +tileidx_t tileidx_player_job(const job_type job, bool recommended); +tileidx_t tileidx_player_species(const species_type species, bool recommended); tileidx_t tileidx_known_brand(const item_def &item); tileidx_t tileidx_corpse_brand(const item_def &item); diff --git a/crawl-ref/source/tilereg-dgn.cc b/crawl-ref/source/tilereg-dgn.cc index 221122e1bef3..32f9e071cb43 100644 --- a/crawl-ref/source/tilereg-dgn.cc +++ b/crawl-ref/source/tilereg-dgn.cc @@ -119,21 +119,9 @@ void DungeonRegion::pack_buffers() { coord_def gc(x + m_cx_to_gx, y + m_cy_to_gy); - packed_cell tile_cell = packed_cell(vbuf_cell->tile); if (map_bounds(gc)) - { - tile_cell.flv = env.tile_flv(gc); - pack_cell_overlays(gc, &tile_cell); - } - else - { - tile_cell.flv.floor = 0; - tile_cell.flv.wall = 0; - tile_cell.flv.special = 0; - tile_cell.flv.feat = 0; - } - - m_buf_dngn.add(tile_cell, x, y); + pack_cell_overlays(coord_def(x, y), m_vbuf); + m_buf_dngn.add(vbuf_cell->tile, x, y); const int fcol = vbuf_cell->flash_colour; if (fcol) @@ -254,10 +242,8 @@ void DungeonRegion::render() // center this coord, which is at the top left of gc's cell pc.x += dx / 2; - const coord_def min_pos(sx, sy); - const coord_def max_pos(ex, ey); - m_tag_font->render_string(pc.x, pc.y, def.text, - min_pos, max_pos, WHITE, false); + const auto text = formatted_string(def.text, WHITE); + m_tag_font->render_hover_string(pc.x, pc.y, text); } } @@ -482,7 +468,7 @@ static item_def* _get_evokable_item(const actor* target) InvMenu menu(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALLOW_FORMATTING | MF_SELECT_BY_PAGE); - menu.set_type(MT_ANY); + menu.set_type(menu_type::any); menu.set_title("Wand to zap?"); menu.load_items(list); menu.show(); @@ -1004,7 +990,7 @@ bool DungeonRegion::update_tip_text(string &tip) #ifdef WIZARD if (you.wizard) { - if (ret) + if (!tip.empty()) tip += "\n\n"; if (you.see_cell(gc)) @@ -1041,7 +1027,7 @@ bool DungeonRegion::update_tip_text(string &tip) tip += make_stringf("\nFLV: floor: %d (%s) (%d)" "\n wall: %d (%s) (%d)" "\n feat: %d (%s) (%d)" - "\n special: %d\n", + "\n special: %d", env.tile_flv(gc).floor, tile_dngn_name(env.tile_flv(gc).floor), env.tile_flv(gc).floor_idx, diff --git a/crawl-ref/source/tilereg-doll.cc b/crawl-ref/source/tilereg-doll.cc index 7f89294af086..93d3ec46c604 100644 --- a/crawl-ref/source/tilereg-doll.cc +++ b/crawl-ref/source/tilereg-doll.cc @@ -352,8 +352,8 @@ void UIDollEditor::_render() void UIDollEditor::_allocate_region() { - reg->sx = m_region[0]; - reg->sy = m_region[1]; + reg->sx = m_region.x; + reg->sy = m_region.y; } void DollEditRegion::run() @@ -387,9 +387,9 @@ void DollEditRegion::run() auto doll_ui = make_shared(this); auto vbox = make_shared(Widget::VERT); + vbox->align_cross = Widget::CENTER; auto title = make_shared(formatted_string("Doll Editor", YELLOW)); - title->align_self = Widget::CENTER; - title->set_margin_for_sdl({0, 0, 20, 0}); + title->set_margin_for_sdl(0, 0, 20, 0); vbox->add_child(move(title)); vbox->add_child(doll_ui); auto popup = make_shared(move(vbox)); diff --git a/crawl-ref/source/tilereg-grid.cc b/crawl-ref/source/tilereg-grid.cc index 218da0c42eec..8b1b5e509170 100644 --- a/crawl-ref/source/tilereg-grid.cc +++ b/crawl-ref/source/tilereg-grid.cc @@ -4,6 +4,7 @@ #include "tilereg-grid.h" +#include "format.h" #include "libutil.h" #include "random.h" #include "tile-inventory-flags.h" @@ -80,17 +81,14 @@ void GridRegion::draw_desc(const char *desc) ASSERT(desc); // Always draw the description in the inventory header. (jpeg) - int x = sx + ox + dx / 2; - int y = sy + oy; + int x = sx + ox; + int y = sy + oy - dx / 2; // ... unless we're using the touch UI, in which case: put at bottom if (tiles.is_using_small_layout()) y = wy; - const coord_def min_pos(sx, sy - dy); - const coord_def max_pos(ex, ey); - - m_tag_font->render_string(x, y, desc, min_pos, max_pos, WHITE, false, 200); + m_tag_font->render_string(x, y, formatted_string(desc, WHITE)); } bool GridRegion::place_cursor(MouseEvent &event, unsigned int &item_idx) diff --git a/crawl-ref/source/tilereg-inv.cc b/crawl-ref/source/tilereg-inv.cc index 1ba442636264..8ca5abee789b 100644 --- a/crawl-ref/source/tilereg-inv.cc +++ b/crawl-ref/source/tilereg-inv.cc @@ -8,6 +8,7 @@ #include "cio.h" #include "describe.h" #include "env.h" +#include "food.h" #include "invent.h" #include "item-name.h" #include "item-prop.h" @@ -232,14 +233,6 @@ static bool _can_use_item(const item_def &item, bool equipped) return false; #endif - // Vampires can drain corpses. - if (item.base_type == OBJ_CORPSES) - { - return you.species == SP_VAMPIRE - && item.sub_type != CORPSE_SKELETON - && mons_has_blood(item.mon_type); - } - if (equipped && item.cursed()) { // Evocable items (e.g. dispater staff) are still evocable when cursed. @@ -352,23 +345,11 @@ bool InventoryRegion::update_tip_text(string& tip) if (item.base_type == OBJ_CORPSES && item.sub_type != CORPSE_SKELETON) { - tip += "\n[Shift + L-Click] "; - if (can_bottle_blood_from_corpse(item.mon_type)) - tip += "Bottle blood"; - else - tip += "Chop up"; - tip += " (%)"; + tip += "\n[Shift + L-Click] Chop up (%)"; cmd.push_back(CMD_BUTCHER); - - if (you.species == SP_VAMPIRE) - { - tip += "\n\n[Shift + R-Click] Drink blood (e)"; - cmd.push_back(CMD_EAT); - } } else if (item.base_type == OBJ_FOOD - && you.undead_state() != US_UNDEAD - && you.species != SP_VAMPIRE) + && !you_foodless()) { tip += "\n[Shift + R-Click] Eat (e)"; cmd.push_back(CMD_EAT); @@ -512,18 +493,8 @@ bool InventoryRegion::update_tip_text(string& tip) _handle_wield_tip(tmp, cmd, "\n[Ctrl + L-Click] ", true); break; case OBJ_CORPSES: - if (you.species == SP_VAMPIRE) - { - tmp += "Drink blood (%)"; - cmd.push_back(CMD_EAT); - } - if (wielded) - { - if (you.species == SP_VAMPIRE) - tmp += "\n"; _handle_wield_tip(tmp, cmd, "\n[Ctrl + L-Click] ", true); - } break; default: tmp += "Use"; diff --git a/crawl-ref/source/tilereg-map.cc b/crawl-ref/source/tilereg-map.cc index c3ec854a1c3f..052be20ea5b0 100644 --- a/crawl-ref/source/tilereg-map.cc +++ b/crawl-ref/source/tilereg-map.cc @@ -86,11 +86,11 @@ void MapRegion::pack_buffers() float pos_sx = (m_win_start.x - m_min_gx); float pos_sy = (m_win_start.y - m_min_gy); - float pos_ex = (m_win_end.x - m_min_gx) - 1 / (float)dx; - float pos_ey = (m_win_end.y - m_min_gy) - 1 / (float)dy; + float pos_ex = (m_win_end.x - m_min_gx); + float pos_ey = (m_win_end.y - m_min_gy); - set_transform(); - m_buf_lines.add_square(pos_sx, pos_sy, pos_ex, pos_ey, + set_transform(true); + m_buf_lines.add_square(pos_sx*dx, pos_sy*dy, pos_ex*dx + 1, pos_ey*dy + 1, Options.tile_window_col); } @@ -111,6 +111,7 @@ void MapRegion::render() set_transform(); glmanager->set_scissor(sx, sy, wx, wy); m_buf_map.draw(); + set_transform(true); m_buf_lines.draw(); glmanager->reset_scissor(); } diff --git a/crawl-ref/source/tilereg-msg.cc b/crawl-ref/source/tilereg-msg.cc index 8aea641b8844..dc8d868e4ac4 100644 --- a/crawl-ref/source/tilereg-msg.cc +++ b/crawl-ref/source/tilereg-msg.cc @@ -71,8 +71,7 @@ void MessageRegion::render() if (ends_with(text, "..")) text = text.substr(0, text.find_last_of('\n')) + "\n..."; - m_font->render_string(sx + ox, sy + oy, text.c_str(), - min_pos, max_pos, WHITE, false); + m_font->render_string(sx + ox, sy + oy, formatted_string(text, WHITE)); return; } diff --git a/crawl-ref/source/tilereg-skl.cc b/crawl-ref/source/tilereg-skl.cc index 62e31e9f3361..68fdb818b98f 100644 --- a/crawl-ref/source/tilereg-skl.cc +++ b/crawl-ref/source/tilereg-skl.cc @@ -72,7 +72,7 @@ int SkillRegion::handle_mouse(MouseEvent &event) } #endif m_last_clicked_item = item_idx; - if (!you.can_train[skill]) + if (!you.can_currently_train[skill]) mpr("You cannot train this skill."); else if (you.species == SP_GNOLL) mpr("Gnolls can't change their training allocations!"); @@ -248,7 +248,7 @@ void SkillRegion::update() desc.idx = idx; desc.quantity = you.skills[skill]; - if (!you.can_train[skill] || you.skills[skill] >= 27) + if (!you.can_currently_train[skill] || you.skills[skill] >= 27) desc.flag |= TILEI_FLAG_INVALID; m_items.push_back(desc); diff --git a/crawl-ref/source/tilesdl.cc b/crawl-ref/source/tilesdl.cc index e089f03539fa..4a9b9aac4458 100644 --- a/crawl-ref/source/tilesdl.cc +++ b/crawl-ref/source/tilesdl.cc @@ -164,12 +164,7 @@ TilesFramework::TilesFramework() : m_fullscreen(false), m_need_redraw(false), m_active_layer(LAYER_CRT), - m_crt_font(-1), - m_msg_font(-1), - m_tip_font(-1), - m_lbl_font(-1), m_mouse(-1, -1), - m_last_tick_moved(0), m_last_tick_redraw(0) { } @@ -181,8 +176,7 @@ TilesFramework::~TilesFramework() bool TilesFramework::fonts_initialized() { // TODO should in principle check the m_fonts vector as well - return !(m_crt_font == -1 || m_msg_font == -1 - || m_tip_font == -1 || m_lbl_font == -1); + return m_crt_font && m_msg_font && m_stat_font && m_tip_font && m_lbl_font; } static void _init_consoles() @@ -288,7 +282,7 @@ void TilesFramework::shutdown() void TilesFramework::draw_doll_edit() { - DollEditRegion reg(m_image, m_fonts[m_msg_font].font); + DollEditRegion reg(m_image, m_msg_font); reg.run(); } @@ -297,6 +291,8 @@ void TilesFramework::set_map_display(const bool display) m_map_mode_enabled = display; if (!display && !tiles.is_using_small_layout()) m_region_tab->activate_tab(TAB_ITEM); + do_layout(); // recalculate the viewport setup for zoom levels + redraw_screen(false); } bool TilesFramework::get_map_display() @@ -307,6 +303,8 @@ bool TilesFramework::get_map_display() void TilesFramework::do_map_display() { m_map_mode_enabled = true; + do_layout(); // recalculate the viewport setup for zoom levels + redraw_screen(false); m_region_tab->activate_tab(TAB_NAVIGATION); } @@ -351,11 +349,7 @@ bool TilesFramework::initialise() DATA_DIR_PATH #endif #endif -#ifdef TARGET_OS_MACOSX "dat/tiles/stone_soup_icon-512x512.png"; -#else - "dat/tiles/stone_soup_icon-32x32.png"; -#endif string title = string(CRAWL " ") + Version::Long; @@ -398,20 +392,18 @@ bool TilesFramework::initialise() Options.tile_font_crt_size, true); m_msg_font = load_font(Options.tile_font_msg_file.c_str(), Options.tile_font_msg_size, true); - int stat_font = load_font(Options.tile_font_stat_file.c_str(), + m_stat_font = load_font(Options.tile_font_stat_file.c_str(), Options.tile_font_stat_size, true); m_tip_font = load_font(Options.tile_font_tip_file.c_str(), Options.tile_font_tip_size, true); m_lbl_font = load_font(Options.tile_font_lbl_file.c_str(), Options.tile_font_lbl_size, true); - if (!fonts_initialized() || stat_font == -1) + if (!fonts_initialized()) return false; - if (tiles.is_using_small_layout()) - m_init = TileRegionInit(m_image, m_fonts[m_crt_font].font, TILE_X, TILE_Y); - else - m_init = TileRegionInit(m_image, m_fonts[m_lbl_font].font, TILE_X, TILE_Y); + const auto font = tiles.is_using_small_layout() ? m_crt_font : m_lbl_font; + m_init = TileRegionInit(m_image, font, TILE_X, TILE_Y); m_region_tile = new DungeonRegion(m_init); m_region_tab = new TabbedRegion(m_init); m_region_inv = new InventoryRegion(m_init); @@ -458,10 +450,9 @@ bool TilesFramework::initialise() m_region_tab->activate_tab(TAB_ITEM); #endif - m_region_msg = new MessageRegion(m_fonts[m_msg_font].font); - m_region_stat = new StatRegion(m_fonts[stat_font].font); - m_fonts[stat_font].font->char_width(); - m_region_crt = new CRTRegion(m_fonts[m_crt_font].font); + m_region_msg = new MessageRegion(m_msg_font); + m_region_stat = new StatRegion(m_stat_font); + m_region_crt = new CRTRegion(m_crt_font); m_layers[LAYER_NORMAL].m_regions.push_back(m_region_tile); m_layers[LAYER_NORMAL].m_regions.push_back(m_region_msg); @@ -484,14 +475,14 @@ void TilesFramework::reconfigure_fonts() finfo.font->configure_font(); } -int TilesFramework::load_font(const char *font_file, int font_size, +FontWrapper* TilesFramework::load_font(const char *font_file, int font_size, bool default_on_fail) { for (unsigned int i = 0; i < m_fonts.size(); i++) { font_info &finfo = m_fonts[i]; if (finfo.name == font_file && finfo.size == font_size) - return i; + return finfo.font; } FontWrapper *font = FontWrapper::create(); @@ -509,7 +500,7 @@ int TilesFramework::load_font(const char *font_file, int font_size, return load_font(MONOSPACED_FONT, 12, false); } else - return -1; + return nullptr; } font_info finfo; @@ -518,7 +509,7 @@ int TilesFramework::load_font(const char *font_file, int font_size, finfo.size = font_size; m_fonts.push_back(finfo); - return m_fonts.size() - 1; + return font; } void TilesFramework::load_dungeon(const crawl_view_buffer &vbuf, const coord_def &gc) @@ -625,8 +616,7 @@ static unsigned int _timer_callback(unsigned int ticks, void *param) // force the event loop to break wm->raise_custom_event(); - unsigned int res = Options.tile_tooltip_ms; - return res; + return 0; } int TilesFramework::getch_ck() @@ -643,8 +633,6 @@ int TilesFramework::getch_ck() const unsigned int ticks_per_screen_redraw = Options.tile_update_rate; - unsigned int res = Options.tile_tooltip_ms; - unsigned int timer_id = wm->set_timer(res, &_timer_callback); m_tooltip.clear(); m_region_msg->alt_text().clear(); @@ -715,11 +703,13 @@ int TilesFramework::getch_ck() // If you hit a key, disable tooltips until the mouse // is moved again. - m_last_tick_moved = UINT_MAX; + m_show_tooltip = false; + wm->remove_timer(m_tooltip_timer_id); break; case WME_KEYUP: - m_last_tick_moved = UINT_MAX; + m_show_tooltip = false; + wm->remove_timer(m_tooltip_timer_id); break; case WME_MOUSEMOTION: @@ -735,11 +725,6 @@ int TilesFramework::getch_ck() continue; // Record mouse pos for tooltip timer - if (m_mouse.x != (int)event.mouse_event.px - || m_mouse.y != (int)event.mouse_event.py) - { - m_last_tick_moved = ticks; - } m_mouse.x = event.mouse_event.px; m_mouse.y = event.mouse_event.py; @@ -776,21 +761,16 @@ int TilesFramework::getch_ck() prev_msg_alt_text = m_region_msg->alt_text(); set_need_redraw(); } + + wm->remove_timer(m_tooltip_timer_id); + m_tooltip_timer_id = wm->set_timer(Options.tile_tooltip_ms, &_timer_callback); + m_show_tooltip = false; } break; case WME_MOUSEBUTTONUP: - { - key = handle_mouse(event.mouse_event); - m_last_tick_moved = UINT_MAX; - } - break; - case WME_MOUSEBUTTONDOWN: - { - key = handle_mouse(event.mouse_event); - m_last_tick_moved = UINT_MAX; - } + key = handle_mouse(event.mouse_event); break; case WME_QUIT: @@ -820,17 +800,14 @@ int TilesFramework::getch_ck() case WME_CUSTOMEVENT: default: // This is only used to refresh the tooltip. + m_show_tooltip = true; break; } } if (!mouse_target_mode) { - const bool timeout = (ticks > m_last_tick_moved - && (ticks - m_last_tick_moved - > (unsigned int)Options.tile_tooltip_ms)); - - if (timeout) + if (m_show_tooltip) { tiles.clear_text_tags(TAG_CELL_DESC); if (Options.tile_tooltip_ms > 0 && m_tooltip.empty()) @@ -868,8 +845,6 @@ int TilesFramework::getch_ck() // We got some input, so we'll probably have to redraw something. set_need_redraw(); - wm->remove_timer(timer_id); - return key; } @@ -896,8 +871,10 @@ static int round_up_to_multiple(int a, int b) void TilesFramework::do_layout() { // View size in pixels is (m_viewsc * crawl_view.viewsz) - m_viewsc.x = Options.tile_cell_pixels; - m_viewsc.y = Options.tile_cell_pixels; + const int scale = m_map_mode_enabled ? Options.tile_map_scale + : Options.tile_viewport_scale; + m_viewsc.x = Options.tile_cell_pixels * scale / 100; + m_viewsc.y = Options.tile_cell_pixels * scale / 100; crawl_view.viewsz.x = Options.view_max_width; crawl_view.viewsz.y = Options.view_max_height; @@ -1028,8 +1005,8 @@ void TilesFramework::do_layout() // Expand dungeon region to cover partial tiles, then offset to keep player centred int tile_iw = m_stat_x_divider; int tile_ih = message_y_divider; - int tile_ow = round_up_to_multiple(tile_iw, m_region_tile->dx*2); - int tile_oh = round_up_to_multiple(tile_ih, m_region_tile->dy*2); + int tile_ow = round_up_to_multiple(tile_iw, m_region_tile->dx*2) + m_region_tile->dx; + int tile_oh = round_up_to_multiple(tile_ih, m_region_tile->dy*2) + m_region_tile->dx; m_region_tile->resize_to_fit(tile_ow, tile_oh); m_region_tile->place(-(tile_ow - tile_iw)/2, -(tile_oh - tile_ih)/2, 0); m_region_tile->tile_iw = tile_iw; @@ -1092,9 +1069,27 @@ bool TilesFramework::is_using_small_layout() return Options.tile_use_small_layout == MB_TRUE; #endif } + +#define ZOOM_INC 10 + void TilesFramework::zoom_dungeon(bool in) { +#ifdef TOUCH_UI m_region_tile->zoom(in); +#elif defined(USE_TILE_LOCAL) + int ¤t_scale = m_map_mode_enabled ? Options.tile_map_scale + : Options.tile_viewport_scale; + // max zoom relative to to tile size that keeps LOS in view + int max_zoom = 100 * m_windowsz.y / Options.tile_cell_pixels + / ENV_SHOW_DIAMETER; + if (max_zoom % ZOOM_INC != 0) + max_zoom += ZOOM_INC - max_zoom % ZOOM_INC; // round up + current_scale = min(max_zoom, max(20, + current_scale + (in ? ZOOM_INC : -ZOOM_INC))); + do_layout(); // recalculate the viewport setup + dprf("Zooming to %d", current_scale); + redraw_screen(false); +#endif } bool TilesFramework::zoom_to_minimap() @@ -1158,9 +1153,11 @@ void TilesFramework::autosize_minimap() const int vert = (m_statcol_bottom - (m_region_map->sy ? m_region_map->sy : m_statcol_top) - map_margin * 2) / GYM; - m_region_map->dx = m_region_map->dy = min(horiz, vert); + + int sz = min(horiz, vert); if (Options.tile_map_pixels) - m_region_map->dx = min(m_region_map->dx, Options.tile_map_pixels); + sz = min(sz, Options.tile_map_pixels); + m_region_map->dx = m_region_map->dy = sz; } void TilesFramework::place_minimap() @@ -1452,12 +1449,11 @@ void TilesFramework::redraw() // Draw tooltip if (Options.tile_tooltip_ms > 0 && !m_tooltip.empty()) { - const coord_def min_pos(0, 0); - FontWrapper *font = m_fonts[m_tip_font].font; - - font->render_string(m_mouse.x, m_mouse.y - 2, m_tooltip.c_str(), - min_pos, m_windowsz, WHITE, false, 220, BLUE, 5, - true); + const int buffer = 5; + const coord_def min_pos = coord_def() + buffer; + const coord_def max_pos = m_windowsz - buffer; + m_tip_font->render_tooltip(m_mouse.x, m_mouse.y, formatted_string(m_tooltip), + min_pos, max_pos); } wm->swap_buffers(); diff --git a/crawl-ref/source/tilesdl.h b/crawl-ref/source/tilesdl.h index aea7016972fc..aee4a293dadd 100644 --- a/crawl-ref/source/tilesdl.h +++ b/crawl-ref/source/tilesdl.h @@ -147,13 +147,20 @@ class TilesFramework bool is_fullscreen() { return m_fullscreen; } bool fonts_initialized(); - FontWrapper* get_crt_font() { return m_fonts.at(m_crt_font).font; } + + FontWrapper* get_crt_font() const { return m_crt_font; } + FontWrapper* get_msg_font() const { return m_msg_font; } + FontWrapper* get_stat_font() const { return m_stat_font; } + FontWrapper* get_tip_font() const { return m_tip_font; } + FontWrapper* get_lbl_font() const { return m_lbl_font; } + CRTRegion* get_crt() { return m_region_crt; } + const ImageManager* get_image_manager() { return m_image; } int to_lines(int num_tiles, int tile_height = TILE_Y); protected: void reconfigure_fonts(); - int load_font(const char *font_file, int font_size, + FontWrapper* load_font(const char *font_file, int font_size, bool default_on_fail); int handle_mouse(MouseEvent &event); @@ -222,10 +229,11 @@ class TilesFramework FontWrapper *font; }; vector m_fonts; - int m_crt_font; - int m_msg_font; - int m_tip_font; - int m_lbl_font; + FontWrapper* m_crt_font = nullptr; + FontWrapper* m_msg_font = nullptr; + FontWrapper* m_stat_font = nullptr; + FontWrapper* m_tip_font = nullptr; + FontWrapper* m_lbl_font = nullptr; int m_tab_margin; int m_stat_col; @@ -245,10 +253,11 @@ class TilesFramework // Mouse state. coord_def m_mouse; - unsigned int m_last_tick_moved; unsigned int m_last_tick_redraw; string m_tooltip; + bool m_show_tooltip = false; + unsigned int m_tooltip_timer_id = 0; int m_screen_width; int m_screen_height; diff --git a/crawl-ref/source/tileview.cc b/crawl-ref/source/tileview.cc index 72d7f0d8d51d..0a3bc5ba65ae 100644 --- a/crawl-ref/source/tileview.cc +++ b/crawl-ref/source/tileview.cc @@ -308,23 +308,36 @@ void tile_clear_flavour() tile_clear_flavour(*ri); } +static bool _level_uses_dominoes() +{ + return you.where_are_you == BRANCH_CRYPT; +} + // For floors and walls that have not already been set to a particular tile, // set them to a random instance of the default floor and wall tileset. void tile_init_flavour() { - vector output; - { - domino::DominoSet dominoes(domino::cohen_set, 8); - uint64_t seed[] = { static_cast(you.where_are_you ^ you.game_seed), - static_cast(you.depth) }; - PcgRNG rng(seed, ARRAYSZ(seed)); - dominoes.Generate(X_WIDTH, Y_WIDTH, output, rng); - } - for (rectangle_iterator ri(0); ri; ++ri) + if (_level_uses_dominoes()) { - unsigned int idx = ri->x + ri->y * GXM; - tile_init_flavour(*ri, output[idx]); + vector output; + + { + rng::subgenerator sub_rng( + static_cast(you.where_are_you ^ you.game_seed), + static_cast(you.depth)); + output.reserve(X_WIDTH * Y_WIDTH); + domino::DominoSet dominoes(domino::cohen_set, 8); + // TODO: don't pass a PcgRNG object + dominoes.Generate(X_WIDTH, Y_WIDTH, output, + rng::current_generator()); + } + + for (rectangle_iterator ri(0); ri; ++ri) + tile_init_flavour(*ri, output[ri->x + ri->y * GXM]); } + else + for (rectangle_iterator ri(0); ri; ++ri) + tile_init_flavour(*ri, 0); } // 11111333333 55555555 @@ -1024,11 +1037,6 @@ void tile_reset_feat(const coord_def &gc) static void _tile_place_cloud(const coord_def &gc, const cloud_info &cl) { - // In the Shoals, ink is handled differently. (jpeg) - // I'm not sure it is even possible anywhere else, but just to be safe... - if (cl.type == CLOUD_INK && player_in_branch(BRANCH_SHOALS)) - return; - if (you.see_cell(gc)) { const coord_def ep = grid2show(gc); @@ -1412,6 +1420,9 @@ void tile_apply_properties(const coord_def &gc, packed_cell &cell) else cell.halo = HALO_NONE; + if (mc.monsterinfo() && mc.monsterinfo()->is(MB_HIGHLIGHTED_SUMMONER)) + cell.is_highlighted_summoner = true; + if (mc.flags & MAP_LIQUEFIED) cell.is_liquefied = true; else if (print_blood && (_suppress_blood(mc) @@ -1420,15 +1431,10 @@ void tile_apply_properties(const coord_def &gc, packed_cell &cell) print_blood = false; } - // Mold has the same restrictions as blood but takes precedence. if (print_blood) { - if (mc.flags & MAP_GLOWING_MOLDY) - cell.glowing_mold = true; - else if (mc.flags & MAP_MOLDY) - cell.is_moldy = true; // Corpses have a blood puddle of their own. - else if (mc.flags & MAP_BLOODY && !_top_item_is_corpse(mc)) + if (mc.flags & MAP_BLOODY && !_top_item_is_corpse(mc)) { cell.is_bloody = true; cell.blood_rotation = blood_rotation(gc); @@ -1481,5 +1487,7 @@ void tile_apply_properties(const coord_def &gc, packed_cell &cell) } } } + + cell.flv = env.tile_flv(gc); } #endif diff --git a/crawl-ref/source/tileweb.cc b/crawl-ref/source/tileweb.cc index 1ec48340c5cb..4fe2dab511c5 100644 --- a/crawl-ref/source/tileweb.cc +++ b/crawl-ref/source/tileweb.cc @@ -27,6 +27,7 @@ #include "libutil.h" #include "map-knowledge.h" #include "menu.h" +#include "outer-menu.h" #include "message.h" #include "mon-util.h" #include "notes.h" @@ -75,6 +76,8 @@ TilesFramework::TilesFramework() : _send_lock(false), m_last_ui_state(UI_INIT), m_view_loaded(false), + m_current_view(coord_def(GXM, GYM)), + m_next_view(coord_def(GXM, GYM)), m_next_view_tl(0, 0), m_next_view_br(-1, -1), m_current_flash_colour(BLACK), @@ -85,8 +88,8 @@ TilesFramework::TilesFramework() : { screen_cell_t default_cell; default_cell.tile.bg = TILE_FLAG_UNSEEN; - m_next_view.init(default_cell); - m_current_view.init(default_cell); + m_current_view.fill(default_cell); + m_next_view.fill(default_cell); } TilesFramework::~TilesFramework() @@ -429,6 +432,14 @@ wint_t TilesFramework::_handle_control_message(sockaddr_un addr, string data) scroll.check(JSON_NUMBER); recv_formatted_scroller_scroll((int)scroll->number_); } + else if (msgtype == "outer_menu_focus") + { + JsonWrapper menu_id = json_find_member(obj.node, "menu_id"); + JsonWrapper hotkey = json_find_member(obj.node, "hotkey"); + menu_id.check(JSON_STRING); + hotkey.check(JSON_NUMBER); + OuterMenu::recv_outer_menu_focus(menu_id->string_, (int)hotkey->number_); + } return c; } @@ -550,6 +561,39 @@ void TilesFramework::_send_options() finish_message(); } +#define ZOOM_INC 10 + +static void _set_option_int(string name, int value) +{ + tiles.json_open_object(); + tiles.json_write_string("msg", "set_option"); + tiles.json_write_string("name", name); + tiles.json_write_int("value", value); + tiles.json_close_object(); + tiles.finish_message(); +} + +void TilesFramework::zoom_dungeon(bool in) +{ + if (m_ui_state == UI_VIEW_MAP) + { + Options.tile_map_scale = min(300, max(20, + Options.tile_map_scale + (in ? ZOOM_INC : -ZOOM_INC))); + _set_option_int("tile_map_scale", Options.tile_map_scale); + dprf("Zooming map to %d", Options.tile_map_scale); + } + else + { + Options.tile_viewport_scale = min(300, max(20, + Options.tile_viewport_scale + (in ? ZOOM_INC : -ZOOM_INC))); + _set_option_int("tile_viewport_scale", Options.tile_viewport_scale); + dprf("Zooming to %d", Options.tile_viewport_scale); + } + // calling redraw explicitly is not needed here: it triggers from a + // listener on the webtiles side. + // TODO: how to implement dynamic max zoom that reacts to the webtiles side? +} + void TilesFramework::_send_layout() { tiles.json_open_object(); @@ -675,7 +719,7 @@ void TilesFramework::push_ui_cutoff() void TilesFramework::pop_ui_cutoff() { m_ui_cutoff_stack.pop_back(); - int cutoff = m_ui_cutoff_stack.empty() ? 0 : m_ui_cutoff_stack.back(); + int cutoff = m_ui_cutoff_stack.empty() ? -1 : m_ui_cutoff_stack.back(); send_message("{\"msg\":\"ui_cutoff\",\"cutoff\":%d}", cutoff); } @@ -1063,7 +1107,7 @@ void TilesFramework::_send_item(item_info& current, const item_info& next, } } -static void _send_doll(const dolls_data &doll, bool submerged, bool ghost) +void TilesFramework::send_doll(const dolls_data &doll, bool submerged, bool ghost) { // Ordered from back to front. // FIXME: Implement this logic in one place in e.g. pack_doll_buf(). @@ -1099,6 +1143,17 @@ static void _send_doll(const dolls_data &doll, bool submerged, bool ghost) p_order[6] = TILEP_PART_LEG; } + // Draw scarves above other clothing. + if (doll.parts[TILEP_PART_CLOAK] >= TILEP_CLOAK_SCARF_FIRST_NORM) + { + p_order[4] = p_order[5]; + p_order[5] = p_order[6]; + p_order[6] = p_order[7]; + p_order[7] = p_order[8]; + p_order[8] = p_order[9]; + p_order[9] = TILEP_PART_CLOAK; + } + // Special case bardings from being cut off. const bool is_naga = is_player_tile(doll.parts[TILEP_PART_BASE], TILEP_BASE_NAGA); @@ -1146,17 +1201,17 @@ static void _send_doll(const dolls_data &doll, bool submerged, bool ghost) tiles.json_close_array(); } -void TilesFramework::send_mcache(mcache_entry *entry, bool submerged, bool send_doll) +void TilesFramework::send_mcache(mcache_entry *entry, bool submerged, bool send) { bool trans = entry->transparent(); - if (trans && send_doll) + if (trans && send) tiles.json_write_int("trans", 1); const dolls_data *doll = entry->doll(); - if (send_doll) + if (send) { if (doll) - _send_doll(*doll, submerged, trans); + send_doll(*doll, submerged, trans); else { tiles.json_write_comma(); @@ -1188,12 +1243,10 @@ static bool _needs_flavour(const packed_cell &cell) tileidx_t bg_idx = cell.bg & TILE_FLAG_MASK; if (bg_idx >= TILE_DNGN_FIRST_TRANSPARENT) return true; // Needs flv.floor - if (cell.is_liquefied || cell.is_bloody || - cell.is_moldy || cell.glowing_mold) - { + if (cell.is_liquefied || cell.is_bloody) return true; // Needs flv.special - } return false; + } static inline unsigned _get_brand(int col) @@ -1299,11 +1352,12 @@ void TilesFramework::_send_cell(const coord_def &gc, if (next_pc.halo != current_pc.halo) json_write_int("halo", next_pc.halo); - if (next_pc.is_moldy != current_pc.is_moldy) - json_write_bool("moldy", next_pc.is_moldy); - - if (next_pc.glowing_mold != current_pc.glowing_mold) - json_write_bool("glowing_mold", next_pc.glowing_mold); + if (next_pc.is_highlighted_summoner + != current_pc.is_highlighted_summoner) + { + json_write_bool("highlighted_summoner", + next_pc.is_highlighted_summoner); + } if (next_pc.is_sanctuary != current_pc.is_sanctuary) json_write_bool("sanctuary", next_pc.is_sanctuary); @@ -1372,7 +1426,7 @@ void TilesFramework::_send_cell(const coord_def &gc, } if (fg_changed || player_doll_changed) { - _send_doll(last_player_doll, in_water, false); + send_doll(last_player_doll, in_water, false); if (Options.tile_use_monster != MONS_0) { monster_info minfo(MONS_PLAYER, MONS_PLAYER); @@ -1543,7 +1597,7 @@ void TilesFramework::_send_map(bool force_full) draw_cell(cell, gc, false, m_current_flash_colour); cell->tile.flv = env.tile_flv(gc); - pack_cell_overlays(gc, &(cell->tile)); + pack_cell_overlays(gc, m_next_view); } mark_clean(gc); @@ -1691,6 +1745,16 @@ void TilesFramework::load_dungeon(const crawl_view_buffer &vbuf, mark_for_redraw(coord_def(x, y)); } + // re-cache the map knowledge for the whole map, not just the updated portion + // fixes render bugs for out-of-LOS when transitioning levels in shoals/slime + for (int y = 0; y < GYM; y++) + for (int x = 0; x < GXM; x++) + { + const coord_def cache_gc(x, y); + screen_cell_t *cell = &m_next_view(cache_gc); + cell->tile.map_knowledge = map_bounds(cache_gc) ? env.map_knowledge(cache_gc) : map_cell(); + } + m_next_view_tl = view2grid(coord_def(1, 1)); m_next_view_br = view2grid(crawl_view.viewsz); @@ -1708,7 +1772,7 @@ void TilesFramework::load_dungeon(const crawl_view_buffer &vbuf, *cell = ((const screen_cell_t *) vbuf)[x + vbuf.size().x * y]; cell->tile.flv = env.tile_flv(grid); - pack_cell_overlays(grid, &(cell->tile)); + pack_cell_overlays(grid, m_next_view); mark_clean(grid); // Remove redraw flag mark_dirty(grid); @@ -1778,6 +1842,7 @@ void TilesFramework::_send_everything() json_open_array("items"); for (UIStackFrame &frame : m_menu_stack) { + json_write_comma(); // noop immediately following open if (frame.type == UIStackFrame::MENU) frame.menu->webtiles_write_menu(); else if (frame.type == UIStackFrame::CRT) @@ -1799,7 +1864,6 @@ void TilesFramework::_send_everything() } continue; } - json_write_comma(); } json_close_array(); json_close_object(); @@ -1831,11 +1895,6 @@ void TilesFramework::cgotoxy(int x, int y, GotoRegion region) m_print_x = x - 1; m_print_y = y - 1; - // XXX: an ugly hack necessary for webtiles X to work properly - // when showing message prompts (e.g. X!, XG) - if (region == GOTO_STAT || region == GOTO_MSG) - set_ui_state(UI_NORMAL); - bool crt_popup = region == GOTO_CRT && !m_menu_stack.empty() && m_menu_stack.back().type == UIStackFrame::CRT; m_print_area = crt_popup ? &m_text_menu : nullptr; diff --git a/crawl-ref/source/tileweb.h b/crawl-ref/source/tileweb.h index 359fb762f307..ec59d96380e0 100644 --- a/crawl-ref/source/tileweb.h +++ b/crawl-ref/source/tileweb.h @@ -205,9 +205,13 @@ class TilesFramework void update_input_mode(mouse_mode mode); void send_mcache(mcache_entry *entry, bool submerged, - bool send_doll = true); + bool send = true); void write_tileidx(tileidx_t t); + void zoom_dungeon(bool in); + + void send_doll(const dolls_data &doll, bool submerged, bool ghost); + protected: int m_sock; int m_max_msg_size; @@ -257,10 +261,10 @@ class TilesFramework bool m_view_loaded; bool m_player_on_level; - FixedArray m_current_view; + crawl_view_buffer m_current_view; coord_def m_current_gc; - FixedArray m_next_view; + crawl_view_buffer m_next_view; coord_def m_next_gc; coord_def m_next_view_tl; coord_def m_next_view_br; diff --git a/crawl-ref/source/timed-effects.cc b/crawl-ref/source/timed-effects.cc index e4fb33040349..83ce6975b7b0 100644 --- a/crawl-ref/source/timed-effects.cc +++ b/crawl-ref/source/timed-effects.cc @@ -431,6 +431,8 @@ struct timed_effect bool arena; }; +// If you add an entry to this list, remember to add a matching entry +// to timed_effect_type in timef-effect-type.h! static struct timed_effect timed_effects[] = { { rot_floor_items, 200, 200, true }, @@ -695,10 +697,6 @@ static void _catchup_monster_moves(monster* mon, int turns) return; } - // Don't shift ballistomycete spores since that would disrupt their trail. - if (mon->type == MONS_BALLISTOMYCETE_SPORE) - return; - // special movement code for ioods if (mons_is_projectile(*mon)) { diff --git a/crawl-ref/source/transform.cc b/crawl-ref/source/transform.cc index 6e9b6ddefc8d..4ddd0d7fe3ba 100644 --- a/crawl-ref/source/transform.cc +++ b/crawl-ref/source/transform.cc @@ -980,7 +980,9 @@ static const Form* forms[] = &FormPig::instance(), &FormAppendage::instance(), &FormTree::instance(), +#if TAG_MAJOR_VERSION == 34 &FormPorcupine::instance(), +#endif &FormWisp::instance(), #if TAG_MAJOR_VERSION == 34 @@ -1545,16 +1547,16 @@ undead_form_reason lifeless_prevents_form(transformation which_trans, if (which_trans == transformation::lich) return UFR_TOO_DEAD; // vampires can never lichform - if (which_trans == transformation::bat) // can batform on satiated or below + if (which_trans == transformation::bat) // can batform bloodless { if (involuntary) return UFR_TOO_DEAD; // but not as a forced polymorph effect - return you.hunger_state <= HS_SATIATED ? UFR_GOOD : UFR_TOO_ALIVE; + return !you.vampire_alive ? UFR_GOOD : UFR_TOO_ALIVE; } - // other forms can only be entered when satiated or above. - return you.hunger_state >= HS_SATIATED ? UFR_GOOD : UFR_TOO_DEAD; + // other forms can only be entered when alive + return you.vampire_alive ? UFR_GOOD : UFR_TOO_DEAD; } /** @@ -1663,7 +1665,7 @@ bool transform(int pow, transformation which_trans, bool involuntary, else if (which_trans == transformation::lich && you.duration[DUR_DEATHS_DOOR]) { - msg = "You cannot become a lich while in Death's Door."; + msg = "You cannot become a lich while in death's door."; success = false; } @@ -1878,6 +1880,14 @@ bool transform(int pow, transformation which_trans, bool involuntary, if (was_flying && !you.airborne()) move_player_to_grid(you.pos(), false); + // Stop emergency flight if it's activated and this form can fly + if (you.props[EMERGENCY_FLIGHT_KEY] + && form_can_fly() + && you.airborne()) + { + you.props.erase(EMERGENCY_FLIGHT_KEY); + } + // Update merfolk swimming for the form change. if (you.species == SP_MERFOLK) merfolk_check_swimming(false); @@ -2091,3 +2101,16 @@ void merfolk_stop_swimming() init_player_doll(); #endif } + +void vampire_update_transformations() +{ + const undead_form_reason form_reason = lifeless_prevents_form(); + if (form_reason != UFR_GOOD && you.duration[DUR_TRANSFORMATION]) + { + print_stats(); + mprf(MSGCH_WARN, + "Your blood-%s body can't sustain your transformation.", + form_reason == UFR_TOO_DEAD ? "deprived" : "filled"); + untransform(); + } +} diff --git a/crawl-ref/source/transform.h b/crawl-ref/source/transform.h index 92bc7110384b..9d46a8631257 100644 --- a/crawl-ref/source/transform.h +++ b/crawl-ref/source/transform.h @@ -300,3 +300,4 @@ void emergency_untransform(); void merfolk_check_swimming(bool stepped = false); void merfolk_start_swimming(bool step = false); void merfolk_stop_swimming(); +void vampire_update_transformations(); diff --git a/crawl-ref/source/trap-type.h b/crawl-ref/source/trap-type.h index 2c0a7c43c44c..919722ebe288 100644 --- a/crawl-ref/source/trap-type.h +++ b/crawl-ref/source/trap-type.h @@ -2,9 +2,7 @@ enum trap_type { -#if TAG_MAJOR_VERSION == 34 TRAP_DART, -#endif TRAP_ARROW, TRAP_SPEAR, #if TAG_MAJOR_VERSION > 34 @@ -17,7 +15,9 @@ enum trap_type TRAP_BOLT, TRAP_NET, TRAP_ZOT, +#if TAG_MAJOR_VERSION == 34 TRAP_NEEDLE, +#endif TRAP_SHAFT, TRAP_GOLUBRIA, TRAP_PLATE, diff --git a/crawl-ref/source/traps.cc b/crawl-ref/source/traps.cc index f30f4bbdd004..10d472d5f123 100644 --- a/crawl-ref/source/traps.cc +++ b/crawl-ref/source/traps.cc @@ -64,10 +64,10 @@ bool trap_def::type_has_ammo() const switch (type) { #if TAG_MAJOR_VERSION == 34 - case TRAP_DART: + case TRAP_NEEDLE: #endif case TRAP_ARROW: case TRAP_BOLT: - case TRAP_NEEDLE: case TRAP_SPEAR: + case TRAP_DART: case TRAP_SPEAR: return true; default: break; @@ -102,7 +102,7 @@ void trap_def::prepare_ammo(int charges) { case TRAP_ARROW: case TRAP_BOLT: - case TRAP_NEEDLE: + case TRAP_DART: ammo_qty = 3 + random2avg(9, 3); break; case TRAP_SPEAR: @@ -188,7 +188,7 @@ bool trap_def::is_safe(actor* act) const return true; #endif - if (type == TRAP_NEEDLE) + if (type == TRAP_DART) return you.hp > 15; else if (type == TRAP_ARROW) return you.hp > 35; @@ -420,9 +420,11 @@ vector find_golubria_on_level() return ret; } -enum passage_type +enum class passage_type { - PASSAGE_FREE, PASSAGE_BLOCKED, PASSAGE_NONE + free, + blocked, + none, }; static passage_type _find_other_passage_side(coord_def& to) @@ -441,9 +443,9 @@ static passage_type _find_other_passage_side(coord_def& to) } const int choices = clear_passages.size(); if (choices < 1) - return has_blocks ? PASSAGE_BLOCKED : PASSAGE_NONE; + return has_blocks ? passage_type::blocked : passage_type::none; to = clear_passages[random2(choices)]; - return PASSAGE_FREE; + return passage_type::free; } void trap_def::trigger(actor& triggerer) @@ -494,7 +496,7 @@ void trap_def::trigger(actor& triggerer) { coord_def to = p; passage_type search_result = _find_other_passage_side(to); - if (search_result == PASSAGE_FREE) + if (search_result == passage_type::free) { if (you_trigger) mpr("You enter the passage of Golubria."); @@ -511,7 +513,7 @@ void trap_def::trigger(actor& triggerer) } else if (you_trigger) { - mprf("This passage %s!", search_result == PASSAGE_BLOCKED ? + mprf("This passage %s!", search_result == passage_type::blocked ? "seems to be blocked by something" : "doesn't lead anywhere"); } break; @@ -636,15 +638,8 @@ void trap_def::trigger(actor& triggerer) // Nets need LOF to hit the player, no netting through glass. if (!you.see_cell_no_trans(pos)) break; - bool triggered = false; - if (you_trigger) - { - if (one_chance_in(3)) - mpr("A net swings high above you."); - else - triggered = true; - } - else if (m) + bool triggered = you_trigger; + if (m) { if (mons_intel(*m) < I_HUMAN) { @@ -848,7 +843,7 @@ int trap_def::max_damage(const actor& act) switch (type) { - case TRAP_NEEDLE: return 0; + case TRAP_DART: return 0; case TRAP_ARROW: return mon ? 7 : 15; case TRAP_SPEAR: return mon ? 10 : 26; case TRAP_BOLT: return mon ? 18 : 40; @@ -881,7 +876,7 @@ int trap_def::to_hit_bonus() return 15; case TRAP_NET: return 5; - case TRAP_NEEDLE: + case TRAP_DART: return 8; // Irrelevant: default: @@ -1094,12 +1089,12 @@ item_def trap_def::generate_trap_item() switch (type) { #if TAG_MAJOR_VERSION == 34 - case TRAP_DART: base = OBJ_MISSILES; sub = MI_DART; break; + case TRAP_NEEDLE: base = OBJ_MISSILES; sub = MI_NEEDLE; break; #endif case TRAP_ARROW: base = OBJ_MISSILES; sub = MI_ARROW; break; case TRAP_BOLT: base = OBJ_MISSILES; sub = MI_BOLT; break; case TRAP_SPEAR: base = OBJ_WEAPONS; sub = WPN_SPEAR; break; - case TRAP_NEEDLE: base = OBJ_MISSILES; sub = MI_NEEDLE; break; + case TRAP_DART: base = OBJ_MISSILES; sub = MI_DART; break; case TRAP_NET: base = OBJ_MISSILES; sub = MI_THROWING_NET; break; default: return item; } @@ -1111,7 +1106,7 @@ item_def trap_def::generate_trap_item() if (base == OBJ_MISSILES) { set_item_ego_type(item, base, - (sub == MI_NEEDLE) ? SPMSL_POISONED : SPMSL_NORMAL); + (sub == MI_DART) ? SPMSL_POISONED : SPMSL_NORMAL); } else set_item_ego_type(item, base, SPWPN_NORMAL); @@ -1193,7 +1188,7 @@ void trap_def::shoot_ammo(actor& act, bool was_known) } else // OK, we've been hit. { - bool poison = type == TRAP_NEEDLE + bool poison = type == TRAP_DART && (x_chance_in_y(50 - (3*act.armour_class()) / 2, 100)); int damage_taken = act.apply_ac(shot_damage(act)); @@ -1264,12 +1259,12 @@ dungeon_feature_type trap_category(trap_type type) case TRAP_ARROW: case TRAP_SPEAR: case TRAP_BLADE: + case TRAP_DART: case TRAP_BOLT: - case TRAP_NEEDLE: case TRAP_NET: #if TAG_MAJOR_VERSION == 34 + case TRAP_NEEDLE: case TRAP_GAS: - case TRAP_DART: #endif case TRAP_PLATE: return DNGN_TRAP_MECHANICAL; @@ -1298,7 +1293,7 @@ bool is_valid_shaft_level() const Branch &branch = branches[place.branch]; - if (branch.branch_flags & BFLAG_NO_SHAFTS) + if (branch.branch_flags & brflag::no_shafts) return false; // Don't allow shafts from the bottom of a branch. @@ -1318,7 +1313,7 @@ bool is_valid_shaft_effect_level() // Don't shaft the player when we can't, and also when it would be into a // dangerous end. return is_valid_shaft_level() - && !(branch.branch_flags & BFLAG_DANGEROUS_END + && !(branch.branch_flags & brflag::dangerous_end && brdepth[place.branch] - place.depth == 1); } @@ -1344,7 +1339,7 @@ static level_id _generic_shaft_dest(level_pos lpos, bool known = false) // Only shafts on the level immediately above a dangerous branch // bottom will take you to that dangerous bottom. - if (branches[lid.branch].branch_flags & BFLAG_DANGEROUS_END + if (branches[lid.branch].branch_flags & brflag::dangerous_end && lid.depth == max_depth && (max_depth - curr_depth) > 1) { @@ -1366,46 +1361,52 @@ void roll_trap_effects() } /*** - * Separate from the previous function so the trap triggers when crawl is in an + * Separate from roll_trap_effects so the trap triggers when crawl is in an * appropriate state */ void do_trap_effects() { - const level_id place = level_id::current(); - const Branch &branch = branches[place.branch]; - // Try to shaft, teleport, or alarm the player. - int roll = random2(3); - switch (roll) + + // We figure out which possibilites are allowed before picking which happens + // so that the overall chance of being trapped doesn't depend on which + // possibilities are allowed. + + // Teleport effects are allowed everywhere, no need to check + vector available_traps = { TRAP_TELEPORT }; + // Don't shaft the player when shafts aren't allowed in the location or when + // it would be into a dangerous end. + if (is_valid_shaft_effect_level()) + available_traps.push_back(TRAP_SHAFT); + // No alarms on the first 3 floors + if (env.absdepth0 > 3) + available_traps.push_back(TRAP_ALARM); + + switch (*random_iterator(available_traps)) { - case 0: - // Don't shaft the player when we can't, and also when it would be into a - // dangerous end. - if (is_valid_shaft_level() - && !(branch.branch_flags & BFLAG_DANGEROUS_END - && brdepth[place.branch] - place.depth == 1)) - { - dprf("Attempting to shaft player."); - you.do_shaft(); - } + case TRAP_SHAFT: + dprf("Attempting to shaft player."); + you.do_shaft(); break; - case 1: - // No alarms on the first 3 floors - if (env.absdepth0 > 3) - { - // Alarm effect alarms are always noisy, even if the player is - // silenced, to avoid "travel only while silenced" behavior. - // XXX: improve messaging to make it clear theres a wail outside of the - // player's silence - mprf("You set off the alarm!"); - fake_noisy(40, you.pos()); - you.sentinel_mark(true); - } + + case TRAP_ALARM: + // Alarm effect alarms are always noisy, even if the player is + // silenced, to avoid "travel only while silenced" behavior. + // XXX: improve messaging to make it clear theres a wail outside of the + // player's silence + mprf("You set off the alarm!"); + fake_noisy(40, you.pos()); + you.sentinel_mark(true); break; - case 2: - // Teleportitis + + case TRAP_TELEPORT: you_teleport_now(false, true, "You stumble into a teleport trap!"); break; + + // Other cases shouldn't be possible, but having a default here quiets + // compiler warnings + default: + break; } } @@ -1489,7 +1490,7 @@ trap_type random_vault_trap() trap_type type = TRAP_ARROW; if ((random2(1 + level_number) > 1) && one_chance_in(4)) - type = TRAP_NEEDLE; + type = TRAP_DART; if (random2(1 + level_number) > 3) type = TRAP_SPEAR; diff --git a/crawl-ref/source/travel.cc b/crawl-ref/source/travel.cc index a716e5f5cca4..e1c1b92658ea 100644 --- a/crawl-ref/source/travel.cc +++ b/crawl-ref/source/travel.cc @@ -73,6 +73,9 @@ enum IntertravelDestination // Repeat last travel ID_REPEAT = -101, + // Altar as target + ID_ALTAR = -102, + // Cancel interlevel travel ID_CANCEL = -1000, }; @@ -2037,7 +2040,7 @@ bool is_known_branch_id(branch_type branch) // If the overview knows the stairs to this branch, we know the branch. return stair_level.find(static_cast(branch)) - != stair_level.end(); + != stair_level.end() && stair_level[branch].size(); } static bool _is_known_branch(const Branch &br) @@ -2152,6 +2155,8 @@ static int _prompt_travel_branch(int prompt_flags) show_interlevel_travel_branch_help(); redraw_screen(); break; + case '_': + return ID_ALTAR; case '\n': case '\r': return ID_REPEAT; case '<': @@ -2175,7 +2180,7 @@ static int _prompt_travel_branch(int prompt_flags) // Is this a branch hotkey? for (branch_type br : brs) { - if (toupper(keyin) == branches[br].travel_shortcut) + if (toupper_safe(keyin) == branches[br].travel_shortcut) { #ifdef WIZARD const Branch &target = branches[br]; @@ -2214,6 +2219,128 @@ static int _prompt_travel_branch(int prompt_flags) } } +static god_type _god_from_initial(const char god_initial) +{ + switch (toupper_safe(god_initial)) + { + case '1': return GOD_SHINING_ONE; + case 'A': return GOD_ASHENZARI; + case 'B': return GOD_BEOGH; + case 'C': return GOD_CHEIBRIADOS; + case 'D': return GOD_DITHMENOS; + case 'E': return GOD_ELYVILON; + case 'F': return GOD_FEDHAS; + case 'G': return GOD_GOZAG; + case 'H': return GOD_HEPLIAKLQANA; + case 'J': return GOD_JIYVA; + case 'K': return GOD_KIKUBAAQUDGHA; + case 'L': return GOD_LUGONU; + case 'M': return GOD_MAKHLEB; + case 'N': return GOD_NEMELEX_XOBEH; + case 'O': return GOD_OKAWARU; +#if TAG_MAJOR_VERSION == 34 + case 'P': return GOD_PAKELLAS; +#endif + case 'Q': return GOD_QAZLAL; + case 'R': return GOD_RU; + case 'S': return GOD_SIF_MUNA; + case 'T': return GOD_TROG; + case 'U': return GOD_USKAYAW; + case 'V': return GOD_VEHUMET; + case 'W': return GOD_WU_JIAN; + case 'X': return GOD_XOM; + case 'Y': return GOD_YREDELEMNUL; + case 'Z': return GOD_ZIN; + default: return GOD_NO_GOD; + } +} + +static level_pos _prompt_travel_altar() +{ + extern map altars_present; + + if (altars_present.empty()) + return level_pos(); + + level_pos nearest_altars[NUM_GODS]; + const level_id curr = level_id::current(); + + // Populate nearest_altars[] with nearest altars + for (const auto &entry : altars_present) + { + // This is necessary because faded altars (i.e., GOD_ECUMENICAL) + // are also recorded in altars_present + if (entry.second >= NUM_GODS) + continue; + + int dist = level_distance(curr, entry.first.id); + if (dist == -1) + continue; + + level_pos *best = &nearest_altars[entry.second]; + int old_dist = best->id.is_valid() ? level_distance(curr, best->id) : INT_MAX; + + if (dist < old_dist) + *best = entry.first; + } + + while (true) + { + clear_messages(); + + int col = 0; + string line; + string altar_name; + char god_initial; + vector god_list = temple_god_list(); + vector nt_god_list = nontemple_god_list(); + god_list.insert(god_list.end(), nt_god_list.begin(), nt_god_list.end()); + + // list gods in the same order as dgn-overview.cc lists them. + for (const god_type god : god_list) + { + if (!nearest_altars[god].is_valid()) + continue; + + // "The Shining One" is too long to keep the same G menu layout + altar_name = god == GOD_SHINING_ONE ? "TSO" : god_name(god); + god_initial = god == GOD_SHINING_ONE ? '1' : altar_name.at(0); + + if (col == 4) + { + col = 0; + mpr(line); + line = ""; + } + line += make_stringf("(%c) %-14s ", god_initial, altar_name.c_str()); + ++col; + } + if (!line.empty()) + mpr(line); + + mprf(MSGCH_PROMPT, "Go to which altar? (? - help) "); + + int keyin = get_ch(); + switch (keyin) + { + CASE_ESCAPE + return level_pos(); + case '?': + show_interlevel_travel_altar_help(); + redraw_screen(); + break; + case '\n': case '\r': + return level_target; + default: + const level_pos altar_pos = nearest_altars[_god_from_initial(keyin)]; + if (altar_pos.is_valid()) + return altar_pos; + + return level_pos(); + } + } +} + level_id find_up_level(level_id curr, bool up_branch) { --curr.depth; @@ -2457,6 +2584,9 @@ level_pos prompt_translevel_target(int prompt_flags, string& dest_name) if (branch == ID_CANCEL) return target; + if (branch == ID_ALTAR) + return _prompt_travel_altar(); + // If user chose to repeat last travel, return that. if (branch == ID_REPEAT) return level_target; @@ -3962,8 +4092,13 @@ void TravelCache::add_waypoint(int x, int y) return; } - int waynum = keyin - '0'; + set_waypoint(keyin - '0', x, y); +} + +void TravelCache::set_waypoint(int waynum, int x, int y) +{ + ASSERT_RANGE(waynum, 0, TRAVEL_WAYPOINT_COUNT); coord_def pos(x,y); if (x == -1 || y == -1) pos = you.pos(); @@ -3984,7 +4119,7 @@ void TravelCache::add_waypoint(int x, int y) if (overwrite) { if (lid == old_lid) // same level - mprf("Waypoint %d re-assigned to your current position.", waynum); + mprf("Waypoint %d re-assigned to %s.", waynum, new_dest.c_str()); else { mprf("Waypoint %d re-assigned from %s to %s.", @@ -4646,10 +4781,10 @@ template void explore_discoveries::say_any( return; } - const string message = "Found " + - comma_separated_line(coll.begin(), coll.end()) + "."; + const auto message = formatted_string::parse_string("Found " + + comma_separated_line(coll.begin(), coll.end()) + "."); - if (printed_width(message) >= get_number_of_cols()) + if (message.width() >= get_number_of_cols()) mprf("Found %s %s.", number_in_words(size).c_str(), category); else mpr(message); diff --git a/crawl-ref/source/travel.h b/crawl-ref/source/travel.h index bff9e8cfa942..8e3ee8ae281f 100644 --- a/crawl-ref/source/travel.h +++ b/crawl-ref/source/travel.h @@ -449,6 +449,7 @@ class TravelCache void set_level_excludes(); void add_waypoint(int x = -1, int y = -1); + void set_waypoint(int waynum, int x, int y); void delete_waypoint(); uint8_t is_waypoint(const level_pos &lp) const; void list_waypoints() const; diff --git a/crawl-ref/source/ui.cc b/crawl-ref/source/ui.cc index f1269f5633d3..83a9ac116dec 100644 --- a/crawl-ref/source/ui.cc +++ b/crawl-ref/source/ui.cc @@ -12,51 +12,40 @@ #include "ui.h" #include "cio.h" #include "macro.h" -# include "state.h" +#include "state.h" #include "tileweb.h" #ifdef USE_TILE_LOCAL # include "glwrapper.h" # include "tilebuf.h" +# include "tilepick.h" +# include "tilepick-p.h" +# include "tile-player-flag-cut.h" #else # include # include "output.h" -# include "view.h" # include "stringutil.h" +# include "view.h" #endif namespace ui { -static i4 aabb_intersect(i4 a, i4 b) +static Region aabb_intersect(Region a, Region b) { - a[2] += a[0]; a[3] += a[1]; - b[2] += b[0]; b[3] += b[1]; - i4 i = { max(a[0], b[0]), max(a[1], b[1]), min(a[2], b[2]), min(a[3], b[3]) }; - i[2] -= i[0]; i[3] -= i[1]; + Region i = { max(a.x, b.x), max(a.y, b.y), min(a.ex(), b.ex()), min(a.ey(), b.ey()) }; + i.width -= i.x; i.height -= i.y; return i; } -static i4 aabb_union(i4 a, i4 b) +static Region aabb_union(Region a, Region b) { - a[2] += a[0]; a[3] += a[1]; - b[2] += b[0]; b[3] += b[1]; - i4 i = { min(a[0], b[0]), min(a[1], b[1]), max(a[2], b[2]), max(a[3], b[3]) }; - i[2] -= i[0]; i[3] -= i[1]; + Region i = { min(a.x, b.x), min(a.y, b.y), max(a.ex(), b.ex()), max(a.ey(), b.ey()) }; + i.width -= i.x; i.height -= i.y; return i; } - -static inline bool pos_in_rect(i2 pos, i4 rect) -{ - if (pos[0] < rect[0] || pos[0] >= rect[0]+rect[2]) - return false; - if (pos[1] < rect[1] || pos[1] >= rect[1]+rect[3]) - return false; - return true; -} - #ifndef USE_TILE_LOCAL -static void clear_text_region(i4 region); +static void clear_text_region(Region region, COLOURS bg); #endif // must be before ui_root declaration for correct destruction order @@ -73,16 +62,24 @@ static struct UIRoot }; size_t num_children() const { return m_root.num_children(); }; + bool widget_is_in_layout(const Widget* w) + { + for (; w; w = w->_get_parent()) + if (w == &m_root) + return true; + return false; + } + void resize(int w, int h); void layout(); void render(); bool on_event(const wm_event& event); void queue_layout() { m_needs_layout = true; }; - void expose_region(i4 r) { - if (r[2] == 0 || r[3] == 0) + void expose_region(Region r) { + if (r.empty()) return; - if (m_dirty_region[2] == 0) + if (m_dirty_region.empty()) m_dirty_region = r; else m_dirty_region = aabb_union(m_dirty_region, r); @@ -91,81 +88,54 @@ static struct UIRoot void send_mouse_enter_leave_events(int mx, int my); bool needs_paint; +#ifdef DEBUG + bool debug_draw = false; +#endif vector keymap_stack; vector cutoff_stack; + vector focus_stack; + + struct RestartAllocation {}; protected: int m_w, m_h; - i4 m_region; - i4 m_dirty_region{0, 0, 0, 0}; + Region m_region; + Region m_dirty_region; Stack m_root; bool m_needs_layout{false}; + bool m_changed_layout_since_click = false; } ui_root; -static stack scissor_stack; +static stack scissor_stack; struct Widget::slots Widget::slots = {}; -bool Widget::on_event(const wm_event& event) +Widget::~Widget() { - return Widget::slots.event.emit(this, event); + Widget::slots.event.remove_by_target(this); + if (m_parent && get_focused_widget() == this) + set_focused_widget(nullptr); + _set_parent(nullptr); } -static inline bool _maybe_propagate_event(wm_event event, shared_ptr &child) +bool Widget::on_event(const wm_event& event) { -#ifdef USE_TILE_LOCAL - /* WME_MOUSELEAVE is conspicuously absent because it is emitted - * only on widgets that the cursor just left */ - if (event.type == WME_MOUSEMOTION - || event.type == WME_MOUSEENTER - || event.type == WME_MOUSEBUTTONDOWN - || event.type == WME_MOUSEBUTTONUP) - { - i2 pos = {(int)event.mouse_event.px, (int)event.mouse_event.py}; - if (!pos_in_rect(pos, child->get_region())) - return false; - } -#endif - return child->on_event(event); + return Widget::slots.event.emit(this, event); } shared_ptr ContainerVec::get_child_at_offset(int x, int y) { for (shared_ptr& child : m_children) - { - const i4 region = child->get_region(); - bool inside = (x >= region[0] && x < region[0] + region[2]) - && (y >= region[1] && y < region[1] + region[3]); - if (inside) + if (child->get_region().contains_point(x, y)) return child; - } return nullptr; } -bool Container::on_event(const wm_event& event) -{ - if (Widget::on_event(event)) - return true; - for (shared_ptr &child : *this) - if (_maybe_propagate_event(event, child)) - return true; - return false; -} - -bool Bin::on_event(const wm_event& event) -{ - if (Widget::on_event(event)) - return true; - if (_maybe_propagate_event(event, m_child)) - return true; - return false; -} - shared_ptr Bin::get_child_at_offset(int x, int y) { - bool inside = (x > m_region[0] && x < m_region[0] + m_region[2]) - && (y > m_region[1] && y < m_region[1] + m_region[3]); - return inside ? m_child : nullptr; + if (m_child && m_child->get_region().contains_point(x, y)) + return m_child; + return nullptr; } void Bin::set_child(shared_ptr child) @@ -177,17 +147,21 @@ void Bin::set_child(shared_ptr child) void Widget::render() { - _render(); + if (m_visible) + _render(); } SizeReq Widget::get_preferred_size(Direction dim, int prosp_width) { ASSERT((dim == HORZ) == (prosp_width == -1)); + if (!m_visible) + return { 0, 0 }; + if (cached_sr_valid[dim] && (!dim || cached_sr_pw == prosp_width)) return cached_sr[dim]; - prosp_width = dim ? prosp_width - margin[1] - margin[3] : prosp_width; + prosp_width = dim ? prosp_width - margin.right - margin.left : prosp_width; SizeReq ret = _get_preferred_size(dim, prosp_width); ASSERT(ret.min <= ret.nat); @@ -202,13 +176,16 @@ SizeReq Widget::get_preferred_size(Direction dim, int prosp_width) else if (shrink) ret.nat = ret.min; - ASSERT(m_min_size[dim] <= m_max_size[dim]); - ret.min = max(ret.min, m_min_size[dim]); - ret.nat = min(ret.nat, max(m_max_size[dim], ret.min)); + int& min_size = dim ? m_min_size.height : m_min_size.width; + int& max_size = dim ? m_max_size.height : m_max_size.width; + + ASSERT(min_size <= max_size); + ret.min = max(ret.min, min_size); + ret.nat = min(ret.nat, max(max_size, ret.min)); ret.nat = max(ret.nat, ret.min); ASSERT(ret.min <= ret.nat); - int m = margin[1-dim] + margin[3-dim]; + const int m = dim ? margin.top + margin.bottom : margin.left + margin.right; ret.min += m; ret.nat += m; @@ -222,13 +199,16 @@ SizeReq Widget::get_preferred_size(Direction dim, int prosp_width) return ret; } -void Widget::allocate_region(i4 region) +void Widget::allocate_region(Region region) { - i4 new_region = { - region[0] + margin[3], - region[1] + margin[0], - region[2] - margin[3] - margin[1], - region[3] - margin[0] - margin[2], + if (!m_visible) + return; + + Region new_region = { + region.x + margin.left, + region.y + margin.top, + region.width - margin.left - margin.right, + region.height - margin.top - margin.bottom, }; if (m_region == new_region && !alloc_queued) @@ -238,8 +218,8 @@ void Widget::allocate_region(i4 region) m_region = new_region; alloc_queued = false; - ASSERT(m_region[2] >= 0); - ASSERT(m_region[3] >= 0); + ASSERT(m_region.width >= 0); + ASSERT(m_region.height >= 0); _allocate_region(); } @@ -253,6 +233,20 @@ void Widget::_allocate_region() { } +/** + * Determine whether a widget contains the given widget. + * + * @param child The other widget. + * @return True if the other widget is a descendant of this widget. + */ +bool Widget::is_ancestor_of(const shared_ptr& other) +{ + for (Widget* w = other.get(); w; w = w->_get_parent()) + if (w == this) + return true; + return false; +} + void Widget::_set_parent(Widget* p) { if (!p) @@ -267,6 +261,20 @@ void Widget::_set_parent(Widget* p) m_parent = p; } +/** + * Unparent a widget. + * + * This function verifies that a widget has the correct parent before orphaning. + * Intended for use in container widget destructors. + * + * @param child The child widget to unparent. + */ +void Widget::_unparent(shared_ptr& child) +{ + if (child->m_parent == this) + child->_set_parent(nullptr); +} + void Widget::_invalidate_sizereq(bool immediate) { for (auto w = this; w; w = w->m_parent) @@ -288,6 +296,14 @@ void Widget::_expose() ui_root.expose_region(m_region); } +void Widget::set_visible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + _invalidate_sizereq(); +} + void Box::add_child(shared_ptr child) { child->_set_parent(this); @@ -311,6 +327,8 @@ vector Box::layout_main_axis(vector& ch_psz, int main_sz) { ch_sz[i] = ch_psz[i].min; extra -= ch_psz[i].min; + if (align_main == Align::STRETCH) + ch_psz[i].nat = INT_MAX; } ASSERT(extra >= 0); @@ -344,10 +362,7 @@ vector Box::layout_cross_axis(vector& ch_psz, int cross_sz) for (size_t i = 0; i < m_children.size(); i++) { - auto const& child = m_children[i]; - // find the child's size on the cross axis - bool stretch = child->align_self == STRETCH ? true - : align_items == STRETCH; + const bool stretch = align_cross == STRETCH; ch_sz[i] = stretch ? cross_sz : min(max(ch_psz[i].min, cross_sz), ch_psz[i].nat); } @@ -392,35 +407,39 @@ void Box::_allocate_region() sr[i] = m_children[i]->get_preferred_size(Widget::HORZ, -1); // Get actual widths - vector cw = horz ? layout_main_axis(sr, m_region[2]) : layout_cross_axis(sr, m_region[2]); + vector cw = horz ? layout_main_axis(sr, m_region.width) : layout_cross_axis(sr, m_region.width); // Get preferred heights for (size_t i = 0; i < m_children.size(); i++) sr[i] = m_children[i]->get_preferred_size(Widget::VERT, cw[i]); // Get actual heights - vector ch = horz ? layout_cross_axis(sr, m_region[3]) : layout_main_axis(sr, m_region[3]); + vector ch = horz ? layout_cross_axis(sr, m_region.height) : layout_main_axis(sr, m_region.height); auto const &m = horz ? cw : ch; - int extra_main_space = m_region[horz ? 2 : 3] - accumulate(m.begin(), m.end(), 0); + int extra_main_space = (horz ? m_region.width : m_region.height) - accumulate(m.begin(), m.end(), 0); ASSERT(extra_main_space >= 0); // main axis offset - int mo = extra_main_space*(justify_items - Box::Justify::START)/2; - int ho = m_region[0] + (horz ? mo : 0); - int vo = m_region[1] + (!horz ? mo : 0); + int mo; + switch (align_main) + { + case Widget::START: mo = 0; break; + case Widget::CENTER: mo = extra_main_space/2; break; + case Widget::END: mo = extra_main_space; break; + case Widget::STRETCH: mo = 0; break; + default: ASSERT(0); + } + int ho = m_region.x + (horz ? mo : 0); + int vo = m_region.y + (!horz ? mo : 0); - i4 cr = {ho, vo, 0, 0}; + Region cr = {ho, vo, 0, 0}; for (size_t i = 0; i < m_children.size(); i++) { // cross axis offset - int extra_cross_space = horz ? m_region[3] - ch[i] : m_region[2] - cw[i]; - int xp = horz ? 1 : 0, xs = xp + 2; + int extra_cross_space = horz ? m_region.height - ch[i] : m_region.width - cw[i]; - auto const& child = m_children[i]; - Align child_align = child->align_self ? child->align_self - : align_items ? align_items - : Align::START; + const Align child_align = align_cross; int xo; switch (child_align) { @@ -430,27 +449,51 @@ void Box::_allocate_region() case Widget::STRETCH: xo = 0; break; default: ASSERT(0); } - cr[xp] = (horz ? vo : ho) + xo; - cr[2] = cw[i]; - cr[3] = ch[i]; + int& cr_cross_offset = horz ? cr.y : cr.x; + int& cr_cross_size = horz ? cr.height : cr.width; + + cr_cross_offset = (horz ? vo : ho) + xo; + cr.width = cw[i]; + cr.height = ch[i]; if (child_align == STRETCH) - cr[xs] = (horz ? ch : cw)[i]; + cr_cross_size = (horz ? ch : cw)[i]; m_children[i]->allocate_region(cr); - cr[horz ? 0 : 1] += cr[horz ? 2 : 3]; + + int& cr_main_offset = horz ? cr.x : cr.y; + int& cr_main_size = horz ? cr.width : cr.height; + cr_main_offset += cr_main_size; } } +Text::Text() +{ +#ifdef USE_TILE_LOCAL + set_font(tiles.get_crt_font()); +#endif +} + void Text::set_text(const formatted_string &fs) { + if (fs == m_text) + return; m_text.clear(); m_text += fs; _invalidate_sizereq(); _expose(); - m_wrapped_size = { -1, -1 }; + m_wrapped_size = Size(-1); _queue_allocation(); } +#ifdef USE_TILE_LOCAL +void Text::set_font(FontWrapper *font) +{ + ASSERT(font); + m_font = font; + _queue_allocation(); +} +#endif + void Text::set_highlight_pattern(string pattern, bool line) { hl_pat = pattern; @@ -460,7 +503,7 @@ void Text::set_highlight_pattern(string pattern, bool line) void Text::wrap_text_to_size(int width, int height) { - i2 wrapped_size = { width, height }; + Size wrapped_size = { width, height }; if (m_wrapped_size == wrapped_size) return; m_wrapped_size = wrapped_size; @@ -469,7 +512,7 @@ void Text::wrap_text_to_size(int width, int height) #ifdef USE_TILE_LOCAL if (wrap_text || ellipsize) - m_text_wrapped = tiles.get_crt_font()->split(m_text, width, height); + m_text_wrapped = m_font->split(m_text, width, height); else m_text_wrapped = m_text; @@ -503,6 +546,8 @@ void Text::wrap_text_to_size(int width, int height) last_line += formatted_string(".."); m_wrapped_lines.resize(height); } + if (m_wrapped_lines.empty()) + m_wrapped_lines.emplace_back(""); #endif } @@ -520,20 +565,20 @@ static vector _find_highlights(const string& haystack, const string& nee void Text::_render() { - i4 region = m_region; + Region region = m_region; if (scissor_stack.size() > 0) region = aabb_intersect(region, scissor_stack.top()); - if (region[2] <= 0 || region[3] <= 0) + if (region.width <= 0 || region.height <= 0) return; - wrap_text_to_size(m_region[2], m_region[3]); + wrap_text_to_size(m_region.width, m_region.height); #ifdef USE_TILE_LOCAL - const int dev_line_height = tiles.get_crt_font()->char_height(false); + const int dev_line_height = m_font->char_height(false); const int line_min_pos = display_density.logical_to_device( - region[1] - m_region[1]); + region.y - m_region.y); const int line_max_pos = display_density.logical_to_device( - region[1] + region[3] - m_region[1]); + region.y + region.height - m_region.y); const int line_min = line_min_pos / dev_line_height; const int line_max = line_max_pos / dev_line_height; @@ -587,13 +632,12 @@ void Text::_render() vector highlights = _find_highlights(full_text, hl_pat, begin_idx, end_idx); - int ox = m_region[0]; - const int oy = display_density.logical_to_device(m_region[1]) + + int ox = m_region.x; + const int oy = display_density.logical_to_device(m_region.y) + dev_line_height * line_off; size_t lacc = 0; // the start char of the current op relative to region size_t line = 0; // the line we are at relative to the region - FontWrapper *font = tiles.get_crt_font(); bool inside = false; // Iterate over formatted_string op slices, looking for highlight // sequences. Highlight sequences may span multiple op slices, hence @@ -621,13 +665,13 @@ void Text::_render() string line_before_op = full_text.substr(op_line_start, begin_idx + lacc - op_line_start); // pixel positions for the current op - size_t op_x = font->string_width(line_before_op.c_str()); + size_t op_x = m_font->string_width(line_before_op.c_str()); const size_t op_y = - font->string_height(line_before_op.c_str(), false) + m_font->string_height(line_before_op.c_str(), false) - dev_line_height; // positions in device pixels to highlight relative to current op - size_t sx = 0, ex = font->string_width(op.text.c_str()); + size_t sx = 0, ex = m_font->string_width(op.text.c_str()); size_t sy = 0, ey = dev_line_height; bool started = false; // does the highlight start in the current op? @@ -643,16 +687,16 @@ void Text::_render() op_x = 0; // start position is somewhere in the current op const string before = full_text.substr(begin_idx + lacc, start); - sx = font->string_width(before.c_str()); - sy = font->string_height(before.c_str(), false) + sx = m_font->string_width(before.c_str()); + sy = m_font->string_height(before.c_str(), false) - dev_line_height; started = true; } if (end <= oplen) // assume end is unsigned and so >=0 { const string to_end = full_text.substr(begin_idx + lacc, end); - ex = font->string_width(to_end.c_str()); - ey = font->string_height(to_end.c_str(), false); + ex = m_font->string_width(to_end.c_str()); + ey = m_font->string_height(to_end.c_str(), false); ended = true; } @@ -670,9 +714,9 @@ void Text::_render() if (hl_line) { block_lines.insert(y); - m_hl_buf.add(region[0], + m_hl_buf.add(region.x, display_density.device_to_logical(y), - region[0] + region[2], + region.x + region.width, display_density.device_to_logical(y + dev_line_height), VColour(255, 255, 0, 50)); @@ -699,7 +743,7 @@ void Text::_render() else { lacc += oplen; - line += font->string_height(op.text.c_str(), false) + line += m_font->string_height(op.text.c_str(), false) - dev_line_height; } } @@ -708,8 +752,8 @@ void Text::_render() // XXX: should be moved into a new function render_formatted_string() // in FTFontWrapper, that, like render_textblock(), would automatically // handle swapping atlas glyphs as necessary. - FontBuffer m_font_buf(tiles.get_crt_font()); - m_font_buf.add(slice, m_region[0], m_region[1] + + FontBuffer m_font_buf(m_font); + m_font_buf.add(slice, m_region.x, m_region.y + display_density.device_to_logical(dev_line_height * line_off)); m_font_buf.draw(); #else @@ -717,21 +761,23 @@ void Text::_render() vector highlights; int begin_idx = 0; + clear_text_region(m_region, m_bg_colour); + if (!hl_pat.empty()) { - for (int i = 0; i < region[1]-m_region[1]; i++) + for (int i = 0; i < region.y-m_region.y; i++) begin_idx += m_wrapped_lines[i].tostring().size()+1; int end_idx = begin_idx; - for (int i = region[1]-m_region[1]; i < region[1]-m_region[1]+region[3]; i++) + for (int i = region.y-m_region.y; i < region.y-m_region.y+region.height; i++) end_idx += m_wrapped_lines[i].tostring().size()+1; highlights = _find_highlights(m_text.tostring(), hl_pat, begin_idx, end_idx); } unsigned int hl_idx = 0; - for (size_t i = 0; i < min(lines.size(), (size_t)region[3]); i++) + for (size_t i = 0; i < min(lines.size(), (size_t)region.height); i++) { - cgotoxy(region[0]+1, region[1]+1+i); - formatted_string line = lines[i+region[1]-m_region[1]]; + cgotoxy(region.x+1, region.y+1+i); + formatted_string line = lines[i+region.y-m_region.y]; int end_idx = begin_idx + line.tostring().size(); // convert highlights on this line to a list of line cuts @@ -764,7 +810,7 @@ void Text::_render() else out += slice; } - out.chop(region[2]).display(0); + out.chop(region.width).display(0); begin_idx = end_idx + 1; // +1 is for the newline } @@ -774,10 +820,9 @@ void Text::_render() SizeReq Text::_get_preferred_size(Direction dim, int prosp_width) { #ifdef USE_TILE_LOCAL - FontWrapper *font = tiles.get_crt_font(); if (!dim) { - int w = font->string_width(m_text); + int w = m_font->string_width(m_text); // XXX: should be width of '..', unless string itself is shorter than '..' static constexpr int min_ellipsized_width = 0; static constexpr int min_wrapped_width = 0; // XXX: should be width of longest word @@ -786,8 +831,8 @@ SizeReq Text::_get_preferred_size(Direction dim, int prosp_width) else { wrap_text_to_size(prosp_width, 0); - int height = font->string_height(m_text_wrapped); - return { ellipsize ? (int)font->char_height() : height, height }; + int height = m_font->string_height(m_text_wrapped); + return { ellipsize ? (int)m_font->char_height() : height, height }; } #else if (!dim) @@ -816,18 +861,28 @@ SizeReq Text::_get_preferred_size(Direction dim, int prosp_width) void Text::_allocate_region() { - wrap_text_to_size(m_region[2], m_region[3]); + wrap_text_to_size(m_region.width, m_region.height); } +#ifndef USE_TILE_LOCAL +void Text::set_bg_colour(COLOURS colour) +{ + m_bg_colour = colour; + _expose(); +} +#endif + void Image::set_tile(tile_def tile) { -#ifdef USE_TILE_LOCAL +#ifdef USE_TILE m_tile = tile; +#ifdef USE_TILE_LOCAL const tile_info &ti = tiles.get_image_manager()->tile_def_info(m_tile); m_tw = ti.width; m_th = ti.height; _invalidate_sizereq(); #endif +#endif } void Image::_render() @@ -837,8 +892,8 @@ void Image::_render() TileBuffer tb; tb.set_tex(&tiles.get_image_manager()->m_textures[m_tile.tex]); - for (int y = m_region[1]; y < m_region[1]+m_region[3]; y+=m_th) - for (int x = m_region[0]; x < m_region[0]+m_region[2]; x+=m_tw) + for (int y = m_region.y; y < m_region.y+m_region.height; y+=m_th) + for (int x = m_region.x; x < m_region.x+m_region.width; x+=m_tw) tb.add(m_tile.tile, x, y, 0, 0, false, m_th, 1.0, 1.0); tb.draw(); @@ -883,9 +938,7 @@ shared_ptr Stack::get_child_at_offset(int x, int y) { if (m_children.size() == 0) return nullptr; - const i4 region = m_children.back()->get_region(); - bool inside = (x > region[0] && x < region[0] + region[2]) - && (y > region[1] && y < region[1] + region[3]); + bool inside = m_children.back()->get_region().contains_point(x, y); return inside ? m_children.back() : nullptr; } @@ -911,26 +964,20 @@ void Stack::_allocate_region() { for (auto const& child : m_children) { - i4 cr = m_region; + Region cr = m_region; SizeReq pw = child->get_preferred_size(Widget::HORZ, -1); - cr[2] = min(max(pw.min, m_region[2]), pw.nat); - SizeReq ph = child->get_preferred_size(Widget::VERT, cr[2]); - cr[3] = min(max(ph.min, m_region[3]), ph.nat); + cr.width = min(max(pw.min, m_region.width), pw.nat); + SizeReq ph = child->get_preferred_size(Widget::VERT, cr.width); + cr.height = min(max(ph.min, m_region.height), ph.nat); child->allocate_region(cr); } } -bool Stack::on_event(const wm_event& event) -{ - if (Widget::on_event(event)) - return true; - if (m_children.size() > 0 &&_maybe_propagate_event(event, m_children.back())) - return true; - return false; -} - void Switcher::add_child(shared_ptr child) { + // TODO XXX: if there's a focused widget + // - it must be in the current top child + // - unfocus it before we child->_set_parent(this); m_children.push_back(move(child)); _invalidate_sizereq(); @@ -939,15 +986,25 @@ void Switcher::add_child(shared_ptr child) int& Switcher::current() { + // TODO XXX: we need to update the focused widget + // so we need an API change _expose(); return m_current; } +shared_ptr Switcher::current_widget() +{ + if (m_children.size() == 0) + return nullptr; + m_current = max(0, min(m_current, (int)m_children.size()-1)); + return m_children[m_current]; +} + void Switcher::_render() { if (m_children.size() == 0) return; - m_current = max(0, min(m_current, (int)m_children.size())); + m_current = max(0, min(m_current, (int)m_children.size()-1)); m_children[m_current]->render(); } @@ -967,29 +1024,52 @@ void Switcher::_allocate_region() { for (auto const& child : m_children) { - i4 cr = m_region; + Region cr = m_region; SizeReq pw = child->get_preferred_size(Widget::HORZ, -1); - cr[2] = min(max(pw.min, m_region[2]), pw.nat); - SizeReq ph = child->get_preferred_size(Widget::VERT, cr[2]); - cr[3] = min(max(ph.min, m_region[3]), ph.nat); + cr.width = min(max(pw.min, m_region.width), pw.nat); + SizeReq ph = child->get_preferred_size(Widget::VERT, cr.width); + cr.height = min(max(ph.min, m_region.height), ph.nat); + int xo, yo; + switch (align_x) + { + case Widget::START: xo = 0; break; + case Widget::CENTER: xo = (m_region.width - cr.width)/2; break; + case Widget::END: xo = m_region.width - cr.width; break; + case Widget::STRETCH: xo = 0; break; + default: ASSERT(0); + } + switch (align_y) + { + case Widget::START: yo = 0; break; + case Widget::CENTER: yo = (m_region.height - cr.height)/2; break; + case Widget::END: yo = m_region.height - cr.height; break; + case Widget::STRETCH: yo = 0; break; + default: ASSERT(0); + } + cr.width += xo; + cr.height += yo; + if (align_x == Widget::STRETCH) + cr.width = m_region.width; + if (align_y == Widget::STRETCH) + cr.height = m_region.height; child->allocate_region(cr); } } -bool Switcher::on_event(const wm_event& event) +shared_ptr Switcher::get_child_at_offset(int x, int y) { - if (Widget::on_event(event)) - return true; - m_current = max(0, min(m_current, (int)m_children.size())); - if (m_children.size() > 0 &&_maybe_propagate_event(event, m_children[m_current])) - return true; - return false; + if (m_children.size() == 0) + return nullptr; + + int c = max(0, min(m_current, (int)m_children.size())); + bool inside = m_children[c]->get_region().contains_point(x, y); + return inside ? m_children[c] : nullptr; } shared_ptr Grid::get_child_at_offset(int x, int y) { - int lx = x - m_region[0]; - int ly = y - m_region[1]; + int lx = x - m_region.x; + int ly = y - m_region.y; int row = -1, col = -1; for (int i = 0; i < (int)m_col_info.size(); i++) { @@ -1013,9 +1093,9 @@ shared_ptr Grid::get_child_at_offset(int x, int y) return nullptr; for (auto& child : m_child_info) { - if (child.pos[0] <= col && col < child.pos[0] + child.span[0]) - if (child.pos[1] <= row && row < child.pos[1] + child.span[1]) - if (pos_in_rect({x, y}, child.widget->get_region())) + if (child.pos.x <= col && col < child.pos.x + child.span.width) + if (child.pos.y <= row && row < child.pos.y + child.span.height) + if (child.widget->get_region().contains_point(x, y)) return child.widget; } return nullptr; @@ -1040,31 +1120,31 @@ void Grid::init_track_info() int n_rows = 0, n_cols = 0; for (auto info : m_child_info) { - n_rows = max(n_rows, info.pos[1]+info.span[1]); - n_cols = max(n_cols, info.pos[0]+info.span[0]); + n_rows = max(n_rows, info.pos.y+info.span.height); + n_cols = max(n_cols, info.pos.x+info.span.width); } m_row_info.resize(n_rows); m_col_info.resize(n_cols); sort(m_child_info.begin(), m_child_info.end(), [](const child_info& a, const child_info& b) { - return a.pos[1] < b.pos[1]; + return a.pos.y < b.pos.y; }); } void Grid::_render() { // Find the visible rows - i4 scissor = get_scissor(); + const auto scissor = get_scissor(); int row_min = 0, row_max = m_row_info.size()-1, i = 0; for (; i < (int)m_row_info.size(); i++) - if (m_row_info[i].offset+m_row_info[i].size+m_region[1] >= scissor[1]) + if (m_row_info[i].offset+m_row_info[i].size+m_region.y >= scissor.y) { row_min = i; break; } for (; i < (int)m_row_info.size(); i++) - if (m_row_info[i].offset+m_region[1] >= scissor[1]+scissor[3]) + if (m_row_info[i].offset+m_region.y >= scissor.ey()) { row_max = i-1; break; @@ -1072,8 +1152,8 @@ void Grid::_render() for (auto const& child : m_child_info) { - if (child.pos[1] < row_min) continue; - if (child.pos[1] > row_max) break; + if (child.pos.y < row_min) continue; + if (child.pos.y > row_max) break; child.widget->render(); } } @@ -1087,15 +1167,16 @@ void Grid::compute_track_sizereqs(Direction dim) t.sr = {0, 0}; for (size_t i = 0; i < m_child_info.size(); i++) { - auto& cp = m_child_info[i].pos, cs = m_child_info[i].span; + auto& cp = m_child_info[i].pos; + auto& cs = m_child_info[i].span; // if merging horizontally, need to find (possibly multi-col) width - int prosp_width = dim ? get_tracks_region(cp[0], cp[1], cs[0], cs[1])[2] : -1; + int prosp_width = dim ? get_tracks_region(cp.x, cp.y, cs.width, cs.height).width : -1; const SizeReq c = m_child_info[i].widget->get_preferred_size(dim, prosp_width); // NOTE: items spanning multiple rows/cols don't contribute! - if (cs[0] == 1 && cs[1] == 1) + if (cs.width == 1 && cs.height == 1) { - auto& s = track[cp[dim]].sr; + auto& s = track[dim ? cp.y : cp.x].sr; s.min = max(s.min, c.min); s.nat = max(s.nat, c.nat); } @@ -1157,20 +1238,33 @@ void Grid::layout_track(Direction dim, SizeReq sr, int size) for (size_t i = 0; i < infos.size(); ++i) infos[i].size = infos[i].sr.min; + const bool stretch = dim ? stretch_v : stretch_h; + bool stretching = false; + while (true) { int sum_flex_grow = 0, sum_taken = 0; for (const auto& info : infos) sum_flex_grow += info.size < info.sr.nat ? info.flex_grow : 0; if (!sum_flex_grow) - break; + { + if (!stretch) + break; + stretching = true; + for (const auto& info : infos) + sum_flex_grow += info.flex_grow; + if (!sum_flex_grow) + break; + } for (size_t i = 0; i < infos.size(); ++i) { - float efg = infos[i].size < infos[i].sr.nat ? infos[i].flex_grow : 0; + float efg = (infos[i].size < infos[i].sr.nat || stretching) + ? infos[i].flex_grow : 0; int tr_extra = extra * efg / sum_flex_grow; - ASSERT(infos[i].size <= infos[i].sr.nat); - int taken = min(tr_extra, infos[i].sr.nat - infos[i].size); + ASSERT(stretching || infos[i].size <= infos[i].sr.nat); + int taken = stretching ? tr_extra + : min(tr_extra, infos[i].sr.nat - infos[i].size); infos[i].size += taken; sum_taken += taken; } @@ -1183,17 +1277,18 @@ void Grid::layout_track(Direction dim, SizeReq sr, int size) void Grid::_allocate_region() { // Use of _-prefixed member function is necessary here - SizeReq h_sr = _get_preferred_size(Widget::VERT, m_region[2]); + SizeReq h_sr = _get_preferred_size(Widget::VERT, m_region.width); - layout_track(Widget::VERT, h_sr, m_region[3]); + layout_track(Widget::VERT, h_sr, m_region.height); set_track_offsets(m_row_info); for (size_t i = 0; i < m_child_info.size(); i++) { - auto& cp = m_child_info[i].pos, cs = m_child_info[i].span; - i4 cell_reg = get_tracks_region(cp[0], cp[1], cs[0], cs[1]); - cell_reg[0] += m_region[0]; - cell_reg[1] += m_region[1]; + auto& cp = m_child_info[i].pos; + auto& cs = m_child_info[i].span; + Region cell_reg = get_tracks_region(cp.x, cp.y, cs.width, cs.height); + cell_reg.x += m_region.x; + cell_reg.y += m_region.y; m_child_info[i].widget->allocate_region(cell_reg); } } @@ -1244,38 +1339,38 @@ SizeReq Scroller::_get_preferred_size(Direction dim, int prosp_width) void Scroller::_allocate_region() { - SizeReq sr = m_child->get_preferred_size(Widget::VERT, m_region[2]); - m_scroll = max(0, min(m_scroll, sr.nat-m_region[3])); - i4 ch_reg = {m_region[0], m_region[1]-m_scroll, m_region[2], sr.nat}; + SizeReq sr = m_child->get_preferred_size(Widget::VERT, m_region.width); + m_scroll = max(0, min(m_scroll, sr.nat-m_region.height)); + Region ch_reg = {m_region.x, m_region.y-m_scroll, m_region.width, sr.nat}; m_child->allocate_region(ch_reg); #ifdef USE_TILE_LOCAL int shade_height = 12, ds = 4; - int shade_top = min({m_scroll/ds, shade_height, m_region[3]/2}); - int shade_bot = min({(sr.nat-m_region[3]-m_scroll)/ds, shade_height, m_region[3]/2}); + int shade_top = min({m_scroll/ds, shade_height, m_region.height/2}); + int shade_bot = min({(sr.nat-m_region.height-m_scroll)/ds, shade_height, m_region.height/2}); VColour col_a(4,2,4,0), col_b(4,2,4,200); m_shade_buf.clear(); m_scrollbar_buf.clear(); { - GLWPrim rect(m_region[0], m_region[1]+shade_top-shade_height, - m_region[0]+m_region[2], m_region[1]+shade_top); + GLWPrim rect(m_region.x, m_region.y+shade_top-shade_height, + m_region.x+m_region.width, m_region.y+shade_top); rect.set_col(col_b, col_a); m_shade_buf.add_primitive(rect); } { - GLWPrim rect(m_region[0], m_region[1]+m_region[3]-shade_bot, - m_region[0]+m_region[2], m_region[1]+m_region[3]-shade_bot+shade_height); + GLWPrim rect(m_region.x, m_region.y+m_region.height-shade_bot, + m_region.x+m_region.width, m_region.y+m_region.height-shade_bot+shade_height); rect.set_col(col_a, col_b); m_shade_buf.add_primitive(rect); } - if (ch_reg[3] > m_region[3]) { - const int x = m_region[0]+m_region[2]; - const float h_percent = m_region[3] / (float)ch_reg[3]; - const int h = m_region[3]*min(max(0.05f, h_percent), 1.0f); - const float scroll_percent = m_scroll/(float)(ch_reg[3]-m_region[3]); - const int y = m_region[1] + (m_region[3]-h)*scroll_percent; - GLWPrim bg_rect(x+10, m_region[1], x+12, m_region[1]+m_region[3]); + if (ch_reg.height > m_region.height && m_scrolbar_visible) { + const int x = m_region.x+m_region.width; + const float h_percent = m_region.height / (float)ch_reg.height; + const int h = m_region.height*min(max(0.05f, h_percent), 1.0f); + const float scroll_percent = m_scroll/(float)(ch_reg.height-m_region.height); + const int y = m_region.y + (m_region.height-h)*scroll_percent; + GLWPrim bg_rect(x+10, m_region.y, x+12, m_region.y+m_region.height); bg_rect.set_col(VColour(41,41,41)); m_scrollbar_buf.add_primitive(bg_rect); GLWPrim fg_rect(x+10, y, x+12, y+h); @@ -1300,11 +1395,11 @@ bool Scroller::on_event(const wm_event& event) switch (event.key.keysym.sym) { case ' ': case '+': case CK_PGDN: case '>': case '\'': - delta = m_region[3]; + delta = m_region.height; break; case '-': case CK_PGUP: case '<': case ';': - delta = -m_region[3]; + delta = -m_region.height; break; case CK_UP: @@ -1337,7 +1432,7 @@ bool Scroller::on_event(const wm_event& event) return false; } -Popup::Popup(shared_ptr child) +Layout::Layout(shared_ptr child) { #ifdef USE_TILE_LOCAL m_depth = ui_root.num_children(); @@ -1347,6 +1442,21 @@ Popup::Popup(shared_ptr child) expand_h = expand_v = true; } +void Layout::_render() +{ + m_child->render(); +} + +SizeReq Layout::_get_preferred_size(Direction dim, int prosp_width) +{ + return m_child->get_preferred_size(dim, prosp_width); +} + +void Layout::_allocate_region() +{ + m_child->allocate_region(m_region); +} + void Popup::_render() { #ifdef USE_TILE_LOCAL @@ -1368,7 +1478,7 @@ SizeReq Popup::_get_preferred_size(Direction dim, int prosp_width) #endif SizeReq sr = m_child->get_preferred_size(dim, prosp_width); #ifdef USE_TILE_LOCAL - constexpr int pad = m_base_margin + m_padding; + const int pad = base_margin() + m_padding; return { 0, sr.nat + 2*pad + (dim ? m_depth*m_depth_indent*(!m_centred) : 0) }; @@ -1379,59 +1489,71 @@ SizeReq Popup::_get_preferred_size(Direction dim, int prosp_width) void Popup::_allocate_region() { - i4 region = m_region; + Region region = m_region; #ifdef USE_TILE_LOCAL m_buf.clear(); - m_buf.add(m_region[0], m_region[1], - m_region[0] + m_region[2], m_region[1] + m_region[3], + m_buf.add(m_region.x, m_region.y, + m_region.x + m_region.width, m_region.y + m_region.height, VColour(0, 0, 0, 150)); - constexpr int pad = m_base_margin + m_padding; - region[2] -= 2*pad; - region[3] -= 2*pad + m_depth*m_depth_indent*(!m_centred); + const int pad = base_margin() + m_padding; + region.width -= 2*pad; + region.height -= 2*pad + m_depth*m_depth_indent*(!m_centred); SizeReq hsr = m_child->get_preferred_size(HORZ, -1); - region[2] = max(hsr.min, min(region[2], hsr.nat)); - SizeReq vsr = m_child->get_preferred_size(VERT, region[2]); - region[3] = max(vsr.min, min(region[3], vsr.nat)); + region.width = max(hsr.min, min(region.width, hsr.nat)); + SizeReq vsr = m_child->get_preferred_size(VERT, region.width); + region.height = max(vsr.min, min(region.height, vsr.nat)); - region[0] += pad + (m_region[2]-2*pad-region[2])/2; - region[1] += pad + (m_centred ? (m_region[3]-2*pad-region[3])/2 + region.x += pad + (m_region.width-2*pad-region.width)/2; + region.y += pad + (m_centred ? (m_region.height-2*pad-region.height)/2 : m_depth*m_depth_indent); - m_buf.add(region[0] - m_padding, region[1] - m_padding, - region[0] + region[2] + m_padding, - region[1] + region[3] + m_padding, + m_buf.add(region.x - m_padding, region.y - m_padding, + region.x + region.width + m_padding, + region.y + region.height + m_padding, VColour(125, 98, 60)); - m_buf.add(region[0] - m_padding + 2, region[1] - m_padding + 2, - region[0] + region[2] + m_padding - 2, - region[1] + region[3] + m_padding - 2, + m_buf.add(region.x - m_padding + 2, region.y - m_padding + 2, + region.x + region.width + m_padding - 2, + region.y + region.height + m_padding - 2, VColour(0, 0, 0)); - m_buf.add(region[0] - m_padding + 3, region[1] - m_padding + 3, - region[0] + region[2] + m_padding - 3, - region[1] + region[3] + m_padding - 3, + m_buf.add(region.x - m_padding + 3, region.y - m_padding + 3, + region.x + region.width + m_padding - 3, + region.y + region.height + m_padding - 3, VColour(4, 2, 4)); +#else + SizeReq hsr = m_child->get_preferred_size(HORZ, -1); + region.width = max(hsr.min, min(region.width, hsr.nat)); + SizeReq vsr = m_child->get_preferred_size(VERT, region.width); + region.height = max(vsr.min, min(region.height, vsr.nat)); #endif m_child->allocate_region(region); } -i2 Popup::get_max_child_size() +Size Popup::get_max_child_size() { + Size max_child_size = Size(m_region.width, m_region.height); #ifdef USE_TILE_LOCAL - constexpr int pad = m_base_margin + m_padding; - return { - (m_region[2] - 2*pad) & ~0x1, - (m_region[3] - 2*pad - m_depth*m_depth_indent) & ~0x1, - }; -#else - return { m_region[2], m_region[3] }; + const int pad = base_margin() + m_padding; + max_child_size.width = (max_child_size.width - 2*pad) & ~0x1; + max_child_size.height = (max_child_size.height - 2*pad - m_depth*m_depth_indent) & ~0x1; #endif + return max_child_size; } #ifdef USE_TILE_LOCAL +int Popup::base_margin() +{ + const int screen_small = 800, screen_large = 1000; + const int margin_small = 10, margin_large = 50; + const int clipped = max(screen_small, min(screen_large, m_region.height)); + return margin_small + (clipped-screen_small) + *(margin_large-margin_small)/(screen_large-screen_small); +} + void Dungeon::_render() { #ifdef USE_TILE_LOCAL - GLW_3VF t = {(float)m_region[0], (float)m_region[1], 0}, s = {32, 32, 1}; + GLW_3VF t = {(float)m_region.x, (float)m_region.y, 0}, s = {32, 32, 1}; glmanager->set_transform(t, s); m_buf.draw(); glmanager->reset_transform(); @@ -1443,13 +1565,133 @@ SizeReq Dungeon::_get_preferred_size(Direction dim, int prosp_width) int sz = (dim ? height : width)*32; return {sz, sz}; } + +PlayerDoll::PlayerDoll(dolls_data doll) +{ + m_save_doll = doll; + for (int i = 0; i < TEX_MAX; i++) + m_tile_buf[i].set_tex(&tiles.get_image_manager()->m_textures[i]); + _pack_doll(); +} + +PlayerDoll::~PlayerDoll() +{ + for (int t = 0; t < TEX_MAX; t++) + m_tile_buf[t].clear(); +} + +void PlayerDoll::_pack_doll() +{ + m_tiles.clear(); + // FIXME: Implement this logic in one place in e.g. pack_doll_buf(). + int p_order[TILEP_PART_MAX] = + { + TILEP_PART_SHADOW, // 0 + TILEP_PART_HALO, + TILEP_PART_ENCH, + TILEP_PART_DRCWING, + TILEP_PART_CLOAK, + TILEP_PART_BASE, // 5 + TILEP_PART_BOOTS, + TILEP_PART_LEG, + TILEP_PART_BODY, + TILEP_PART_ARM, + TILEP_PART_HAIR, + TILEP_PART_BEARD, + TILEP_PART_DRCHEAD, // 15 + TILEP_PART_HELM, + TILEP_PART_HAND1, // 10 + TILEP_PART_HAND2, + }; + + int flags[TILEP_PART_MAX]; + tilep_calc_flags(m_save_doll, flags); + + // For skirts, boots go under the leg armour. For pants, they go over. + if (m_save_doll.parts[TILEP_PART_LEG] < TILEP_LEG_SKIRT_OFS) + { + p_order[6] = TILEP_PART_BOOTS; + p_order[7] = TILEP_PART_LEG; + } + + // Special case bardings from being cut off. + bool is_naga = (m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_NAGA + || m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_NAGA + 1); + if (m_save_doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_NAGA_BARDING + && m_save_doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_NAGA_BARDING_RED) + { + flags[TILEP_PART_BOOTS] = is_naga ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE; + } + + bool is_cent = (m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_CENTAUR + || m_save_doll.parts[TILEP_PART_BASE] == TILEP_BASE_CENTAUR + 1); + if (m_save_doll.parts[TILEP_PART_BOOTS] >= TILEP_BOOTS_CENTAUR_BARDING + && m_save_doll.parts[TILEP_PART_BOOTS] <= TILEP_BOOTS_CENTAUR_BARDING_RED) + { + flags[TILEP_PART_BOOTS] = is_cent ? TILEP_FLAG_NORMAL : TILEP_FLAG_HIDE; + } + + for (int i = 0; i < TILEP_PART_MAX; ++i) + { + const int p = p_order[i]; + const tileidx_t idx = m_save_doll.parts[p]; + if (idx == 0 || idx == TILEP_SHOW_EQUIP || flags[p] == TILEP_FLAG_HIDE) + continue; + + ASSERT_RANGE(idx, TILE_MAIN_MAX, TILEP_PLAYER_MAX); + + int ymax = TILE_Y; + + if (flags[p] == TILEP_FLAG_CUT_CENTAUR + || flags[p] == TILEP_FLAG_CUT_NAGA) + { + ymax = 18; + } + + m_tiles.emplace_back(idx, TEX_PLAYER, ymax); + } +} + +void PlayerDoll::_render() +{ + for (int i = 0; i < TEX_MAX; i++) + m_tile_buf[i].draw(); +} + +SizeReq PlayerDoll::_get_preferred_size(Direction dim, int prosp_width) +{ + return { TILE_Y, TILE_Y }; +} + +void PlayerDoll::_allocate_region() +{ + for (int t = 0; t < TEX_MAX; t++) + m_tile_buf[t].clear(); + for (const tile_def &tdef : m_tiles) + { + int tile = tdef.tile; + TextureID tex = tdef.tex; + m_tile_buf[tex].add_unscaled(tile, m_region.x, m_region.y, tdef.ymax); + } +} + +bool PlayerDoll::on_event(const wm_event& event) +{ + return false; +} + #endif void UIRoot::push_child(shared_ptr ch, KeymapContext km) { + if (auto popup = dynamic_cast(ch.get())) + focus_stack.push_back(popup->get_child().get()); + else + focus_stack.push_back(ch.get()); m_root.add_child(move(ch)); m_needs_layout = true; keymap_stack.push_back(km); + m_changed_layout_since_click = true; #ifndef USE_TILE_LOCAL if (m_root.num_children() == 1) { @@ -1464,6 +1706,8 @@ void UIRoot::pop_child() m_root.pop_child(); m_needs_layout = true; keymap_stack.pop_back(); + focus_stack.pop_back(); + m_changed_layout_since_click = true; #ifndef USE_TILE_LOCAL if (m_root.num_children() == 0) clrscr(); @@ -1537,7 +1781,7 @@ void UIRoot::render() m_dirty_region = aabb_intersect(m_dirty_region, m_region); textcolour(LIGHTGREY); textbackground(BLACK); - clear_text_region(m_dirty_region); + clear_text_region(m_dirty_region, BLACK); #endif push_scissor(m_region); @@ -1546,6 +1790,41 @@ void UIRoot::render() ASSERT(cutoff <= static_cast(m_root.num_children())); for (int i = cutoff; i < static_cast(m_root.num_children()); i++) m_root.get_child(i)->render(); +#ifdef DEBUG + if (debug_draw) + { + LineBuffer lb; + ShapeBuffer sb; + size_t i = 0; + for (const auto& w : prev_hover_path) + { + const auto r = w->get_region(); + i++; + VColour lc; + lc = i == prev_hover_path.size() ? + VColour(255, 100, 0, 100) : VColour(0, 50 + i*40, 0, 100); + lb.add_square(r.x+1, r.y+1, r.ex(), r.ey(), lc); + } + if (!prev_hover_path.empty()) + { + const auto& hovered_widget = prev_hover_path.back(); + Region r = hovered_widget->get_region(); + const Margin m = hovered_widget->get_margin(); + + VColour lc = VColour(0, 0, 100, 100); + sb.add(r.x, r.y-m.top, r.ex(), r.y, lc); + sb.add(r.ex(), r.y, r.ex()+m.right, r.ey(), lc); + sb.add(r.x, r.ey(), r.ex(), r.ey()+m.bottom, lc); + sb.add(r.x-m.left, r.y, r.x, r.ey(), lc); + } + if (auto w = get_focused_widget()) { + Region r = w->get_region(); + lb.add_square(r.x+1, r.y+1, r.ex(), r.ey(), VColour(128,31,239,255)); + } + lb.draw(); + sb.draw(); + } +#endif #else // Render only the top of the UI stack on console if (m_root.num_children() > 0) @@ -1578,6 +1857,7 @@ void UIRoot::send_mouse_enter_leave_events(int mx, int my) current = current->get_child_at_offset(mx, my); } + size_t new_hover_path_size = hover_path.size(); size_t sz = max(prev_hover_path.size(), hover_path.size()); prev_hover_path.resize(sz, nullptr); hover_path.resize(sz, nullptr); @@ -1597,33 +1877,97 @@ void UIRoot::send_mouse_enter_leave_events(int mx, int my) { ev.type = WME_MOUSELEAVE; if (!event_filter || !event_filter(ev)) - prev_hover_path[diff]->on_event(ev); + for (size_t i = sz; i > diff; --i) + { + if (!prev_hover_path[i-1]) + continue; + prev_hover_path[i-1]->on_event(ev); + } } if (hover_path[diff]) { ev.type = WME_MOUSEENTER; if (!event_filter || !event_filter(ev)) - hover_path[diff]->on_event(ev); + for (size_t i = diff; i < sz; ++i) + { + if (!hover_path[i]) + break; + hover_path[i]->on_event(ev); + } } } prev_hover_path = move(hover_path); + prev_hover_path.resize(new_hover_path_size); } bool UIRoot::on_event(const wm_event& event) { if (event_filter && event_filter(event)) return true; - return m_root.on_event(event); + +#ifdef DEBUG + if (event.type == WME_KEYDOWN && event.key.keysym.sym == CK_INSERT) + { + ui_root.debug_draw = !ui_root.debug_draw; + ui_root.queue_layout(); + ui_root.expose_region({0,0,INT_MAX,INT_MAX}); + return true; + } +#endif + + switch (event.type) + { + case WME_MOUSEBUTTONDOWN: + case WME_MOUSEBUTTONUP: + case WME_MOUSEMOTION: +#ifdef DEBUG + if (ui_root.debug_draw) + { + ui_root.queue_layout(); + ui_root.expose_region({0,0,INT_MAX,INT_MAX}); + } +#endif + case WME_MOUSEWHEEL: + if (event.type == WME_MOUSEBUTTONDOWN) + m_changed_layout_since_click = false; + else if (event.type == WME_MOUSEBUTTONUP) + if (m_changed_layout_since_click) + break; + if (!prev_hover_path.empty()) { + for (auto w = prev_hover_path.back(); w; w = w->_get_parent()) + if (w->on_event(event)) + return true; + } + break; + case WME_MOUSEENTER: + case WME_MOUSELEAVE: + die("unreachable"); + break; + default: + size_t layer_idx = m_root.num_children(); + if (!layer_idx) + return false; + Widget* layer_root = m_root.get_child(layer_idx-1).get(); + Layout* layout_root = dynamic_cast(layer_root); + if (layout_root && layout_root->event_filters.emit(layout_root, event)) + return true; + for (Widget* w = focus_stack.back(); w; w = w->_get_parent()) + if (w->on_event(event)) + return true; + break; + } + + return false; } -void push_scissor(i4 scissor) +void push_scissor(Region scissor) { if (scissor_stack.size() > 0) scissor = aabb_intersect(scissor, scissor_stack.top()); scissor_stack.push(scissor); #ifdef USE_TILE_LOCAL - glmanager->set_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + glmanager->set_scissor(scissor.x, scissor.y, scissor.width, scissor.height); #endif } @@ -1634,32 +1978,34 @@ void pop_scissor() #ifdef USE_TILE_LOCAL if (scissor_stack.size() > 0) { - i4 scissor = scissor_stack.top(); - glmanager->set_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + Region scissor = scissor_stack.top(); + glmanager->set_scissor(scissor.x, scissor.y, scissor.width, scissor.height); } else glmanager->reset_scissor(); #endif } -i4 get_scissor() +Region get_scissor() { - return scissor_stack.top(); + if (scissor_stack.size() > 0) + return scissor_stack.top(); + return {0, 0, INT_MAX, INT_MAX}; } #ifndef USE_TILE_LOCAL -static void clear_text_region(i4 region) +static void clear_text_region(Region region, COLOURS bg) { if (scissor_stack.size() > 0) region = aabb_intersect(region, scissor_stack.top()); - if (region[2] <= 0 || region[3] <= 0) + if (region.width <= 0 || region.height <= 0) return; textcolour(LIGHTGREY); - textbackground(BLACK); - for (int y=region[1]; y < region[1]+region[3]; y++) + textbackground(bg); + for (int y=region.y; y < region.y+region.height; y++) { - cgotoxy(region[0]+1, y+1); - cprintf("%*s", region[2], ""); + cgotoxy(region.x+1, y+1); + cprintf("%*s", region.width, ""); } } #endif @@ -1720,7 +2066,14 @@ static void remap_key(wm_event &event) ASSERT(event.key.keysym.sym != -1); } -void ui_force_render() +void force_render() +{ + ui_root.layout(); + ui_root.needs_paint = true; + ui_root.render(); +} + +void render() { ui_root.layout(); ui_root.render(); @@ -1881,6 +2234,11 @@ bool has_layout() return ui_root.num_children() > 0; } +NORETURN void restart_layout() +{ + throw UIRoot::RestartAllocation(); +} + int getch(KeymapContext km) { // getch() can be called when there are no widget layouts, i.e. @@ -1910,7 +2268,7 @@ int getch(KeymapContext km) return key; } -void ui_delay(unsigned int ms) +void delay(unsigned int ms) { if (crawl_state.disables[DIS_DELAY]) ms = 0; @@ -1971,7 +2329,14 @@ progress_popup::progress_popup(string title, int width) : position(0), bar_width(width), no_more(crawl_state.show_more_prompt, false) { auto container = make_shared(Widget::VERT); - container->align_items = Widget::CENTER; + container->align_cross = Widget::CENTER; +#ifndef USE_TILE_LOCAL + // Center the popup in console. + // if webtiles browser ever uses this property, then this will probably + // look bad there and need another solution. But right now, webtiles ignores + // expand_h. + container->expand_h = true; +#endif formatted_string bar_string = get_progress_string(bar_width); progress_bar = make_shared(bar_string); auto title_text = make_shared(title); @@ -2054,4 +2419,52 @@ formatted_string progress_popup::get_progress_string(unsigned int len) return formatted_string::parse_string(bar); } +void set_focused_widget(Widget* w) +{ + static bool sent_focusout; + static Widget* new_focus; + + const auto top = top_layout(); + + if (!top) + return; + + if (w && !top->is_ancestor_of(w->get_shared())) + return; + + auto current_focus = ui_root.focus_stack.back(); + + if (w == current_focus) + return; + + new_focus = w; + + if (current_focus && !sent_focusout) + { + sent_focusout = true; + wm_event ev = {0}; + ev.type = WME_FOCUSOUT; + current_focus->on_event(ev); + } + + if (new_focus != w) + return; + + ui_root.focus_stack.back() = new_focus; + + sent_focusout = false; + + if (new_focus) + { + wm_event ev = {0}; + ev.type = WME_FOCUSIN; + new_focus->on_event(ev); + } +} + +Widget* get_focused_widget() +{ + return ui_root.focus_stack.empty() ? nullptr : ui_root.focus_stack.back(); +} + } diff --git a/crawl-ref/source/ui.h b/crawl-ref/source/ui.h index 4231670b3d07..17beef31296a 100644 --- a/crawl-ref/source/ui.h +++ b/crawl-ref/source/ui.h @@ -11,14 +11,15 @@ #include "format.h" #include "KeymapContext.h" #include "state.h" -#include "tilefont.h" #include "tiledef-gui.h" +#include "tilefont.h" #include "unwind.h" #include "windowmanager.h" #ifdef USE_TILE_LOCAL -# include "tilesdl.h" # include "tilebuf.h" # include "tiledgnbuf.h" +# include "tiledoll.h" +# include "tilesdl.h" #endif #ifdef USE_TILE_WEB # include "tileweb.h" @@ -26,28 +27,70 @@ namespace ui { -// This is used instead of std::array because travis uses older versions of GCC -// and Clang that demand braces around initializer lists everywhere. -template -struct vec { - int items[N]; - template vec (Ts... l) : items{l...} {} - const int& operator[](int index) const { return items[index]; } - int& operator[](int index) { return items[index]; } - inline bool operator==(const vec& rhs) { - return equal(begin(items), end(items), begin(rhs.items)); - } - inline bool operator!=(const vec& rhs) { return !(*this == rhs); } -}; -using i2 = vec<2>; -using i4 = vec<4>; - struct SizeReq { int min, nat; }; -struct RestartAllocation {}; +class Margin +{ +public: + constexpr Margin() : top(0), right(0), bottom(0), left(0) {}; + constexpr Margin(int v) : top(v), right(v), bottom(v), left(v) {}; + constexpr Margin(int v, int h) : top(v), right(h), bottom(v), left(h) {}; + constexpr Margin(int t, int lr, int b) : top(t), right(lr), bottom(b), left(lr) {}; + constexpr Margin(int t, int r, int b, int l) : top(t), right(r), bottom(b), left(l) {}; + + int top, right, bottom, left; +}; + +class Region +{ +public: + constexpr Region() : x(0), y(0), width(0), height(0) {}; + constexpr Region(int _x, int _y, int _width, int _height) : x(_x), y(_y), width(_width), height(_height) {}; + + constexpr bool operator == (const Region& other) const + { + return x == other.x && y == other.y + && width == other.width && height == other.height; + } + + constexpr bool empty() const + { + return width == 0 || height == 0; + } + + constexpr int ex() const { return x + width; } + constexpr int ey() const { return y + height; } + + constexpr bool contains_point(int _x, int _y) const + { + return _x >= x && _x < ex() && _y >= y && _y < ey(); + } + + int x, y, width, height; +}; + +class Size +{ +public: + constexpr Size() : width(0), height(0) {}; + constexpr Size(int v) : width(v), height(v) {}; + constexpr Size(int w, int h) : width(w), height(h) {}; + + constexpr bool operator <= (const Size& other) const + { + return width <= other.width && height <= other.height; + } + + constexpr bool operator == (const Size& other) const + { + return width == other.width && height == other.height; + } + + int width, height; +}; template class Slot; @@ -55,6 +98,7 @@ template class Slot { public: + ~Slot() { alive = false; } typedef function HandlerSig; typedef multimap HandlerMap; bool emit(Target *target, Args&... args) @@ -75,18 +119,19 @@ class Slot } void remove_by_target(Target *target) { - handlers.erase(target); + if (alive) + handlers.erase(target); } protected: + bool alive {true}; HandlerMap handlers; }; -class Widget +class Widget : public enable_shared_from_this { public: enum Align { - UNSET = 0, - START, + START = 0, END, CENTER, STRETCH, @@ -97,48 +142,62 @@ class Widget VERT, }; - virtual ~Widget() { - Widget::slots.event.remove_by_target(this); - _set_parent(nullptr); - } + virtual ~Widget(); - i4 margin = {0,0,0,0}; int flex_grow = 1; - Align align_self = UNSET; bool expand_h = false, expand_v = false; bool shrink_h = false, shrink_v = false; - const i4 get_region() const { return m_region; } - i2& min_size() { _invalidate_sizereq(); return m_min_size; } - i2& max_size() { _invalidate_sizereq(); return m_max_size; } + Region get_region() const { return m_region; } + + Size& min_size() { _invalidate_sizereq(); return m_min_size; } + Size& max_size() { _invalidate_sizereq(); return m_max_size; } virtual void _render() = 0; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width); virtual void _allocate_region(); void _set_parent(Widget* p); + Widget* _get_parent() const { return m_parent; }; + shared_ptr get_shared() { + return shared_from_this(); + }; void _invalidate_sizereq(bool immediate = true); void _queue_allocation(bool immediate = true); void set_allocation_needed() { alloc_queued = true; }; void _expose(); + bool is_visible() const { return m_visible; } + void set_visible(bool); + + bool is_ancestor_of(const shared_ptr& other); + // Wrapper functions which handle common behavior // - margins // - caching void render(); SizeReq get_preferred_size(Direction dim, int prosp_width); - void allocate_region(i4 region); + void allocate_region(Region region); + + Margin get_margin() const { + return margin; + } - void set_margin_for_crt(i4 _margin) + template + void set_margin_for_crt(Args&&... args) { #ifndef USE_TILE_LOCAL - margin = _margin; + margin = Margin(forward(args)...); + _invalidate_sizereq(); #endif - }; - void set_margin_for_sdl(i4 _margin) + } + + template + void set_margin_for_sdl(Args&&... args) { #ifdef USE_TILE_LOCAL - margin = _margin; + margin = Margin(forward(args)...); + _invalidate_sizereq(); #endif - }; + } virtual bool on_event(const wm_event& event); @@ -151,79 +210,34 @@ class Widget Slot event; } slots; + // XXX: add documentation virtual shared_ptr get_child_at_offset(int x, int y) { return nullptr; }; protected: - i4 m_region; + Region m_region; + Margin margin = { 0 }; + + void _unparent(shared_ptr& child); private: bool cached_sr_valid[2] = { false, false }; SizeReq cached_sr[2]; int cached_sr_pw; bool alloc_queued = false; + bool m_visible = true; Widget* m_parent = nullptr; - i2 m_min_size = { 0, 0 }, m_max_size = { INT_MAX, INT_MAX }; + + Size m_min_size = { 0 }; + Size m_max_size = { INT_MAX }; }; class Container : public Widget { -protected: - class iter_impl - { - public: - virtual ~iter_impl() {}; - virtual void operator++() = 0; - virtual shared_ptr& operator*() = 0; - virtual iter_impl* clone() const = 0; - virtual bool equal(iter_impl &other) const = 0; - }; - public: virtual ~Container() {} - class iterator : public std::iterator> - { - public: - iterator(iter_impl *_it) : it(_it) {}; - ~iterator() { delete it; it = nullptr; }; - iterator(const iterator& other) : it(other.it->clone()) {}; - iterator& operator=(const iterator& other) - { - if (it != other.it) - { - delete it; - it = other.it->clone(); - } - return *this; - } - iterator& operator=(iterator&& other) - { - if (it != other.it) - { - delete it; - it = other.it; - other.it = nullptr; - } - return *this; - } - - void operator++() { ++(*it); }; - bool operator==(const iterator& other) const - { - return typeid(it) == typeid(other.it) && it->equal(*other.it); - }; - bool operator!=(const iterator& other) const { return !(*this == other); } - shared_ptr& operator*() { return **it; }; - protected: - iter_impl *it; - }; - - virtual bool on_event(const wm_event& event) override; - -public: - virtual iterator begin() = 0; - virtual iterator end() = 0; + virtual void foreach(function&)> f) = 0; }; class Bin : public Container @@ -231,38 +245,41 @@ class Bin : public Container public: virtual ~Bin() { if (m_child) - m_child->_set_parent(nullptr); + _unparent(m_child); }; - virtual bool on_event(const wm_event& event) override; void set_child(shared_ptr child); virtual shared_ptr get_child() { return m_child; }; virtual shared_ptr get_child_at_offset(int x, int y) override; -private: - typedef Container::iterator I; - - class iter_impl_bin : public iter_impl +protected: + class iterator { - private: - typedef shared_ptr C; public: - explicit iter_impl_bin (C& _c, bool _state) : c(_c), state(_state) {}; - protected: - virtual void operator++() override { state = true; }; - virtual shared_ptr& operator*() override { return c; }; - virtual iter_impl_bin* clone() const override { return new iter_impl_bin(c, state); }; - virtual bool equal (iter_impl &_other) const override { - iter_impl_bin &other = static_cast(_other); - return c == other.c && state == other.state; - }; - - C& c; + typedef shared_ptr value_type; + typedef ptrdiff_t distance; + typedef shared_ptr* pointer; + typedef shared_ptr& reference; + typedef input_iterator_tag iterator_category; + + value_type c; bool state; + + iterator(value_type& _c, bool _state) : c(_c), state(_state) {}; + void operator++ () { state = true; }; + value_type& operator* () { return c; }; + bool operator== (const iterator& other) { return c == other.c && state == other.state; } + bool operator!= (const iterator& other) { return !(*this == other); } }; public: - virtual I begin() override { return I(new iter_impl_bin(m_child, false)); } - virtual I end() override { return I(new iter_impl_bin(m_child, true)); } + iterator begin() { return iterator(m_child, false); } + iterator end() { return iterator(m_child, true); } + virtual void foreach(function&)> f) override + { + for (auto& child : *this) + f(child); + } + protected: shared_ptr m_child; }; @@ -273,35 +290,42 @@ class ContainerVec : public Container virtual ~ContainerVec() { for (auto& child : m_children) if (child) - child->_set_parent(nullptr); + _unparent(child); } virtual shared_ptr get_child_at_offset(int x, int y) override; size_t num_children() const { return m_children.size(); } -private: - typedef Container::iterator I; + shared_ptr& operator[](size_t pos) { return m_children[pos]; }; + const shared_ptr& operator[](size_t pos) const { return m_children[pos]; }; - class iter_impl_vec : public iter_impl +protected: + class iterator { - private: - typedef vector> C; public: - explicit iter_impl_vec (C& _c, C::iterator _it) : c(_c), it(_it) {}; - protected: - virtual void operator++() override { ++it; }; - virtual shared_ptr& operator*() override { return *it; }; - virtual iter_impl_vec* clone() const override { return new iter_impl_vec(c, it); }; - virtual bool equal (iter_impl &_other) const override { - iter_impl_vec &other = static_cast(_other); - return c == other.c && it == other.it; - }; - - C& c; - C::iterator it; + typedef shared_ptr value_type; + typedef ptrdiff_t distance; + typedef shared_ptr* pointer; + typedef shared_ptr& reference; + typedef input_iterator_tag iterator_category; + + vector& c; + vector::iterator it; + + iterator(vector& _c, vector::iterator _it) : c(_c), it(_it) {}; + void operator++ () { ++it; }; + value_type& operator* () { return *it; }; + bool operator== (const iterator& other) { return c == other.c && it == other.it; } + bool operator!= (const iterator& other) { return !(*this == other); } }; public: - virtual I begin() override { return I(new iter_impl_vec(m_children, m_children.begin())); } - virtual I end() override { return I(new iter_impl_vec(m_children, m_children.end())); } + iterator begin() { return iterator(m_children, m_children.begin()); } + iterator end() { return iterator(m_children, m_children.end()); } + virtual void foreach(function&)> f) override + { + for (auto& child : *this) + f(child); + } + protected: vector> m_children; }; @@ -309,17 +333,10 @@ class ContainerVec : public Container // Box widget: similar to the CSS flexbox (without wrapping) // - Lays its children out in either a row or a column // - Extra space is allocated according to each child's flex_grow property -// - align and justify properties work like flexbox's class Box : public ContainerVec { public: - enum Justify { - START = 0, - CENTER, - END, - }; - enum Expand { NONE = 0x0, EXPAND_H = 0x1, @@ -336,15 +353,16 @@ class Box : public ContainerVec }; virtual ~Box() {} void add_child(shared_ptr child); - bool horz; - Justify justify_items = START; - Widget::Align align_items = UNSET; + Widget::Align align_main = START; + Widget::Align align_cross = START; virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; protected: + bool horz; + vector layout_main_axis(vector& ch_psz, int main_sz); vector layout_cross_axis(vector& ch_psz, int cross_sz); }; @@ -352,7 +370,7 @@ class Box : public ContainerVec class Text : public Widget { public: - Text() {} + Text(); Text(string text) : Text() { set_text(formatted_string(text)); @@ -369,6 +387,8 @@ class Text : public Widget set_text(formatted_string(text)); }; + void set_font(FontWrapper *font); + const formatted_string& get_text() { return m_text; }; void set_highlight_pattern(string pattern, bool hl_line = false); @@ -376,22 +396,44 @@ class Text : public Widget virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; - bool wrap_text = false; - bool ellipsize = false; +#ifndef USE_TILE_LOCAL + void set_bg_colour(COLOURS colour); +#endif + + void set_wrap_text(bool _wrap_text) + { + if (wrap_text == _wrap_text) + return; + wrap_text = _wrap_text; + _invalidate_sizereq(); + }; + + void set_ellipsize(bool _ellipsize) + { + if (ellipsize == _ellipsize) + return; + ellipsize = _ellipsize; + _invalidate_sizereq(); + }; protected: void wrap_text_to_size(int width, int height); + bool wrap_text = false; + bool ellipsize = false; + formatted_string m_text; #ifdef USE_TILE_LOCAL struct brkpt { unsigned int op, line; }; vector m_brkpts; formatted_string m_text_wrapped; ShapeBuffer m_hl_buf; + FontWrapper *m_font; #else vector m_wrapped_lines; + COLOURS m_bg_colour = BLACK; #endif - i2 m_wrapped_size = { -1, -1 }; + Size m_wrapped_size = { -1 }; string hl_pat; bool hl_line; }; @@ -403,13 +445,14 @@ class Image : public Widget Image(tile_def tile) { set_tile(tile); }; virtual ~Image() {} void set_tile(tile_def tile); + tile_def get_tile() const { return m_tile; }; virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; protected: tile_def m_tile = {TILEG_ERROR, TEX_GUI}; - int m_tw, m_th; + int m_tw {0}, m_th {0}; #ifdef USE_TILE_LOCAL GenericTexture m_img; @@ -428,7 +471,6 @@ class Stack : public ContainerVec virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; - virtual bool on_event(const wm_event& event) override; }; class Switcher : public ContainerVec @@ -437,11 +479,14 @@ class Switcher : public ContainerVec virtual ~Switcher() {}; void add_child(shared_ptr child); int& current(); + shared_ptr current_widget(); + + Widget::Align align_x = START, align_y = START; virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; - virtual bool on_event(const wm_event& event) override; + virtual shared_ptr get_child_at_offset(int x, int y) override; protected: int m_current; @@ -453,20 +498,20 @@ class Grid : public Container virtual ~Grid() { for (auto& child : m_child_info) if (child.widget) - child.widget->_set_parent(nullptr); + _unparent(child.widget); }; void add_child(shared_ptr child, int x, int y, int w = 1, int h = 1); - const int column_flex_grow(int x) const { return m_col_info[x].flex_grow; } - const int row_flex_grow(int y) const { return m_row_info[y].flex_grow; } + const int column_flex_grow(int x) const { return m_col_info.at(x).flex_grow; } + const int row_flex_grow(int y) const { return m_row_info.at(y).flex_grow; } int& column_flex_grow(int x) { init_track_info(); - return m_col_info[x].flex_grow; + return m_col_info.at(x).flex_grow; } int& row_flex_grow(int y) { init_track_info(); - return m_row_info[y].flex_grow; + return m_row_info.at(y).flex_grow; } virtual shared_ptr get_child_at_offset(int x, int y) override; @@ -474,14 +519,17 @@ class Grid : public Container virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; + bool stretch_h = false, stretch_v = false; + protected: - i4 get_tracks_region(int x, int y, int w, int h) const + Region get_tracks_region(int x, int y, int w, int h) const { - return { - m_col_info[x].offset, m_row_info[y].offset, - m_col_info[x+w-1].size + m_col_info[x+w-1].offset - m_col_info[x].offset, - m_row_info[y+h-1].size + m_row_info[y+h-1].offset - m_row_info[y].offset, - }; + Region track_region; + track_region.x = m_col_info[x].offset; + track_region.y = m_row_info[y].offset; + track_region.width = m_col_info[x+w-1].size + m_col_info[x+w-1].offset - m_col_info[x].offset; + track_region.height = m_row_info[y+h-1].size + m_row_info[y+h-1].offset - m_row_info[y].offset; + return track_region; } struct track_info { @@ -494,8 +542,10 @@ class Grid : public Container vector m_row_info; struct child_info { - i2 pos; - i2 span; + struct { + int x, y; + } pos; + Size span; shared_ptr widget; inline bool operator==(const child_info& rhs) const { return widget == rhs.widget; } }; @@ -507,33 +557,34 @@ class Grid : public Container void init_track_info(); bool m_track_info_dirty = false; -private: - typedef Container::iterator I; - - class iter_impl_grid : public iter_impl +protected: + class iterator { - private: - typedef vector C; public: - explicit iter_impl_grid (C& _c, C::iterator _it) : c(_c), it(_it) {}; - protected: - virtual void operator++() override { ++it; }; - virtual shared_ptr& operator*() override { return it->widget; }; - virtual iter_impl_grid* clone() const override { - return new iter_impl_grid(c, it); - }; - virtual bool equal (iter_impl &_other) const override { - iter_impl_grid &other = static_cast(_other); - return c == other.c && it == other.it; - }; - - C& c; - C::iterator it; + typedef shared_ptr value_type; + typedef ptrdiff_t distance; + typedef shared_ptr* pointer; + typedef shared_ptr& reference; + typedef input_iterator_tag iterator_category; + + vector& c; + vector::iterator it; + + iterator(vector& _c, vector::iterator _it) : c(_c), it(_it) {}; + void operator++ () { ++it; }; + value_type& operator* () { return it->widget; }; + bool operator== (const iterator& other) { return c == other.c && it == other.it; } + bool operator!= (const iterator& other) { return !(*this == other); } }; public: - virtual I begin() override { return I(new iter_impl_grid(m_child_info, m_child_info.begin())); } - virtual I end() override { return I(new iter_impl_grid(m_child_info, m_child_info.end())); } + iterator begin() { return iterator(m_child_info, m_child_info.begin()); } + iterator end() { return iterator(m_child_info, m_child_info.end()); } + virtual void foreach(function&)> f) override + { + for (auto& child : *this) + f(child); + } }; class Scroller : public Bin @@ -543,35 +594,57 @@ class Scroller : public Bin virtual void set_scroll(int y); int get_scroll() const { return m_scroll; }; + void set_scrollbar_visible(bool vis) { m_scrolbar_visible = vis; }; virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; virtual bool on_event(const wm_event& event) override; protected: int m_scroll = 0; + bool m_scrolbar_visible = true; #ifdef USE_TILE_LOCAL VertBuffer m_shade_buf = VertBuffer(false, true); ShapeBuffer m_scrollbar_buf; #endif }; -class Popup : public Bin +class Layout : public Bin +{ + friend struct UIRoot; +public: + Layout(shared_ptr child); + virtual void _render() override; + virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; + virtual void _allocate_region() override; + + void add_event_filter(function handler) + { + event_filters.on(this, handler); + } +protected: +#ifdef USE_TILE_LOCAL + int m_depth; +#endif + Slot event_filters; +}; + +class Popup : public Layout { public: - Popup(shared_ptr child); + Popup(shared_ptr child) : Layout(move(child)) {}; virtual void _render() override; virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; virtual void _allocate_region() override; - i2 get_max_child_size(); + Size get_max_child_size(); protected: #ifdef USE_TILE_LOCAL ShapeBuffer m_buf; - int m_depth; static constexpr int m_depth_indent = 20; - static constexpr int m_base_margin = 50; static constexpr int m_padding = 23; + + int base_margin(); #endif bool m_centred{!crawl_state.need_save}; }; @@ -592,6 +665,25 @@ class Dungeon : public Widget DungeonCellBuffer m_buf; bool m_dirty; }; + +class PlayerDoll : public Widget +{ +public: + PlayerDoll(dolls_data doll); + virtual ~PlayerDoll(); + + virtual void _render() override; + virtual SizeReq _get_preferred_size(Direction dim, int prosp_width) override; + virtual void _allocate_region() override; + virtual bool on_event(const wm_event& event) override; + +protected: + void _pack_doll(); + dolls_data m_save_doll; + + vector m_tiles; + FixedVector m_tile_buf; +}; #endif #ifdef USE_TILE @@ -612,13 +704,18 @@ shared_ptr top_layout(); void pump_events(int wait_event_timeout = INT_MAX); void run_layout(shared_ptr root, const bool& done); bool has_layout(); +NORETURN void restart_layout(); int getch(KeymapContext km = KMC_DEFAULT); -void ui_force_render(); -void ui_delay(unsigned int ms); +void force_render(); +void render(); +void delay(unsigned int ms); -void push_scissor(i4 scissor); +void push_scissor(Region scissor); void pop_scissor(); -i4 get_scissor(); +Region get_scissor(); + +void set_focused_widget(Widget* w); +Widget* get_focused_widget(); // XXX: this is a hack used to ensure that when switching to a // layout-based UI, the starting window size is correct. This is necessary diff --git a/crawl-ref/source/unicode.cc b/crawl-ref/source/unicode.cc index b167395ae880..efe375d0b39e 100644 --- a/crawl-ref/source/unicode.cc +++ b/crawl-ref/source/unicode.cc @@ -557,57 +557,3 @@ string chop_string(const string &s, int width, bool spaces) { return chop_string(s.c_str(), width, spaces); } - -string chop_tagged_string(const char *s, int width, bool spaces) -{ - const char * const s0 = s; - bool in_tag = false; // are we in a tag - bool tag_first = false; // is this the first character of a tag? - char32_t c; - - while (int clen = utf8towc(&c, s)) - { - bool visible = true; - - if (in_tag) - { - if (c == '>') - in_tag = false; - - // << is an escape for <, not a tag. Count the second < as visible. - if (c == '<' && tag_first) - in_tag = false; - else - visible = false; - tag_first = false; - } - else if (c == '<') - { - in_tag = true; - tag_first = true; - visible = false; - } - - if (visible) - { - const int cw = wcwidth(c); - // Due to combining chars, we can't stop at merely reaching the - // target width, the next character needs to exceed it. - if (cw > width) // note: a CJK character might leave one space left - break; - if (cw >= 0) // should we assert on control chars instead? - width -= cw; - } - - s += clen; - } - - if (spaces && width) - return string(s0, s - s0) + string(width, ' '); - return string(s0, s - s0);; -} - -string chop_tagged_string(const string &s, int width, bool spaces) -{ - return chop_tagged_string(s.c_str(), width, spaces); -} diff --git a/crawl-ref/source/unicode.h b/crawl-ref/source/unicode.h index a451fb8c56a3..8c423eb00073 100644 --- a/crawl-ref/source/unicode.h +++ b/crawl-ref/source/unicode.h @@ -9,8 +9,6 @@ int strwidth(const char *s); int strwidth(const string &s); string chop_string(const char *s, int width, bool spaces = true); string chop_string(const string &s, int width, bool spaces = true); -string chop_tagged_string(const char *s, int width, bool spaces = true); -string chop_tagged_string(const string &s, int width, bool spaces = true); int wctoutf8(char *d, char32_t s); int utf8towc(char32_t *d, const char *s); diff --git a/crawl-ref/source/util/art-data.pl b/crawl-ref/source/util/art-data.pl index 254824ff4664..b8412a1fd81d 100755 --- a/crawl-ref/source/util/art-data.pl +++ b/crawl-ref/source/util/art-data.pl @@ -28,7 +28,6 @@ CLARITY => "bool", COLD => "num", COLOUR => "enum", - CORPSE_VIOLATING => "bool", CORRODE => "bool", CURSE => "bool", DEX => "num", @@ -92,6 +91,9 @@ plus2 => "num", base_type => "enum", sub_type => "enum", + fallback_base_type => "enum", + fallback_sub_type => "enum", + FB_BRAND => "enum", unused => "unused", ); @@ -171,6 +173,17 @@ sub finish_art } } + if (!exists($artefact->{FALLBACK})) + { + $artefact->{fallback_base_type} = "OBJ_UNASSIGNED"; + $artefact->{fallback_sub_type} = 250; + } + + if (!exists($artefact->{FB_BRAND})) + { + $artefact->{FB_BRAND} = "-1"; + } + # Appearance is no longer mandatory. $artefact->{APPEAR} = $artefact->{NAME} unless defined($artefact->{APPEAR}); @@ -242,8 +255,8 @@ sub finish_art my $flags = ""; my $flag; - foreach $flag ("SPECIAL", "HOLY", "EVIL", "CHAOTIC", - "CORPSE_VIOLATING", "NOGEN", "RANDAPP", "UNIDED", "SKIP_EGO") + foreach $flag ("SPECIAL", "HOLY", "EVIL", "CHAOTIC", "NOGEN", "RANDAPP", + "UNIDED", "SKIP_EGO") { if ($artefact->{$flag}) { @@ -364,6 +377,29 @@ sub process_line $artefact->{base_type} = $parts[0]; $artefact->{sub_type} = $parts[1]; } + elsif ($field eq "FALLBACK") + { + my @parts = split(m!/!, $value); + + if (@parts > 2) + { + error($artefact, "Too many parts to FALLBACK"); + return; + } + elsif (@parts == 1) + { + error($artefact, "Too few parts to FALLBACK"); + return; + } + + if ($parts[0] !~ /^OBJ_/) + { + error($artefact, "FALLBACK base type must start with 'OBJ_'"); + return; + } + $artefact->{fallback_base_type} = $parts[0]; + $artefact->{fallback_sub_type} = $parts[1]; + } elsif ($field eq "PLUS") { my @parts = split(m!/!, $value); @@ -480,7 +516,9 @@ sub process_line my @art_order = ( "NAME", "APPEAR", "TYPE", "INSCRIP", "\n", - "base_type", "sub_type", "plus", "plus2", "COLOUR", "VALUE", "\n", + "base_type", "sub_type", "\n", + "fallback_base_type", "fallback_sub_type", "FB_BRAND", "\n", + "plus", "plus2", "COLOUR", "VALUE", "\n", "flags", # Move FOG after FLY, and remove four copies of "unused", when diff --git a/crawl-ref/source/util/fake_pty.c b/crawl-ref/source/util/fake_pty.c index c688b1396ccc..a8b4c19299c8 100644 --- a/crawl-ref/source/util/fake_pty.c +++ b/crawl-ref/source/util/fake_pty.c @@ -21,6 +21,15 @@ static void sigalrm(int signum) kill(crawl, SIGTERM); } +static void kill_crawl(int signum) +{ + // I can't figure out any other signal than this that works. SIGTERM + // prints a crash log, which is not what I'm going for. SIGHUP works + // normally, but just seems to freeze a crawl process run from fake_pty + // for some reason... + kill(crawl, SIGKILL); +} + static void slurp_output() { struct pollfd pfd; @@ -82,6 +91,14 @@ int main(int argc, char * const *argv) return 1; default: + { + struct sigaction sa; + sa.sa_handler = kill_crawl; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* Handle error */; close(slave); slurp_output(); if (waitpid(crawl, &ret, 0) != crawl) @@ -94,4 +111,5 @@ int main(int argc, char * const *argv) // fo the mother-in-law or what? return 1; } + } } diff --git a/crawl-ref/source/util/monster/monster-main.cc b/crawl-ref/source/util/monster/monster-main.cc index 5c935b06a16d..c58cb3d159f7 100644 --- a/crawl-ref/source/util/monster/monster-main.cc +++ b/crawl-ref/source/util/monster/monster-main.cc @@ -287,7 +287,8 @@ static string mi_calc_smiting_damage(monster* mons) { return "7-17"; } static string mi_calc_airstrike_damage(monster* mons) { - return make_stringf("0-%d", 10 + 2 * mons->get_experience_level()); + int pow = 12 * mons->get_experience_level(); + return make_stringf("8-%d", 2 + ( 6 + pow ) / 7); } static string mi_calc_glaciate_damage(monster* mons) @@ -346,7 +347,7 @@ static string mons_human_readable_spell_damage_string(monster* monster, spell_type sp) { bolt spell_beam = mons_spell_beam( - monster, sp, mons_power_for_hd(sp, monster->spell_hd(sp), false), true); + monster, sp, mons_power_for_hd(sp, monster->spell_hd(sp)), true); switch (sp) { case SPELL_PORTAL_PROJECTILE: @@ -703,7 +704,7 @@ int main(int argc, char* argv[]) } else if (!strcmp(argv[1], "-name") || !strcmp(argv[1], "--name")) { - seed_rng(); + rng::seed(); printf("%s\n", make_name().c_str()); return 0; } diff --git a/crawl-ref/source/util/species-gen.py b/crawl-ref/source/util/species-gen.py index 4df4a115c0a1..a4b103afc1e7 100755 --- a/crawl-ref/source/util/species-gen.py +++ b/crawl-ref/source/util/species-gen.py @@ -152,9 +152,9 @@ def from_yaml(self, s): SpeciesGroupEntry = collections.namedtuple('SpeciesGroupEntry', ['priority', 'enum']) SPECIES_GROUPS_TEMPLATE = { - 'Simple': SpeciesGroup('coord_def(0, 0)', '20', []), - 'Intermediate': SpeciesGroup('coord_def(25, 0)', '20', []), - 'Advanced': SpeciesGroup('coord_def(50, 0)', '20', []), + 'Simple': SpeciesGroup('coord_def(0, 0)', '50', []), + 'Intermediate': SpeciesGroup('coord_def(1, 0)', '20', []), + 'Advanced': SpeciesGroup('coord_def(2, 0)', '20', []), } SPECIES_GROUP_TEMPLATE = """ {{ diff --git a/crawl-ref/source/version.cc b/crawl-ref/source/version.cc index b2056f26fccd..951ab56f30ce 100644 --- a/crawl-ref/source/version.cc +++ b/crawl-ref/source/version.cc @@ -39,7 +39,7 @@ namespace Version #endif const char* compilation_info = - "Compiled with " COMPILER " on " __DATE__ " at " __TIME__ "\n" + "Compiled with " COMPILER "\n" "Build platform: " CRAWL_HOST "\n" "Platform: " CRAWL_ARCH "\n" "CFLAGS: " CRAWL_CFLAGS "\n" diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc index d052c5b812b9..a2e88336d312 100644 --- a/crawl-ref/source/view.cc +++ b/crawl-ref/source/view.cc @@ -102,7 +102,10 @@ bool handle_seen_interrupt(monster* mons, vector* msgs_buf) aid.context = SC_NEWLY_SEEN; if (!mons_is_safe(mons)) - return interrupt_activity(AI_SEE_MONSTER, aid, msgs_buf); + { + return interrupt_activity(activity_interrupt::see_monster, + aid, msgs_buf); + } return false; } @@ -199,7 +202,7 @@ static string _desc_mons_type_map(map types) message += ", "; ++count; } - return make_stringf("%s come into view.", message.c_str()); + return message; } static monster_type _mons_genus_keep_uniques(monster_type mc) @@ -267,7 +270,7 @@ static bool _is_weapon_worth_listing(const item_def *wpn) /// Return a warning for the player about newly-seen monsters, as appropriate. static string _monster_headsup(const vector &monsters, - map &types, + const map &types, bool divine) { string warning_msg = ""; @@ -296,13 +299,18 @@ static string _monster_headsup(const vector &monsters, monname = mon->pronoun(PRONOUN_SUBJECTIVE); else if (mon->type == MONS_DANCING_WEAPON) monname = "There"; - else if (types[mon->type] == 1) + else if (types.at(mon->type) == 1) monname = mon->full_name(DESC_THE); else monname = mon->full_name(DESC_A); warning_msg += uppercase_first(monname); - warning_msg += " is"; + warning_msg += " "; + if (monsters.size() == 1) + warning_msg += conjugate_verb("are", mon->pronoun_plurality()); + else + warning_msg += "is"; + if (!divine) { warning_msg += get_monster_equipment_desc(mi, DESC_WEAPON_WARNING, @@ -333,7 +341,7 @@ static string _monster_headsup(const vector &monsters, /// Let Ash/Zin warn the player about newly-seen monsters, as appropriate. static void _divine_headsup(const vector &monsters, - map &types) + const map &types) { const string warnings = _monster_headsup(monsters, types, true); if (!warnings.size()) @@ -349,7 +357,7 @@ static void _divine_headsup(const vector &monsters, } static void _secular_headsup(const vector &monsters, - map &types) + const map &types) { const string warnings = _monster_headsup(monsters, types, false); if (!warnings.size()) @@ -357,6 +365,35 @@ static void _secular_headsup(const vector &monsters, mprf(MSGCH_MONSTER_WARNING, "%s", warnings.c_str()); } +static map _count_monster_types(const vector& monsters, + const unsigned int max_types = UINT_MAX) +{ + map types; + map genera; // This is the plural for genus! + for (const monster *mon : monsters) + { + const monster_type type = mon->type; + types[type]++; + genera[_mons_genus_keep_uniques(type)]++; + } + + while (types.size() > max_types && !genera.empty()) + _genus_factoring(types, genera); + + return types; +} + +/** + * Return a string listing monsters in a human readable form. + * E.g. "a hydra and 2 liches". + * + * @param monsters A list of monsters that just became visible. + */ +string describe_monsters_condensed(const vector& monsters) +{ + return _desc_mons_type_map(_count_monster_types(monsters, 4)); +} + /** * Handle printing "foo comes into view" messages for newly appeared monsters. * Also let Ash/Zin warn the player about newly-seen monsters, as appropriate. @@ -368,28 +405,13 @@ static void _secular_headsup(const vector &monsters, static void _handle_comes_into_view(const vector &msgs, const vector monsters) { - const unsigned int max_msgs = 4; - - map types; - map genera; // This is the plural for genus! - for (const monster *mon : monsters) - { - const monster_type type = mon->type; - types[type]++; - genera[_mons_genus_keep_uniques(type)]++; - } - - unsigned int size = monsters.size(); - if (size == 1) + if (monsters.size() == 1) mprf(MSGCH_MONSTER_WARNING, "%s", msgs[0].c_str()); else - { - while (types.size() > max_msgs && !genera.empty()) - _genus_factoring(types, genera); - mprf(MSGCH_MONSTER_WARNING, "%s", - _desc_mons_type_map(types).c_str()); - } + mprf(MSGCH_MONSTER_WARNING, "%s come into view.", + describe_monsters_condensed(monsters).c_str()); + const auto& types = _count_monster_types(monsters); _divine_headsup(monsters, types); _secular_headsup(monsters, types); } @@ -571,6 +593,13 @@ static const FixedArray& _tile_difficulties(bool random) return cache; } +static colour_t _feat_default_map_colour(dungeon_feature_type feat) +{ + if (player_in_branch(BRANCH_SEWER) && feat_is_water(feat)) + return feat == DNGN_DEEP_WATER ? GREEN : LIGHTGREEN; + return BLACK; +} + // Returns true if it succeeded. bool magic_mapping(int map_radius, int proportion, bool suppress_msg, bool force, bool deterministic, @@ -669,12 +698,16 @@ bool magic_mapping(int map_radius, int proportion, bool suppress_msg, { if (wizard_map) { - knowledge.set_feature(grd(pos), 0, + knowledge.set_feature(feat, _feat_default_map_colour(feat), feat_is_trap(grd(pos)) ? get_trap_type(pos) : TRAP_UNASSIGNED); } else if (!knowledge.feat()) - knowledge.set_feature(magic_map_base_feat(grd(pos))); + { + auto base_feat = magic_map_base_feat(feat); + auto colour = _feat_default_map_colour(base_feat); + knowledge.set_feature(base_feat, colour); + } if (emphasise(pos)) knowledge.flags |= MAP_EMPHASIZE; @@ -1410,6 +1443,8 @@ void viewwindow(bool show_updates, bool tiles_only, animation *a) bool run_dont_draw = you.running && Options.travel_delay < 0 && (!you.running.is_explore() || Options.explore_delay < 0); + if (you.running && you.running.is_rest()) + run_dont_draw = Options.rest_delay == -1; if (run_dont_draw || you.asleep()) { @@ -1493,6 +1528,10 @@ void draw_cell(screen_cell_t *cell, const coord_def &gc, else _draw_outside_los(cell, gc, ep); // in los bounds but not visible +#ifdef USE_TILE + cell->tile.map_knowledge = map_bounds(gc) ? env.map_knowledge(gc) : map_cell(); +#endif + cell->flash_colour = BLACK; // Don't hide important information by recolouring monsters. @@ -1700,7 +1739,7 @@ void reset_show_terrain() //////////////////////////////////////////////////////////////////////////// // Term resize handling (generic). -void handle_terminal_resize(bool redraw) +void handle_terminal_resize() { crawl_state.terminal_resized = false; @@ -1709,6 +1748,5 @@ void handle_terminal_resize(bool redraw) else crawl_view.init_geometry(); - if (redraw) - redraw_screen(); + redraw_screen(); } diff --git a/crawl-ref/source/view.h b/crawl-ref/source/view.h index f447b5a68977..5d5c9ecbdaac 100644 --- a/crawl-ref/source/view.h +++ b/crawl-ref/source/view.h @@ -13,6 +13,8 @@ bool mon_enemies_around(const monster* mons); void seen_monsters_react(int stealth = player_stealth()); +string describe_monsters_condensed(const vector& monsters); + bool magic_mapping(int map_radius, int proportion, bool suppress_msg, bool force = false, bool deterministic = false, coord_def origin = coord_def(-1, -1)); @@ -78,4 +80,4 @@ void flush_comes_into_view(); void toggle_show_terrain(); void reset_show_terrain(); -void handle_terminal_resize(bool redraw = true); +void handle_terminal_resize(); diff --git a/crawl-ref/source/viewchar.cc b/crawl-ref/source/viewchar.cc index faef4c2d3557..399dc8661e4c 100644 --- a/crawl-ref/source/viewchar.cc +++ b/crawl-ref/source/viewchar.cc @@ -65,8 +65,13 @@ static const char32_t dchar_table[NUM_CSET][NUM_DCHAR_TYPES] = '#', '#', '*', '.', ',', '\'', '+', '^', '>', '<', '#', '_', // arch .. item_food '\\', '}', '~', '8', '{', '{', '{', '}', ')', '[', '/', '%', - // item_scroll .. item_amulet - '?', '=', '!', '(', ':', '|', '|', '}', '%', '%', '$', '"', + // item_scroll .. item_staff + '?', '=', '!', '(', ':', '|', +#if TAG_MAJOR_VERSION == 34 + '|', // rod +#endif + // miscellany .. amulet + '}', '%', '%', '$', '"', // cloud .. tree '0', '0', '0', '0', '7', #if TAG_MAJOR_VERSION == 34 diff --git a/crawl-ref/source/viewgeom.cc b/crawl-ref/source/viewgeom.cc index 729bffc3d998..4c9866501fb7 100644 --- a/crawl-ref/source/viewgeom.cc +++ b/crawl-ref/source/viewgeom.cc @@ -265,6 +265,12 @@ const crawl_view_buffer &crawl_view_buffer::operator = (const crawl_view_buffer return *this; } +void crawl_view_buffer::fill(const screen_cell_t& value) +{ + for (int i = 0; i < m_size.x * m_size.y; ++i) + m_buffer[i] = value; +} + void crawl_view_buffer::clear() { delete [] m_buffer; diff --git a/crawl-ref/source/viewgeom.h b/crawl-ref/source/viewgeom.h index bf731161ce20..a9b141b9e252 100644 --- a/crawl-ref/source/viewgeom.h +++ b/crawl-ref/source/viewgeom.h @@ -27,6 +27,14 @@ class crawl_view_buffer operator const screen_cell_t * () const { return m_buffer; } const crawl_view_buffer & operator = (const crawl_view_buffer &rhs); + template + screen_cell_t& operator () (const Indexer &i) + { + ASSERT(i.x >= 0 && i.y >= 0 && i.x < m_size.x && i.y < m_size.y); + return m_buffer[i.y*m_size.x + i.x]; + } + + void fill(const screen_cell_t& value); void clear(); void draw(); private: diff --git a/crawl-ref/source/viewmap.cc b/crawl-ref/source/viewmap.cc index ab9465e0e22c..326b0fd0daf0 100644 --- a/crawl-ref/source/viewmap.cc +++ b/crawl-ref/source/viewmap.cc @@ -641,7 +641,6 @@ bool show_map(level_pos &lpos, lpos.id = level_id::current(); cursor_control ccon(!Options.use_fake_cursor); - int i, j; int move_x = 0, move_y = 0, scroll_y = 0; @@ -702,29 +701,11 @@ bool show_map(level_pos &lpos, feats.init(); - min_x = GXM, max_x = 0, min_y = 0, max_y = 0; - bool found_y = false; - - for (j = 0; j < GYM; j++) - for (i = 0; i < GXM; i++) - { - if (env.map_knowledge[i][j].known()) - { - if (!found_y) - { - found_y = true; - min_y = j; - } - - max_y = j; - - if (i < min_x) - min_x = i; - - if (i > max_x) - max_x = i; - } - } + std::pair bounds = known_map_bounds(); + min_x = bounds.first.x; + min_y = bounds.first.y; + max_x = bounds.second.x; + max_y = bounds.second.y; map_lines = max_y - min_y + 1; @@ -1028,26 +1009,29 @@ bool show_map(level_pos &lpos, } case CMD_MAP_GOTO_LEVEL: - { if (!allow_offlevel) break; - string name; - const level_pos pos - = prompt_translevel_target(TPF_DEFAULT_OPTIONS, name); - - if (pos.id.depth < 1 - || pos.id.depth > brdepth[pos.id.branch] - || !you.level_visited(pos.id)) { - canned_msg(MSG_OK); - redraw_map = true; - break; - } + string name; +#ifdef USE_TILE_WEB + tiles_ui_control msgwin(UI_NORMAL); +#endif + const level_pos pos + = prompt_translevel_target(TPF_DEFAULT_OPTIONS, name); - lpos = pos; + if (pos.id.depth < 1 + || pos.id.depth > brdepth[pos.id.branch] + || !you.level_visited(pos.id)) + { + canned_msg(MSG_OK); + redraw_map = true; + break; + } + + lpos = pos; + } continue; - } case CMD_MAP_JUMP_DOWN_LEFT: move_x = -block_step; @@ -1109,6 +1093,13 @@ bool show_map(level_pos &lpos, } break; +#ifdef USE_TILE + case CMD_MAP_ZOOM_IN: + case CMD_MAP_ZOOM_OUT: + tiles.zoom_dungeon(cmd == CMD_MAP_ZOOM_IN); + break; +#endif + case CMD_MAP_FIND_UPSTAIR: case CMD_MAP_FIND_DOWNSTAIR: case CMD_MAP_FIND_PORTAL: @@ -1188,6 +1179,7 @@ bool show_map(level_pos &lpos, move_y = you.travel_y - lpos.pos.y; } else if (allow_offlevel && you.travel_z.is_valid() + && can_travel_to(you.travel_z) && you.level_visited(you.travel_z)) { // previous travel target is offlevel @@ -1213,7 +1205,12 @@ bool show_map(level_pos &lpos, if (!is_map_persistent()) mpr("You can't annotate this level."); else + { +#ifdef USE_TILE_WEB + tiles_ui_control msgwin(UI_NORMAL); +#endif do_annotate(lpos.id); + } redraw_map = true; break; diff --git a/crawl-ref/source/webserver/config.py b/crawl-ref/source/webserver/config.py index 0b68afc15d27..c89d33b536a9 100644 --- a/crawl-ref/source/webserver/config.py +++ b/crawl-ref/source/webserver/config.py @@ -153,6 +153,15 @@ # This needs a patch currently not in mainline tornado. http_connection_timeout = None +# Set this to true if you are behind a reverse proxy +# Your proxy must set header X-Real-IP +# +# Enabling this option when webtiles is NOT protected behind a reverse proxy +# introduces a security risk. An attacker could inject a false address into the +# X-Real-IP header. Do not enable this option if the webtiles server is +# directly exposed to users. +http_xheaders = None + kill_timeout = 10 # Seconds until crawl is killed after HUP is sent nick_regex = r"^[a-zA-Z0-9]{3,20}$" diff --git a/crawl-ref/source/webserver/game_data/static/cell_renderer.js b/crawl-ref/source/webserver/game_data/static/cell_renderer.js index b889e4c18f00..dbe2f04f7115 100644 --- a/crawl-ref/source/webserver/game_data/static/cell_renderer.js +++ b/crawl-ref/source/webserver/game_data/static/cell_renderer.js @@ -7,8 +7,11 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, function DungeonCellRenderer() { + // see also, renderer_settings in game.js. In fact, do these values + // do anything? + var ratio = window.devicePixelRatio; this.set_cell_size(32, 32); - this.glyph_mode_font_size = 24; + this.glyph_mode_font_size = 24 * ratio; this.glyph_mode_font = "monospace"; } @@ -92,14 +95,20 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, { this.cell_width = Math.floor(w); this.cell_height = Math.floor(h); - this.x_scale = w / 32; - this.y_scale = h / 32; + this.x_scale = this.cell_width / 32; + this.y_scale = this.cell_height / 32; }, glyph_mode_font_name: function () { - return (this.glyph_mode_font_size + "px " + - this.glyph_mode_font); + var glyph_scale; + if (this.ui_state == enums.ui.VIEW_MAP) + glyph_scale = options.get("tile_map_scale"); + else + glyph_scale = options.get("tile_viewport_scale"); + + return (Math.floor(this.glyph_mode_font_size * glyph_scale / 100) + + "px " + this.glyph_mode_font); }, render_cursors: function(cx, cy, x, y) @@ -692,6 +701,9 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, this.draw_dngn(dngn.HALO_GD_NEUTRAL, x, y); else if (fg.NEUTRAL) this.draw_dngn(dngn.HALO_NEUTRAL, x, y); + + if (cell.highlighted_summoner) + this.draw_dngn(dngn.HALO_SUMMONER, x, y); } diff --git a/crawl-ref/source/webserver/game_data/static/dungeon_renderer.js b/crawl-ref/source/webserver/game_data/static/dungeon_renderer.js index 6f1d64784447..5c94f5f68b60 100644 --- a/crawl-ref/source/webserver/game_data/static/dungeon_renderer.js +++ b/crawl-ref/source/webserver/game_data/static/dungeon_renderer.js @@ -51,6 +51,7 @@ function ($, cr, map_knowledge, options, dngn, util, view_data, enums) { this.view = { x: 0, y: 0 }; this.view_center = { x: 0, y: 0 }; + this.ui_state = -1; } DungeonViewRenderer.prototype = new cr.DungeonCellRenderer(); @@ -236,24 +237,36 @@ function ($, cr, map_knowledge, options, dngn, util, view_data, enums) { fit_to: function(width, height, min_diameter) { var ratio = window.devicePixelRatio; + var scale; + if (this.ui_state == enums.ui.VIEW_MAP) + scale = options.get("tile_map_scale"); + else + scale = options.get("tile_viewport_scale"); + var tile_size = Math.floor(options.get("tile_cell_pixels") + * scale / 100); var cell_size = { - w: Math.floor(options.get("tile_cell_pixels") * ratio), - h: Math.floor(options.get("tile_cell_pixels") * ratio) + w: Math.floor(tile_size * ratio), + h: Math.floor(tile_size * ratio) }; if (options.get("tile_display_mode") == "glyphs") { + // font size ratio: handled in cell_renderer.js + var margin = 2 * ratio; this.ctx.font = this.glyph_mode_font_name(); var metrics = this.ctx.measureText("@"); - this.set_cell_size(metrics.width + 2, this.glyph_mode_font_size + 2); + this.set_cell_size(metrics.width + margin, + Math.floor(this.glyph_mode_font_size * scale / 100) + + margin); } else if ((min_diameter * cell_size.w / ratio > width) || (min_diameter * cell_size.h / ratio > height)) { - var scale = Math.min(width * ratio / (min_diameter * cell_size.w), + // scale down if necessary, so that los is in view + var rescale = Math.min(width * ratio / (min_diameter * cell_size.w), height * ratio / (min_diameter * cell_size.h)); - this.set_cell_size(Math.floor(cell_size.w * scale), - Math.floor(cell_size.h * scale)); + this.set_cell_size(Math.floor(cell_size.w * rescale), + Math.floor(cell_size.h * rescale)); } else this.set_cell_size(cell_size.w, cell_size.h); @@ -348,6 +361,11 @@ function ($, cr, map_knowledge, options, dngn, util, view_data, enums) { renderer.draw_tiles(tiles); return renderer; + }, + + set_ui_state: function(s) + { + this.ui_state = s; } }); diff --git a/crawl-ref/source/webserver/game_data/static/game.js b/crawl-ref/source/webserver/game_data/static/game.js index 9a611e20cfc6..37c4fdb72e62 100644 --- a/crawl-ref/source/webserver/game_data/static/game.js +++ b/crawl-ref/source/webserver/game_data/static/game.js @@ -137,13 +137,16 @@ function ($, comm, client, dungeon_renderer, display, minimap, enums, messages, messages.show(); } minimap.stop_minimap_farview(); + minimap.update_overlay(); display.invalidate(true); display.display(); } function set_ui_state(state) { + dungeon_renderer.set_ui_state(state); if (state == ui_state) return; + var old_state = ui_state; ui_state = state; switch (ui_state) @@ -222,8 +225,9 @@ function ($, comm, client, dungeon_renderer, display, minimap, enums, messages, document.title = data.text; } + var device_ratio = window.devicePixelRatio; var renderer_settings = { - glyph_mode_font_size: 24, + glyph_mode_font_size: 24 * device_ratio, glyph_mode_font: "monospace" }; diff --git a/crawl-ref/source/webserver/game_data/static/options.js b/crawl-ref/source/webserver/game_data/static/options.js index b7759b732e1a..15440bb0ad3a 100644 --- a/crawl-ref/source/webserver/game_data/static/options.js +++ b/crawl-ref/source/webserver/game_data/static/options.js @@ -27,6 +27,7 @@ function ($, comm) { return options[name]; } + // writes all options at once: usable only on first connecting. function handle_options_message(data) { if (options == null || data["watcher"] == true) @@ -41,21 +42,29 @@ function ($, comm) { listeners.add(callback); } - // for testing purposes only - window.set_option = function(name, value) + function set_option(name, value) { var old = get_option(name); console.log(name + ": '" + old + "' => '" + value + "'"); options[name] = value; listeners.fire(); + } + + function handle_set_option(data) + { + set_option(data.name, data.value); }; + window.set_option = set_option; + comm.register_handlers({ "options": handle_options_message, + "set_option": handle_set_option }); return { get: get_option, + set: set_option, clear: clear_options, add_listener: add_listener, }; diff --git a/crawl-ref/source/webserver/game_data/static/player.js b/crawl-ref/source/webserver/game_data/static/player.js index d61e2581b2e5..97d73b96cd5d 100644 --- a/crawl-ref/source/webserver/game_data/static/player.js +++ b/crawl-ref/source/webserver/game_data/static/player.js @@ -406,8 +406,6 @@ function ($, comm, enums, map_knowledge, messages, options) { { $("#stats_time_caption").text("Time:"); $("#stats_time").text((player.time / 10.0).toFixed(1)); - if (player.time_delta) - $("#stats_time").append(" (" + (player.time_delta / 10.0).toFixed(1) + ")"); } else { @@ -415,6 +413,9 @@ function ($, comm, enums, map_knowledge, messages, options) { $("#stats_time").text(player.turn); } + if (player.time_delta) + $("#stats_time").append(" (" + (player.time_delta / 10.0).toFixed(1) + ")"); + var place_desc = player.place; if (player.depth) place_desc += ":" + player.depth; $("#stats_place").text(place_desc); diff --git a/crawl-ref/source/webserver/game_data/static/style.css b/crawl-ref/source/webserver/game_data/static/style.css index d317b290611b..f355bd144a79 100644 --- a/crawl-ref/source/webserver/game_data/static/style.css +++ b/crawl-ref/source/webserver/game_data/static/style.css @@ -591,6 +591,7 @@ body { .describe-feature-feat > .body { margin-top: 15px; font-size: 0.9em; + white-space: pre-wrap; } .describe-item { @@ -1036,3 +1037,215 @@ body { #ui-stack:not([data-display-mode="tiles"]) .glyph-mode-hidden { display: none !important; } + +.newgame-choice { + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.newgame-choice .header +{ + margin-bottom: 15px; + white-space: pre; + display: flex; + align-items: center; + flex: 1 0 auto; +} + +.newgame-choice .header > canvas +{ + margin-right: 10px; +} + +.newgame-choice > .body { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; +} + +.newgame-choice .grid +{ + display: grid; + list-style: none; + min-height: 0; + flex: 1 1 auto; +} +.newgame-choice .sub-items +{ + flex: 1 0 auto; +} + +.newgame-choice .grid .button { + background: #040204; + white-space: pre; + padding: 1px; + border: 1px solid rgba(255, 255, 255, 0.001); +} + +@media screen and (max-height: 780px) { + .newgame-choice .grid .button canvas { + margin: -1px 0; + } + .ui-popup-outer { + margin: 20px; + max-height: calc(100vh - 40px); + } + .ui-popup-inner { + max-height: calc(100vh - 40px - 46px); + } +} + +.newgame-choice .grid .button.selected +{ + background: black; + border: 1px solid #222; +} + +.newgame-choice .grid .button.selected.hlc-7 { border: 1px solid #babdb6; } /* lightgrey */ +.newgame-choice .grid .button.selected.hlc-1 { border: 1px solid #204a87; } /* blue */ +.newgame-choice .grid .button.selected.hlc-2 { border: 1px solid #4e9a06; } /* green */ + +.newgame-choice .grid .button.selected:hover { + cursor: pointer; +} + +.newgame-choice .grid .button:active +{ + background: black; + border: 1px solid #222 !important; +} + +.newgame-choice .grid .button +{ + display: flex; + align-items: center; + padding-right: 1em; +} + +.newgame-choice .grid .button > span +{ + margin-left: 6px; +} + +#ui-stack[data-display-mode="tiles"] .newgame-choice .grid .label { + align-self: end; + margin-bottom: 0.75em; + margin-left: 2px; +} + +#ui-stack[data-display-mode="tiles"] .newgame-choice .grid .label::before { + content: " "; + display: inline-block; + margin-left: 32px; +} + +.newgame-choice .descriptions +{ + margin: 15px 0; + max-width: 70ch; + flex: 1 0 auto; +} + +.game-over { + max-width: 80ch; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; /* for IE11 */ + max-height: inherit; +} +.game-over > .header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 20px; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; +} +.game-over > .header > canvas { + margin-right: 10px; +} +.game-over > .body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + white-space: pre-wrap; +} + +.seed-selection { + max-width: 80ch; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; /* for IE11 */ + max-height: inherit; +} + +.seed-selection > .header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 20px; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; +} + +.seed-selection > .body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 20px; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; +} + +.seed-selection > .footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; +} + +.seed-selection > .seed-input { + line-height: 1.2em; + white-space: pre; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.seed-selection > .seed-input > span > input { + line-height: 1.2em; + min-width: 20em; /* TODO: this is too wide, but it's a safe value */ + margin-left: 1em; + white-space: pre; +} diff --git a/crawl-ref/source/webserver/game_data/static/textinput.js b/crawl-ref/source/webserver/game_data/static/textinput.js index 99a5846eeafe..16cd81239343 100644 --- a/crawl-ref/source/webserver/game_data/static/textinput.js +++ b/crawl-ref/source/webserver/game_data/static/textinput.js @@ -18,6 +18,8 @@ function ($, comm, client, enums, util, options, ui) { assert(input_data); if (input_data.type == "messages") return $("#messages .game_message input"); + else if (input_data.type == "seed-selection") + return $(".seed-selection .seed-input input"); else //"generic" return $("#input_dialog input"); } @@ -54,6 +56,15 @@ function ($, comm, client, enums, util, options, ui) { prompt = $("#messages .game_message").last(); prompt.append(input); } + else if (input_data.type == "seed-selection") + { + // n.b. called from ui-layouts.js, not from client + var input_div = $(".seed-selection .seed-input").last(); + if (prompt) + input_div.append($("").append(prompt)); + // TODO: what is this class? + input_div.append($("").append(input)); + } else // "generic" { var input_div = $("
"); @@ -91,7 +102,11 @@ function ($, comm, client, enums, util, options, ui) { comm.send_message("key", { keycode: 11 }); } comm.send_message("input", { text: text }); - close_input(); + // seed-selection handles this on its own, because there is first + // a validation step -- the input loop only terminates on abort + // or if there is an actual number to be found. + if (input_data.type != "seed-selection") + close_input(); } input.keydown(function (ev) { @@ -163,6 +178,24 @@ function ($, comm, client, enums, util, options, ui) { return false; } } + else if (input_data.type == "seed-selection") + { + var ch = String.fromCharCode(ev.which); + if (ch == "-" // clear input + || ch == "?" // help + || ch == "d") // daily seed + { + comm.send_message("key", { keycode: ev.which }); + ev.preventDefault(); + return false; + } + // mimic keyfunc on crawl side: prevent non-numbers + if ("0123456789".indexOf(ch) == -1) + { + ev.preventDefault(); + return false; + } + } }); } @@ -210,8 +243,11 @@ function ($, comm, client, enums, util, options, ui) { if (input) { input.blur(); - if (input_data.type == "messages") + if (input_data.type == "messages" + || input_data.type == "seed-selection") + { input.remove(); + } else // "generic" ui.hide_popup(); } @@ -220,9 +256,24 @@ function ($, comm, client, enums, util, options, ui) { input_cleanup(); } + function update_input(msg) + { + if (input_data) + { + var input = find_input(); + if (input) + { + input.val(msg.input_text); + if (msg.select) + input.select(); + } + } + } + comm.register_handlers({ "init_input": init_input, - "close_input": close_input + "close_input": close_input, + "update_input": update_input }); diff --git a/crawl-ref/source/webserver/game_data/static/ui-layouts.js b/crawl-ref/source/webserver/game_data/static/ui-layouts.js index 242201be0cca..000fd1aa98ca 100644 --- a/crawl-ref/source/webserver/game_data/static/ui-layouts.js +++ b/crawl-ref/source/webserver/game_data/static/ui-layouts.js @@ -32,7 +32,8 @@ function ($, comm, client, ui, enums, cr, util, scroller, main, gui, player) { return; } $.each(spellset, function (i, book) { - $container.append(book.label); + if (book.label.trim()) + $container.append(book.label); var $list = $("
    "); $.each(book.spells, function (i, spell) { var $item = $("
  1. "); @@ -133,7 +134,12 @@ function ($, comm, client, ui, enums, cr, util, scroller, main, gui, player) { var $feat = $feat_tmpl.clone().removeClass("hidden").addClass("describe-feature-feat"); $feat.find(".header > span").html(feat.title); if (feat.body != feat.title) - $feat.find(".body").html(feat.body); + { + var text = feat.body; + if (feat.quote) + text += "\n\n" + feat.quote; + $feat.find(".body").html(text); + } else $feat.find(".body").remove(); @@ -716,6 +722,159 @@ function ($, comm, client, ui, enums, cr, util, scroller, main, gui, player) { $popup.children(".body")[0].textContent = msg.text; } + function focus_button($button) + { + var $popup = $button.closest(".newgame-choice"); + var menu_id = $button.closest("[menu_id]").attr("menu_id"); + $popup.find(".button.selected").removeClass("selected"); + $button.addClass("selected"); + var $scr = $button.closest(".simplebar-scroll-content"); + if ($scr.length === 1) + { + var br = $button[0].getBoundingClientRect(); + var gr = $scr.parent()[0].getBoundingClientRect(); + var delta = br.top < gr.top ? br.top - gr.top : + br.bottom > gr.bottom ? br.bottom - gr.bottom : 0; + $scr[0].scrollTop += delta; + } + + var $descriptions = $popup.find(".descriptions"); + paneset_cycle($descriptions, $button.attr("data-description-index")); + comm.send_message("outer_menu_focus", { + hotkey: parseInt($button.attr("data-hotkey"), 10), + menu_id: menu_id, + }); + } + + function newgame_choice(msg) + { + var $popup = $(".templates > .newgame-choice").clone(); + if (msg.doll) + { + var $canvas = $(""); + var $title = $(""); + var $prompt = $("
    "); + $popup.children(".header").append($canvas).append($title).after($prompt); + $title.html(util.formatted_string_to_html(msg.title)); + $prompt.html(util.formatted_string_to_html(msg.prompt)); + var renderer = new cr.DungeonCellRenderer(); + util.init_canvas($canvas[0], renderer.cell_width, renderer.cell_height); + renderer.init($canvas[0]); + $.each(msg.doll, function (i, doll_part) { + renderer.draw_player(doll_part[0], 0, 0, 0, 0, doll_part[1]); + }); + } + else + $popup.children(".header").html(util.formatted_string_to_html(msg.title)); + var renderer = new cr.DungeonCellRenderer(); + var $descriptions = $popup.find(".descriptions"); + + function build_item_grid(data, $container, fat) + { + $container.attr("menu_id", data.menu_id); + $.each(data.buttons, function (i, button) { + var $button = $("
    "); + if ((button.tile || []).length > 0 || fat) + { + var $canvas = $(""); + util.init_canvas($canvas[0], renderer.cell_width, renderer.cell_height); + renderer.init($canvas[0]); + + $.each(button.tile || [], function (_, tile) { + renderer.draw_from_texture(tile.t, 0, 0, tile.tex, 0, 0, tile.ymax, false); + }); + $button.append($canvas); + } + $.each(button.labels || [button.label], function (i, label) { + var $lbl = $(util.formatted_string_to_html(label)).css("flex-grow", "1"); + $button.append($lbl); + }); + $button.attr("style", "grid-row:"+(button.y+1)+"; grid-column:"+(button.x+1)+";"); + $descriptions.append(" " + button.description + ""); + $button.attr("data-description-index", $descriptions.children().length - 1); + $button.attr("data-hotkey", button.hotkey); + $button.addClass("hlc-" + button.highlight_colour); + $container.append($button); + + $button.on("click", function () { + comm.send_message("key", { keycode: button.hotkey }); + }).on('hover', function () { focus_button($(this)) }); + }); + $.each(data.labels, function (i, button) { + var $button = $("
    "); + $button.append(util.formatted_string_to_html(button.label)); + $button.attr("style", "grid-row:"+(button.y+1)+"; grid-column:"+(button.x+1)+";"); + $container.append($button); + }); + } + + var $main = $popup.find(".main-items"); + build_item_grid(msg["main-items"], $main, true); + build_item_grid(msg["sub-items"], $popup.find(".sub-items")); + scroller($main.parent()[0]); + focus_button($popup.find(".main-items").eq(0)); + + return $popup; + } + + function newgame_choice_update(msg) + { + if (!client.is_watching() && msg.from_client) + return; + var $popup = ui.top_popup(); + if (!$popup || !$popup.hasClass("newgame-choice")) + return; + focus_button($popup.find("[data-hotkey='"+msg.button_focus+"']")); + } + + function newgame_random_combo(msg) + { + var $popup = $(".templates > .describe-generic").clone(); + $popup.find(".header > span").html(util.formatted_string_to_html(msg.prompt)); + $popup.find(".body").html("Do you want to play this combination? [Y/n/q]"); + + var $canvas = $popup.find(".header > canvas"); + var renderer = new cr.DungeonCellRenderer(); + util.init_canvas($canvas[0], renderer.cell_width, renderer.cell_height); + renderer.init($canvas[0]); + $.each(msg.doll, function (i, doll_part) { + renderer.draw_player(doll_part[0], 0, 0, 0, 0, doll_part[1]); + }); + + return $popup; + } + + function game_over(desc) + { + var $popup = $(".templates > .game-over").clone(); + $popup.find(".header > span").html(desc.title); + $popup.children(".body").html(fmt_body_txt(util.formatted_string_to_html(desc.body))); + var s = scroller($popup.children(".body")[0]); + $popup.on("keydown keypress", function (event) { + scroller_handle_key(s, event); + }); + + var canvas = $popup.find(".header > canvas"); + var renderer = new cr.DungeonCellRenderer(); + util.init_canvas(canvas[0], renderer.cell_width, renderer.cell_height); + renderer.init(canvas[0]); + renderer.draw_from_texture(desc.tile.t, 0, 0, desc.tile.tex, 0, 0, desc.tile.ymax, false); + + return $popup; + } + + function seed_selection(desc) + { + var $popup = $(".templates > .seed-selection").clone(); + $popup.find(".header > span").html(desc.title); + $popup.find(".body > span").html(fmt_body_txt( + util.formatted_string_to_html(desc.body))); + $popup.find(".footer > span").html(fmt_body_txt( + util.formatted_string_to_html(desc.footer))); + // TODO: handle input here? currently handled from c++ side. + return $popup; + } + var ui_handlers = { "describe-generic" : describe_generic, "describe-feature-wide" : describe_feature_wide, @@ -728,7 +887,11 @@ function ($, comm, client, ui, enums, cr, util, scroller, main, gui, player) { "version" : version, "formatted-scroller" : formatted_scroller, "progress-bar" : progress_bar, + "seed-selection" : seed_selection, "msgwin-get-line" : msgwin_get_line, + "newgame-choice": newgame_choice, + "newgame-random-combo": newgame_random_combo, + "game-over" : game_over, }; function register_ui_handlers(dict) @@ -765,6 +928,7 @@ function ($, comm, client, ui, enums, cr, util, scroller, main, gui, player) { "formatted-scroller" : formatted_scroller_update, "msgwin-get-line" : msgwin_get_line_update, "progress-bar" : progress_bar_update, + "newgame-choice" : newgame_choice_update, }; var handler = ui_handlers[msg.type]; if (handler) diff --git a/crawl-ref/source/webserver/game_data/templates/game.html b/crawl-ref/source/webserver/game_data/templates/game.html index 7de056a473e3..643f985be007 100644 --- a/crawl-ref/source/webserver/game_data/templates/game.html +++ b/crawl-ref/source/webserver/game_data/templates/game.html @@ -165,30 +165,29 @@
    - + - - - - - - - - - - - - - + + + + + + + + + + + +
    FullSatiatedThirstyBloodless
    AliveBloodless
    Metabolismfastnormalslownone
    Regenerationfastnormalslownone
    Stealth boostnonenoneminormajor
    Hunger costsfullfullhalvednone
    Resistances
    Poison resistance+immune
    Cold resistance+++
    Negative resistance++++++
    Rot resistance++
    Torment resistance+
    Transformations
    Bat formnoyesyesyes
    Other forms / berserkyesyesnono
    Regenerationfastnone with monsters in sight
    Stealth boostnonemajor
    HP modifiernone-20%
    Resistances
    Poison resistanceimmune
    Cold resistance++
    Negative resistance+++
    Rot resistance+
    Torment resistance+
    Transformations
    Bat formnoyes
    Other forms / berserkyesno
    @@ -294,6 +293,33 @@
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    +
diff --git a/crawl-ref/source/webserver/server.py b/crawl-ref/source/webserver/server.py index f741aedbd0f5..9f9a5d96468a 100755 --- a/crawl-ref/source/webserver/server.py +++ b/crawl-ref/source/webserver/server.py @@ -136,6 +136,8 @@ def bind_server(): kwargs = {} if http_connection_timeout is not None: kwargs["connection_timeout"] = http_connection_timeout + if getattr(config, "http_xheaders", False): + kwargs["xheaders"] = http_xheaders servers = [] diff --git a/crawl-ref/source/webserver/static/scripts/client.js b/crawl-ref/source/webserver/static/scripts/client.js index ebcc2ba0ccee..6f02b909a0b0 100644 --- a/crawl-ref/source/webserver/static/scripts/client.js +++ b/crawl-ref/source/webserver/static/scripts/client.js @@ -870,7 +870,8 @@ function (exports, $, key_conversion, chat, comm) { if (exit_reason) { - if (was_watching || normal_exit.indexOf(exit_reason) === -1) + if (was_watching || normal_exit.indexOf(exit_reason) === -1 + || exit_message && exit_message.length > 0) { show_exit_dialog(exit_reason, exit_message, exit_dump, was_watching ? watching_username : null); diff --git a/crawl-ref/source/webserver/templates/client.html b/crawl-ref/source/webserver/templates/client.html index 996fcc888b7a..16a387f34c40 100644 --- a/crawl-ref/source/webserver/templates/client.html +++ b/crawl-ref/source/webserver/templates/client.html @@ -207,6 +207,7 @@ + @@ -214,8 +215,14 @@ + + + + + + diff --git a/crawl-ref/source/windowmanager-sdl.cc b/crawl-ref/source/windowmanager-sdl.cc index 284cd4a2c581..dcdc0d90ecf6 100644 --- a/crawl-ref/source/windowmanager-sdl.cc +++ b/crawl-ref/source/windowmanager-sdl.cc @@ -362,8 +362,28 @@ int SDLWrapper::init(coord_def *m_windowsz) return false; } + // find the current display, based on the mouse position + // TODO: probably want to allow configuring this? + int mouse_x, mouse_y; + SDL_GetGlobalMouseState(&mouse_x, &mouse_y); + + int displays = SDL_GetNumVideoDisplays(); + int cur_display; + for (cur_display = 0; cur_display < displays; cur_display++) + { + SDL_Rect bounds; + SDL_GetDisplayBounds(cur_display, &bounds); + if (mouse_x >= bounds.x && mouse_y >= bounds.y && + mouse_x < bounds.x + bounds.w && mouse_y < bounds.y + bounds.h) + { + break; + } + } + if (cur_display >= displays) + cur_display = 0; // can this happen? + SDL_DisplayMode display_mode; - SDL_GetDesktopDisplayMode(0, &display_mode); + SDL_GetDesktopDisplayMode(cur_display, &display_mode); _desktop_width = display_mode.w; _desktop_height = display_mode.h; @@ -439,8 +459,8 @@ int SDLWrapper::init(coord_def *m_windowsz) string title = string(CRAWL " ") + Version::Long; m_window = SDL_CreateWindow(title.c_str(), - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_CENTERED_DISPLAY(cur_display), + SDL_WINDOWPOS_CENTERED_DISPLAY(cur_display), m_windowsz->x, m_windowsz->y, flags); glDebug("SDL_CreateWindow"); if (!m_window) @@ -789,7 +809,15 @@ int SDLWrapper::send_textinput(wm_event *event) int wc_bytelen = utf8towc(&wc, m_textinput_queue.c_str()); m_textinput_queue.erase(0, wc_bytelen); - if (prev_keycode && _key_suppresses_textinput(prev_keycode) == wc) + // SDL2 on linux sends an apparently spurious '=' text event for ctrl-=, + // but not for key combinations like ctrl-f (no 'f' text event is sent). + // this is relevant only for ctrl-- and ctrl-= bindings at the moment, + // and I'm somewhat nervous about blocking genuine text entry via the alt + // key, so for the moment this only blacklists text events with ctrl held + bool nontext_modifier_held = wm->get_mod_state() == TILES_MOD_CTRL; + + bool should_suppress = prev_keycode && _key_suppresses_textinput(prev_keycode) == wc; + if (nontext_modifier_held || should_suppress) { // this needs to return something, or the event loop in // TilesFramework::getch_ck will block. Currently, CK_NO_KEY @@ -906,9 +934,13 @@ unsigned int SDLWrapper::set_timer(unsigned int interval, return SDL_AddTimer(interval, callback, nullptr); } -void SDLWrapper::remove_timer(unsigned int timer_id) +void SDLWrapper::remove_timer(unsigned int& timer_id) { - SDL_RemoveTimer(timer_id); + if (timer_id) + { + SDL_RemoveTimer(timer_id); + timer_id = 0; + } } int SDLWrapper::raise_custom_event() diff --git a/crawl-ref/source/windowmanager-sdl.h b/crawl-ref/source/windowmanager-sdl.h index 21fe18dc2f51..2da07c5b1076 100644 --- a/crawl-ref/source/windowmanager-sdl.h +++ b/crawl-ref/source/windowmanager-sdl.h @@ -38,7 +38,7 @@ class SDLWrapper : public WindowManager // System time functions virtual unsigned int set_timer(unsigned int interval, wm_timer_callback callback) override; - virtual void remove_timer(unsigned int timer_id) override; + virtual void remove_timer(unsigned int& timer_id) override; virtual unsigned int get_ticks() const override; virtual void delay(unsigned int ms) override; diff --git a/crawl-ref/source/windowmanager.h b/crawl-ref/source/windowmanager.h index 1af2b78dcccf..032927f721c2 100644 --- a/crawl-ref/source/windowmanager.h +++ b/crawl-ref/source/windowmanager.h @@ -22,6 +22,8 @@ enum wm_event_type WME_RESIZE, WME_EXPOSE, WME_MOVE, + WME_FOCUSIN, + WME_FOCUSOUT, WME_NUMEVENTS = 15 }; @@ -160,7 +162,7 @@ class WindowManager // System time functions virtual unsigned int set_timer(unsigned int interval, wm_timer_callback callback) = 0; - virtual void remove_timer(unsigned int timer_id) = 0; + virtual void remove_timer(unsigned int& timer_id) = 0; virtual unsigned int get_ticks() const = 0; virtual void delay(unsigned int ms) = 0; diff --git a/crawl-ref/source/wiz-dgn.cc b/crawl-ref/source/wiz-dgn.cc index 94c447c6e106..2bdae7b9e11e 100644 --- a/crawl-ref/source/wiz-dgn.cc +++ b/crawl-ref/source/wiz-dgn.cc @@ -588,6 +588,8 @@ static void debug_load_map_by_name(string name, bool primary) { unwind_var um(you.uniq_map_names, string_set()); unwind_var umt(you.uniq_map_tags, string_set()); + unwind_var um_a(you.uniq_map_names_abyss, string_set()); + unwind_var umt_a(you.uniq_map_tags_abyss, string_set()); unwind_var lum(env.level_uniq_maps, string_set()); unwind_var lumt(env.level_uniq_map_tags, string_set()); if (dgn_place_map(toplace, false, false, where)) @@ -790,6 +792,8 @@ void wizard_clear_used_vaults() { you.uniq_map_tags.clear(); you.uniq_map_names.clear(); + you.uniq_map_tags_abyss.clear(); + you.uniq_map_names_abyss.clear(); env.level_uniq_maps.clear(); env.level_uniq_map_tags.clear(); mpr("All vaults are now eligible for [re]use."); diff --git a/crawl-ref/source/wiz-dump.cc b/crawl-ref/source/wiz-dump.cc index a28cd2127e5c..ff01c367efe2 100644 --- a/crawl-ref/source/wiz-dump.cc +++ b/crawl-ref/source/wiz-dump.cc @@ -369,7 +369,7 @@ bool chardump_parser::_check_stats1(const vector &tokens) { size_t size = tokens.size(); // Health: 248/248 AC: 44 Str: 35 XL: 26 Next: 58% - if (size <= 7 || (tokens[0] != "HP" && tokens[0] != "Health:")) + if (size <= 7 || (tokens[0] != "HP" && tokens[0] != "HP:"&& tokens[0] != "Health:")) return false; bool found = false; @@ -396,7 +396,7 @@ bool chardump_parser::_check_stats2(const vector &tokens) { size_t size = tokens.size(); // Magic: 36/36 EV: 31 Int: 17 God: Cheibriados [****..] - if (size <= 8 || (tokens[0] != "MP" && tokens[0] != "Magic:")) + if (size <= 7 || (tokens[0] != "MP" && tokens[0] != "MP:" && tokens[0] != "Magic:")) return false; bool found = false; @@ -423,6 +423,8 @@ bool chardump_parser::_check_stats2(const vector &tokens) join_religion(god); } + if (god == GOD_GOZAG) + continue; string piety = tokens[k+2]; int piety_levels = std::count(piety.begin(), piety.end(), '*'); wizard_set_piety_to(piety_levels > 0 @@ -607,7 +609,7 @@ bool chardump_parser::_parse_from_file(const string &full_filename) if (seen_skills) { init_skill_order(); - init_can_train(); + init_can_currently_train(); init_train(); init_training(); } diff --git a/crawl-ref/source/wiz-fsim.cc b/crawl-ref/source/wiz-fsim.cc index ea9098418012..44f5c6f41e54 100644 --- a/crawl-ref/source/wiz-fsim.cc +++ b/crawl-ref/source/wiz-fsim.cc @@ -86,7 +86,7 @@ static skill_type _equipped_skill() const int weapon = you.equip[EQ_WEAPON]; const item_def * iweap = weapon != -1 ? &you.inv[weapon] : nullptr; - if (iweap && iweap->base_type == OBJ_WEAPONS) + if (iweap && is_weapon(*iweap)) return item_attack_skill(*iweap); if (!iweap && you.m_quiver.get_fire_item() != -1) diff --git a/crawl-ref/source/wiz-item.cc b/crawl-ref/source/wiz-item.cc index 17d890c467d5..95c723612acb 100644 --- a/crawl-ref/source/wiz-item.cc +++ b/crawl-ref/source/wiz-item.cc @@ -392,7 +392,7 @@ void wizard_tweak_object() char specs[50]; int keyin; - int item = prompt_invent_item("Tweak which item? ", MT_INVLIST, -1); + int item = prompt_invent_item("Tweak which item? ", menu_type::invlist, OSEL_ANY); if (prompt_failed(item)) return; @@ -521,7 +521,8 @@ static bool _make_book_randart(item_def &book) /// Prompt for an item in inventory & print its base shop value. void wizard_value_item() { - const int i = prompt_invent_item("Value of which item?", MT_INVLIST, -1); + const int i = prompt_invent_item("Value of which item?", + menu_type::invlist, OSEL_ANY); if (prompt_failed(i)) return; @@ -535,7 +536,14 @@ void wizard_value_item() mprf("Value: %d", real_value); } -void wizard_create_all_artefacts() +/** + * Generate every unrand (including removed ones). + * + * @param override_unique if true, will generate unrands that have alread + * placed in the game. If false, will generate fallback randarts for any + * unrands that have already placed. + */ +void wizard_create_all_artefacts(bool override_unique) { you.octopus_king_rings = 0x00; int octorings = 8; @@ -550,18 +558,50 @@ void wizard_create_all_artefacts() if (entry->base_type == OBJ_UNASSIGNED) continue; - int islot = get_mitm_slot(); - if (islot == NON_ITEM) - break; + int islot; - item_def& item = mitm[islot]; - make_item_unrandart(item, index); - item.quantity = 1; + if (override_unique) + { + // force create: use make_item_unrandart to override a bunch of the + // usual checks on getting randarts. + islot = get_mitm_slot(); + if (islot == NON_ITEM) + break; + + item_def &tmp_item = mitm[islot]; + make_item_unrandart(tmp_item, index); + tmp_item.quantity = 1; + } + else + { + // mimic the way unrands are created normally, and respect + // uniqueness. If an unrand has already generated, this will place + // a relevant fallback randart instead. Useful for testing fallback + // properties, since various error conditions will print. + islot = items(true, entry->base_type, 0, 0, -index, -1); + if (islot == NON_ITEM) + { + mprf(MSGCH_ERROR, "Failed to generate item for '%s'", + entry->name); + continue; + } + } + item_def &item = mitm[islot]; set_ident_flags(item, ISFLAG_IDENT_MASK); - msg::streams(MSGCH_DIAGNOSTICS) << "Made " << item.name(DESC_A) - << " (" << debug_art_val_str(item) - << ")" << endl; + if (!is_artefact(item)) + { + // for now, staves are ok... + mprf(item.base_type == OBJ_STAVES ? MSGCH_DIAGNOSTICS : MSGCH_ERROR, + "Made non-artefact '%s' when trying to make '%s'", + item.name(DESC_A).c_str(), entry->name); + } + else + { + msg::streams(MSGCH_DIAGNOSTICS) << "Made " << item.name(DESC_A) + << " (" << debug_art_val_str(item) + << ")" << endl; + } move_item_to_grid(&islot, you.pos()); // Make all eight. @@ -591,7 +631,7 @@ void wizard_create_all_artefacts() void wizard_make_object_randart() { int i = prompt_invent_item("Make an artefact out of which item?", - MT_INVLIST, -1); + menu_type::invlist, OSEL_ANY); if (prompt_failed(i)) return; @@ -681,7 +721,8 @@ static bool _item_type_can_be_cursed(int type) void wizard_uncurse_item() { - const int i = prompt_invent_item("(Un)curse which item?", MT_INVLIST, -1); + const int i = prompt_invent_item("(Un)curse which item?", + menu_type::invlist, OSEL_ANY); if (!prompt_failed(i)) { @@ -1098,8 +1139,8 @@ static void _debug_acquirement_stats(FILE *ostat) "returning", #endif "chaos", - "evasion", #if TAG_MAJOR_VERSION == 34 + "evasion", "confusion", #endif "penetration", @@ -1300,7 +1341,7 @@ static void _debug_rap_stats(FILE *ostat) { const int inv_index = prompt_invent_item("Generate randart stats on which item?", - MT_INVLIST, -1); + menu_type::invlist, OSEL_ANY); if (prompt_failed(inv_index)) return; @@ -1461,6 +1502,9 @@ static void _debug_rap_stats(FILE *ostat) "ARTP_SEE_INVISIBLE", "ARTP_INVISIBLE", "ARTP_FLY", +#if TAG_MAJOR_VERSION > 34 + "ARTP_FOG", +#endif "ARTP_BLINK", "ARTP_BERSERK", "ARTP_NOISE", @@ -1600,6 +1644,9 @@ void wizard_identify_all_items() for (auto &item : mitm) if (item.defined()) set_ident_flags(item, ISFLAG_IDENT_MASK); + for (auto& entry : env.shop) + for (auto &item : entry.second.stock) + set_ident_flags(item, ISFLAG_IDENT_MASK); for (int ii = 0; ii < NUM_OBJECT_CLASSES; ii++) { object_class_type i = (object_class_type)ii; @@ -1616,6 +1663,9 @@ void wizard_unidentify_all_items() for (auto &item : mitm) if (item.defined()) _forget_item(item); + for (auto& entry : env.shop) + for (auto &item : entry.second.stock) + _forget_item(item); for (int ii = 0; ii < NUM_OBJECT_CLASSES; ii++) { object_class_type i = (object_class_type)ii; diff --git a/crawl-ref/source/wiz-item.h b/crawl-ref/source/wiz-item.h index 67987cca9fd5..13d6fa172237 100644 --- a/crawl-ref/source/wiz-item.h +++ b/crawl-ref/source/wiz-item.h @@ -11,7 +11,7 @@ void wizard_tweak_object(); void wizard_make_object_randart(); void wizard_value_item(); void wizard_uncurse_item(); -void wizard_create_all_artefacts(); +void wizard_create_all_artefacts(bool override_unique = true); void wizard_identify_pack(); void wizard_unidentify_pack(); void wizard_draw_card(); diff --git a/crawl-ref/source/wiz-mon.cc b/crawl-ref/source/wiz-mon.cc index 387e9309e7bd..ea409cecd843 100644 --- a/crawl-ref/source/wiz-mon.cc +++ b/crawl-ref/source/wiz-mon.cc @@ -709,7 +709,7 @@ void wizard_give_monster_item(monster* mon) } int player_slot = prompt_invent_item("Give which item to monster?", - MT_DROP, -1); + menu_type::drop, OSEL_ANY); if (prompt_failed(player_slot)) return; diff --git a/crawl-ref/source/wizard.cc b/crawl-ref/source/wizard.cc index 8c73062f23b2..033ede8b2bbb 100644 --- a/crawl-ref/source/wizard.cc +++ b/crawl-ref/source/wizard.cc @@ -115,7 +115,7 @@ static void _do_wizard_command(int wiz_command) case 'l': wizard_set_xl(); break; case 'L': debug_place_map(false); break; - // case CONTROL('L'): break; + case CONTROL('L'): debug_show_builder_logs(); break; case 'M': case 'm': wizard_create_spec_monster_name(); break; @@ -210,7 +210,8 @@ static void _do_wizard_command(int wiz_command) break; case '\\': debug_make_shop(); break; - case '|': wizard_create_all_artefacts(); break; + case '|': wizard_create_all_artefacts(true); break; + case CONTROL('\\'): wizard_create_all_artefacts(false); break; case ';': wizard_list_levels(); break; case ':': wizard_list_branches(); break; @@ -316,7 +317,7 @@ void handle_wizard_command() cursor_control con(true); wiz_command = getchm(); if (wiz_command == '*') - wiz_command = CONTROL(toupper(getchm())); + wiz_command = CONTROL(toupper_safe(getchm())); } if (crawl_state.cmd_repeat_start) @@ -442,6 +443,12 @@ int list_wizard_commands(bool do_redraw_screen) "{ magic mapping\n" "Ctrl-W change Shoals' tide speed\n" "Ctrl-E dump level builder information\n" +#ifdef DEBUG + // might be present in any save, but only generated + // in debug builds; hide this for regular wizmode so as + // to not confuse non-devs. The command will still work. + "Ctrl-L show builder logs for level\n" +#endif "Ctrl-R regenerate current level\n" "P create a level based on a vault\n", true); @@ -480,6 +487,7 @@ int list_wizard_commands(bool do_redraw_screen) "Ctrl-V show gold value of an item\n" "- get a god gift\n" "| create all unrand artefacts\n" + "Ctrl-\\ create all unrands / fallbacks\n" "+ make randart from item\n" "' list items\n" "J Jiyva off-level sacrifice\n" diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc index e966d40f6851..9cc869a0e973 100644 --- a/crawl-ref/source/xom.cc +++ b/crawl-ref/source/xom.cc @@ -1271,7 +1271,7 @@ static int _xom_random_stickable(const int HD) { WPN_CLUB, WPN_SPEAR, WPN_TRIDENT, WPN_HALBERD, WPN_SCYTHE, WPN_GLAIVE, WPN_QUARTERSTAFF, - WPN_BLOWGUN, WPN_SHORTBOW, WPN_LONGBOW, WPN_GIANT_CLUB, + WPN_SHORTBOW, WPN_LONGBOW, WPN_GIANT_CLUB, WPN_GIANT_SPIKED_CLUB };