summaryrefslogtreecommitdiff
path: root/src/map/pc.c
diff options
context:
space:
mode:
authorHaru <haru@dotalux.com>2016-01-03 03:45:30 +0100
committerHaru <haru@dotalux.com>2016-01-03 21:46:15 +0100
commit818acc5866707b9e294a87d2dd902aebb670707f (patch)
tree1d507deb7d066ab066bdab2cf7bd4f549c471278 /src/map/pc.c
parent87476b17df574f4c1cb1ef46c3c8e47419ea5a08 (diff)
downloadhercules-818acc5866707b9e294a87d2dd902aebb670707f.tar.gz
hercules-818acc5866707b9e294a87d2dd902aebb670707f.tar.bz2
hercules-818acc5866707b9e294a87d2dd902aebb670707f.tar.xz
hercules-818acc5866707b9e294a87d2dd902aebb670707f.zip
Rewritten skill_tree parser in a more robust way
- Fixes an issue that prevented skills with more than 4 pre-requisites or more than 3 pre-requisites and a minimum level from being parsed correctly (and without any warning or error messages). - Removes the limit on 5 pre-requisites (replaced a fixed size array with a VECTOR) - Reduces memory usage of skill_tree from 794kB to 440kB (32 bit) or 523kB (64 bit). - Fixes an issue that prevented multiple inheritance from working correctly in rare cases (incorrect definition order), without any warning or error messages. Now a warning is displayed if a job is inherited before being defined. - Fixes an issue that prevented skills inherited from being correctly merged with the skills defined for the current job. - Prevents a job from inheriting itself by accident. - Correctly detects skills defined twice for the same job. Signed-off-by: Haru <haru@dotalux.com>
Diffstat (limited to 'src/map/pc.c')
-rw-r--r--src/map/pc.c390
1 files changed, 243 insertions, 147 deletions
diff --git a/src/map/pc.c b/src/map/pc.c
index d697cd9f4..1045a25be 100644
--- a/src/map/pc.c
+++ b/src/map/pc.c
@@ -2,7 +2,7 @@
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
- * Copyright (C) 2012-2015 Hercules Dev Team
+ * Copyright (C) 2012-2016 Hercules Dev Team
* Copyright (C) Athena Dev Teams
*
* Hercules is free software: you can redistribute it and/or modify
@@ -1526,43 +1526,42 @@ int pc_calc_skilltree(struct map_session_data *sd)
do {
flag = 0;
- for( i = 0; i < MAX_SKILL_TREE && (id = pc->skill_tree[c][i].id) > 0; i++ ) {
- int f, idx = pc->skill_tree[c][i].idx;
- if( sd->status.skill[idx].id )
+ for (i = 0; i < MAX_SKILL_TREE && (id = pc->skill_tree[c][i].id) > 0; i++) {
+ int idx = pc->skill_tree[c][i].idx;
+ bool satisfied = true;
+ if (sd->status.skill[idx].id > 0)
continue; //Skill already known.
- f = 1;
- if(!battle_config.skillfree) {
+ if (!battle_config.skillfree) {
int j;
- for(j = 0; j < MAX_PC_SKILL_REQUIRE; j++) {
- int k;
- if((k=pc->skill_tree[c][i].need[j].id)) {
- int idx2 = pc->skill_tree[c][i].need[j].idx;
- if (sd->status.skill[idx2].id == 0 || sd->status.skill[idx2].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[idx2].flag == SKILL_FLAG_PLAGIARIZED)
- k = 0; //Not learned.
- else if (sd->status.skill[idx2].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level
- k = sd->status.skill[idx2].flag - SKILL_FLAG_REPLACED_LV_0;
- else
- k = pc->checkskill2(sd,idx2);
- if (k < pc->skill_tree[c][i].need[j].lv) {
- f = 0;
- break;
- }
+ for (j = 0; j < VECTOR_LENGTH(pc->skill_tree[c][i].need); j++) {
+ struct skill_tree_requirement *req = &VECTOR_INDEX(pc->skill_tree[c][i].need, j);
+ int level;
+ if (sd->status.skill[req->idx].id == 0
+ || sd->status.skill[req->idx].flag == SKILL_FLAG_TEMPORARY
+ || sd->status.skill[req->idx].flag == SKILL_FLAG_PLAGIARIZED)
+ level = 0; //Not learned.
+ else if (sd->status.skill[req->idx].flag >= SKILL_FLAG_REPLACED_LV_0) //Real learned level
+ level = sd->status.skill[req->idx].flag - SKILL_FLAG_REPLACED_LV_0;
+ else
+ level = pc->checkskill2(sd, req->idx);
+ if (level < req->lv) {
+ satisfied = false;
+ break;
}
}
- if ( sd->status.job_level < pc->skill_tree[c][i].joblv ) {
- int x = pc->mapid2jobid(sd->class_, sd->status.sex); // need to get its own skilltree
- if ( x > -1 ) {
- x = pc->class2idx(x);
- if ( !pc->skill_tree[x][i].inherited )
- f = 0; // job level requirement wasn't satisfied
- } else
- f = 0;
+ if (sd->status.job_level < pc->skill_tree[c][i].joblv) {
+ int jobid = pc->mapid2jobid(sd->class_, sd->status.sex); // need to get its own skilltree
+ if (jobid > -1) {
+ if (!pc->skill_tree[pc->class2idx(jobid)][i].inherited)
+ satisfied = false; // job level requirement wasn't satisfied
+ } else {
+ satisfied = false;
+ }
}
}
- if( f ) {
- int inf2;
- inf2 = skill->dbs->db[idx].inf2;
+ if (satisfied) {
+ int inf2 = skill->dbs->db[idx].inf2;
if(!sd->status.skill[idx].lv && (
(inf2&INF2_QUEST_SKILL && !battle_config.quest_skill_learn) ||
@@ -1628,39 +1627,40 @@ void pc_check_skilltree(struct map_session_data *sd, int skill_id)
c = pc->class2idx(c);
do {
flag = 0;
- for (i = 0; i < MAX_SKILL_TREE && (id=pc->skill_tree[c][i].id)>0; i++) {
- int j, f = 1, idx = pc->skill_tree[c][i].idx;
+ for (i = 0; i < MAX_SKILL_TREE && (id = pc->skill_tree[c][i].id) > 0; i++) {
+ int j, idx = pc->skill_tree[c][i].idx;
+ bool satisfied = true;
- if( sd->status.skill[idx].id ) //Already learned
+ if (sd->status.skill[idx].id) //Already learned
continue;
- for (j = 0; j < MAX_PC_SKILL_REQUIRE; j++) {
- int k = pc->skill_tree[c][i].need[j].id;
- if (k) {
- int idx2 = pc->skill_tree[c][i].need[j].idx;
- if (sd->status.skill[idx2].id == 0 || sd->status.skill[idx2].flag == SKILL_FLAG_TEMPORARY || sd->status.skill[idx2].flag == SKILL_FLAG_PLAGIARIZED)
- k = 0; //Not learned.
- else if (sd->status.skill[idx2].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level
- k = sd->status.skill[idx2].flag - SKILL_FLAG_REPLACED_LV_0;
- else
- k = pc->checkskill2(sd,idx2);
- if (k < pc->skill_tree[c][i].need[j].lv) {
- f = 0;
- break;
- }
+ for (j = 0; j < VECTOR_LENGTH(pc->skill_tree[c][i].need); j++) {
+ struct skill_tree_requirement *req = &VECTOR_INDEX(pc->skill_tree[c][i].need, j);
+ int level;
+ if (sd->status.skill[req->idx].id == 0
+ || sd->status.skill[req->idx].flag == SKILL_FLAG_TEMPORARY
+ || sd->status.skill[req->idx].flag == SKILL_FLAG_PLAGIARIZED)
+ level = 0; //Not learned.
+ else if (sd->status.skill[req->idx].flag >= SKILL_FLAG_REPLACED_LV_0) //Real lerned level
+ level = sd->status.skill[req->idx].flag - SKILL_FLAG_REPLACED_LV_0;
+ else
+ level = pc->checkskill2(sd,req->idx);
+ if (level < req->lv) {
+ satisfied = false;
+ break;
}
}
- if( !f )
+ if (!satisfied)
continue;
- if ( sd->status.job_level < pc->skill_tree[c][i].joblv ) {
- int x = pc->mapid2jobid(sd->class_, sd->status.sex); // need to get its own skilltree
- if ( x > -1 ) {
- x = pc->class2idx(x);
- if ( !pc->skill_tree[x][i].inherited )
+ if (sd->status.job_level < pc->skill_tree[c][i].joblv) {
+ int jobid = pc->mapid2jobid(sd->class_, sd->status.sex); // need to get its own skilltree
+ if (jobid > -1) {
+ if (!pc->skill_tree[pc->class2idx(jobid)][i].inherited)
continue;
- } else
+ } else {
continue;
+ }
}
j = skill->dbs->db[idx].inf2;
@@ -10592,128 +10592,203 @@ int pc_split_atoui(char* str, unsigned int* val, char sep, int max)
val[j] = 0;
return i;
}
-/* [Ind/Hercules] */
-void pc_read_skill_tree(void) {
+
+/**
+ * Parses the skill tree config file.
+ *
+ * In order to reclaim the memory allocated by this function
+ * `pc->clear_skill_tree()` should be used.
+ *
+ * @remark
+ * This function assumes that the skill tree is clear and zeroed.
+ * If it has been already loaded (ie reloading), it needs to be cleared
+ * before calling this function again.
+ *
+ * @author [Ind/Hercules]
+ */
+void pc_read_skill_tree(void)
+{
config_t skill_tree_conf;
- config_setting_t *skt = NULL, *inherit = NULL, *skills = NULL, *sk = NULL;
-#ifdef RENEWAL
- const char *config_filename = "db/re/skill_tree.conf"; // FIXME hardcoded name
-#else
- const char *config_filename = "db/pre-re/skill_tree.conf"; // FIXME hardcoded name
-#endif
+ config_setting_t *skt = NULL;
+ char config_filename[128];
int i = 0;
struct s_mapiterator *iter;
struct map_session_data *sd;
+ bool loaded[CLASS_COUNT] = { false };
+ safesnprintf(config_filename, sizeof(config_filename), "%s/"DBPATH"skill_tree.conf", map->db_path);
if (libconfig->read_file(&skill_tree_conf, config_filename)) {
ShowError("can't read %s\n", config_filename);
return;
}
- while ((skt = libconfig->setting_get_elem(skill_tree_conf.root,i++))) {
- int k;
- const char *name = config_setting_name(skt);
+ // Foreach job
+ while ((skt = libconfig->setting_get_elem(skill_tree_conf.root, i++))) {
+ config_setting_t *t = NULL;
+ int job_idx;
+ const char *job_name = config_setting_name(skt);
+ int job_id = pc->check_job_name(job_name);
- if ( (k = pc->check_job_name(name)) == -1 ) {
- ShowWarning("pc_read_skill_tree: '%s' unknown job name!\n", name);
+ if (job_id == -1) {
+ ShowWarning("pc_read_skill_tree: '%s' unknown job name!\n", job_name);
+ continue;
+ }
+ job_idx = pc->class2idx(job_id);
+ if (loaded[job_idx]) {
+ ShowWarning("pc_read_skill_tree: Duplicate entry for job '%s'. Skipping.\n", job_name);
continue;
}
+ loaded[job_idx] = true;
- if( ( skills = libconfig->setting_get_member(skt,"skills") ) ) {
- int c = 0;
- int idx = pc->class2idx(k);
+ if ((t = libconfig->setting_get_member(skt, "inherit")) != NULL) {
+ int j = 0;
+ const char *ijob_name = NULL;
+ // Foreach inherited job
+ while ((ijob_name = libconfig->setting_get_string_elem(t, j++)) != NULL) {
+ int k, ijob_idx;
+ int ijob_id = pc->check_job_name(ijob_name);
- while ((sk = libconfig->setting_get_elem(skills,c++))) {
- const char *sk_name = config_setting_name(sk);
- int skill_id;
+ if (ijob_id == -1) {
+ ShowWarning("pc_read_skill_tree: '%s' trying to inherit unknown '%s'!\n", job_name, ijob_name);
+ continue;
+ }
+ ijob_idx = pc->class2idx(ijob_id);
+ if (ijob_idx == job_idx) {
+ ShowWarning("pc_read_skill_tree: '%s' trying to inherit itself. Skipping.\n", job_name);
+ continue;
+ }
+ if (!loaded[ijob_idx]) {
+ ShowWarning("pc_read_skill_tree: '%s' trying to inherit not yet loaded '%s' (wrong order in the tree). Skipping.\n", job_name, ijob_name);
+ continue;
+ }
- if( ( skill_id = skill->name2id(sk_name) ) ) {
- int skidx, offset = 0, h = 0, rlen = 0;
+ for (k = 0; k < MAX_SKILL_TREE; k++) {
+ int cur;
+ struct skill_tree_entry *dst = NULL;
+ const struct skill_tree_entry *src = &pc->skill_tree[ijob_idx][k];
- ARR_FIND( 0, MAX_SKILL_TREE, skidx, pc->skill_tree[idx][skidx].id == 0 || pc->skill_tree[idx][skidx].id == skill_id );
- if (skidx == MAX_SKILL_TREE) {
- ShowWarning("pc_read_skill_tree: Unable to load skill %d (%s) into '%s's tree. Maximum number of skills per class has been reached.\n", skill_id, sk_name, name);
- continue;
- } else if (pc->skill_tree[idx][skidx].id) {
- ShowNotice("pc_read_skill_tree: Overwriting %d for '%s' (%d)\n", skill_id, name, k);
- }
+ if (src->id == 0)
+ break; // No more skills to copy
- pc->skill_tree[idx][skidx].id = skill_id;
- pc->skill_tree[idx][skidx].idx = skill->get_index(skill_id);
-
- if( config_setting_is_group(sk) ) {
- int max = 0, jlevel = 0;
- libconfig->setting_lookup_int(sk, "MaxLevel", &max);
- libconfig->setting_lookup_int(sk, "MinJobLevel", &jlevel);
- pc->skill_tree[idx][skidx].max = (unsigned char)max;
- pc->skill_tree[idx][skidx].joblv = (unsigned char)jlevel;
- rlen = libconfig->setting_length(sk);
- offset += jlevel ? 2 : 1;
- } else {
- pc->skill_tree[idx][skidx].max = (unsigned char)libconfig->setting_get_int(sk);
- pc->skill_tree[idx][skidx].joblv = 0;
+ ARR_FIND(0, MAX_SKILL_TREE, cur, pc->skill_tree[job_idx][cur].id == 0 || pc->skill_tree[job_idx][cur].id == src->id);
+ if (cur == MAX_SKILL_TREE) {
+ ShowWarning("pc_read_skill_tree: '%s' can't inherit '%s', skill tree is full!\n", job_name, ijob_name);
+ break;
}
-
- for (h = offset; h < rlen && h < MAX_PC_SKILL_REQUIRE; h++) {
- config_setting_t *rsk = libconfig->setting_get_elem(sk,h);
- int rskid;
- if (rsk && (rskid = skill->name2id(config_setting_name(rsk))) != 0) {
- pc->skill_tree[idx][skidx].need[h].id = rskid;
- pc->skill_tree[idx][skidx].need[h].idx = skill->get_index(rskid);
- pc->skill_tree[idx][skidx].need[h].lv = (unsigned char)libconfig->setting_get_int(rsk);
- } else if( rsk ) {
- ShowWarning("pc_read_skill_tree: unknown requirement '%s' for '%s' in '%s'\n",config_setting_name(rsk),sk_name,name);
- } else {
- ShowWarning("pc_read_skill_tree: error for '%s' in '%s'\n",sk_name,name);
+ if (src->id == NV_TRICKDEAD && ((pc->jobid2mapid(job_id)&(MAPID_BASEMASK | JOBL_2)) != MAPID_NOVICE))
+ continue; // skip trickdead for non-novices
+ dst = &pc->skill_tree[job_idx][cur];
+ dst->inherited = 1;
+ if (dst->id == 0) {
+ // Not existing yet, copy
+ dst->id = src->id;
+ dst->idx = src->idx;
+ dst->max = src->max;
+ dst->joblv = src->joblv;
+ VECTOR_INIT(dst->need);
+ if (VECTOR_LENGTH(src->need) > 0) {
+ VECTOR_ENSURE(dst->need, VECTOR_LENGTH(src->need), 1);
+ VECTOR_PUSHARRAY(dst->need, VECTOR_DATA(src->need), VECTOR_LENGTH(src->need));
+ }
+ } else {
+ int l;
+ // Already existing, merge
+ if (src->max > dst->max)
+ dst->max = src->max;
+ dst->joblv = src->joblv;
+ for (l = 0; l < VECTOR_LENGTH(src->need); l++) {
+ int m;
+ struct skill_tree_requirement *sreq = &VECTOR_INDEX(src->need, l);
+ ARR_FIND(0, VECTOR_LENGTH(dst->need), m, VECTOR_INDEX(dst->need, m).id == sreq->id);
+ if (m == VECTOR_LENGTH(dst->need)) {
+ VECTOR_ENSURE(dst->need, 1, 1);
+ VECTOR_PUSHCOPY(dst->need, sreq);
+ } else {
+ struct skill_tree_requirement *dreq = &VECTOR_INDEX(dst->need, m);
+ dreq->lv = sreq->lv;
+ }
}
}
- } else {
- ShowWarning("pc_read_skill_tree: unknown skill '%s' in '%s'\n",sk_name,name);
}
}
}
- }
-
- i = 0;
- while( (skt = libconfig->setting_get_elem(skill_tree_conf.root,i++)) ) {
- int k, idx;
- const char *name = config_setting_name(skt);
-
- if ( (k = pc->check_job_name(name)) == -1 ) {
- ShowWarning("pc_read_skill_tree: '%s' unknown job name!\n", name);
- continue;
- }
-
- idx = pc->class2idx(k);
-
- if( ( inherit = libconfig->setting_get_member(skt,"inherit") ) ) {
- const char *iname;
- int v = 0;
- while ( (iname = libconfig->setting_get_string_elem(inherit, v++)) ) {
- int b = 0, a, d, f, fidx;
+ if ((t = libconfig->setting_get_member(skt, "skills")) != NULL) {
+ int j = 0;
+ config_setting_t *sk = NULL;
+ // Foreach skill
+ while ((sk = libconfig->setting_get_elem(t, j++)) != NULL) {
+ int skill_id, sk_idx;
+ config_setting_t *rsk = NULL;
+ const char *sk_name = config_setting_name(sk);
+ struct skill_tree_entry *tree_entry = NULL;
- if ( (b = pc->check_job_name(iname)) == -1 ) {
- ShowWarning("pc_read_skill_tree: '%s' trying to inherit unknown '%s'!\n", name, iname);
+ if ((skill_id = skill->name2id(sk_name)) == 0) {
+ ShowWarning("pc_read_skill_tree: unknown skill '%s' in '%s'\n", sk_name, job_name);
continue;
}
- fidx = pc->class2idx(b);
-
- ARR_FIND(0, MAX_SKILL_TREE, d, pc->skill_tree[fidx][d].id == 0);
-
- for ( f = 0; f < d; f++ ) {
-
- ARR_FIND(0, MAX_SKILL_TREE, a, pc->skill_tree[idx][a].id == 0 || pc->skill_tree[idx][a].id == pc->skill_tree[fidx][f].id);
+ ARR_FIND(0, MAX_SKILL_TREE, sk_idx, pc->skill_tree[job_idx][sk_idx].id == 0 || pc->skill_tree[job_idx][sk_idx].id == skill_id);
+ if (sk_idx == MAX_SKILL_TREE) {
+ ShowWarning("pc_read_skill_tree: Unable to load skill %d (%s) into '%s's tree. Maximum number of skills per class has been reached.\n", skill_id, sk_name, job_name);
+ continue;
+ }
+ tree_entry = &pc->skill_tree[job_idx][sk_idx];
- if ( a == MAX_SKILL_TREE ) {
- ShowWarning("pc_read_skill_tree: '%s' can't inherit '%s', skill tree is full!\n", name, iname);
- break;
- } else if ( pc->skill_tree[idx][a].id || (pc->skill_tree[idx][a].id == NV_TRICKDEAD && ((pc->jobid2mapid(k)&(MAPID_BASEMASK | JOBL_2)) != MAPID_NOVICE)) ) /* we skip trickdead for non-novices */
- continue;/* skip */
- memcpy(&pc->skill_tree[idx][a], &pc->skill_tree[fidx][f], sizeof(pc->skill_tree[fidx][f]));
- pc->skill_tree[idx][a].inherited = 1;
+ if (tree_entry->id != 0 && !tree_entry->inherited) {
+ ShowNotice("pc_read_skill_tree: Duplicate %d for '%s' (%d). Skipping.\n", skill_id, job_name, job_id);
+ continue;
+ }
+ if (config_setting_is_group(sk)) {
+ int i32 = 0;
+ if (libconfig->setting_lookup_int(sk, "MaxLevel", &i32) && i32 > 0) {
+ tree_entry->max = (unsigned char)i32;
+ } else {
+ ShowWarning("pc_read_skill_tree: missing MaxLevel for skill %d (%s) class '%s'. Skipping.\n", skill_id, sk_name, job_name);
+ continue;
+ }
+ if (libconfig->setting_lookup_int(sk, "MinJobLevel", &i32) && i32 > 0) {
+ tree_entry->joblv = (unsigned char)i32;
+ } else if (!tree_entry->inherited) {
+ tree_entry->joblv = 0;
+ }
+ } else {
+ tree_entry->max = (unsigned char)libconfig->setting_get_int(sk);
+ if (!tree_entry->inherited)
+ tree_entry->joblv = 0;
+ }
+ if (!tree_entry->inherited) {
+ tree_entry->id = skill_id;
+ tree_entry->idx = skill->get_index(skill_id);
+ VECTOR_INIT(tree_entry->need);
}
+ if (config_setting_is_group(sk)) {
+ int k = 0;
+ // Foreach requirement
+ while ((rsk = libconfig->setting_get_elem(sk, k++)) != NULL) {
+ const char *rsk_name = config_setting_name(rsk);
+ int rsk_id = skill->name2id(rsk_name);
+ struct skill_tree_requirement *req = NULL;
+ int l;
+
+ if (rsk_id == 0) {
+ if (strcmp(rsk_name, "MaxLevel") != 0 && strcmp(rsk_name, "MinJobLevel") != 0)
+ ShowWarning("pc_read_skill_tree: unknown requirement '%s' for '%s' in '%s'\n", rsk_name, sk_name, job_name);
+ continue;
+ }
+ ARR_FIND(0, VECTOR_LENGTH(tree_entry->need), l, VECTOR_INDEX(tree_entry->need, l).id == rsk_id);
+ if (l == VECTOR_LENGTH(tree_entry->need)) {
+ VECTOR_ENSURE(tree_entry->need, 1, 1);
+ VECTOR_PUSHZEROED(tree_entry->need);
+ req = &VECTOR_LAST(tree_entry->need);
+ req->id = rsk_id;
+ req->idx = skill->get_index(rsk_id);
+ } else {
+ req = &VECTOR_INDEX(tree_entry->need, l);
+ }
+ req->lv = (unsigned char)libconfig->setting_get_int(rsk);
+ }
+ }
}
}
}
@@ -10726,6 +10801,24 @@ void pc_read_skill_tree(void) {
clif->skillinfoblock(sd);
mapit->free(iter);
}
+
+/**
+ * Clears the skill tree and frees any allocated memory.
+ */
+void pc_clear_skill_tree(void)
+{
+ int i;
+ for (i = 0; i < CLASS_COUNT; i++) {
+ int j;
+ for (j = 0; j < MAX_SKILL_TREE; j++) {
+ if (pc->skill_tree[i][j].id == 0)
+ continue;
+ VECTOR_CLEAR(pc->skill_tree[i][j].need);
+ }
+ }
+ memset(pc->skill_tree, 0, sizeof(pc->skill_tree));
+}
+
bool pc_readdb_levelpenalty(char* fields[], int columns, int current) {
#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP)
int type, race, diff;
@@ -10842,7 +10935,7 @@ int pc_readdb(void) {
ShowStatus("Done reading '"CL_WHITE"%u"CL_RESET"' entries in '"CL_WHITE"%s/"DBPATH"%s"CL_RESET"'.\n",count,map->db_path,"exp.txt");
count = 0;
// Reset and read skilltree
- memset(pc->skill_tree,0,sizeof(pc->skill_tree));
+ pc->clear_skill_tree();
pc->read_skill_tree();
#if defined(RENEWAL_DROP) || defined(RENEWAL_EXP)
sv->readdb(map->db_path, "re/level_penalty.txt", ',', 4, 4, -1, pc->readdb_levelpenalty);
@@ -11367,6 +11460,8 @@ void do_final_pc(void) {
pcg->final();
+ pc->clear_skill_tree();
+
ers_destroy(pc->sc_display_ers);
ers_destroy(pc->num_reg_ers);
ers_destroy(pc->str_reg_ers);
@@ -11693,6 +11788,7 @@ void pc_defaults(void) {
pc->autosave = pc_autosave;
pc->follow_timer = pc_follow_timer;
pc->read_skill_tree = pc_read_skill_tree;
+ pc->clear_skill_tree = pc_clear_skill_tree;
pc->isUseitem = pc_isUseitem;
pc->show_steal = pc_show_steal;
pc->checkcombo = pc_checkcombo;