Converts the Skill Tree Tables file into YAML (#6070)

* Converts the Skill Tree Tables file into YAML.
* Includes CSV2YAML converter.
* Corrected the tree according to the client (SkillInfoList)
* Removed 3rd jobs tree from pre-renewal, some required stats not being loaded by default

Co-authored-by: Aleos <aleos89@users.noreply.github.com>
Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
This commit is contained in:
Atemo 2021-12-14 00:00:33 +01:00 committed by GitHub
parent 8eef9f9d36
commit b644bcfe69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 11035 additions and 12560 deletions

View File

@ -1,2 +0,0 @@
//JobNo,Skill-ID,MaxLv{,BaseLvReq,JobLvReq},Prerequisite Skill-ID-1,Prerequisite Skill-ID-1-Lv,PrereqSkill-ID-2,PrereqSkill-ID-2-Lv,PrereqSkill-ID-3,PrereqSkill-ID-3-Lv,PrereqSkill-ID-4,PrereqSkill-ID-4-Lv,PrereqSkill-ID-5,PrereqSkill-ID-5-Lv//CLASS_SKILLNAME#Skill Name#

View File

@ -0,0 +1,40 @@
# This file is a part of rAthena.
# Copyright(C) 2021 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 <http://www.gnu.org/licenses/>.
############################################################################
# Skill Tree Database
###########################################################################
#
# Skill Tree Settings
#
###########################################################################
# - Job Job name.
# Inherit Map of job name from which Job will inherit the skill tree. (Default: null)
# Note that Job doesn't inherit the child skills, it only inherits the skills defined in Tree of the given job name.
# Tree: List of skills available for the job. (Default: null)
# - Name Skill name.
# MaxLevel Max level of the skill. Set to 0 to remove the skill.
# Exclude Whether the skill is excluded from being inherited. (Default: false)
# BaseLevel Minimum base level required to unlock the skill. (Default: 0)
# JobLevel Minimum job level required to unlock the skill. (Default: 0)
# Requires: List of skills required to unlock the skill. (Default: null)
# - Name Skill name.
# Level Skill level required. Set to 0 to remove the skill.
###########################################################################
Header:
Type: SKILL_TREE_DB
Version: 1

File diff suppressed because it is too large Load Diff

3578
db/pre-re/skill_tree.yml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

6644
db/re/skill_tree.yml Normal file

File diff suppressed because it is too large Load Diff

48
db/skill_tree.yml Normal file
View File

@ -0,0 +1,48 @@
# This file is a part of rAthena.
# Copyright(C) 2021 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 <http://www.gnu.org/licenses/>.
############################################################################
# Skill Tree Database
###########################################################################
#
# Skill Tree Settings
#
###########################################################################
# - Job Job name.
# Inherit Map of job name from which Job will inherit the skill tree. (Default: null)
# Note that Job doesn't inherit the child skills, it only inherits the skills defined in Tree of the given job name.
# Tree: List of skills available for the job. (Default: null)
# - Name Skill name.
# MaxLevel Max level of the skill. Set to 0 to remove the skill.
# Exclude Whether the skill is excluded from being inherited. (Default: false)
# BaseLevel Minimum base level required to unlock the skill. (Default: 0)
# JobLevel Minimum job level required to unlock the skill. (Default: 0)
# Requires: List of skills required to unlock the skill. (Default: null)
# - Name Skill name.
# Level Skill level required. Set to 0 to remove the skill.
###########################################################################
Header:
Type: SKILL_TREE_DB
Version: 1
Footer:
Imports:
- Path: db/pre-re/skill_tree.yml
Mode: Prerenewal
- Path: db/re/skill_tree.yml
Mode: Renewal
- Path: db/import/skill_tree.yml

View File

@ -0,0 +1,20 @@
###########################################################################
# Skill Tree Database
###########################################################################
#
# Skill Tree Settings
#
###########################################################################
# - Job Job name.
# Inherit Map of job name from which Job will inherit the skill tree. (Default: null)
# Note that Job doesn't inherit the child skills, it only inherits the skills defined in Tree of the given job name.
# Tree: List of skills available for the job. (Default: null)
# - Name Skill name.
# MaxLevel Max level of the skill. Set to 0 to remove the skill.
# Exclude Whether the skill is excluded from being inherited. (Default: false)
# BaseLevel Minimum base level required to unlock the skill. (Default: 0)
# JobLevel Minimum job level required to unlock the skill. (Default: 0)
# Requires: List of skills required to unlock the skill. (Default: null)
# - Name Skill name.
# Level Skill level required. Set to 0 to remove the skill.
###########################################################################

View File

@ -6058,8 +6058,6 @@ ACMD_FUNC(skilltree)
{
struct map_session_data *pl_sd = NULL;
uint16 skill_id;
int meets, j, c=0;
struct skill_tree_entry *ent;
nullpo_retr(-1, sd);
memset(atcmd_player_name, '\0', sizeof(atcmd_player_name));
@ -6075,33 +6073,29 @@ ACMD_FUNC(skilltree)
return -1;
}
c = pc_mapid2jobid( pc_calc_skilltree_normalize_job( pl_sd ), pl_sd->status.sex );
int c = pc_mapid2jobid( pc_calc_skilltree_normalize_job( pl_sd ), pl_sd->status.sex );
sprintf(atcmd_output, msg_txt(sd,1168), job_name(c), pc_checkskill(pl_sd, NV_BASIC)); // Player is using %s skill tree (%d basic points).
clif_displaymessage(fd, atcmd_output);
c = pc_class2idx(c);
auto entry = skill_tree_db.get_skill_data(c, skill_id);
ARR_FIND( 0, MAX_SKILL_TREE, j, skill_tree[c][j].skill_id == 0 || skill_tree[c][j].skill_id == skill_id );
if( j == MAX_SKILL_TREE || skill_tree[c][j].skill_id == 0 )
{
if (entry == nullptr) {
clif_displaymessage(fd, msg_txt(sd,1169)); // The player cannot use that skill.
return 0;
}
ent = &skill_tree[c][j];
bool meets = true;
meets = 1;
for(j=0;j<MAX_PC_SKILL_REQUIRE;j++)
{
if( ent->need[j].skill_id && pc_checkskill(sd,ent->need[j].skill_id) < ent->need[j].skill_lv)
{
sprintf(atcmd_output, msg_txt(sd,1170), ent->need[j].skill_lv, skill_get_desc(ent->need[j].skill_id)); // Player requires level %d of skill %s.
for (const auto &it : entry->need) {
if (pc_checkskill(sd, it.first) < it.second) {
sprintf(atcmd_output, msg_txt(sd,1170), it.second, skill_get_desc(it.first)); // Player requires level %d of skill %s.
clif_displaymessage(fd, atcmd_output);
meets = 0;
meets = false;
}
}
if (meets == 1) {
if (meets) {
clif_displaymessage(fd, msg_txt(sd,1171)); // The player meets all the requirements for that skill.
}

View File

@ -339,7 +339,7 @@
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\skill_damage_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\skill_damage_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\skill_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\skill_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\skill_nocast_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\skill_nocast_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\skill_tree.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\skill_tree.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\skill_tree.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\skill_tree.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\spellbook_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\spellbook_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\statpoint.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\statpoint.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\status_disabled.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\status_disabled.txt')" />

View File

@ -3966,7 +3966,7 @@ static bool mob_clone_disabled_skills(uint16 skill_id) {
int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, enum e_mode mode, int flag, unsigned int duration)
{
int mob_id;
int i,j,inf, fd;
int inf, fd;
struct mob_data *md;
struct status_data *status;
@ -4015,122 +4015,132 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons
sd->fd = 0;
//Go Backwards to give better priority to advanced skills.
for (i=0,j = MAX_SKILL_TREE-1;j>=0 && i< MAX_MOBSKILL ;j--) {
uint16 skill_id = skill_tree[pc_class2idx(sd->status.class_)][j].skill_id;
uint16 sk_idx = 0;
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(sd->status.class_);
if (!skill_id || !(sk_idx = skill_get_index(skill_id)) || sd->status.skill[sk_idx].lv < 1 ||
skill_get_inf2_(skill_id, { INF2_ISWEDDING, INF2_ISGUILD }) ||
mob_clone_disabled_skills(skill_id)
)
continue;
//Normal aggressive mob, disable skills that cannot help them fight
//against players (those with flags UF_NOMOB and UF_NOPC are specific
//to always aid players!) [Skotlex]
if (!(flag&1) &&
skill_get_unit_id(skill_id) &&
skill_get_unit_flag_(skill_id, { UF_NOMOB, UF_NOPC }))
continue;
/**
* The clone should be able to cast the skill (e.g. have the required weapon) bugreport:5299)
**/
if( !skill_check_condition_castbegin(sd,skill_id,sd->status.skill[sk_idx].lv) )
continue;
if( tree != nullptr && !tree->skills.empty() ){
std::vector<uint16> skill_list;
std::shared_ptr<s_mob_skill> ms = std::make_shared<s_mob_skill>();
for (const auto &it : tree->skills)
skill_list.push_back(it.first);
std::sort(skill_list.rbegin(), skill_list.rend());
ms->skill_id = skill_id;
ms->skill_lv = sd->status.skill[sk_idx].lv;
ms->state = MSS_ANY;
ms->permillage = 500*battle_config.mob_skill_rate/100; //Default chance of all skills: 5%
ms->emotion = -1;
ms->cancel = 0;
ms->casttime = skill_castfix(&sd->bl,skill_id, ms->skill_lv);
ms->delay = 5000+skill_delayfix(&sd->bl,skill_id, ms->skill_lv);
ms->msg_id = 0;
for (const auto &it : skill_list) {
if (db->skill.size() >= MAX_MOBSKILL)
break;
uint16 skill_id = it;
uint16 sk_idx = 0;
inf = skill_get_inf(skill_id);
if (inf&INF_ATTACK_SKILL) {
ms->target = MST_TARGET;
ms->cond1 = MSC_ALWAYS;
if (skill_get_range(skill_id, ms->skill_lv) > 3)
ms->state = MSS_ANYTARGET;
else
ms->state = MSS_BERSERK;
} else if(inf&INF_GROUND_SKILL) {
if (skill_get_inf2(skill_id, INF2_ISTRAP)) { //Traps!
ms->state = MSS_IDLE;
ms->target = MST_AROUND2;
ms->delay = 60000;
} else if (skill_get_unit_target(skill_id) == BCT_ENEMY) { //Target Enemy
ms->state = MSS_ANYTARGET;
if (!skill_id || !(sk_idx = skill_get_index(skill_id)) || sd->status.skill[sk_idx].lv < 1 ||
skill_get_inf2_(skill_id, { INF2_ISWEDDING, INF2_ISGUILD }) ||
mob_clone_disabled_skills(skill_id)
)
continue;
//Normal aggressive mob, disable skills that cannot help them fight
//against players (those with flags UF_NOMOB and UF_NOPC are specific
//to always aid players!) [Skotlex]
if (!(flag&1) &&
skill_get_unit_id(skill_id) &&
skill_get_unit_flag_(skill_id, { UF_NOMOB, UF_NOPC }))
continue;
/**
* The clone should be able to cast the skill (e.g. have the required weapon) bugreport:5299)
**/
if( !skill_check_condition_castbegin(sd,skill_id,sd->status.skill[sk_idx].lv) )
continue;
std::shared_ptr<s_mob_skill> ms = std::make_shared<s_mob_skill>();
ms->skill_id = skill_id;
ms->skill_lv = sd->status.skill[sk_idx].lv;
ms->state = MSS_ANY;
ms->permillage = 500*battle_config.mob_skill_rate/100; //Default chance of all skills: 5%
ms->emotion = -1;
ms->cancel = 0;
ms->casttime = skill_castfix(&sd->bl,skill_id, ms->skill_lv);
ms->delay = 5000+skill_delayfix(&sd->bl,skill_id, ms->skill_lv);
ms->msg_id = 0;
inf = skill_get_inf(skill_id);
if (inf&INF_ATTACK_SKILL) {
ms->target = MST_TARGET;
ms->cond1 = MSC_ALWAYS;
} else { //Target allies
ms->target = MST_FRIEND;
ms->cond1 = MSC_FRIENDHPLTMAXRATE;
ms->cond2 = 95;
}
} else if (inf&INF_SELF_SKILL) {
if (skill_get_inf2(skill_id, INF2_NOTARGETSELF)) { //auto-select target skill.
ms->target = MST_TARGET;
ms->cond1 = MSC_ALWAYS;
if (skill_get_range(skill_id, ms->skill_lv) > 3) {
if (skill_get_range(skill_id, ms->skill_lv) > 3)
ms->state = MSS_ANYTARGET;
} else {
ms->state = MSS_BERSERK;
}
} else { //Self skill
ms->target = MST_SELF;
ms->cond1 = MSC_MYHPLTMAXRATE;
ms->cond2 = 90;
ms->permillage = 2000;
//Delay: Remove the stock 5 secs and add half of the support time.
ms->delay += -5000 +(skill_get_time(skill_id, ms->skill_lv) + skill_get_time2(skill_id, ms->skill_lv))/2;
if (ms->delay < 5000)
ms->delay = 5000; //With a minimum of 5 secs.
}
} else if (inf&INF_SUPPORT_SKILL) {
ms->target = MST_FRIEND;
ms->cond1 = MSC_FRIENDHPLTMAXRATE;
ms->cond2 = 90;
if (skill_id == AL_HEAL)
ms->permillage = 5000; //Higher skill rate usage for heal.
else if (skill_id == ALL_RESURRECTION)
ms->cond2 = 1;
//Delay: Remove the stock 5 secs and add half of the support time.
ms->delay += -5000 +(skill_get_time(skill_id, ms->skill_lv) + skill_get_time2(skill_id, ms->skill_lv))/2;
if (ms->delay < 2000)
ms->delay = 2000; //With a minimum of 2 secs.
if (i+1 < MAX_MOBSKILL) { //duplicate this so it also triggers on self.
ms->target = MST_SELF;
ms->cond1 = MSC_MYHPLTMAXRATE;
db->skill.insert(db->skill.begin() + i, ms);
++i;
}
} else {
switch (skill_id) { //Certain Special skills that are passive, and thus, never triggered.
case MO_TRIPLEATTACK:
case TF_DOUBLE:
case GS_CHAINACTION:
else
ms->state = MSS_BERSERK;
} else if(inf&INF_GROUND_SKILL) {
if (skill_get_inf2(skill_id, INF2_ISTRAP)) { //Traps!
ms->state = MSS_IDLE;
ms->target = MST_AROUND2;
ms->delay = 60000;
} else if (skill_get_unit_target(skill_id) == BCT_ENEMY) { //Target Enemy
ms->state = MSS_ANYTARGET;
ms->target = MST_TARGET;
ms->cond1 = MSC_ALWAYS;
ms->permillage = skill_id==MO_TRIPLEATTACK?(3000-ms->skill_lv*100):(ms->skill_lv*500);
ms->delay -= 5000; //Remove the added delay as these could trigger on "all hits".
break;
default: //Untreated Skill
continue;
}
}
if (battle_config.mob_skill_rate!= 100)
ms->permillage = ms->permillage*battle_config.mob_skill_rate/100;
if (battle_config.mob_skill_delay != 100)
ms->delay = ms->delay*battle_config.mob_skill_delay/100;
} else { //Target allies
ms->target = MST_FRIEND;
ms->cond1 = MSC_FRIENDHPLTMAXRATE;
ms->cond2 = 95;
}
} else if (inf&INF_SELF_SKILL) {
if (skill_get_inf2(skill_id, INF2_NOTARGETSELF)) { //auto-select target skill.
ms->target = MST_TARGET;
ms->cond1 = MSC_ALWAYS;
if (skill_get_range(skill_id, ms->skill_lv) > 3) {
ms->state = MSS_ANYTARGET;
} else {
ms->state = MSS_BERSERK;
}
} else { //Self skill
ms->target = MST_SELF;
ms->cond1 = MSC_MYHPLTMAXRATE;
ms->cond2 = 90;
ms->permillage = 2000;
//Delay: Remove the stock 5 secs and add half of the support time.
ms->delay += -5000 +(skill_get_time(skill_id, ms->skill_lv) + skill_get_time2(skill_id, ms->skill_lv))/2;
if (ms->delay < 5000)
ms->delay = 5000; //With a minimum of 5 secs.
}
} else if (inf&INF_SUPPORT_SKILL) {
ms->target = MST_FRIEND;
ms->cond1 = MSC_FRIENDHPLTMAXRATE;
ms->cond2 = 90;
if (skill_id == AL_HEAL)
ms->permillage = 5000; //Higher skill rate usage for heal.
else if (skill_id == ALL_RESURRECTION)
ms->cond2 = 1;
//Delay: Remove the stock 5 secs and add half of the support time.
ms->delay += -5000 +(skill_get_time(skill_id, ms->skill_lv) + skill_get_time2(skill_id, ms->skill_lv))/2;
if (ms->delay < 2000)
ms->delay = 2000; //With a minimum of 2 secs.
db->skill.push_back(ms);
++i;
if (db->skill.size() < MAX_MOBSKILL) { //duplicate this so it also triggers on self.
ms->target = MST_SELF;
ms->cond1 = MSC_MYHPLTMAXRATE;
db->skill.push_back(ms);
}
} else {
switch (skill_id) { //Certain Special skills that are passive, and thus, never triggered.
case MO_TRIPLEATTACK:
case TF_DOUBLE:
case GS_CHAINACTION:
ms->state = MSS_BERSERK;
ms->target = MST_TARGET;
ms->cond1 = MSC_ALWAYS;
ms->permillage = skill_id==MO_TRIPLEATTACK?(3000-ms->skill_lv*100):(ms->skill_lv*500);
ms->delay -= 5000; //Remove the added delay as these could trigger on "all hits".
break;
default: //Untreated Skill
continue;
}
}
if (battle_config.mob_skill_rate!= 100)
ms->permillage = ms->permillage*battle_config.mob_skill_rate/100;
if (battle_config.mob_skill_delay != 100)
ms->delay = ms->delay*battle_config.mob_skill_delay/100;
db->skill.push_back(ms);
}
}
/**

View File

@ -70,8 +70,8 @@ static inline bool pc_attendance_rewarded_today( struct map_session_data* sd );
PlayerStatPointDatabase statpoint_db;
// h-files are for declarations, not for implementations... [Shinomori]
struct skill_tree_entry skill_tree[CLASS_COUNT][MAX_SKILL_TREE];
SkillTreeDatabase skill_tree_db;
// timer for night.day implementation
int day_timer_tid = INVALID_TIMER;
int night_timer_tid = INVALID_TIMER;
@ -2085,6 +2085,7 @@ void pc_calc_skilltree(struct map_session_data *sd)
ShowError( "pc_calc_skilltree: Unable to normalize job %s(%d) for character %s (%d:%d)\n", job_name( sd->status.class_ ), sd->status.class_, sd->status.name, sd->status.account_id, sd->status.char_id );
return;
}
uint16 job_id = class_;
class_ = pc_class2idx(class_);
for (const auto &skill : skill_db) {
@ -2125,19 +2126,18 @@ void pc_calc_skilltree(struct map_session_data *sd)
// Removes Taekwon Ranker skill bonus
if ((sd->class_&MAPID_UPPERMASK) != MAPID_TAEKWON) {
uint16 c_ = pc_class2idx(JOB_TAEKWON);
for (uint16 i = 0; i < MAX_SKILL_TREE; i++) {
uint16 sk_id = skill_tree[c_][i].skill_id;
uint16 sk_idx = 0;
if (!sk_id || !(sk_idx = skill_get_index(skill_tree[c_][i].skill_id)))
continue;
if (sd->status.skill[sk_idx].flag != SKILL_FLAG_PLAGIARIZED && sd->status.skill[sk_idx].flag != SKILL_FLAG_PERM_GRANTED) {
if (sk_id == NV_BASIC || sk_id == NV_FIRSTAID || sk_id == WE_CALLBABY)
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(JOB_TAEKWON);
if (tree != nullptr && !tree->skills.empty()) {
for (const auto &it : tree->skills) {
uint16 sk_idx = skill_get_index(it.first);
if (sk_idx == 0)
continue;
sd->status.skill[sk_idx].id = 0;
if (sd->status.skill[sk_idx].flag != SKILL_FLAG_PLAGIARIZED && sd->status.skill[sk_idx].flag != SKILL_FLAG_PERM_GRANTED) {
if (it.first == NV_BASIC || it.first == NV_FIRSTAID || it.first == WE_CALLBABY)
continue;
sd->status.skill[sk_idx].id = 0;
}
}
}
}
@ -2146,13 +2146,16 @@ void pc_calc_skilltree(struct map_session_data *sd)
pc_grant_allskills(sd, false);
int flag;
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(job_id);
do {
uint16 skid = 0;
flag = 0;
for (uint16 i = 0; i < MAX_SKILL_TREE && (skid = skill_tree[class_][i].skill_id) > 0; i++) {
if (tree == nullptr || tree->skills.empty())
break;
for (const auto &skillsit : tree->skills) {
bool fail = false;
uint16 skid = skillsit.first;
uint16 sk_idx = skill_get_index(skid);
if (sd->status.skill[sk_idx].id)
@ -2160,39 +2163,43 @@ void pc_calc_skilltree(struct map_session_data *sd)
if (!battle_config.skillfree) {
// Checking required skills
for(uint8 j = 0; j < MAX_PC_SKILL_REQUIRE; j++) {
uint16 sk_need_id = skill_tree[class_][i].need[j].skill_id;
uint16 sk_need_idx = 0;
std::shared_ptr<s_skill_tree_entry> entry = skillsit.second;
if (sk_need_id && (sk_need_idx = skill_get_index(sk_need_id))) {
short sk_need = sk_need_id;
if (entry != nullptr && !entry->need.empty()) {
for (const auto &it : entry->need) {
uint16 sk_need_id = it.first;
uint16 sk_need_idx = skill_get_index(sk_need_id);
if (sd->status.skill[sk_need_idx].id == 0 || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_PLAGIARIZED)
sk_need = 0; //Not learned.
else if (sd->status.skill[sk_need_idx].flag >= SKILL_FLAG_REPLACED_LV_0) //Real learned level
sk_need = sd->status.skill[sk_need_idx].flag - SKILL_FLAG_REPLACED_LV_0;
else
sk_need = pc_checkskill(sd,sk_need_id);
if (sk_need_idx > 0) {
uint16 sk_need = sk_need_id;
if (sk_need < skill_tree[class_][i].need[j].skill_lv) {
fail = true;
break;
if (sd->status.skill[sk_need_idx].id == 0 || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_PLAGIARIZED)
sk_need = 0; //Not learned.
else if (sd->status.skill[sk_need_idx].flag >= SKILL_FLAG_REPLACED_LV_0) //Real learned level
sk_need = sd->status.skill[sk_need_idx].flag - SKILL_FLAG_REPLACED_LV_0;
else
sk_need = pc_checkskill(sd,sk_need_id);
if (sk_need < it.second) {
fail = true;
break;
}
}
}
}
if (sd->status.base_level < skill_tree[class_][i].baselv) { //We need to get the actual class in this case
if (sd->status.base_level < entry->baselv) { //We need to get the actual class in this case
int c_ = pc_mapid2jobid(sd->class_, sd->status.sex);
c_ = pc_class2idx(c_);
if (class_ == c_ || (class_ != c_ && sd->status.base_level < skill_tree[class_][i].baselv))
if (class_ == c_ || (class_ != c_ && sd->status.base_level < entry->baselv))
fail = true; // base level requirement wasn't satisfied
}
if (sd->status.job_level < skill_tree[class_][i].joblv) { //We need to get the actual class in this case
if (sd->status.job_level < entry->joblv) { //We need to get the actual class in this case
int c_ = pc_mapid2jobid(sd->class_, sd->status.sex);
c_ = pc_class2idx(c_);
if (class_ == c_ || (class_ != c_ && sd->status.job_level < skill_tree[class_][i].joblv))
if (class_ == c_ || (class_ != c_ && sd->status.job_level < entry->joblv))
fail = true; // job level requirement wasn't satisfied
}
}
@ -2227,20 +2234,25 @@ void pc_calc_skilltree(struct map_session_data *sd)
- (c > 0) to avoid grant Novice Skill Tree in case of Skill Reset (need more logic)
- (sd->status.skill_point == 0) to wait until all skill points are assigned to avoid problems with Job Change quest. */
for( uint16 i = 0; i < MAX_SKILL_TREE && (skid = skill_tree[class_][i].skill_id) > 0; i++ ) {
uint16 sk_idx = skill_get_index(skid);
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(job_id);
if (sk_idx == 0)
continue;
if (tree != nullptr && !tree->skills.empty()) {
for (const auto &it : tree->skills) {
skid = it.first;
uint16 sk_idx = skill_get_index(skid);
if( skill_get_inf2_(skid, { INF2_ISQUEST, INF2_ISWEDDING }) )
continue; //Do not include Quest/Wedding skills.
if( sd->status.skill[sk_idx].id == 0 ) {
sd->status.skill[sk_idx].id = skid;
sd->status.skill[sk_idx].flag = SKILL_FLAG_TEMPORARY; // So it is not saved, and tagged as a "bonus" skill.
} else if( skid != NV_BASIC )
sd->status.skill[sk_idx].flag = SKILL_FLAG_REPLACED_LV_0 + sd->status.skill[sk_idx].lv; // Remember original level
sd->status.skill[sk_idx].lv = skill_tree_get_max(skid, sd->status.class_);
if (sk_idx == 0)
continue;
if( skill_get_inf2_(skid, { INF2_ISQUEST, INF2_ISWEDDING }) )
continue; //Do not include Quest/Wedding skills.
if( sd->status.skill[sk_idx].id == 0 ) {
sd->status.skill[sk_idx].id = skid;
sd->status.skill[sk_idx].flag = SKILL_FLAG_TEMPORARY; // So it is not saved, and tagged as a "bonus" skill.
} else if( skid != NV_BASIC )
sd->status.skill[sk_idx].flag = SKILL_FLAG_REPLACED_LV_0 + sd->status.skill[sk_idx].lv; // Remember original level
sd->status.skill[sk_idx].lv = skill_tree_get_max(skid, sd->status.class_);
}
}
}
@ -2274,13 +2286,15 @@ static void pc_check_skilltree(struct map_session_data *sd)
ShowError("pc_check_skilltree: Unable to normalize job %d for character %s (%d:%d)\n", sd->status.class_, sd->status.name, sd->status.account_id, sd->status.char_id);
return;
}
c = pc_class2idx(c);
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(c);
if (tree == nullptr || tree->skills.empty())
return;
do {
uint16 skid = 0;
flag = 0;
for (int i = 0; i < MAX_SKILL_TREE && (skid = skill_tree[c][i].skill_id) > 0; i++ ) {
for (const auto &skillsit : tree->skills) {
uint16 skid = skillsit.first;
uint16 sk_idx = skill_get_index(skid);
bool fail = false;
@ -2288,30 +2302,34 @@ static void pc_check_skilltree(struct map_session_data *sd)
continue;
// Checking required skills
for (uint8 j = 0; j < MAX_PC_SKILL_REQUIRE; j++) {
uint16 sk_need_id = skill_tree[c][i].need[j].skill_id;
uint16 sk_need_idx = 0;
std::shared_ptr<s_skill_tree_entry> entry = skillsit.second;
if (sk_need_id && (sk_need_idx = skill_get_index(sk_need_id))) {
short sk_need = sk_need_id;
if (entry != nullptr && !entry->need.empty()) {
for (const auto &it : entry->need) {
uint16 sk_need_id = it.first;
uint16 sk_need_idx = skill_get_index(sk_need_id);
if (sd->status.skill[sk_need_idx].id == 0 || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_PLAGIARIZED)
sk_need = 0; //Not learned.
else if (sd->status.skill[sk_need_idx].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level
sk_need = sd->status.skill[sk_need_idx].flag - SKILL_FLAG_REPLACED_LV_0;
else
sk_need = pc_checkskill(sd,sk_need_id);
if (sk_need_id > 0) {
short sk_need = sk_need_id;
if (sk_need < skill_tree[c][i].need[j].skill_lv) {
fail = true;
break;
if (sd->status.skill[sk_need_idx].id == 0 || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[sk_need_idx].flag == SKILL_FLAG_PLAGIARIZED)
sk_need = 0; //Not learned.
else if (sd->status.skill[sk_need_idx].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level
sk_need = sd->status.skill[sk_need_idx].flag - SKILL_FLAG_REPLACED_LV_0;
else
sk_need = pc_checkskill(sd,sk_need_id);
if (sk_need < it.second) {
fail = true;
break;
}
}
}
}
if( fail )
continue;
if (sd->status.base_level < skill_tree[c][i].baselv || sd->status.job_level < skill_tree[c][i].joblv)
if (sd->status.base_level < entry->baselv || sd->status.job_level < entry->joblv)
continue;
std::shared_ptr<s_skill_db> skill = skill_db.find(skid);
@ -7980,23 +7998,28 @@ int pc_allskillup(struct map_session_data *sd)
if (!pc_grant_allskills(sd, true)) {
uint16 sk_id;
for (i = 0; i < MAX_SKILL_TREE && (sk_id = skill_tree[pc_class2idx(sd->status.class_)][i].skill_id) > 0;i++){
uint16 sk_idx = skill_get_index(sk_id);
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(sd->status.class_);
if (sk_id == 0 || sk_idx == 0)
continue;
if (tree != nullptr && !tree->skills.empty()) {
for (const auto &skillsit : tree->skills) {
sk_id = skillsit.first;
uint16 sk_idx = skill_get_index(sk_id);
std::shared_ptr<s_skill_db> skill = skill_db.find(sk_id);
if (sk_idx == 0)
continue;
if (
(skill->inf2[INF2_ISQUEST] && !battle_config.quest_skill_learn) ||
((skill->inf2[INF2_ISWEDDING] || skill->inf2[INF2_ISSPIRIT])) ||
sk_id == SG_DEVIL
)
continue; //Cannot be learned normally.
std::shared_ptr<s_skill_db> skill = skill_db.find(sk_id);
sd->status.skill[sk_idx].id = sk_id;
sd->status.skill[sk_idx].lv = skill_tree_get_max(sk_id, sd->status.class_); // celest
if (
(skill->inf2[INF2_ISQUEST] && !battle_config.quest_skill_learn) ||
((skill->inf2[INF2_ISWEDDING] || skill->inf2[INF2_ISSPIRIT])) ||
sk_id == SG_DEVIL
)
continue; //Cannot be learned normally.
sd->status.skill[sk_idx].id = sk_id;
sd->status.skill[sk_idx].lv = skill_tree_get_max(sk_id, sd->status.class_); // celest
}
}
}
status_calc_pc(sd,SCO_NONE);
@ -9585,13 +9608,15 @@ bool pc_jobchange(struct map_session_data *sd,int job, char upper)
}
if ( (b_class&MAPID_UPPERMASK) != (sd->class_&MAPID_UPPERMASK) ) { //Things to remove when changing class tree.
const int class_ = pc_class2idx(sd->status.class_);
uint16 skill_id;
for(i = 0; i < MAX_SKILL_TREE && (skill_id = skill_tree[class_][i].skill_id) > 0; i++) {
//Remove status specific to your current tree skills.
enum sc_type sc = status_skill2sc(skill_id);
if (sc > SC_COMMON_MAX && sd->sc.data[sc])
status_change_end(&sd->bl, sc, INVALID_TIMER);
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(sd->status.class_);
if (tree != nullptr && !tree->skills.empty()) {
for (const auto &skillsit : tree->skills) {
//Remove status specific to your current tree skills.
enum sc_type sc = status_skill2sc(skillsit.first);
if (sc > SC_COMMON_MAX && sd->sc.data[sc])
status_change_end(&sd->bl, sc, INVALID_TIMER);
}
}
}
@ -12073,103 +12098,304 @@ int pc_split_atoui(char* str, unsigned int* val, char sep, int max)
return i;
}
/*==========================================
* sub DB reading.
* Function used to read skill_tree.txt
*------------------------------------------*/
static bool pc_readdb_skilltree(char* fields[], int columns, int current)
{
uint32 baselv, joblv, baselv_max, joblv_max;
uint16 skill_id, skill_lv, skill_lv_max;
int idx, class_;
unsigned int i, offset, skill_idx;
class_ = atoi(fields[0]);
skill_id = (uint16)atoi(fields[1]);
skill_lv = (uint16)atoi(fields[2]);
std::shared_ptr<s_skill_tree_entry> SkillTreeDatabase::get_skill_data(int class_, uint16 skill_id) {
std::shared_ptr<s_skill_tree> tree = this->find(class_);
if (columns == 5 + MAX_PC_SKILL_REQUIRE * 2) { // Base/Job level requirement extra columns
baselv = (uint32)atoi(fields[3]);
joblv = (uint32)atoi(fields[4]);
offset = 5;
}
else if (columns == 3 + MAX_PC_SKILL_REQUIRE * 2) {
baselv = joblv = 0;
offset = 3;
}
else {
ShowWarning("pc_readdb_skilltree: Invalid number of colums in skill %hu of job %d's tree.\n", skill_id, class_);
return false;
}
if (tree != nullptr)
return util::umap_find(tree->skills, skill_id);
if(!pcdb_checkid(class_))
{
ShowWarning("pc_readdb_skilltree: Invalid job class %d specified.\n", class_);
return false;
}
idx = pc_class2idx(class_);
return nullptr;
}
if (!skill_get_index(skill_id)) {
ShowWarning("pc_readdb_skilltree: Unable to load skill %hu into job %d's tree.\n", skill_id, class_);
return false;
}
if (skill_lv > (skill_lv_max = skill_get_max(skill_id))) {
ShowWarning("pc_readdb_skilltree: Skill %hu's level %hu exceeds job %d's max level %hu. Capping skill level.\n", skill_id, skill_lv, class_, skill_lv_max);
skill_lv = skill_lv_max;
}
if (baselv > (baselv_max = job_db.get_maxBaseLv(class_))) {
ShowWarning("pc_readdb_skilltree: Skill %hu's base level requirement %d exceeds job %d's max base level %d. Capping skill base level.\n", skill_id, baselv, class_, baselv_max);
baselv = baselv_max;
}
if (joblv > (joblv_max = job_db.get_maxJobLv(class_))) {
ShowWarning("pc_readdb_skilltree: Skill %hu's job level requirement %d exceeds job %d's max job level %d. Capping skill job level.\n", skill_id, joblv, class_, joblv_max);
joblv = joblv_max;
}
const std::string SkillTreeDatabase::getDefaultLocation() {
return std::string(db_path) + "/skill_tree.yml";
}
//This is to avoid adding two lines for the same skill. [Skotlex]
ARR_FIND( 0, MAX_SKILL_TREE, skill_idx, skill_tree[idx][skill_idx].skill_id == 0 || skill_tree[idx][skill_idx].skill_id == skill_id );
if( skill_idx == MAX_SKILL_TREE )
{
ShowWarning("pc_readdb_skilltree: Unable to load skill %hu into job %d's tree. Maximum number of skills per job has been reached.\n", skill_id, class_);
return false;
}
else if(skill_tree[idx][skill_idx].skill_id)
{
ShowNotice("pc_readdb_skilltree: Overwriting skill %hu for job %d.\n", skill_id, class_);
/**
* Reads and parses an entry from the skill_tree.
* @param node: YAML node containing the entry.
* @return count of successfully parsed rows
*/
uint64 SkillTreeDatabase::parseBodyNode(const YAML::Node &node) {
std::string job_name;
if (!this->asString(node, "Job", job_name))
return 0;
int64 constant;
std::string job_name_constant = "JOB_" + job_name;
if (!script_get_constant(job_name_constant.c_str(), &constant) || !pcdb_checkid(constant)) {
this->invalidWarning(node["Job"], "Invalid job %s.\n", job_name.c_str());
return 0;
}
uint16 job_id = static_cast<uint16>(constant);
skill_tree[idx][skill_idx].skill_id = skill_id;
skill_tree[idx][skill_idx].skill_lv = skill_lv;
skill_tree[idx][skill_idx].baselv = baselv;
skill_tree[idx][skill_idx].joblv = joblv;
std::shared_ptr<s_skill_tree> tree = this->find(job_id);
bool exists = tree != nullptr;
for(i = 0; i < MAX_PC_SKILL_REQUIRE; i++)
{
skill_id = (uint16)atoi(fields[i * 2 + offset]);
skill_lv = (uint16)atoi(fields[i * 2 + offset + 1]);
if (!exists)
tree = std::make_shared<s_skill_tree>();
if (skill_id == 0) {
if (skill_tree[idx][skill_idx].need[i].skill_id > 0) { // Remove pre-requisite
skill_tree[idx][skill_idx].need[i].skill_id = 0;
skill_tree[idx][skill_idx].need[i].skill_lv = 0;
if (this->nodeExists(node, "Inherit")) {
const YAML::Node &InheritNode = node["Inherit"];
for (const auto &Inheritit : InheritNode) {
std::string inheritname = Inheritit.first.as<std::string>();
std::string inheritname_constant = "JOB_" + inheritname;
if (!script_get_constant(inheritname_constant.c_str(), &constant) || !pcdb_checkid(constant)) {
this->invalidWarning(InheritNode[inheritname], "Invalid job %s.\n", inheritname.c_str());
return 0;
}
continue;
}
if (skill_id > MAX_SKILL_ID || !skill_get_index(skill_id)) {
ShowWarning("pc_readdb_skilltree: Unable to load requirement skill %hu into job %d's tree.", skill_id, class_);
return false;
}
if (skill_lv > (skill_lv_max = skill_get_max(skill_id))) {
ShowWarning("pc_readdb_skilltree: Skill %hu's level %hu exceeds job %d's max level %hu. Capping skill level.\n", skill_id, skill_lv, class_, skill_lv_max);
skill_lv = skill_lv_max;
}
skill_tree[idx][skill_idx].need[i].skill_id = skill_id;
skill_tree[idx][skill_idx].need[i].skill_lv = skill_lv;
bool active;
if (!this->asBool(InheritNode, inheritname, active))
return 0;
uint16 inherit_job = static_cast<uint16>(constant);
if (!active) {
if (exists)
util::vector_erase_if_exists(tree->inherit_job, inherit_job);
}
else {
if (!util::vector_exists(tree->inherit_job, inherit_job))
tree->inherit_job.push_back(inherit_job);
}
}
}
if (this->nodeExists(node, "Tree")) {
for (const auto &it : node["Tree"]) {
std::string skill_name;
if (!this->asString(it, "Name", skill_name))
return 0;
uint16 skill_id = skill_name2id(skill_name.c_str());
if (skill_id == 0) {
this->invalidWarning(it["Name"], "Invalid skill name \"%s\".\n", skill_name.c_str());
return 0;
}
if (!skill_get_index(skill_id)) {
this->invalidWarning(it["Name"], "Unable to load skill %s into job %hu's tree.\n", skill_name.c_str(), job_id);
return 0;
}
std::shared_ptr<s_skill_tree_entry> entry;
bool skill_exists = tree->skills.count(skill_id) > 0;
if (skill_exists)
entry = tree->skills[skill_id];
else
entry = std::make_shared<s_skill_tree_entry>();
entry->skill_id = skill_id;
uint16 max_lv;
if (!this->asUInt16(it, "MaxLevel", max_lv))
return 0;
if (max_lv > MAX_SKILL_LEVEL) {
this->invalidWarning(it["MaxLevel"], "MaxLevel exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL);
return 0;
}
uint16 skill_lv_max = skill_get_max(skill_id);
if (max_lv > skill_lv_max) {
this->invalidWarning(it["MaxLevel"], "Skill %s's level %hu exceeds the skill's max level %hu. Capping skill level.\n", skill_name.c_str(), max_lv, skill_lv_max);
max_lv = skill_lv_max;
}
// if (max_lv == 0) { // skill lvl 0 removed on loadingFinished (because of inherit)
// if (!skill_exists || entry->skill_id.erase(skill_id) == 0)
// this->invalidWarning(it["Name"], "Failed to erase %s, the skill doesn't exist in for job %s, skipping.\n", skill_name.c_str(), job_name.c_str());
// continue;
// }
entry->max_lv = max_lv;
if (this->nodeExists(it, "BaseLevel")) {
uint32 baselv;
if (!this->asUInt32(it, "BaseLevel", baselv))
return 0;
uint32 baselv_max = job_db.get_maxBaseLv(job_id);
if (baselv > baselv_max) {
this->invalidWarning(it["BaseLevel"], "Skill %hu's base level requirement %hu exceeds job %s's max base level %d. Capping skill base level.\n",
skill_id, baselv, job_name.c_str(), baselv_max);
baselv = baselv_max;
}
entry->baselv = baselv;
} else {
if (!skill_exists)
entry->baselv = 0;
}
if (this->nodeExists(it, "JobLevel")) {
uint32 joblv;
if (!this->asUInt32(it, "JobLevel", joblv))
return 0;
uint32 joblv_max = job_db.get_maxJobLv(job_id);
if (joblv > joblv_max) {
this->invalidWarning(it["JobLevel"], "Skill %hu's job level requirement %hu exceeds job %s's max job level %d. Capping skill job level.\n",
skill_id, joblv, job_name.c_str(), joblv_max);
joblv = joblv_max;
}
entry->joblv = joblv;
} else {
if (!skill_exists)
entry->joblv = 0;
}
if (this->nodeExists(it, "Requires")) {
for (const auto &Requiresit : it["Requires"]) {
if (!this->nodesExist(Requiresit, { "Name" }))
return 0;
std::string skill_name_req;
if (!this->asString(Requiresit, "Name", skill_name_req))
return 0;
uint16 skill_id_req = skill_name2id(skill_name_req.c_str());
if (skill_id_req == 0) {
this->invalidWarning(Requiresit["Name"], "Invalid skill name \"%s\".\n", skill_name_req.c_str());
return 0;
}
uint16 lv_req;
if (!this->asUInt16(Requiresit, "Level", lv_req))
return 0;
if (lv_req > MAX_SKILL_LEVEL) {
this->invalidWarning(Requiresit["Level"], "Level exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL);
return 0;
}
uint16 lv_req_max = skill_get_max(skill_id_req);
if (lv_req > lv_req_max) {
this->invalidWarning(it["MaxLevel"], "Required skill %s's level %hu exceeds the skill's max level %hu. Capping skill level.\n", skill_name.c_str(), lv_req, lv_req_max);
lv_req = lv_req_max;
}
if (lv_req == 0) {
if (entry->need.erase(skill_id_req) == 0)
this->invalidWarning(Requiresit["Name"], "Failed to erase %s, the skill doesn't exist in for job %s, skipping.\n", skill_name_req.c_str(), job_name.c_str());
continue;
}
entry->need[skill_id_req] = lv_req;
}
}
if (this->nodeExists(it, "Exclude")) {
bool exclude;
if (!this->asBool(it, "Exclude", exclude))
return 0;
entry->exclude_inherit = exclude;
} else {
if (!skill_exists)
entry->exclude_inherit = false;
}
if (!skill_exists)
tree->skills.insert({ skill_id, entry });
}
}
if (!exists)
this->put(job_id, tree);
return true;
}
void SkillTreeDatabase::loadingFinished() {
std::unordered_map<uint16, std::shared_ptr<s_skill_tree>> job_tree; // get the data from skill_tree_db before populate it
for (auto &data : *this) {
if (data.second->inherit_job.empty())
continue;
std::shared_ptr<s_skill_tree> skill_tree = std::make_shared<s_skill_tree>();
uint32 baselv_max = job_db.get_maxBaseLv(data.first);
uint32 joblv_max = job_db.get_maxJobLv(data.first);
for (const auto &inherit_job : data.second->inherit_job) {
std::shared_ptr<s_skill_tree> tree = this->find(inherit_job);
if (tree == nullptr || tree->skills.empty())
continue;
for (const auto &it : tree->skills) {
if (it.second->exclude_inherit)
continue;
if (data.second->skills.count(it.first) > 0) // skill already in the skill tree
continue;
if (skill_tree->skills.count(it.first) > 0) // replaced by the last inheritance
skill_tree->skills[it.first] = it.second;
else
skill_tree->skills.insert({ it.first, it.second });
std::shared_ptr<s_skill_tree_entry> skill = skill_tree->skills[it.first];
if (skill->baselv > baselv_max) {
ShowWarning("SkillTreeDatabase: Skill %s (%hu)'s base level requirement %hu exceeds job %s's max base level %d. Capping skill base level.\n",
skill_get_name(skill->skill_id), skill->skill_id, skill->baselv, job_name(data.first), baselv_max);
skill->baselv = baselv_max;
}
if (skill->joblv > joblv_max) {
ShowWarning("SkillTreeDatabase: Skill %s (%hu)'s job level requirement %hu exceeds job %s's max job level %d. Capping skill job level.\n",
skill_get_name(skill->skill_id), skill->skill_id, skill->joblv, job_name(data.first), joblv_max);
skill->joblv = joblv_max;
}
}
}
if (skill_tree != nullptr && !skill_tree->skills.empty())
job_tree.insert({ data.first, skill_tree });
}
if (!job_tree.empty()) {
for (auto &data : *this) {
if (job_tree.count(data.first) == 0)
continue;
data.second->skills.insert(job_tree[data.first]->skills.begin(), job_tree[data.first]->skills.end());
}
}
// remove skills with max_lv = 0
for (const auto &job : *this) {
if (job.second->skills.empty())
continue;
auto it = job.second->skills.begin();
while( it != job.second->skills.end() ){
if( it->second->max_lv == 0 ){
it = job.second->skills.erase( it );
}else{
it++;
}
}
}
}
/**
* Calculates base hp of player. Reference: http://irowiki.org/wiki/Max_HP
* @param level: Base level of player
@ -12844,9 +13070,7 @@ void pc_readdb(void) {
}
// Reset and read skilltree - needs to be read after pc_readdb_job_exp to get max base and job levels
memset(skill_tree, 0, sizeof(skill_tree));
sv_readdb(db_path, DBPATH"skill_tree.txt", ',', 3 + MAX_PC_SKILL_REQUIRE * 2, 5 + MAX_PC_SKILL_REQUIRE * 2, -1, &pc_readdb_skilltree, 0);
sv_readdb(db_path, DBIMPORT"/skill_tree.txt", ',', 3 + MAX_PC_SKILL_REQUIRE * 2, 5 + MAX_PC_SKILL_REQUIRE * 2, -1, &pc_readdb_skilltree, 1);
skill_tree_db.reload();
statpoint_db.load();
}

View File

@ -32,7 +32,6 @@ enum e_log_pick_type : uint32;
enum sc_type : int16;
#define MAX_PC_BONUS 50 /// Max bonus, usually used by item bonus
#define MAX_PC_SKILL_REQUIRE 5 /// Max skill tree requirement
#define MAX_PC_FEELHATE 3 /// Max feel hate info
#define DAMAGELOG_SIZE_PC 100 /// Damage log
#define MAX_SPIRITBALL 15 /// Max spirit balls
@ -59,9 +58,6 @@ enum sc_type : int16;
#define ATTENDANCE_COUNT_VAR "#AttendanceCounter"
#define ACHIEVEMENTLEVEL "AchievementLevel"
//Update this max as necessary. 55 is the value needed for Super Baby currently
//Raised to 105 since Expanded Super Baby needs it.
#define MAX_SKILL_TREE 105
//Total number of classes (for data storage)
#define CLASS_COUNT (JOB_MAX - JOB_NOVICE_HIGH + JOB_MAX_BASIC)
@ -1432,14 +1428,33 @@ int pc_mapid2jobid(uint64 class_, int sex); // Skotlex
const char * job_name(int class_);
struct skill_tree_entry {
uint16 skill_id, skill_lv;
struct s_skill_tree_entry {
uint16 skill_id, max_lv;
uint32 baselv, joblv;
struct {
uint16 skill_id, skill_lv;
} need[MAX_PC_SKILL_REQUIRE];
}; // Celest
extern struct skill_tree_entry skill_tree[CLASS_COUNT][MAX_SKILL_TREE];
std::unordered_map<uint16, uint16> need; /// skill_id, skill_lv
bool exclude_inherit; // exclude the skill from inherit when loading the table
};
struct s_skill_tree {
std::vector<uint16> inherit_job;
std::unordered_map<uint16, std::shared_ptr<s_skill_tree_entry>> skills; /// skill_id, entry
};
class SkillTreeDatabase : public TypesafeYamlDatabase<uint16, s_skill_tree> {
public:
SkillTreeDatabase() : TypesafeYamlDatabase("SKILL_TREE_DB", 1) {
}
const std::string getDefaultLocation();
uint64 parseBodyNode(const YAML::Node& node);
void loadingFinished();
// Additional
std::shared_ptr<s_skill_tree_entry> get_skill_data(int class_, uint16 skill_id);
};
extern SkillTreeDatabase skill_tree_db;
struct sg_data {
short anger_id;

View File

@ -11952,11 +11952,16 @@ BUILDIN_FUNC(sc_end_class)
return SCRIPT_CMD_FAILURE;
}
for (int i = 0; i < MAX_SKILL_TREE && (skill_id = skill_tree[pc_class2idx(class_)][i].skill_id) > 0; i++) {
enum sc_type sc = status_skill2sc(skill_id);
std::shared_ptr<s_skill_tree> tree = skill_tree_db.find(class_);
if (sc > SC_COMMON_MAX && sd->sc.data[sc])
status_change_end(&sd->bl, sc, INVALID_TIMER);
if( tree != nullptr ){
for (const auto &it : tree->skills) {
skill_id = it.first;
enum sc_type sc = status_skill2sc(skill_id);
if (sc > SC_COMMON_MAX && sd->sc.data[sc])
status_change_end(&sd->bl, sc, INVALID_TIMER);
}
}
return SCRIPT_CMD_SUCCESS;

View File

@ -311,14 +311,10 @@ bool skill_get_unit_flag_(uint16 skill_id, std::vector<e_skill_unit_flag> unit)
return false;
}
int skill_tree_get_max(uint16 skill_id, int b_class)
{
int i;
b_class = pc_class2idx(b_class);
ARR_FIND( 0, MAX_SKILL_TREE, i, skill_tree[b_class][i].skill_id == 0 || skill_tree[b_class][i].skill_id == skill_id );
if( i < MAX_SKILL_TREE && skill_tree[b_class][i].skill_id == skill_id )
return skill_tree[b_class][i].skill_lv;
int skill_tree_get_max(uint16 skill_id, int b_class) {
auto skill = skill_tree_db.get_skill_data(b_class, skill_id);
if (skill != nullptr)
return skill->max_lv;
else
return skill_get_max(skill_id);
}

View File

@ -138,6 +138,14 @@ static void mercenary_skill_txt_data(const std::string& modePath, const std::str
sv_readdb(fixedPath.c_str(), "mercenary_skill_db.txt", ',', 3, 3, -1, mercenary_read_skilldb, false);
}
// Skill tree database data to memory
static void skilltree_txt_data(const std::string &modePath, const std::string &fixedPath) {
skill_tree.clear();
if (fileExists(modePath + "/skill_tree.txt"))
sv_readdb(modePath.c_str(), "skill_tree.txt", ',', 3 + MAX_PC_SKILL_REQUIRE * 2, 5 + MAX_PC_SKILL_REQUIRE * 2, -1, pc_readdb_skilltree, false);
}
template<typename Func>
bool process( const std::string& type, uint32 version, const std::vector<std::string>& paths, const std::string& name, Func lambda, const std::string& rename = "" ){
for( const std::string& path : paths ){
@ -503,6 +511,21 @@ int do_init( int argc, char** argv ){
})) {
return 0;
}
skilltree_txt_data(path_db_mode, path_db);
if (!process("SKILL_TREE_DB", 1, { path_db_mode }, "skill_tree", [](const std::string &path, const std::string &name_ext) -> bool {
return pc_readdb_skilltree_yaml();
})) {
return 0;
}
skilltree_txt_data(path_db_import, path_db_import);
if (!process("SKILL_TREE_DB", 1, { path_db_import }, "skill_tree", [](const std::string &path, const std::string &name_ext) -> bool {
return pc_readdb_skilltree_yaml();
})) {
return 0;
}
// TODO: add implementations ;-)
return 0;
@ -4650,3 +4673,111 @@ static bool mercenary_readdb(char* str[], int columns, int current) {
return true;
}
// Copied and adjusted from pc.cpp
static bool pc_readdb_skilltree(char* fields[], int columns, int current) {
uint16 baselv, joblv, offset;
uint16 class_ = (uint16)atoi(fields[0]);
uint16 skill_id = (uint16)atoi(fields[1]);
if (columns == 5 + MAX_PC_SKILL_REQUIRE * 2) { // Base/Job level requirement extra columns
baselv = (uint16)atoi(fields[3]);
joblv = (uint16)atoi(fields[4]);
offset = 5;
}
else if (columns == 3 + MAX_PC_SKILL_REQUIRE * 2) {
baselv = joblv = 0;
offset = 3;
}
else {
ShowWarning("pc_readdb_skilltree: Invalid number of colums in skill %hu of job %d's tree.\n", skill_id, class_);
return false;
}
const char* constant = constant_lookup(class_, "JOB_");
if (constant == nullptr) {
ShowWarning("pc_readdb_skilltree: Invalid job class %d specified.\n", class_);
return false;
}
std::string job_name(constant);
std::string* skill_name = util::umap_find( aegis_skillnames, skill_id );
if( skill_name == nullptr ){
ShowWarning("pc_readdb_skilltree: Unable to load skill %hu into job %d's tree.\n", skill_id, class_);
return false;
}
uint16 skill_lv = (uint16)atoi(fields[2]);
std::vector<s_skill_tree_entry_csv> *job = util::map_find(skill_tree, class_);
bool exists = job != nullptr;
s_skill_tree_entry_csv entry;
entry.skill_name = *skill_name;
entry.skill_id = skill_id;
entry.skill_lv = skill_lv;
entry.baselv = baselv;
entry.joblv = joblv;
for (uint16 i = 0; i < MAX_PC_SKILL_REQUIRE; i++) {
uint16 req_skill_id = (uint16)atoi(fields[i * 2 + offset]);
skill_lv = (uint16)atoi(fields[i * 2 + offset + 1]);
if (req_skill_id == 0)
continue;
skill_name = util::umap_find( aegis_skillnames, req_skill_id );
if( skill_name == nullptr ){
ShowWarning("pc_readdb_skilltree: Unable to load requirement skill %hu into job %d's tree.", req_skill_id, class_);
return false;
}
entry.need.insert({ *skill_name, skill_lv });
}
if (exists)
job->push_back(entry);
else {
std::vector<s_skill_tree_entry_csv> tree;
tree.push_back(entry);
skill_tree.insert({ class_, tree });
}
return true;
}
static bool pc_readdb_skilltree_yaml(void) {
for (const auto &it : skill_tree) {
body << YAML::BeginMap;
body << YAML::Key << "Job" << YAML::Value << constant_lookup(it.first, "JOB_");
body << YAML::Key << "Tree";
body << YAML::BeginSeq;
for (const auto &subit : it.second) {
body << YAML::BeginMap;
body << YAML::Key << "Name" << YAML::Value << subit.skill_name;
body << YAML::Key << "MaxLevel" << YAML::Value << subit.skill_lv;
if (!subit.need.empty()) {
body << YAML::Key << "Requires";
body << YAML::BeginSeq;
for (const auto &terit : subit.need) {
body << YAML::BeginMap;
body << YAML::Key << "Name" << YAML::Value << terit.first;
body << YAML::Key << "Level" << YAML::Value << terit.second;
body << YAML::EndMap;
}
body << YAML::EndSeq;
}
if (subit.baselv > 0)
body << YAML::Key << "BaseLevel" << YAML::Value << subit.baselv;
if (subit.joblv > 0)
body << YAML::Key << "JobLevel" << YAML::Value << subit.joblv;
body << YAML::EndMap;
}
body << YAML::EndSeq;
body << YAML::EndMap;
}
return true;
}

View File

@ -16,6 +16,17 @@
#define MAX_ARROW_RESULT 5 /// Max Arrow results/created
#define MAX_SKILL_ARROW_DB 150 /// Max Arrow Creation DB
#define MAX_ITEMRATIO_MOBS 10
//Update this max as necessary. 55 is the value needed for Super Baby currently
//Raised to 105 since Expanded Super Baby needs it.
#define MAX_SKILL_TREE 105
#define MAX_PC_SKILL_REQUIRE 5 /// Max skill tree requirement
struct s_skill_tree_entry_csv {
std::string skill_name;
uint16 skill_id, skill_lv, baselv, joblv;
std::map<std::string, uint16> need; /// skill_id, skill_lv
};
std::map<uint16, std::vector<s_skill_tree_entry_csv>> skill_tree; /// job id (for order), entry
// Database to memory maps
struct s_skill_unit_csv : s_skill_db {
@ -495,5 +506,7 @@ static bool read_elemental_skilldb(char* str[], int columns, int current);
static bool read_elementaldb(char* str[], int columns, int current);
static bool mercenary_read_skilldb(char* str[], int columns, int current);
static bool mercenary_readdb(char* str[], int columns, int current);
static bool pc_readdb_skilltree(char* str[], int columns, int current);
static bool pc_readdb_skilltree_yaml(void);
#endif /* CSV2YAML_HPP */