diff options
author | Haru <haru@dotalux.com> | 2016-01-03 03:45:30 +0100 |
---|---|---|
committer | Haru <haru@dotalux.com> | 2016-01-03 21:46:15 +0100 |
commit | 818acc5866707b9e294a87d2dd902aebb670707f (patch) | |
tree | 1d507deb7d066ab066bdab2cf7bd4f549c471278 /src/map/pc.c | |
parent | 87476b17df574f4c1cb1ef46c3c8e47419ea5a08 (diff) | |
download | hercules-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.c | 390 |
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; |