diff --git a/CHANGELOG.md b/CHANGELOG.md index d46480ac5b9..ddcf69743fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -792,10 +792,20 @@ All these changes only affect Renewal. Pre-renewal is unchanged. - The `is_quest` argument to `pc->gainexp()` has been changed to a `flags` bitmask enum, in order to allow expansion to different flags. (#3279) -## [v2024.08] `August 2024` +## [v2024.09] `September 2024` ### Added +- Implemented the script command `getunitparam()` to query values defined in `unit_parameters_db.conf`, and the related `UNIT_PARAM_*` constants. See the `script_commands.txt` documentation for usage details. (#3323) +- Added validation of the name length for configuration entries added through the HPM `addBattleConf()`, `addLoginConf()`, `addCharConf()`, `addCharInterConf()`, `addLogConf()`, `addScriptConf()` methods, to prevent silent truncation. (#3324) + +### Fixed + +- Fixed an issue causing item-granted skills that were overriding an existing skill level, not to be correctly cleared when unequipping the item. (#3322) +- Fixed previously plagiarized skills re-appearing on subsequent logins due to the related script variables not getting cleared properly. (#3325) + +## [v2024.08] `August 2024` + ### Changed - Converted packets `CHARLOGIN_ONLINE_ACCOUNTS`, `MAPCHAR_AUTH_REQ`, `CHARLOGIN_SET_ACCOUNT_ONLINE` to the struct format. (#3304, #3312, #3314) @@ -3930,6 +3940,7 @@ Note: everything included in this release is part of PR #3198 which consists of - New versioning scheme and project changelogs/release notes (#1853) [Unreleased]: https://github.com/HerculesWS/Hercules/compare/stable...master +[v2024.09]: https://github.com/HerculesWS/Hercules/compare/v2024.08...v2024.09 [v2024.08]: https://github.com/HerculesWS/Hercules/compare/v2024.06...v2024.08 [v2024.06]: https://github.com/HerculesWS/Hercules/compare/v2024.05...v2024.06 [v2024.05]: https://github.com/HerculesWS/Hercules/compare/v2024.04...v2024.05 diff --git a/doc/constants_pre-re.md b/doc/constants_pre-re.md index 25ab8f2ae30..7c46aba797a 100644 --- a/doc/constants_pre-re.md +++ b/doc/constants_pre-re.md @@ -4880,7 +4880,7 @@ ### Server defines - `PACKETVER`: 20190530 -- `HERCULES_VERSION`: 202408000 +- `HERCULES_VERSION`: 202409000 - `MAX_LEVEL`: 175 - `MAX_STORAGE`: 600 - `MAX_GUILD_STORAGE`: 500 @@ -5870,6 +5870,14 @@ - `HOMINFO_RENAME`: 5 - `HOMINFO_LEVEL`: 6 +### getunitparam param-types + +- `UNIT_PARAM_NAME`: 0 +- `UNIT_PARAM_NATHEAL_WEIGHT_RATE`: 1 +- `UNIT_PARAM_MAX_ASPD`: 2 +- `UNIT_PARAM_MAX_HP`: 3 +- `UNIT_PARAM_MAX_STATS`: 4 + ### Renewal - `RENEWAL`: 0 diff --git a/doc/constants_re.md b/doc/constants_re.md index 40dc94ff76c..7f116999fb3 100644 --- a/doc/constants_re.md +++ b/doc/constants_re.md @@ -4878,7 +4878,7 @@ ### Server defines - `PACKETVER`: 20190530 -- `HERCULES_VERSION`: 202408000 +- `HERCULES_VERSION`: 202409000 - `MAX_LEVEL`: 175 - `MAX_STORAGE`: 600 - `MAX_GUILD_STORAGE`: 500 @@ -5868,6 +5868,14 @@ - `HOMINFO_RENAME`: 5 - `HOMINFO_LEVEL`: 6 +### getunitparam param-types + +- `UNIT_PARAM_NAME`: 0 +- `UNIT_PARAM_NATHEAL_WEIGHT_RATE`: 1 +- `UNIT_PARAM_MAX_ASPD`: 2 +- `UNIT_PARAM_MAX_HP`: 3 +- `UNIT_PARAM_MAX_STATS`: 4 + ### Renewal - `RENEWAL`: 1 diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 1c4bb8aa3ab..939ea863f4f 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -3101,6 +3101,37 @@ Examples: --------------------------------------- +*getunitparam({, {, , }}) + +Thi will return the requested parameter's value or fill the required arrays in +the case of UNIT_PARAM_MAX_HP. + +: +- UNIT_PARAM_NAME ^= name of unit_parameters_db.conf entry used by class. +- UNIT_PARAM_NATHEAL_WEIGHT_RATE ^= NaturalHealWeightRate value +- UNIT_PARAM_MAX_ASPD ^= MaxASPD +- UNIT_PARAM_MAX_HP ^= MaxHP +- UNIT_PARAM_MAX_STATS ^= MaxStats + + can be -1 if attached players class is desired, such as in the case of UNIT_PARAM_MAX_HP, +where is mandatory. + +Examples: + +// Outputs the possible maximum ASPD of attached player. + mesf("MAX_ASPD: %d", getunitparam(UNIT_PARAM_MAX_ASPD)); + +// Outputs the possible maximum stats of Job_Baby. + mesf("MAX_STATS: %d", getunitparam(UNIT_PARAM_MAX_STATS, Job_Baby)); + +// Saves the entries for MAXHP per level ranges of JOB_SUPER_NOVICE into the given arrays and prints them. + .@count = getunitparam(UNIT_PARAM_MAX_ASPD, JOB_SUPER_NOVICE, .@max_lv, .@max_hp); + for (.@i = 0; .@i < .@count; ++.@i) { + mesf("max_lv: %d, max_hp: %d", .@max_lv[.@i], .@max_hp[.@i]); + } + +--------------------------------------- + *sit({""}) *stand({""}) diff --git a/src/common/HPM.c b/src/common/HPM.c index 9608eec218e..4aa80c0ec38 100644 --- a/src/common/HPM.c +++ b/src/common/HPM.c @@ -451,6 +451,11 @@ static bool hplugins_addconf(unsigned int pluginID, enum HPluginConfType type, c return false; } + if (strnlen(name, HPM_ADDCONF_LENGTH) >= HPM_ADDCONF_LENGTH) { + ShowError("HPM->addConf:%s: config '%s' name/path is too long. Maximum is %d characters (see #define HPM_ADDCONF_LENGTH). Skipping it.\n", HPM->pid2name(pluginID), name, HPM_ADDCONF_LENGTH - 1); + return false; + } + ARR_FIND(0, VECTOR_LENGTH(HPM->config_listeners[type]), i, strcmpi(name, VECTOR_INDEX(HPM->config_listeners[type], i).key) == 0); if (i != VECTOR_LENGTH(HPM->config_listeners[type])) { ShowError("HPM->addConf:%s: duplicate '%s', already in use by '%s'!", diff --git a/src/common/HPMi.h b/src/common/HPMi.h index d7083bbf8ff..a44984495f2 100644 --- a/src/common/HPMi.h +++ b/src/common/HPMi.h @@ -35,6 +35,8 @@ struct map_session_data; struct hplugin_data_store; #define HPM_VERSION "1.2" + +// Maximum length of the configuration path for configs added with add*Conf #define HPM_ADDCONF_LENGTH 40 struct hplugin_info { diff --git a/src/config/core.h b/src/config/core.h index 657f29bbe65..83bd05a1c97 100644 --- a/src/config/core.h +++ b/src/config/core.h @@ -22,7 +22,7 @@ #define CONFIG_CORE_H /// Hercules version. From tag vYYYY.MM(+PPP) -> YYYYMMPPP -#define HERCULES_VERSION 202408000 +#define HERCULES_VERSION 202409000 /// Max number of items on @autolootid list #define AUTOLOOTITEM_SIZE 10 diff --git a/src/map/pc.c b/src/map/pc.c index f10b8282e98..65fe219b491 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -1659,7 +1659,8 @@ static void pc_calc_skilltree_clear(struct map_session_data *sd) for (i = 0; i < MAX_SKILL_DB; i++) { if (sd->status.skill[i].flag == SKILL_FLAG_PLAGIARIZED || sd->status.skill[i].flag == SKILL_FLAG_PERM_GRANTED - || sd->status.skill[i].id == sd->cloneskill_id || sd->status.skill[i].id == sd->reproduceskill_id) //Don't touch these + || (sd->cloneskill_id != 0 && sd->status.skill[i].id == sd->cloneskill_id) + || (sd->reproduceskill_id != 0 && sd->status.skill[i].id == sd->reproduceskill_id)) //Don't touch these continue; sd->status.skill[i].id = 0; //First clear skills. /* permanent skills that must be re-checked */ @@ -1695,10 +1696,17 @@ static int pc_calc_skilltree(struct map_session_data *sd) pc->calc_skilltree_clear(sd); for (int i = 0; i < MAX_SKILL_DB; i++) { - if ((sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0 && sd->status.skill[i].id != sd->cloneskill_id && sd->status.skill[i].id != sd->reproduceskill_id) - || sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) { + if (sd->status.skill[i].flag >= SKILL_FLAG_REPLACED_LV_0) { + bool is_cloneskill = sd->cloneskill_id != 0 && sd->status.skill[i].id == sd->cloneskill_id; + bool is_reproduceskill = sd->reproduceskill_id != 0 && sd->status.skill[i].id == sd->reproduceskill_id; + if (is_cloneskill || is_reproduceskill) + continue; // Plagiarized and Reproduce Skills are kept. + // Restore original level of skills after deleting earned skills. - sd->status.skill[i].lv = (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) ? 0 : sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0; + sd->status.skill[i].lv = sd->status.skill[i].flag - SKILL_FLAG_REPLACED_LV_0; + sd->status.skill[i].flag = SKILL_FLAG_PERMANENT; + } else if (sd->status.skill[i].flag == SKILL_FLAG_TEMPORARY) { + sd->status.skill[i].lv = 0; sd->status.skill[i].flag = SKILL_FLAG_PERMANENT; } } diff --git a/src/map/script.c b/src/map/script.c index 0024462c2a7..359299bc31e 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -28628,6 +28628,120 @@ static BUILDIN(mestipbox) return true; } + +/** + * Returns a units 's values + * + * NOTE: UNIT_PARAM_MAX_HP needs two arrays. + * + * getunitparam({, {, , }}) + */ +static BUILDIN(getunitparam) +{ + int class = -1; + if (script_hasdata(st, 3)) { + class = script_getnum(st, 3); + if (class != -1) { + if (!pc->db_checkid(class)) { + ShowError("buildin_getunitparam: invalid class (%d)\n", class); + st->state = END; + return false; + } + class = pc->class2idx(class); + } + } + + struct map_session_data *sd = NULL; + if (class == -1) { + sd = script_rid2sd(st); + if (sd == NULL) { + ShowError("buildin_getunitparam: No player attached, but class == -1.\n"); + return false; + } + class = pc->class2idx(sd->status.class); + } + + struct s_unit_params *entry = status->dbs->unit_params[class]; + int param = script_getnum(st, 2); + switch (param) { + case UNIT_PARAM_NAME: + script_pushconststr(st, entry->name); + break; + case UNIT_PARAM_NATHEAL_WEIGHT_RATE: + script_pushint(st, entry->natural_heal_weight_rate); + break; + case UNIT_PARAM_MAX_ASPD: + script_pushint(st, (2000 - entry->max_aspd) / 10); // max_aspd is actually min_amotion :) + break; + case UNIT_PARAM_MAX_HP: { + if (!script_hasdata(st, 4) || !script_hasdata(st, 5)) { + ShowError("buildin_getunitparam: UNIT_PARAM_MAXP_HP requires 4 parameters: , , , \n"); + st->state = END; + return false; + } + + struct script_data *maxhp_maxlvls = script_getdata(st, 4); + struct script_data *maxhp_values = script_getdata(st, 5); + if (!data_isreference(maxhp_maxlvls) || reference_toconstant(maxhp_maxlvls)) { + ShowError("buildin_getunitparam: argument must be reference and not a reference to constant\n"); + script->reportdata(maxhp_maxlvls); + st->state = END; + return false; + } + if (!data_isreference(maxhp_values) || reference_toconstant(maxhp_values)) { + ShowError("buildin_getunitparam: argument must be reference and not a reference to constant\n"); + script->reportdata(maxhp_values); + st->state = END; + return false; + } + + const char *maxhp_maxlvls_varname = reference_getname(maxhp_maxlvls); + const char *maxhp_values_varname = reference_getname(maxhp_values); + if (!is_int_variable(maxhp_maxlvls_varname)) { + ShowError("buildin_getunitparam: argument must be of integer type\n"); + script->reportdata(maxhp_maxlvls); + st->state = END; + return false; + } + if (!is_int_variable(maxhp_values_varname)) { + ShowError("buildin_getunitparam: argument must be of integer type\n"); + script->reportdata(maxhp_values); + st->state = END; + return false; + } + + if (not_server_variable(*maxhp_maxlvls_varname) || not_server_variable(*maxhp_values_varname)) { + if (sd == NULL) { + sd = script->rid2sd(st); + if (sd == NULL) + return false; // player variable but no player attached + } + } + + int varid1 = reference_getid(maxhp_maxlvls); + int varid2 = reference_getid(maxhp_values); + int count = entry->maxhp_size; + for (int i = 0; i < count; i++) { + script->set_reg(st, sd, reference_uid(varid1, i), maxhp_maxlvls_varname, (const void *)h64BPTRSIZE(entry->maxhp[i].max_level), + reference_getref(maxhp_maxlvls)); + script->set_reg(st, sd, reference_uid(varid2, i), maxhp_values_varname, (const void *)h64BPTRSIZE(entry->maxhp[i].value), + reference_getref(maxhp_values)); + } + script_pushint(st, count); + break; + } + case UNIT_PARAM_MAX_STATS: + script_pushint(st, entry->max_stats); + break; + default: + ShowError("buildin_getunitparam: Received invalid param: %d\n", param); + st->state = END; + return false; + } + + return true; +} + /** * Adds a built-in script function. * @@ -29507,6 +29621,7 @@ static void script_parse_builtin(void) BUILDIN_DEF(mesurl, "ss??"), BUILDIN_DEF(mestipbox, "si"), + BUILDIN_DEF(getunitparam, "i???"), }; int i, len = ARRAYLENGTH(BUILDIN); RECREATE(script->buildin, char *, script->buildin_count + len); // Pre-alloc to speed up @@ -30490,6 +30605,13 @@ static void script_hardcoded_constants(void) script->set_constant("HOMINFO_RENAME", HOMINFO_RENAME, false, false); script->set_constant("HOMINFO_LEVEL", HOMINFO_LEVEL, false, false); + script->constdb_comment("getunitparam param-types"); + script->set_constant("UNIT_PARAM_NAME", UNIT_PARAM_NAME, false, false); + script->set_constant("UNIT_PARAM_NATHEAL_WEIGHT_RATE", UNIT_PARAM_NATHEAL_WEIGHT_RATE, false, false); + script->set_constant("UNIT_PARAM_MAX_ASPD", UNIT_PARAM_MAX_ASPD, false, false); + script->set_constant("UNIT_PARAM_MAX_HP", UNIT_PARAM_MAX_HP, false, false); + script->set_constant("UNIT_PARAM_MAX_STATS", UNIT_PARAM_MAX_STATS, false, false); + script->constdb_comment("autospell db constants"); script->set_constant2("HALF_AUTOSPELL_LEVEL", HALF_AUTOSPELL_LEVEL, false, false); diff --git a/src/map/skill.c b/src/map/skill.c index 8e022cc884a..f8a8feb7a1f 100644 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -3704,12 +3704,13 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li switch(can_copy(tsd, copy_skill)) { case 1: // Plagiarism { - pc->clear_existing_cloneskill(tsd, false); - lv = min(skill_lv, pc->checkskill(tsd, RG_PLAGIARISM)); - if (learned_lv > lv) + if (learned_lv > lv) { + pc->clear_existing_cloneskill(tsd, true); break; // [Aegis] can't overwrite skill of higher level, but will still remove previously copied skill. + } + pc->clear_existing_cloneskill(tsd, false); tsd->cloneskill_id = copy_skill; pc_setglobalreg(tsd, script->add_variable("CLONE_SKILL"), copy_skill); pc_setglobalreg(tsd, script->add_variable("CLONE_SKILL_LV"), lv); @@ -3726,12 +3727,13 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li case 2: // Reproduce { lv = sc ? sc->data[SC__REPRODUCE]->val1 : 1; - pc->clear_existing_reproduceskill(tsd, false); - lv = min(lv, skill->get_max(copy_skill)); - if (learned_lv > lv) + if (learned_lv > lv) { + pc->clear_existing_reproduceskill(tsd, true); break; // unconfirmed, but probably the same behavior as for RG_PLAGIARISM + } + pc->clear_existing_reproduceskill(tsd, false); tsd->reproduceskill_id = copy_skill; pc_setglobalreg(tsd, script->add_variable("REPRODUCE_SKILL"), copy_skill); pc_setglobalreg(tsd, script->add_variable("REPRODUCE_SKILL_LV"), lv); diff --git a/src/map/status.h b/src/map/status.h index 9d1f1687255..c491fb4a641 100644 --- a/src/map/status.h +++ b/src/map/status.h @@ -1351,6 +1351,14 @@ struct s_maxhp_entry { int value; ///< The actual max hp value }; +enum e_unit_params { + UNIT_PARAM_NAME, + UNIT_PARAM_NATHEAL_WEIGHT_RATE, + UNIT_PARAM_MAX_ASPD, + UNIT_PARAM_MAX_HP, + UNIT_PARAM_MAX_STATS, +}; + struct s_unit_params { char name[SCRIPT_VARNAME_LENGTH]; ///< group name as defined in conf