diff --git a/db/import-tmpl/improvise_db.yml b/db/import-tmpl/improvise_db.yml
new file mode 100644
index 0000000000..2c0afce465
--- /dev/null
+++ b/db/import-tmpl/improvise_db.yml
@@ -0,0 +1,31 @@
+# This file is a part of rAthena.
+# Copyright(C) 2019 rAthena Development Team
+# https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+###########################################################################
+# Improvised Song Database
+###########################################################################
+#
+# Improvised Song Settings
+#
+###########################################################################
+# - Skill Skill to be casted by Improvised Song.
+# Probability Probability of skill compared to others in database (1 = 0.01%, 10000 = 100%).
+###########################################################################
+
+Header:
+ Type: IMPROVISED_SONG_DB
+ Version: 1
diff --git a/db/improvise_db.yml b/db/improvise_db.yml
new file mode 100644
index 0000000000..0e53f56229
--- /dev/null
+++ b/db/improvise_db.yml
@@ -0,0 +1,37 @@
+# This file is a part of rAthena.
+# Copyright(C) 2019 rAthena Development Team
+# https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+###########################################################################
+# Improvised Song Database
+###########################################################################
+#
+# Improvised Song Settings
+#
+###########################################################################
+# - Skill Skill to be casted by Improvised Song.
+# Probability Probability of skill compared to others in database (1 = 0.01%, 10000 = 100%).
+###########################################################################
+
+Header:
+ Type: IMPROVISED_SONG_DB
+ Version: 1
+
+Footer:
+ Imports:
+ - Path: db/re/improvise_db.yml
+ Mode: Renewal
+ - Path: db/import/improvise_db.yml
diff --git a/db/re/improvise_db.yml b/db/re/improvise_db.yml
new file mode 100644
index 0000000000..4961d20f8d
--- /dev/null
+++ b/db/re/improvise_db.yml
@@ -0,0 +1,65 @@
+# This file is a part of rAthena.
+# Copyright(C) 2019 rAthena Development Team
+# https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+###########################################################################
+# Improvised Song Database
+###########################################################################
+#
+# Improvised Song Settings
+#
+###########################################################################
+# - Skill Skill to be casted by Improvised Song.
+# Probability Probability of skill compared to others in database (1 = 0.01%, 10000 = 100%).
+###########################################################################
+
+Header:
+ Type: IMPROVISED_SONG_DB
+ Version: 1
+
+Body:
+ - Skill: MG_NAPALMBEAT
+ Probability: 6000
+ - Skill: MG_SAFETYWALL
+ Probability: 4000
+ - Skill: MG_SOULSTRIKE
+ Probability: 6000
+ - Skill: MG_COLDBOLT
+ Probability: 6000
+ - Skill: MG_FROSTDIVER
+ Probability: 6000
+ - Skill: MG_FIREBALL
+ Probability: 6000
+ - Skill: MG_FIREWALL
+ Probability: 4000
+ - Skill: MG_FIREBOLT
+ Probability: 6000
+ - Skill: MG_LIGHTNINGBOLT
+ Probability: 6000
+ - Skill: MG_THUNDERSTORM
+ Probability: 4000
+ - Skill: WZ_FIREPILLAR
+ Probability: 4000
+ - Skill: WZ_METEOR
+ Probability: 4000
+ - Skill: WZ_JUPITEL
+ Probability: 6000
+ - Skill: WZ_VERMILION
+ Probability: 4000
+ - Skill: WZ_WATERBALL
+ Probability: 6000
+ - Skill: WZ_STORMGUST
+ Probability: 4000
diff --git a/db/skill_improvise_db.txt b/db/skill_improvise_db.txt
deleted file mode 100644
index cfd2a3c518..0000000000
--- a/db/skill_improvise_db.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-// Improvise Database
-// Database for skills that can be summoned trough Randomize Spell/Improvised Song (Minstrel/Wanderer Skill).
-//
-// Structure of Database:
-// SkillID,Rate
-//
-// - To remove entry by importing, put 0 on 'Rate'
-
-11,60000 // Napalm Beat
-12,40000 // Safety Wall
-13,60000 // Soul Strike
-14,60000 // Cold Bolt
-15,60000 // Frost Diver
-17,60000 // Fire Ball
-18,40000 // Fire Wall
-19,60000 // Fire Bolt
-20,60000 // Lightning Bolt
-21,40000 // Thunderstorm
-80,40000 // Fire Pillar
-83,40000 // Meteor Storm
-84,60000 // Jupitel Thunder
-85,40000 // Lord of Vermilion
-86,60000 // Water Ball
-89,40000 // Storm Gust
diff --git a/doc/yaml/db/improvise_db.yml b/doc/yaml/db/improvise_db.yml
new file mode 100644
index 0000000000..aa84b08bd3
--- /dev/null
+++ b/doc/yaml/db/improvise_db.yml
@@ -0,0 +1,10 @@
+###########################################################################
+# Improvised Song Database
+###########################################################################
+#
+# Improvised Song Settings
+#
+###########################################################################
+# - Skill Skill to be casted by Improvised Song.
+# Probability Probability of skill compared to others in database (1 = 0.01%, 10000 = 100%).
+###########################################################################
diff --git a/src/map/map-server.vcxproj b/src/map/map-server.vcxproj
index dca3993ecb..35d6146dde 100644
--- a/src/map/map-server.vcxproj
+++ b/src/map/map-server.vcxproj
@@ -305,6 +305,7 @@
+
@@ -363,7 +364,6 @@
-
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index f50c774132..6fb993d8f2 100755
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -89,12 +89,7 @@ static unsigned short skill_arrow_count;
AbraDatabase abra_db;
-struct s_skill_improvise_db {
- uint16 skill_id;
- unsigned short per;//1-10000
-};
-struct s_skill_improvise_db skill_improvise_db[MAX_SKILL_IMPROVISE_DB];
-static unsigned short skill_improvise_count;
+ImprovisedSongDatabase improvised_song_db;
#define MAX_SKILL_CHANGEMATERIAL_DB 75
#define MAX_SKILL_CHANGEMATERIAL_SET 3
@@ -10214,16 +10209,22 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
break;
case WM_RANDOMIZESPELL:
- if (!skill_improvise_count) {
+ if (improvised_song_db.empty()) {
clif_skill_nodamage (src, bl, skill_id, skill_lv, 1);
break;
}
else {
- int improv_skill_id = 0, improv_skill_lv, checked = 0, checked_max = MAX_SKILL_IMPROVISE_DB*3;
+ int improv_skill_id = 0, improv_skill_lv, checked = 0, checked_max = improvised_song_db.size() * 3;
+
do {
- i = rnd() % MAX_SKILL_IMPROVISE_DB;
- improv_skill_id = skill_improvise_db[i].skill_id;
- } while( checked++ < checked_max && (improv_skill_id == 0 || rnd()%10000 >= skill_improvise_db[i].per) );
+ auto improvise_spell = improvised_song_db.random();
+
+ improv_skill_id = improvise_spell->skill_id;
+
+ if( rnd() % 10000 >= improvise_spell->per ){
+ break;
+ }
+ } while (checked++ < checked_max);
if (!skill_get_index(improv_skill_id)) {
if (sd)
@@ -10231,7 +10232,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
break;
}
- improv_skill_lv = 4 + skill_lv;
+ improv_skill_lv = min(4 + skill_lv, skill_get_max(improv_skill_id));
clif_skill_nodamage (src, bl, skill_id, skill_lv, 1);
if( sd ) {
@@ -21455,40 +21456,58 @@ static bool skill_parse_row_spellbookdb(char* split[], int columns, int current)
return false;
}
-/** Reads improvise db
- * Structure: SkillID,Rate
- */
-static bool skill_parse_row_improvisedb(char* split[], int columns, int current)
-{
- unsigned short skill_id = atoi(split[0]), per = atoi(split[1]), i;
- if( !skill_get_index(skill_id) || !skill_get_max(skill_id) ) {
- ShowError("skill_parse_row_improvisedb: Invalid skill ID %d\n", skill_id);
- return false;
- }
- if ( !skill_get_inf(skill_id) ) {
- ShowError("skill_parse_row_improvisedb: Passive skills cannot be casted (%d/%s)\n", skill_id, skill_get_name(skill_id));
- return false;
- }
- ARR_FIND(0, skill_improvise_count, i, skill_improvise_db[i].skill_id == skill_id);
- if (i >= ARRAYLENGTH(skill_improvise_db)) {
- ShowError("skill_parse_row_improvisedb: Maximum amount of entries reached (%d), increase MAX_SKILL_IMPROVISE_DB\n",MAX_SKILL_IMPROVISE_DB);
- return false;
- }
- // Import just for clearing/disabling from original data
- if (per == 0) {
- memset(&skill_improvise_db[i], 0, sizeof(skill_improvise_db[i]));
- //ShowInfo("skill_parse_row_improvisedb: Skill %d removed from list.\n", skill_id);
- return true;
+const std::string ImprovisedSongDatabase::getDefaultLocation() {
+ return std::string(db_path) + "/improvise_db.yml";
+}
+
+/**
+* Reads and parses an entry from the improvise_db.
+* @param node: YAML node containing the entry.
+* @return count of successfully parsed rows
+*/
+uint64 ImprovisedSongDatabase::parseBodyNode(const YAML::Node &node) {
+ std::string skill_name;
+
+ if (!this->asString(node, "Skill", skill_name))
+ return 0;
+
+ uint16 skill_id = skill_name2id(skill_name.c_str());
+
+ if (!skill_id) {
+ this->invalidWarning(node["Skill"], "Invalid Improvised Song skill name \"%s\", skipping.\n", skill_name.c_str());
+ return 0;
}
- skill_improvise_db[i].skill_id = skill_id;
- skill_improvise_db[i].per = per; // Still need confirm it.
+ if (!skill_get_inf(skill_id)) {
+ this->invalidWarning(node["Skill"], "Passive skill %s cannot be casted by Improvised Song.\n", skill_name.c_str());
+ return 0;
+ }
- if (i == skill_improvise_count)
- skill_improvise_count++;
+ std::shared_ptr improvise = this->find(skill_id);
+ bool exists = improvise != nullptr;
- return true;
+ if (!exists) {
+ if (!this->nodesExist(node, { "Probability" }))
+ return 0;
+
+ improvise = std::make_shared();
+ improvise->skill_id = skill_id;
+ }
+
+ if (this->nodeExists(node, "Probability")) {
+ uint16 probability;
+
+ if (!this->asUInt16Rate(node, "Probability", probability))
+ return 0;
+
+ improvise->per = probability;
+ }
+
+ if (!exists)
+ this->put(skill_id, improvise);
+
+ return 1;
}
const std::string MagicMushroomDatabase::getDefaultLocation() {
@@ -21826,7 +21845,7 @@ static void skill_readdb(void)
memset(skill_arrow_db,0,sizeof(skill_arrow_db));
memset(skill_spellbook_db,0,sizeof(skill_spellbook_db));
memset(skill_changematerial_db,0,sizeof(skill_changematerial_db));
- skill_produce_count = skill_arrow_count = skill_improvise_count =
+ skill_produce_count = skill_arrow_count =
skill_changematerial_count = skill_spellbook_count = 0;
for(i=0; i 0);
sv_readdb(dbsubpath1, "spellbook_db.txt" , ',', 3, 3, MAX_SKILL_SPELLBOOK_DB, skill_parse_row_spellbookdb, i > 0);
sv_readdb(dbsubpath1, "skill_copyable_db.txt" , ',', 2, 4, -1, skill_parse_row_copyabledb, i > 0);
- sv_readdb(dbsubpath1, "skill_improvise_db.txt" , ',', 2, 2, MAX_SKILL_IMPROVISE_DB, skill_parse_row_improvisedb, i > 0);
sv_readdb(dbsubpath1, "skill_changematerial_db.txt" , ',', 5, 5+2*MAX_SKILL_CHANGEMATERIAL_SET, MAX_SKILL_CHANGEMATERIAL_DB, skill_parse_row_changematerialdb, i > 0);
sv_readdb(dbsubpath1, "skill_nonearnpc_db.txt" , ',', 2, 3, -1, skill_parse_row_nonearnpcrangedb, i > 0);
sv_readdb(dbsubpath1, "skill_damage_db.txt" , ',', 4, 3+SKILLDMG_MAX, -1, skill_parse_row_skilldamage, i > 0);
@@ -21864,6 +21882,7 @@ static void skill_readdb(void)
}
abra_db.load();
+ improvised_song_db.load();
magic_mushroom_db.load();
skill_init_unit_layout();
diff --git a/src/map/skill.hpp b/src/map/skill.hpp
index 1ff32424bd..e427f03fc2 100644
--- a/src/map/skill.hpp
+++ b/src/map/skill.hpp
@@ -28,7 +28,6 @@ struct status_change_entry;
#define MAX_PRODUCE_RESOURCE 12 /// Max Produce requirements
#define MAX_SKILL_ARROW_DB 150 /// Max Arrow Creation DB
#define MAX_ARROW_RESULT 5 /// Max Arrow results/created
-#define MAX_SKILL_IMPROVISE_DB 30 /// Max Skill for Improvise
#define MAX_SKILL_LEVEL 13 /// Max Skill Level (for skill_db storage)
#define MAX_MOBSKILL_LEVEL 100 /// Max monster skill level (on skill usage)
#define MAX_SKILL_CRIMSON_MARKER 3 /// Max Crimson Marker targets (RL_C_MARKER)
@@ -385,6 +384,20 @@ public:
uint64 parseBodyNode(const YAML::Node& node);
};
+struct s_skill_improvise_db {
+ uint16 skill_id, per;
+};
+
+class ImprovisedSongDatabase : public TypesafeYamlDatabase {
+public:
+ ImprovisedSongDatabase() : TypesafeYamlDatabase("IMPROVISED_SONG_DB", 1) {
+
+ }
+
+ const std::string getDefaultLocation();
+ uint64 parseBodyNode(const YAML::Node& node);
+};
+
void do_init_skill(void);
void do_final_skill(void);
diff --git a/src/tool/csv2yaml.cpp b/src/tool/csv2yaml.cpp
index f65564beb8..e88f87001c 100644
--- a/src/tool/csv2yaml.cpp
+++ b/src/tool/csv2yaml.cpp
@@ -71,6 +71,7 @@ static bool guild_read_guildskill_tree_db( char* split[], int columns, int curre
static size_t pet_read_db( const char* file );
static bool skill_parse_row_magicmushroomdb(char* split[], int column, int current);
static bool skill_parse_row_abradb(char* split[], int columns, int current);
+static bool skill_parse_row_improvisedb(char* split[], int columns, int current);
// Constants for conversion
std::unordered_map aegis_itemnames;
@@ -155,11 +156,11 @@ void finalizeBody(void) {
}
template
-bool process( const std::string& type, uint32 version, const std::vector& paths, const std::string& name, Func lambda ){
+bool process( const std::string& type, uint32 version, const std::vector& paths, const std::string& name, Func lambda, const std::string& rename = "" ){
for( const std::string& path : paths ){
const std::string name_ext = name + ".txt";
const std::string from = path + "/" + name_ext;
- const std::string to = path + "/" + name + ".yml";
+ const std::string to = path + "/" + (rename.size() > 0 ? rename : name) + ".yml";
if( fileExists( from ) ){
if( !askConfirmation( "Found the file \"%s\", which requires migration to yml.\nDo you want to convert it now? (Y/N)\n", from.c_str() ) ){
@@ -181,7 +182,7 @@ bool process( const std::string& type, uint32 version, const std::vector 0 ? rename : name));
prepareBody();
if( !lambda( path, name_ext ) ){
@@ -195,7 +196,7 @@ bool process( const std::string& type, uint32 version, const std::vector bool {
+ return sv_readdb(path.c_str(), name_ext.c_str(), ',', 2, 2, -1, &skill_parse_row_improvisedb, false);
+ }, "improvise_db")) {
+ return 0;
+ }
+
// TODO: add implementations ;-)
return 0;
@@ -714,7 +721,7 @@ static bool skill_parse_row_abradb(char* split[], int columns, int current)
std::string *skill_name = util::umap_find(aegis_skillnames, skill_id);
if (skill_name == nullptr) {
- ShowError("Skill name for Abra skill ID &hu is not known.\n", skill_id);
+ ShowError("Skill name for Abra skill ID %hu is not known.\n", skill_id);
return false;
}
@@ -746,3 +753,21 @@ static bool skill_parse_row_abradb(char* split[], int columns, int current)
return true;
}
+
+static bool skill_parse_row_improvisedb(char* split[], int columns, int current)
+{
+ uint16 skill_id = atoi(split[0]);
+ std::string *skill_name = util::umap_find(aegis_skillnames, skill_id);
+
+ if (skill_name == nullptr) {
+ ShowError("Skill name for Improvised Song skill ID %hu is not known.\n", skill_id);
+ return false;
+ }
+
+ body << YAML::BeginMap;
+ body << YAML::Key << "Skill" << YAML::Value << *skill_name;
+ body << YAML::Key << "Probability" << YAML::Value << atoi(split[1]) / 10;
+ body << YAML::EndMap;
+
+ return true;
+}