summaryrefslogtreecommitdiff
path: root/src/skills.cc
diff options
context:
space:
mode:
authorManoj Srivastava <srivasta@debian.org>2020-05-22 19:57:41 -0700
committerManoj Srivastava <srivasta@debian.org>2020-05-22 20:02:19 -0700
commitc3d2579ad8d7eb33059aa8fdbaf5b564411a57f2 (patch)
tree1570cda0676fdcf4171a69a7fe313c1b89a52b0c /src/skills.cc
parent986b7742bf244b4073ecca0723615f70be8a1ab6 (diff)
parent4e9b9c402ed95bf9a17fd6d795bc49bb4128a6fa (diff)
Merge branch 'upstream' into debian-cmake-fixes
Diffstat (limited to 'src/skills.cc')
-rw-r--r--src/skills.cc1918
1 files changed, 1918 insertions, 0 deletions
diff --git a/src/skills.cc b/src/skills.cc
new file mode 100644
index 00000000..af5e46c7
--- /dev/null
+++ b/src/skills.cc
@@ -0,0 +1,1918 @@
+/*
+ * Copyright (c) 2001 DarkGod
+ *
+ * This software may be copied and distributed for educational, research, and
+ * not for profit purposes provided that this copyright and statement are
+ * included in all such copies.
+ */
+
+#include "skills.hpp"
+
+#include "ability_type.hpp"
+#include "birth.hpp"
+#include "cmd2.hpp"
+#include "cmd3.hpp"
+#include "cmd5.hpp"
+#include "cmd7.hpp"
+#include "game.hpp"
+#include "gods.hpp"
+#include "help.hpp"
+#include "hooks.hpp"
+#include "lua_bind.hpp"
+#include "monster2.hpp"
+#include "monster3.hpp"
+#include "object1.hpp"
+#include "object2.hpp"
+#include "player_class.hpp"
+#include "player_race.hpp"
+#include "player_race_mod.hpp"
+#include "player_spec.hpp"
+#include "player_type.hpp"
+#include "skill_flag.hpp"
+#include "skill_type.hpp"
+#include "spells1.hpp"
+#include "spells4.hpp"
+#include "tables.hpp"
+#include "util.hpp"
+#include "util.h"
+#include "variable.h"
+#include "variable.hpp"
+#include "xtra2.hpp"
+#include "z-rand.hpp"
+
+#include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
+#include <cassert>
+#include <cmath>
+#include <fmt/format.h>
+#include <memory>
+#include <vector>
+#include <tuple>
+
+using boost::algorithm::iequals;
+
+/*
+ * Advance the skill point of the skill specified by i and
+ * modify related skills
+ */
+static void increase_skill(int i, s16b *invest)
+{
+ auto &s_info = game->s_info;
+
+ s32b max_skill_overage;
+
+ /* No skill points to be allocated */
+ if (!p_ptr->skill_points) return;
+
+ /* The skill cannot be increased */
+ if (!s_info[i].mod) return;
+
+ /* The skill is already maxed */
+ if (s_info[i].value >= SKILL_MAX) return;
+
+ /* Cannot allocate more than player level + max_skill_overage levels */
+ max_skill_overage = modules[game_module_idx].skills.max_skill_overage;
+ if (((s_info[i].value + s_info[i].mod) / SKILL_STEP) >= (p_ptr->lev + max_skill_overage + 1))
+ {
+ msg_box_auto(
+ fmt::format(
+ "Cannot raise a skill value above {} + player level.",
+ max_skill_overage
+ ));
+ return;
+ }
+
+ /* Spend an unallocated skill point */
+ p_ptr->skill_points--;
+
+ /* Increase the skill */
+ s_info[i].value += s_info[i].mod;
+ invest[i]++;
+}
+
+
+/*
+ * Descrease the skill point of the skill specified by i and
+ * modify related skills
+ */
+static void decrease_skill(int i, s16b *invest)
+{
+ auto &s_info = game->s_info;
+
+ /* Cannot decrease more */
+ if (!invest[i]) return;
+
+ /* The skill cannot be decreased */
+ if (!s_info[i].mod) return;
+
+ /* The skill already has minimal value */
+ if (!s_info[i].value) return;
+
+ /* Free a skill point */
+ p_ptr->skill_points++;
+
+ /* Decrease the skill */
+ s_info[i].value -= s_info[i].mod;
+ invest[i]--;
+}
+
+
+/*
+ * Given the name of a skill, returns skill index or -1 if no
+ * such skill is found
+ */
+s16b find_skill(cptr needle)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+
+ /* Scan skill list */
+ for (std::size_t i = 1; i < s_descriptors.size(); i++)
+ {
+ auto const &name = s_descriptors[i].name;
+ if (!name.empty() && (name == needle))
+ {
+ return i;
+ }
+ }
+
+ /* No match found */
+ return -1;
+}
+
+s16b find_skill_i(cptr needle)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+
+ /* Scan skill list */
+ for (std::size_t i = 1; i < s_descriptors.size(); i++)
+ {
+ auto const &name = s_descriptors[i].name;
+ if (!name.empty() && iequals(name, needle))
+ {
+ return (i);
+ }
+ }
+
+ /* No match found */
+ return ( -1);
+}
+
+
+/*
+ *
+ */
+s16b get_skill(int skill)
+{
+ auto const &s_info = game->s_info;
+
+ return (s_info[skill].value / SKILL_STEP);
+}
+
+
+/*
+ * Return "scale" (a misnomer -- this is max value) * (current skill value)
+ * / (max skill value)
+ */
+s16b get_skill_scale(int skill, u32b scale)
+{
+ auto const &s_info = game->s_info;
+
+ /*
+ * SKILL_STEP shouldn't matter here because the second parameter is
+ * relatively small (the largest one being somewhere around 200),
+ * AND because we could have used much simpler 0--50 if the ability
+ * progression were only possible at step boundaries.
+ *
+ * Because I'm not at all certain about my interpretation of the mysterious
+ * formula given above, I verified this works the same by using a tiny
+ * scheme program... -- pelpel
+ */
+ s32b temp = scale * s_info[skill].value;
+
+ return (temp / SKILL_MAX);
+}
+
+static std::size_t get_idx(int i)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+
+ for (std::size_t j = 1; j < s_descriptors.size(); j++)
+ {
+ if (s_descriptors[j].order == i)
+ {
+ return j;
+ }
+ }
+
+ return 0;
+}
+
+static bool_ is_known(int s_idx)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto const &s_info = game->s_info;
+
+ if (wizard) return TRUE;
+ if (s_info[s_idx].value || s_info[s_idx].mod) return TRUE;
+
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ /* It is our child, if we don't know it we continue to search, if we know it it is enough*/
+ if (s_descriptors[i].father == s_idx)
+ {
+ if (is_known(i))
+ return TRUE;
+ }
+ }
+
+ /* Ok know none */
+ return FALSE;
+}
+
+namespace { // anonymous
+
+struct skill_entry {
+ std::size_t skill_idx;
+ int indent_level;
+};
+
+}
+
+static void init_table_aux(std::vector<skill_entry> *table, int father, int lev, bool_ full)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto const &s_info = game->s_info;
+
+ for (std::size_t j = 1; j < s_descriptors.size(); j++)
+ {
+ std::size_t i = get_idx(j);
+
+ if (s_descriptors[i].father != father) continue;
+ if (s_info[i].hidden) continue;
+ if (!is_known(i)) continue;
+
+ skill_entry entry;
+ entry.skill_idx = i;
+ entry.indent_level = lev;
+ table->emplace_back(entry);
+
+ if (s_info[i].dev || full)
+ {
+ init_table_aux(table, i, lev + 1, full);
+ }
+ }
+}
+
+static void init_table(std::vector<skill_entry> *table, bool_ full)
+{
+ table->clear();
+ init_table_aux(table, -1, 0, full);
+}
+
+static bool has_child(int sel)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+
+ for (std::size_t i = 1; i < s_descriptors.size(); i++)
+ {
+ if ((s_descriptors[i].father == sel) && is_known(i))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Dump the skill tree
+ */
+void dump_skills(FILE *fff)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto const &s_info = game->s_info;
+
+ char buf[80];
+
+ std::vector<skill_entry> table;
+ table.reserve(s_descriptors.size());
+ init_table(&table, TRUE);
+
+ fprintf(fff, "\nSkills (points left: %d)", p_ptr->skill_points);
+
+ for (auto const &entry: table)
+ {
+ std::size_t i = entry.skill_idx;
+ auto const &skill = s_info[i];
+ auto const &skill_name = s_descriptors[i].name;
+
+ if ((skill.value == 0) && (i != SKILL_MISC))
+ {
+ if (skill.mod == 0)
+ {
+ continue;
+ }
+ }
+
+ sprintf(buf, "\n");
+
+ for (int z = 0; z < entry.indent_level; z++)
+ {
+ strcat(buf, " ");
+ }
+
+ if (!has_child(i))
+ {
+ strcat(buf, format(" . %s", skill_name.c_str()));
+ }
+ else
+ {
+ strcat(buf, format(" - %s", skill_name.c_str()));
+ }
+
+ fprintf(fff, "%-49s%s%06.3f [%05.3f]",
+ buf, skill.value < 0 ? "-" : " ",
+ static_cast<double>(ABS(skill.value)) / SKILL_STEP,
+ static_cast<double>(skill.mod) / 1000);
+ }
+
+ fprintf(fff, "\n");
+}
+
+
+/*
+ * Draw the skill tree
+ */
+static void print_skills(std::vector<skill_entry> const &table, int sel, int start)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto const &s_info = game->s_info;
+
+ int j;
+ int wid, hgt;
+ cptr keys;
+
+ Term_clear();
+ Term_get_size(&wid, &hgt);
+
+ c_prt(TERM_WHITE, format("%s Skills Screen", game_module), 0, 28);
+ keys = format("#BEnter#W to develop a branch, #Bup#W/#Bdown#W to move, #Bright#W/#Bleft#W to modify, #B?#W for help");
+ display_message(0, 1, strlen(keys), TERM_WHITE, keys);
+ c_prt((p_ptr->skill_points) ? TERM_L_BLUE : TERM_L_RED,
+ format("Skill points left: %d", p_ptr->skill_points), 2, 0);
+ print_desc_aux(s_descriptors[table[sel].skill_idx].desc.c_str(), 3, 0);
+
+ for (j = start; j < start + (hgt - 7); j++)
+ {
+ byte color = TERM_WHITE;
+ char deb = ' ', end = ' ';
+
+ if (j >= static_cast<int>(table.size()))
+ {
+ break;
+ }
+
+ std::size_t i = table[j].skill_idx;
+ auto const &name = s_descriptors[i].name;
+ auto const &skill = s_info[i];
+
+ if ((skill.value == 0) && (i != SKILL_MISC))
+ {
+ if (skill.mod == 0)
+ {
+ color = TERM_L_DARK;
+ }
+ else
+ {
+ color = TERM_ORANGE;
+ }
+ }
+ else if (skill.value == SKILL_MAX)
+ {
+ color = TERM_L_BLUE;
+ }
+
+ if (skill.hidden)
+ {
+ color = TERM_L_RED;
+ }
+
+ if (j == sel)
+ {
+ color = TERM_L_GREEN;
+ deb = '[';
+ end = ']';
+ }
+
+ if (!has_child(i))
+ {
+ c_prt(color, format("%c.%c%s", deb, end, name.c_str()),
+ j + 7 - start, table[j].indent_level * 4);
+ }
+ else if (skill.dev)
+ {
+ c_prt(color, format("%c-%c%s", deb, end, name.c_str()),
+ j + 7 - start, table[j].indent_level * 4);
+ }
+ else
+ {
+ c_prt(color, format("%c+%c%s", deb, end, name.c_str()),
+ j + 7 - start, table[j].indent_level * 4);
+ }
+
+ c_prt(color,
+ format("%s%02ld.%03ld [%01d.%03d]",
+ skill.value < 0 ? "-" : " ",
+ ABS(skill.value) / SKILL_STEP,
+ ABS(skill.value) % SKILL_STEP,
+ ABS(skill.mod) / 1000,
+ ABS(skill.mod) % 1000),
+ j + 7 - start, 60);
+ }
+}
+
+/*
+ * Checks various stuff to do when skills change, like new spells, ...
+ */
+void recalc_skills(bool_ init)
+{
+ auto const &s_info = game->s_info;
+
+ static int thaum_level = 0;
+
+ /* TODO: This should be a hook in ToME's lua */
+ if (init)
+ {
+ thaum_level = get_skill_scale(SKILL_THAUMATURGY, 100);
+ }
+ else
+ {
+ auto const &random_spells = p_ptr->random_spells;
+
+ int thaum_gain = 0;
+
+ /* Gain thaum spells while there's more to be gained */
+ while ((thaum_level < get_skill_scale(SKILL_THAUMATURGY, 100)) &&
+ (random_spells.size() < MAX_SPELLS))
+ {
+ thaum_level++;
+ generate_spell((thaum_level + 1) / 2);
+ thaum_gain++;
+ }
+
+ if (thaum_gain)
+ {
+ if (thaum_gain == 1)
+ msg_print("You have gained one new thaumaturgy spell.");
+ else
+ msg_format("You have gained %d new thaumaturgy spells.", thaum_gain);
+ }
+
+ /* Antimagic means you don't believe in gods. */
+ if ((p_ptr->pgod != GOD_NONE) &&
+ (s_info[SKILL_ANTIMAGIC].value > 0))
+ {
+ msg_print("You no longer believe.");
+ abandon_god(GOD_ALL);
+ }
+
+ process_hooks_new(HOOK_RECALC_SKILLS, NULL, NULL);
+
+ /* Update stuffs */
+ p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_POWERS |
+ PU_SANITY | PU_BODY);
+
+ /* Redraw various info */
+ p_ptr->redraw |= (PR_WIPE | PR_FRAME | PR_MAP);
+ }
+}
+
+/*
+ * Recalc the skill value
+ */
+static void recalc_skills_theory(
+ std::vector<s16b> &invest,
+ std::vector<s32b> const &base_val,
+ std::vector<s32b> const &base_mod,
+ std::vector<s32b> const &bonus)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto &s_info = game->s_info;
+
+ /* First we assign the normal points */
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ /* Calc the base */
+ s_info[i].value = base_val[i] + (base_mod[i] * invest[i]) + bonus[i];
+
+ /* It cannot exceed SKILL_MAX */
+ if (s_info[i].value > SKILL_MAX)
+ {
+ s_info[i].value = SKILL_MAX;
+ }
+ }
+
+ /* Then we modify related skills */
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ auto const &s_descriptor = s_descriptors[i];
+
+ // Process all exlusions
+ if (invest[i])
+ {
+ for (auto exclude_si: s_descriptor.excludes)
+ {
+ // Give back skill points invested during this "session"
+ p_ptr->skill_points += invest[exclude_si];
+ invest[exclude_si] = 0;
+
+ // Turn it off
+ s_info[exclude_si].value = 0;
+ }
+ }
+
+ // Add any bonuses
+ for (auto const &increase: s_descriptor.increases)
+ {
+ auto increase_si = std::get<0>(increase);
+ auto increase_pct = std::get<1>(increase);
+
+ /* Increase/decrease by percentage */
+ s32b val = s_info[increase_si].value + (invest[i] * s_info[increase_si].mod * increase_pct / 100);
+
+ /* It cannot exceed SKILL_MAX */
+ if (val > SKILL_MAX)
+ {
+ val = SKILL_MAX;
+ }
+
+ /* Save the modified value */
+ s_info[increase_si].value = val;
+ }
+ }
+}
+
+/*
+ * Interreact with skills
+ */
+void do_cmd_skill()
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto &s_info = game->s_info;
+
+ int sel = 0, start = 0;
+ char c;
+ int wid, hgt;
+ s16b skill_points_save;
+
+ recalc_skills(TRUE);
+
+ /* Save the screen */
+ screen_save();
+
+ /* Allocate arrays to save skill values and track assigned points */
+ std::vector<s32b> skill_values_save; skill_values_save.resize(s_descriptors.size(), 0);
+ std::vector<s32b> skill_mods_save; skill_mods_save.resize(s_descriptors.size(), 0);
+ std::vector<s16b> skill_invest; skill_invest.resize(s_descriptors.size(), 0);
+ std::vector<s32b> skill_bonus; skill_bonus.resize(s_descriptors.size(), 0);
+
+ /* Save skill points */
+ skill_points_save = p_ptr->skill_points;
+
+ /* Save skill values */
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ auto s_ptr = &s_info[i];
+ skill_values_save[i] = s_ptr->value;
+ skill_mods_save[i] = s_ptr->mod;
+ }
+
+ /* Clear the screen */
+ Term_clear();
+
+ /* Initialise the skill list */
+ std::vector<skill_entry> table;
+ table.reserve(s_descriptors.size());
+ init_table(&table, FALSE);
+
+ while (TRUE)
+ {
+ Term_get_size(&wid, &hgt);
+
+ /* Display list of skills */
+ recalc_skills_theory(skill_invest, skill_values_save, skill_mods_save, skill_bonus);
+ print_skills(table, sel, start);
+
+ /* Wait for user input */
+ c = inkey();
+
+ /* Leave the skill screen */
+ if (c == ESCAPE) break;
+
+ /* Expand / collapse list of skills */
+ else if (c == '\r')
+ {
+ // Toggle the selected skill
+ s_info[table[sel].skill_idx].dev = !s_info[table[sel].skill_idx].dev;
+ // Re-populate table
+ init_table(&table, FALSE);
+ }
+
+ /* Next page */
+ else if (c == 'n')
+ {
+ sel += (hgt - 7);
+
+ if (sel >= static_cast<int>(table.size()))
+ {
+ sel = table.size() - 1;
+ }
+ }
+
+ /* Previous page */
+ else if (c == 'p')
+ {
+ sel -= (hgt - 7);
+ if (sel < 0) sel = 0;
+ }
+
+ /* Select / increase a skill */
+ else
+ {
+ int dir;
+
+ /* Allow use of numpad / arrow keys / roguelike keys */
+ dir = get_keymap_dir(c);
+
+ /* Move cursor down */
+ if (dir == 2) sel++;
+
+ /* Move cursor up */
+ if (dir == 8) sel--;
+
+ /* Miscellaneous skills cannot be increased/decreased as a group */
+ if ((sel >= 0) && (sel < static_cast<int>(table.size())) && table[sel].skill_idx == SKILL_MISC) continue;
+
+ /* Increase the current skill */
+ if (dir == 6) increase_skill(table[sel].skill_idx, skill_invest.data());
+
+ /* Decrease the current skill */
+ if (dir == 4) decrease_skill(table[sel].skill_idx, skill_invest.data());
+
+ /* XXX XXX XXX Wizard mode commands outside of wizard2.c */
+
+ /* Increase the skill */
+ if (wizard && (c == '+')) skill_bonus[table[sel].skill_idx] += SKILL_STEP;
+
+ /* Decrease the skill */
+ if (wizard && (c == '-')) skill_bonus[table[sel].skill_idx] -= SKILL_STEP;
+
+ /* Contextual help */
+ if (c == '?')
+ {
+ help_skill(s_descriptors[table[sel].skill_idx].name);
+ }
+
+ /* Handle boundaries and scrolling */
+ if (sel < 0) sel = table.size() - 1;
+ if (sel >= static_cast<int>(table.size())) sel = 0;
+ if (sel < start) start = sel;
+ if (sel >= start + (hgt - 7)) start = sel - (hgt - 7) + 1;
+ }
+ }
+
+
+ /* Some skill points are spent */
+ if (p_ptr->skill_points != skill_points_save)
+ {
+ /* Flush input as we ask an important and irreversible question */
+ flush();
+
+ /* Ask we can commit the change */
+ if (msg_box_auto("Save and use these skill values? (y/n)") != 'y')
+ {
+ /* User declines -- restore the skill values before exiting */
+
+ /* Restore skill points */
+ p_ptr->skill_points = skill_points_save;
+
+ /* Restore skill values */
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ auto s_ptr = &s_info[i];
+ s_ptr->value = skill_values_save[i];
+ s_ptr->mod = skill_mods_save[i];
+ }
+ }
+ }
+
+ /* Load the screen */
+ screen_load();
+
+ recalc_skills(FALSE);
+}
+
+
+
+/*
+ * List of melee skills
+ */
+static s16b melee_skills[MAX_MELEE] =
+{
+ SKILL_MASTERY,
+ SKILL_HAND,
+ SKILL_BEAR,
+};
+static const char *melee_names[MAX_MELEE] =
+{
+ "Weapon combat",
+ "Barehanded combat",
+ "Bearform combat",
+};
+static bool_ melee_bool[MAX_MELEE];
+static int melee_num[MAX_MELEE];
+
+s16b get_melee_skill()
+{
+ int i;
+
+ for (i = 0; i < MAX_MELEE; i++)
+ {
+ if (p_ptr->melee_style == melee_skills[i])
+ return (i);
+ }
+ return (0);
+}
+
+cptr get_melee_name()
+{
+ return melee_names[get_melee_skill()];
+}
+
+s16b get_melee_skills()
+{
+ auto const &s_info = game->s_info;
+
+ int j = 0;
+
+ for (std::size_t i = 0; i < MAX_MELEE; i++)
+ {
+ if ((s_info[melee_skills[i]].value > 0) && (!s_info[melee_skills[i]].hidden))
+ {
+ melee_bool[i] = TRUE;
+ j++;
+ }
+ else
+ melee_bool[i] = FALSE;
+ }
+
+ return (j);
+}
+
+static void choose_melee()
+{
+ int i, j, z = 0;
+ int force_drop = FALSE, style_unchanged = FALSE;
+
+ character_icky = TRUE;
+ Term_save();
+ Term_clear();
+
+ j = get_melee_skills();
+ prt("Choose a melee style:", 0, 0);
+ for (i = 0; i < MAX_MELEE; i++)
+ {
+ if (melee_bool[i])
+ {
+ prt(format("%c) %s", I2A(z), melee_names[i]), z + 1, 0);
+ melee_num[z] = i;
+ z++;
+ }
+ }
+
+ while (TRUE)
+ {
+ char c = inkey();
+
+ if (c == ESCAPE) break;
+ if (A2I(c) < 0) continue;
+ if (A2I(c) >= j) continue;
+
+ for (i = 0, z = 0; z < A2I(c); i++)
+ if (melee_bool[i]) z++;
+
+ if (p_ptr->melee_style == melee_skills[melee_num[z]])
+ {
+ style_unchanged = TRUE;
+ break;
+ }
+
+ for (i = INVEN_WIELD; p_ptr->body_parts[i - INVEN_WIELD] == INVEN_WIELD; i++)
+ {
+ if (p_ptr->inventory[i].k_idx)
+ {
+ if (cursed_p(&p_ptr->inventory[i]))
+ {
+ char name[80];
+ object_desc(name, &p_ptr->inventory[i], 0, 0);
+ msg_format("Hmmm, your %s seems to be cursed.", name);
+ break;
+ }
+ else if (INVEN_PACK == inven_takeoff(i, 255, force_drop))
+ {
+ force_drop = TRUE;
+ }
+ }
+ }
+ p_ptr->melee_style = melee_skills[melee_num[z]];
+ energy_use = 100;
+ break;
+ }
+
+ /* Recalculate bonuses */
+ p_ptr->update |= (PU_BONUS);
+
+ /* Recalculate hitpoint */
+ p_ptr->update |= (PU_HP);
+
+ /* Redraw monster hitpoint */
+ p_ptr->redraw |= (PR_FRAME);
+
+ Term_load();
+ character_icky = FALSE;
+
+ if (style_unchanged)
+ {
+ msg_format("You are already using %s.", melee_names[melee_num[z]]);
+ }
+}
+
+void select_default_melee()
+{
+ int i;
+
+ get_melee_skills();
+ p_ptr->melee_style = SKILL_MASTERY;
+ for (i = 0; i < MAX_MELEE; i++)
+ {
+ if (melee_bool[i])
+ {
+ p_ptr->melee_style = melee_skills[i];
+ break;
+ }
+ }
+}
+
+/*
+ * Print a batch of skills.
+ */
+static void print_skill_batch(const std::vector<std::tuple<std::string, int>> &p, int start)
+{
+ char buff[80];
+ int j = 0;
+
+ prt(format(" %-31s", "Name"), 1, 20);
+
+ for (int i = start; i < (start + 20); i++)
+ {
+ if (static_cast<size_t>(i) >= p.size())
+ {
+ break;
+ }
+
+ sprintf(buff, " %c - %d) %-30s", I2A(j),
+ std::get<1>(p[i]),
+ std::get<0>(p[i]).c_str());
+
+ prt(buff, 2 + j, 20);
+ j++;
+ }
+ prt("", 2 + j, 20);
+ prt(format("Select a skill (a-%c), @ to select by name, +/- to scroll:", I2A(j - 1)), 0, 0);
+}
+
+static int do_cmd_activate_skill_aux()
+{
+ auto const &ab_info = game->edit_data.ab_info;
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto const &s_info = game->s_info;
+
+ char which;
+ int start = 0;
+ int ret;
+
+ std::vector<std::tuple<std::string,int>> p;
+
+ /* More than 1 melee skill ? */
+ if (get_melee_skills() > 1)
+ {
+ p.push_back(std::make_tuple("Change melee mode", 0));
+ }
+
+ for (size_t i = 1; i < s_descriptors.size(); i++)
+ {
+ if (s_descriptors[i].action_mkey && s_info[i].value && ((!s_info[i].hidden) || (i == SKILL_LEARN)))
+ {
+ bool_ next = FALSE;
+
+ /* Already got it ? */
+ for (size_t j = 0; j < p.size(); j++)
+ {
+ if (s_descriptors[i].action_mkey == std::get<1>(p[j]))
+ {
+ next = TRUE;
+ break;
+ }
+ }
+ if (next) continue;
+
+ p.push_back(std::make_tuple(s_descriptors[i].action_desc,
+ s_descriptors[i].action_mkey));
+ }
+ }
+
+ for (size_t i = 0; i < ab_info.size(); i++)
+ {
+ if (ab_info[i].action_mkey && p_ptr->has_ability(i))
+ {
+ bool_ next = FALSE;
+
+ /* Already got it ? */
+ for (size_t j = 0; j < p.size(); j++)
+ {
+ if (ab_info[i].action_mkey == std::get<1>(p[j]))
+ {
+ next = TRUE;
+ break;
+ }
+ }
+ if (next) continue;
+
+ p.push_back(std::make_tuple(ab_info[i].action_desc,
+ ab_info[i].action_mkey));
+ }
+ }
+
+ if (p.empty())
+ {
+ msg_print("You don't have any activable skills or abilities.");
+ return -1;
+ }
+
+ character_icky = TRUE;
+ Term_save();
+
+ while (1)
+ {
+ print_skill_batch(p, start);
+ which = inkey();
+
+ if (which == ESCAPE)
+ {
+ ret = -1;
+ break;
+ }
+ else if (which == '+')
+ {
+ start += 20;
+ if (static_cast<size_t>(start) >= p.size())
+ {
+ start -= 20;
+ }
+ Term_load();
+ character_icky = FALSE;
+ }
+ else if (which == '-')
+ {
+ start -= 20;
+ if (start < 0)
+ {
+ start += 20;
+ }
+ Term_load();
+ character_icky = FALSE;
+ }
+ else if (which == '@')
+ {
+ char buf[80];
+
+ strcpy(buf, "Cast a spell");
+ if (!get_string("Skill action? ", buf, 79))
+ return FALSE;
+
+ /* Find the skill it is related to */
+ size_t i = 0;
+ for (; i < p.size(); i++)
+ {
+ if (std::get<0>(p[i]) == buf)
+ {
+ break;
+ }
+ }
+
+ if (i < p.size())
+ {
+ ret = std::get<1>(p[i]);
+ break;
+ }
+ }
+ else
+ {
+ which = tolower(which);
+ if (start + A2I(which) >= static_cast<int>(p.size()))
+ {
+ bell();
+ continue;
+ }
+ if (start + A2I(which) < 0)
+ {
+ bell();
+ continue;
+ }
+
+ ret = std::get<1>(p[start + A2I(which)]);
+ break;
+ }
+ }
+ Term_load();
+ character_icky = FALSE;
+
+ return ret;
+}
+
+/* Ask & execute a skill */
+void do_cmd_activate_skill()
+{
+ auto const &ab_info = game->edit_data.ab_info;
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto const &s_info = game->s_info;
+
+ int x_idx;
+ bool_ push = TRUE;
+
+ /* Get the skill, if available */
+ if (repeat_pull(&x_idx))
+ {
+ push = FALSE;
+ }
+ else if (!command_arg)
+ {
+ x_idx = do_cmd_activate_skill_aux();
+ }
+ else
+ {
+ x_idx = command_arg;
+
+ /* Check validity */
+ std::size_t i;
+ for (i = 1; i < s_descriptors.size(); i++)
+ {
+ if (s_info[i].value && (s_descriptors[i].action_mkey == x_idx))
+ {
+ break;
+ }
+ }
+
+ std::size_t j;
+ for (j = 0; j < ab_info.size(); j++)
+ {
+ if (p_ptr->has_ability(j) && (ab_info[j].action_mkey == x_idx))
+ {
+ break;
+ }
+ }
+
+ if ((j == ab_info.size()) && (i == s_descriptors.size()))
+ {
+ msg_print("Uh?");
+ return;
+ }
+ }
+
+ if (x_idx == -1) return;
+
+ if (push) repeat_push(x_idx);
+
+ if (!x_idx)
+ {
+ choose_melee();
+ return;
+ }
+
+ /* Break goi/manashield */
+ if (p_ptr->invuln)
+ {
+ set_invuln(0);
+ }
+ if (p_ptr->disrupt_shield)
+ {
+ set_disrupt_shield(0);
+ }
+
+ switch (x_idx)
+ {
+ case MKEY_ANTIMAGIC:
+ do_cmd_unbeliever();
+ break;
+ case MKEY_MINDCRAFT:
+ do_cmd_mindcraft();
+ break;
+ case MKEY_MIMIC:
+ do_cmd_mimic();
+ break;
+ case MKEY_POWER_MAGE:
+ do_cmd_powermage();
+ break;
+ case MKEY_FORGING:
+ do_cmd_archer();
+ break;
+ case MKEY_INCARNATION:
+ do_cmd_possessor();
+ break;
+ case MKEY_SUMMON:
+ do_cmd_summoner();
+ break;
+ case MKEY_NECRO:
+ do_cmd_necromancer();
+ break;
+ case MKEY_SYMBIOTIC:
+ do_cmd_symbiotic();
+ break;
+ case MKEY_STEAL:
+ do_cmd_steal();
+ break;
+ case MKEY_DODGE:
+ use_ability_blade();
+ break;
+ case MKEY_SCHOOL:
+ cast_school_spell();
+ break;
+ case MKEY_COPY:
+ do_cmd_copy_spell();
+ break;
+ case MKEY_BOULDER:
+ do_cmd_create_boulder();
+ break;
+ case MKEY_COMPANION:
+ if (get_skill(SKILL_LORE) >= 12)
+ do_cmd_companion();
+ else
+ msg_print("You need a skill level of at least 12.");
+ break;
+ case MKEY_PIERCING:
+ do_cmd_set_piercing();
+ break;
+ case MKEY_DEATH_TOUCH:
+ {
+ if (p_ptr->csp > 40)
+ {
+ increase_mana(-40);
+ set_project(randint(30) + 10,
+ GF_INSTA_DEATH,
+ 1,
+ 0,
+ PROJECT_STOP | PROJECT_KILL);
+ energy_use = 100;
+ }
+ else
+ {
+ msg_print("You need at least 40 mana.");
+ }
+ break;
+ }
+ case MKEY_GEOMANCY:
+ {
+ s32b s = -1;
+ object_type *o_ptr = NULL;
+
+ /* No magic */
+ if (p_ptr->antimagic > 0)
+ {
+ msg_print("Your anti-magic field disrupts any magic attempts.");
+ break;
+ }
+
+ o_ptr = get_object(INVEN_WIELD);
+ if ((o_ptr->k_idx <= 0) ||
+ (o_ptr->tval != TV_MSTAFF))
+ {
+ msg_print("You must wield a magestaff to use Geomancy.");
+ break;
+ }
+
+ s = get_school_spell("cast", BOOK_GEOMANCY);
+ if (s >= 0)
+ {
+ lua_cast_school_spell(s, FALSE);
+ }
+
+ break;
+ }
+ case MKEY_REACH_ATTACK:
+ {
+ object_type *o_ptr = NULL;
+ int dir, dy, dx, targetx, targety, max_blows, flags;
+
+ o_ptr = get_object(INVEN_WIELD);
+ if (o_ptr->tval != TV_POLEARM)
+ {
+ msg_print("You will need a long polearm for this!");
+ return;
+ }
+
+ if (!get_rep_dir(&dir))
+ {
+ return;
+ }
+
+ dy = ddy[dir];
+ dx = ddx[dir];
+ dy = dy * 2;
+ dx = dx * 2;
+ targety = p_ptr->py + dy;
+ targetx = p_ptr->px + dx;
+
+ max_blows = get_skill_scale(SKILL_POLEARM, p_ptr->num_blow / 2);
+ if (max_blows == 0)
+ {
+ max_blows = 1;
+ }
+
+ energy_use = energy_use + 200;
+
+ flags = PROJECT_BEAM | PROJECT_KILL;
+ if (get_skill(SKILL_POLEARM) < 40)
+ {
+ flags |= PROJECT_STOP;
+ }
+
+ project(0, 0, targety, targetx,
+ max_blows, GF_ATTACK, flags);
+
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+
+/* Which magic forbids non FA gloves */
+bool_ forbid_gloves()
+{
+ if (get_skill(SKILL_SORCERY)) return (TRUE);
+ if (get_skill(SKILL_MANA)) return (TRUE);
+ if (get_skill(SKILL_FIRE)) return (TRUE);
+ if (get_skill(SKILL_AIR)) return (TRUE);
+ if (get_skill(SKILL_WATER)) return (TRUE);
+ if (get_skill(SKILL_EARTH)) return (TRUE);
+ if (get_skill(SKILL_THAUMATURGY)) return (TRUE);
+ return (FALSE);
+}
+
+/* Which gods forbid edged weapons */
+bool_ forbid_non_blessed()
+{
+ if (p_ptr->pgod == GOD_ERU) return (TRUE);
+ return (FALSE);
+}
+
+
+/*
+ * Augment skill value/modifier with the given skill_modifiers
+ */
+static void augment_skills(s32b *v, s32b *m, std::vector<skill_modifier> const &modifiers, std::size_t i)
+{
+ if (i < modifiers.size()) // Ignore if the skill has no modifiers.
+ {
+ auto const &s = modifiers[i];
+ *v = modify_aux(*v, s.base, s.basem);
+ *m = modify_aux(*m, s.mod, s.modm);
+ }
+}
+
+
+/*
+ * Gets the base value of a skill, given a race/class/...
+ */
+void compute_skills(s32b *v, s32b *m, std::size_t i)
+{
+ auto const &gen_skill = game->edit_data.gen_skill;
+
+ augment_skills(v, m, gen_skill.modifiers, i);
+ augment_skills(v, m, rp_ptr->skill_modifiers.modifiers, i);
+ augment_skills(v, m, rmp_ptr->skill_modifiers.modifiers, i);
+ augment_skills(v, m, cp_ptr->skill_modifiers.modifiers, i);
+ augment_skills(v, m, spp_ptr->skill_modifiers.modifiers, i);
+}
+
+/*
+ * Initialize a skill with given values
+ */
+void init_skill(s32b value, s32b mod, std::size_t i)
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto &s_info = game->s_info;
+
+ s_info[i].value = value;
+ s_info[i].mod = mod;
+ s_info[i].hidden = (s_descriptors[i].flags & SKF_HIDDEN)
+ ? true
+ : false
+ ;
+}
+
+/*
+ * Perform weighted random shuffle according to the algorithm given in
+ * "Weighted Random Sampling" (2005, Efraimidis, Spirakis).
+ *
+ * @param weights is the vector of weights.
+ * @return an output vector of the same size as the input weights vector containing,
+ * in order of selection, the indices to select. For example, if you
+ * need to choose two items, you would use v[0], v[1] as the indices
+ * to pick.
+ */
+static std::vector<size_t> wrs(const std::vector<s32b> &unscaled_weights)
+{
+ const size_t n = unscaled_weights.size();
+
+ /* Rescale weights into unit interval for numerical stability */
+ std::vector<double> weights(unscaled_weights.size());
+ {
+ s32b scale = 0;
+ for (s32b weight: unscaled_weights)
+ {
+ scale += weight;
+ }
+
+ for (size_t i = 0; i < n; i++) {
+ weights[i] =
+ ((double) unscaled_weights[i]) /
+ ((double) scale);
+ }
+ }
+
+ /* Generate the keys and indexes to use for selection. This
+ is the only randomized portion of the algorithm. */
+ std::vector<std::tuple<double,size_t>> keys_and_indexes(unscaled_weights.size());
+ for (size_t i = 0; i < n; i++) {
+ /* Randomized keys according to the algorithm. */
+ double u = static_cast<double>(rand_int(100000)) / 100000;
+ double k = std::pow(u, 1/weights[i]);
+ /* Set up the key and index. We negate the k value
+ here so that keys will be sorted in descending
+ order rather than ascending order. */
+ keys_and_indexes[i] = std::make_tuple(-k, i);
+ }
+
+ /* Sort indexes according to keys. Since the keys have been
+ negated and we're using a lexicographical sort, we're
+ effectively sorting in descending order of key. */
+ std::sort(std::begin(keys_and_indexes),
+ std::end(keys_and_indexes));
+
+ /* Produce the output vector consisting of indexes only. */
+ std::vector<size_t> indexes;
+ for (auto const &key_and_index: keys_and_indexes) {
+ indexes.push_back(std::get<1>(key_and_index));
+ }
+ return indexes;
+}
+
+void do_get_new_skill()
+{
+ auto const &s_descriptors = game->edit_data.s_descriptors;
+ auto &s_info = game->s_info;
+
+ /* Check if some skills didn't influence other stuff */
+ recalc_skills(TRUE);
+
+ /* Grab the ones we can gain */
+ std::vector<size_t> available_skills;
+ available_skills.reserve(s_descriptors.size());
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ if (s_descriptors[i].flags & SKF_RANDOM_GAIN)
+ {
+ available_skills.push_back(i);
+ }
+ }
+
+ /* Perform the selection */
+ std::vector<s32b> weights;
+ for (std::size_t i = 0; i < available_skills.size(); i++)
+ {
+ weights.push_back(s_descriptors[available_skills[i]].random_gain_chance);
+ }
+
+ std::vector<size_t> indexes = wrs(weights);
+ assert(indexes.size() >= LOST_SWORD_NSKILLS);
+
+ /* Extract the information needed from the skills */
+ int skl[LOST_SWORD_NSKILLS];
+ s32b val[LOST_SWORD_NSKILLS];
+ s32b mod[LOST_SWORD_NSKILLS];
+ std::vector<std::string> items;
+ for (std::size_t i = 0; i < LOST_SWORD_NSKILLS; i++)
+ {
+ s32b s_idx = available_skills[indexes[i]];
+ auto s_ptr = &s_info[s_idx];
+
+ if (s_ptr->mod)
+ {
+ if (s_ptr->mod < 300)
+ {
+ val[i] = 1000;
+ mod[i] = 300 - s_ptr->mod;
+ }
+ else if (s_ptr->mod < 500)
+ {
+ val[i] = s_ptr->mod * 1;
+ mod[i] = 100;
+ if (mod[i] + s_ptr->mod > 500)
+ {
+ mod[i] = 500 - s_ptr->mod;
+ }
+ }
+ else
+ {
+ val[i] = s_ptr->mod * 3;
+ mod[i] = 0;
+ }
+ }
+ else
+ {
+ mod[i] = 300;
+ val[i] = 1000;
+ }
+
+ if (s_ptr->value + val[i] > SKILL_MAX)
+ {
+ val[i] = SKILL_MAX - s_ptr->value;
+ }
+
+ skl[i] = s_idx;
+ items.push_back(format("%-40s: +%02ld.%03ld value, +%01d.%03d modifier",
+ s_descriptors[s_idx].name.c_str(),
+ val[i] / SKILL_STEP,
+ val[i] % SKILL_STEP,
+ mod[i] / SKILL_STEP,
+ mod[i] % SKILL_STEP));
+ }
+
+ // Ask for a skill
+ while (TRUE)
+ {
+ char last = 'a' + (LOST_SWORD_NSKILLS-1);
+ char buf[80];
+ sprintf(buf, "Choose a skill to learn (a-%c to choose, ESC to cancel)?", last);
+ int res = ask_menu(buf, items);
+
+ /* Ok ? lets learn ! */
+ if (res > -1)
+ {
+ std::size_t chosen_skill = skl[res];
+
+ bool_ oppose = FALSE;
+ int oppose_skill = -1;
+
+ /* Check we don't oppose a skill the player already has */
+ for (std::size_t i = 0; i < s_descriptors.size(); i++)
+ {
+ auto const &s_descriptor = s_descriptors[i];
+
+ // Only bother if player has skill
+ if (s_info[i].value)
+ {
+ // Check if i'th skill opposes the chosen one.
+ auto found = std::find(
+ s_descriptor.excludes.begin(),
+ s_descriptor.excludes.end(),
+ chosen_skill);
+
+ if (found != s_descriptor.excludes.end())
+ {
+ oppose = TRUE;
+ oppose_skill = i;
+ break;
+ }
+ }
+ }
+
+ /* Ok we oppose, so be sure */
+ if (oppose)
+ {
+ cptr msg;
+
+ /*
+ * Because this is SO critical a question, we must flush
+ * input to prevent killing character off -- pelpel
+ */
+ flush();
+
+ /* Prepare prompt */
+ msg = format("This skill is mutually exclusive with "
+ "at least %s, continue?",
+ s_descriptors[oppose_skill].name.c_str());
+
+ /* The player rejected the choice; go back to prompt */
+ if (!get_check(msg))
+ {
+ continue;
+ }
+ }
+
+ auto const s_desc = &s_descriptors[chosen_skill];
+ auto const s_ptr = &s_info[chosen_skill];
+
+ s_ptr->value += val[res];
+ s_ptr->mod += mod[res];
+
+ if (mod[res])
+ {
+ msg_format("You can now learn the %s skill.",
+ s_desc->name.c_str());
+ }
+ else
+ {
+ msg_format("Your knowledge of the %s skill increases.",
+ s_desc->name.c_str());
+ }
+
+ break;
+ }
+ }
+
+ /* Check if some skills didn't influence other stuff */
+ recalc_skills(FALSE);
+}
+
+
+
+
+/**************************************** ABILITIES *****************************************/
+
+/*
+ * Given the name of an ability, returns ability index or -1 if no
+ * such ability is found
+ */
+s16b find_ability(cptr name)
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ for (std::size_t i = 0; i < ab_info.size(); i++)
+ {
+ auto const &ab_name = ab_info[i].name;
+
+ if ((!ab_name.empty()) && (ab_name == name))
+ {
+ return (i);
+ }
+ }
+
+ /* No match found */
+ return ( -1);
+}
+
+/* Do we meet the requirements? */
+static bool can_learn_ability(int ab)
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ auto ab_ptr = &ab_info[ab];
+
+ if (p_ptr->has_ability(ab))
+ {
+ return FALSE;
+ }
+
+ if (p_ptr->skill_points < ab_info[ab].cost)
+ {
+ return FALSE;
+ }
+
+ for (auto const &need_skill: ab_ptr->need_skills)
+ {
+ if (get_skill(need_skill.skill_idx) < need_skill.level)
+ {
+ return FALSE;
+ }
+ }
+
+ for (auto const &need_ability: ab_ptr->need_abilities)
+ {
+ if (!p_ptr->has_ability(need_ability))
+ {
+ return FALSE;
+ }
+ }
+
+ for (std::size_t i = 0; i < 6; i++)
+ {
+ /* Must have stat */
+ if (ab_ptr->stat[i] > -1)
+ {
+ if (p_ptr->stat_ind[i] < ab_ptr->stat[i] - 3)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* Learn an ability */
+static void gain_ability(int ab)
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ if (!can_learn_ability(ab))
+ {
+ msg_box_auto("You cannot learn this ability.");
+ return;
+ }
+
+ /* Flush input as we ask an important and irreversible question */
+ flush();
+
+ /* Ask we can commit the change */
+ if (msg_box_auto("Learn this ability (this is permanent)? (y/n)") != 'y')
+ {
+ return;
+ }
+
+ p_ptr->gain_ability(ab);
+ p_ptr->skill_points -= ab_info[ab].cost;
+}
+
+static bool compare_abilities(std::size_t ab_idx1, std::size_t ab_idx2)
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ return ab_info[ab_idx1].name < ab_info[ab_idx2].name;
+}
+
+/*
+ * Print the abilities list
+ */
+void dump_abilities(FILE *fff)
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ // Find all abilities that the player has.
+ std::vector<std::size_t> table;
+ for (std::size_t i = 0; i < ab_info.size(); i++)
+ {
+ if ((!ab_info[i].name.empty()) && p_ptr->has_ability(i))
+ {
+ table.push_back(i);
+ }
+ }
+
+ // Sort
+ std::sort(std::begin(table),
+ std::end(table),
+ compare_abilities);
+
+ // Show
+ if (!table.empty())
+ {
+ fprintf(fff, "\nAbilities");
+
+ for (int i : table)
+ {
+ fprintf(fff, "\n * %s", ab_info[i].name.c_str());
+ }
+
+ fprintf(fff, "\n");
+ }
+}
+
+/*
+ * Draw the abilities list
+ */
+static void print_abilities(const std::vector<std::size_t> &table, int sel, int start)
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ int i, j;
+ int wid, hgt;
+ cptr keys;
+
+ Term_clear();
+ Term_get_size(&wid, &hgt);
+
+ c_prt(TERM_WHITE, format("%s Abilities Screen", game_module), 0, 28);
+ keys = format("#Bup#W/#Bdown#W to move, #Bright#W to buy, #B?#W for help");
+ display_message(0, 1, strlen(keys), TERM_WHITE, keys);
+ c_prt((p_ptr->skill_points) ? TERM_L_BLUE : TERM_L_RED,
+ format("Skill points left: %d", p_ptr->skill_points), 2, 0);
+
+ print_desc_aux(ab_info[table[sel]].desc.c_str(), 3, 0);
+
+ for (j = start; j < start + (hgt - 7); j++)
+ {
+ byte color = TERM_WHITE;
+ char deb = ' ', end = ' ';
+
+ assert(j >= 0);
+ if (static_cast<size_t>(j) >= table.size())
+ {
+ break;
+ }
+
+ i = table[j];
+
+ if (p_ptr->has_ability(i))
+ {
+ color = TERM_L_BLUE;
+ }
+ else if (can_learn_ability(i))
+ {
+ color = TERM_WHITE;
+ }
+ else
+ {
+ color = TERM_L_DARK;
+ }
+
+
+ if (j == sel)
+ {
+ color = TERM_L_GREEN;
+ deb = '[';
+ end = ']';
+ }
+
+ c_prt(color, format("%c.%c%s", deb, end, ab_info[i].name.c_str()),
+ j + 7 - start, 0);
+
+ if (!p_ptr->has_ability(i))
+ {
+ c_prt(color, format("%d", ab_info[i].cost), j + 7 - start, 60);
+ }
+ else
+ {
+ c_prt(color, "Known", j + 7 - start, 60);
+ }
+ }
+}
+
+/*
+ * Interreact with abilities
+ */
+void do_cmd_ability()
+{
+ auto const &ab_info = game->edit_data.ab_info;
+
+ int sel = 0, start = 0;
+ char c;
+ int wid, hgt;
+
+ /* Save the screen */
+ screen_save();
+
+ /* Clear the screen */
+ Term_clear();
+
+ /* Initialise the abilities list */
+ std::vector<std::size_t> table;
+ for (std::size_t i = 0; i < ab_info.size(); i++)
+ {
+ if (!ab_info[i].name.empty())
+ {
+ table.push_back(i);
+ }
+ }
+
+ std::sort(std::begin(table),
+ std::end(table),
+ compare_abilities);
+
+ while (TRUE)
+ {
+ Term_get_size(&wid, &hgt);
+
+ /* Display list of skills */
+ print_abilities(table, sel, start);
+
+ /* Wait for user input */
+ c = inkey();
+
+ /* Leave the skill screen */
+ if (c == ESCAPE)
+ {
+ break;
+ }
+
+ /* Next page */
+ else if (c == 'n')
+ {
+ sel += (hgt - 7);
+ assert(sel >= 0);
+ if (static_cast<size_t>(sel) >= table.size())
+ {
+ sel = table.size() - 1;
+ }
+ }
+
+ /* Previous page */
+ else if (c == 'p')
+ {
+ sel -= (hgt - 7);
+ if (sel < 0) sel = 0;
+ }
+
+ /* Select / increase a skill */
+ else
+ {
+ int dir;
+
+ /* Allow use of numpad / arrow keys / roguelike keys */
+ dir = get_keymap_dir(c);
+
+ /* Move cursor down */
+ if (dir == 2) sel++;
+
+ /* Move cursor up */
+ if (dir == 8) sel--;
+
+ /* gain ability */
+ if (dir == 6) gain_ability(table[sel]);
+
+ /* Wizard mode allows any ability */
+ if (wizard && (c == '+'))
+ {
+ p_ptr->gain_ability(table[sel]);
+ }
+
+ if (wizard && (c == '-'))
+ {
+ p_ptr->lose_ability(table[sel]);
+ }
+
+ /* Contextual help */
+ if (c == '?')
+ {
+ help_ability(ab_info[table[sel]].name);
+ }
+
+ /* Handle boundaries and scrolling */
+ if (sel < 0)
+ {
+ sel = table.size() - 1;
+ }
+ if (static_cast<size_t>(sel) >= table.size())
+ {
+ sel = 0;
+ }
+ if (sel < start)
+ {
+ start = sel;
+ }
+ if (sel >= start + (hgt - 7))
+ {
+ start = sel - (hgt - 7) + 1;
+ }
+ }
+ }
+
+ /* Load the screen */
+ screen_load();
+
+ /* Update stuffs */
+ p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_POWERS |
+ PU_SANITY | PU_BODY);
+
+ /* Redraw various info */
+ p_ptr->redraw |= (PR_WIPE | PR_FRAME | PR_MAP);
+}
+
+/*
+ * Apply abilities to be granted this level
+ */
+void apply_level_abilities(int level)
+{
+ auto apply = [level](std::vector<player_race_ability_type> const &abilities) -> void
+ {
+ auto const &ab_info = game->edit_data.ab_info;
+
+ for (auto const &a: abilities)
+ {
+ if (a.level == level)
+ {
+ if ((level > 1) && (!p_ptr->has_ability(a.ability)))
+ {
+ cmsg_format(TERM_L_GREEN, "You have learned the ability '%s'.", ab_info[a.ability].name.c_str());
+ }
+
+ p_ptr->gain_ability(a.ability);
+ }
+ }
+ };
+
+ apply(cp_ptr->abilities);
+ apply(spp_ptr->abilities);
+ apply(rp_ptr->abilities);
+ apply(rmp_ptr->abilities);
+}