summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/spl-book.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/spl-book.cc')
-rw-r--r--crawl-ref/source/spl-book.cc286
1 files changed, 284 insertions, 2 deletions
diff --git a/crawl-ref/source/spl-book.cc b/crawl-ref/source/spl-book.cc
index 957a5027e2..f4f4eac6cd 100644
--- a/crawl-ref/source/spl-book.cc
+++ b/crawl-ref/source/spl-book.cc
@@ -12,6 +12,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <algorithm>
#ifdef DOS
#include <conio.h>
@@ -38,6 +39,14 @@
#include "state.h"
#include "stuff.h"
+#define SPELL_LIST_KEY "spell_list"
+
+#define RANDART_BOOK_TYPE_KEY "randart_book_type"
+#define RANDART_BOOK_LEVEL_KEY "randart_book_level"
+
+#define RANDART_BOOK_TYPE_LEVEL "level"
+#define RANDART_BOOK_TYPE_THEME "theme"
+
#define SPELLBOOK_SIZE 8
#define NUMBER_SPELLBOOKS 61
@@ -1032,9 +1041,38 @@ bool player_can_read_spellbook( const item_def &book )
return (true);
}
+void mark_had_book(const item_def &book)
+{
+ ASSERT(book.base_type == OBJ_BOOKS);
+
+ if (book.book_number() == BOOK_MANUAL
+ || book.book_number() == BOOK_DESTRUCTION)
+ {
+ return;
+ }
+
+ for (int i = 0; i < SPELLBOOK_SIZE; i++)
+ {
+ spell_type stype = which_spell_in_book(book, i);
+ if (stype == SPELL_NO_SPELL)
+ continue;
+
+ you.seen_spell[stype] = true;
+ }
+
+ if (!book.props.exists( SPELL_LIST_KEY ))
+ mark_had_book(book.book_number());
+}
void mark_had_book(int booktype)
{
+ ASSERT(booktype >= 0 && booktype < NUM_BOOKS);
+
+ if (booktype == BOOK_MANUAL || booktype == BOOK_DESTRUCTION)
+ {
+ return;
+ }
+
you.had_book[booktype] = true;
if ( booktype == BOOK_MINOR_MAGIC_I
@@ -1065,8 +1103,8 @@ int read_book( item_def &book, read_book_action_type action )
// Remember that this function is called from staff spells as well.
const int keyin = spellbook_contents( book, action );
- if (book.base_type == OBJ_BOOKS && !book.props.exists( SPELL_LIST_KEY ))
- mark_had_book(book.sub_type);
+ if (book.base_type == OBJ_BOOKS)
+ mark_had_book(book);
redraw_screen();
@@ -1557,3 +1595,247 @@ int staff_spell( int staff )
return (roll_dice( 1, 1 + spell_difficulty(spell) / 2 ));
}
+
+static bool _compare_spells(spell_type a, spell_type b)
+{
+ if (a == SPELL_NO_SPELL && b == SPELL_NO_SPELL)
+ return (false);
+ else if (a != SPELL_NO_SPELL && b == SPELL_NO_SPELL)
+ return (true);
+ else if (a == SPELL_NO_SPELL && b != SPELL_NO_SPELL)
+ return (false);
+
+ int level_a = spell_difficulty(a);
+ int level_b = spell_difficulty(b);
+
+ if (level_a != level_b)
+ return (level_a < level_b);
+
+ unsigned int schools_a = get_spell_disciplines(a);
+ unsigned int schools_b = get_spell_disciplines(b);
+
+ if (schools_a != schools_b && schools_a != 0 && schools_b != 0)
+ {
+ const char* a_type = NULL;
+ const char* b_type = NULL;
+
+ // Find lowest/earliest school for each spell.
+ for (int i = 0; i <= SPTYP_LAST_EXPONENT; i++)
+ {
+ int mask = 1 << i;
+ if (a_type == NULL && (schools_a & mask))
+ a_type = spelltype_name(mask);
+ if (b_type == NULL && (schools_b & mask))
+ b_type = spelltype_name(mask);
+ }
+ ASSERT(a_type != NULL && b_type != NULL);
+ return (strcmp(a_type, b_type));
+ }
+
+ return (strcmp(spell_title(a), spell_title(b)));
+}
+
+static bool _is_memorized(spell_type spell)
+{
+ for (int i = 0; i < 25; i++)
+ {
+ if (you.spells[i] == spell)
+ return (true);
+ }
+
+ return (false);
+}
+
+bool make_book_level_randart(item_def &book, int level,
+ int num_spells)
+{
+ ASSERT(book.base_type == OBJ_BOOKS);
+ ASSERT(book.book_number() != BOOK_MANUAL
+ && book.book_number() != BOOK_DESTRUCTION);
+ ASSERT(is_random_artefact(book));
+ ASSERT(!book.props.exists(SPELL_LIST_KEY));
+
+ god_type god;
+ (void) origin_is_god_gift(book, &god);
+
+ const bool avoid_uncastable = (god != GOD_NO_GOD && god != GOD_XOM)
+ || origin_is_acquirement(book);
+ const bool track_levels_had = origin_is_acquirement(book)
+ || god == GOD_SIF_MUNA;
+
+ if (level == -1)
+ {
+ int max_level =
+ avoid_uncastable ? std::min(9, you.get_experience_level())
+ : 9;
+
+ level = random_range(1, max_level);
+
+ // Give a book of a level not seen before, preferably one with
+ // spells of a low enough level for the player to cast, or the
+ // lowest aviable level if all levels which the player can cast
+ // have already been given.
+ if (track_levels_had)
+ {
+ unsigned int seen_levels = you.attribute[ATTR_RND_LVL_BOOKS];
+ std::vector<int> vec;
+ for (int i = 1; i <= 9 && (vec.empty() || i <= max_level); i++)
+ {
+ if (!(seen_levels & (1 << i)))
+ vec.push_back(i);
+ }
+ if (vec.size() > 0)
+ level = vec[random2(vec.size())];
+ }
+ }
+ ASSERT(level > 0 && level <= 9);
+
+ if (num_spells == -1)
+ num_spells = SPELLBOOK_SIZE;
+ ASSERT(num_spells > 0 && num_spells <= SPELLBOOK_SIZE);
+
+
+ int god_discard = 0;
+ int uncastable_discard = 0;
+
+ std::vector<spell_type> spell_list;
+ for (int i = 0; i < NUM_SPELLS; i++)
+ {
+ const spell_type spell = (spell_type) i;
+
+ if (!is_valid_spell(spell))
+ continue;
+
+ if (spell_difficulty(spell) != level)
+ continue;
+
+ const unsigned int flags = get_spell_flags(spell);
+ const unsigned int schools = get_spell_disciplines(spell);
+
+ // Don't include schoolless spells, like Smiting.
+ if (schools == 0)
+ continue;
+
+ // Holy spells don't show up in books.
+ if (schools & SPTYP_HOLY)
+ continue;
+
+ if (flags & (SPFLAG_MONSTER | SPFLAG_CARD | SPFLAG_TESTING))
+ continue;
+
+ // Only wizards gets spells still under development.
+ if (flags & SPFLAG_DEVEL)
+ {
+#ifdef WIZARD
+ if (!you.wizard)
+ continue;
+#else
+ continue;
+#endif
+ }
+
+ if (avoid_uncastable && undead_cannot_memorise(spell, you.is_undead))
+ {
+ uncastable_discard++;
+ continue;
+ }
+
+ if (god_dislikes_spell_type(spell, god))
+ {
+ god_discard++;
+ continue;
+ }
+
+ // Passed all tests.
+ spell_list.push_back(spell);
+ }
+
+ if (spell_list.empty())
+ {
+ char buf[80];
+
+ if (god_discard > 0 && uncastable_discard == 0)
+ sprintf(buf, "%s disliked all level %d spells",
+ god_name(god).c_str(), level);
+ else if (god_discard == 0 && uncastable_discard > 0)
+ sprintf(buf, "No level %d spells can be cast by you", level);
+ else if (god_discard > 0 && uncastable_discard > 0)
+ sprintf(buf, "All level %d spells are either disliked by %s "
+ "or cannot be cast by you.",
+ level, god_name(god).c_str());
+ else
+ sprintf(buf, "No level %d spells?!?!?!", level);
+
+ mprf(MSGCH_ERROR, "Could not create fixed level randart spellbook: %s",
+ buf);
+
+ return (false);
+ }
+ random_shuffle(spell_list.begin(), spell_list.end());
+
+ if (num_spells > (int) spell_list.size())
+ {
+ num_spells = spell_list.size();
+#if DEBUG || DEBUG_DIAGNOSTICS
+ // Not many level 8 or 9 spells
+ if (level < 8)
+ {
+ mprf(MSGCH_WARN, "More spells requested for fixed level (%d) "
+ "randart spellbook than there are valid spells.",
+ level);
+ mprf(MSGCH_WARN, "Discarded %d spells due to being uncastable and "
+ "%d spells due to being disliked by %s.",
+ uncastable_discard, god_discard, god_name(god).c_str());
+ }
+#endif
+ }
+
+ std::vector<bool> spell_used(spell_list.size(), false);
+ std::vector<bool> avoid_memorised(spell_list.size(), true);
+
+ spell_type chosen_spells[SPELLBOOK_SIZE];
+ for (int i = 0; i < SPELLBOOK_SIZE; i++)
+ {
+ chosen_spells[i] = SPELL_NO_SPELL;
+ }
+
+ int book_pos = 0;
+ while (book_pos < num_spells)
+ {
+ int spell_pos = random2(spell_list.size());
+
+ if (spell_used[spell_pos])
+ continue;
+
+ spell_type spell = spell_list[spell_pos];
+ ASSERT(spell != SPELL_NO_SPELL);
+
+ if (avoid_memorised[spell_pos] && _is_memorized(spell))
+ {
+ // Only once.
+ avoid_memorised[spell_pos] = false;
+ continue;
+ }
+
+ spell_used[spell_pos] = true;
+ chosen_spells[book_pos++] = spell;
+ }
+ std::sort(chosen_spells, chosen_spells + SPELLBOOK_SIZE,
+ _compare_spells);
+ ASSERT(chosen_spells[0] != SPELL_NO_SPELL);
+
+ CrawlHashTable &props = book.props;
+ if (!props.exists( SPELL_LIST_KEY ))
+ props[SPELL_LIST_KEY].new_vector(SV_LONG).resize(SPELLBOOK_SIZE);
+
+ CrawlVector &spell_vec = props[SPELL_LIST_KEY];
+ spell_vec.set_max_size(SPELLBOOK_SIZE);
+
+ for (int i = 0; i < SPELLBOOK_SIZE; i++)
+ spell_vec[i] = (long) chosen_spells[i];
+
+ if (track_levels_had)
+ you.attribute[ATTR_RND_LVL_BOOKS] |= (1 << level);
+
+ return (true);
+}