From 8ed9b3b570d0b3fcea6d2fd3b0e22a1ffe75c9b9 Mon Sep 17 00:00:00 2001
From: "Guilherme G. Menaldo" <guilherme.menaldo@outlook.com>
Date: Sun, 8 Oct 2023 14:10:51 -0300
Subject: [PATCH 1/4] add AutoSpell libconfig db

for now, it is still not linked to the application
---
 db/pre-re/autospell_db.conf                   | 135 ++++++++++
 db/re/autospell_db.conf                       | 135 ++++++++++
 src/common/HPMDataCheck.h                     |   1 +
 src/map/skill.c                               | 237 +++++++++++++++++-
 src/map/skill.h                               |  18 ++
 src/plugins/HPMHooking/HPMHooking.Defs.inc    |  10 +
 .../HPMHooking_map.HPMHooksCore.inc           |  20 ++
 .../HPMHooking_map.HookingPoints.inc          |   5 +
 .../HPMHooking/HPMHooking_map.Hooks.inc       | 132 ++++++++++
 9 files changed, 687 insertions(+), 6 deletions(-)
 create mode 100644 db/pre-re/autospell_db.conf
 create mode 100644 db/re/autospell_db.conf

diff --git a/db/pre-re/autospell_db.conf b/db/pre-re/autospell_db.conf
new file mode 100644
index 00000000000..e5752a82a8c
--- /dev/null
+++ b/db/pre-re/autospell_db.conf
@@ -0,0 +1,135 @@
+//================= Hercules Database =====================================
+//=       _   _                     _
+//=      | | | |                   | |
+//=      | |_| | ___ _ __ ___ _   _| | ___  ___
+//=      |  _  |/ _ \ '__/ __| | | | |/ _ \/ __|
+//=      | | | |  __/ | | (__| |_| | |  __/\__ \
+//=      \_| |_/\___|_|  \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2023 Hercules Dev Team
+//=
+//= Hercules 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/>.
+//=========================================================================
+//= AutoSpell skills configuration
+//=
+//= This file lists the skills available for Sage AutoSpell (Hindsight) skill.
+//=
+//= Notes:
+//= - The maximum number of entries is controlled by MAX_AUTOSPELL_DB (src/map/skill.h)
+//= - Additionally, some official clients have a hard limit on the number of skills
+//=   (this is limited by packet size and can't be changed)
+//= - SkillLevel and SpiritBoost configures the basic behavior of the skills, the requirement
+//=   for the player to have learned the skill at this level is applied on source
+//=========================================================================
+
+autospell_db: (
+/**************************************************************************
+ ************* Entry structure ********************************************
+ **************************************************************************
+{
+	SkillId: Skill ID/Name                             (constant string or int)
+	SkillLevel: Highest usable level                   (int, defaults to 0) (can be grouped by AutoSpell Levels)
+	                                                   (Level 0 would mean not usable in this AutoSpell level)
+	SpiritBoost: Use max level when under Sage Spirit? (boolean, defaults to false)
+},
+**************************************************************************/
+
+{
+	SkillId: "MG_NAPALMBEAT"
+	SkillLevel: 3
+	SpiritBoost: false
+},
+{
+	SkillId: "MG_COLDBOLT"
+	SkillLevel: {
+		// Lv1: 0
+		Lv2: 1
+		Lv3: 2
+		Lv4: 3
+		Lv5: 3
+		Lv6: 3
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: true
+},
+{
+	SkillId: "MG_FIREBOLT"
+	SkillLevel: {
+		// Lv1: 0
+		Lv2: 1
+		Lv3: 2
+		Lv4: 3
+		Lv5: 3
+		Lv6: 3
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: true
+},
+{
+	SkillId: "MG_LIGHTNINGBOLT"
+	SkillLevel: {
+		// Lv1: 0
+		Lv2: 1
+		Lv3: 2
+		Lv4: 3
+		Lv5: 3
+		Lv6: 3
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: true
+},
+{
+	SkillId: "MG_SOULSTRIKE"
+	SkillLevel: {
+		// Lv1 .. Lv4: 0
+		Lv5: 1
+		Lv6: 2
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: false
+},
+{
+	SkillId: "MG_FIREBALL"
+	SkillLevel: {
+		// Lv1 .. Lv7: 0
+		Lv8: 1
+		Lv9: 2
+		Lv10: 2
+	}
+	SpiritBoost: false
+},
+{
+	SkillId: "MG_FROSTDIVER"
+	SkillLevel: {
+		// Lv1 .. Lv9: 0
+		Lv10: 1
+	}
+	SpiritBoost: false
+},
+)
diff --git a/db/re/autospell_db.conf b/db/re/autospell_db.conf
new file mode 100644
index 00000000000..e5752a82a8c
--- /dev/null
+++ b/db/re/autospell_db.conf
@@ -0,0 +1,135 @@
+//================= Hercules Database =====================================
+//=       _   _                     _
+//=      | | | |                   | |
+//=      | |_| | ___ _ __ ___ _   _| | ___  ___
+//=      |  _  |/ _ \ '__/ __| | | | |/ _ \/ __|
+//=      | | | |  __/ | | (__| |_| | |  __/\__ \
+//=      \_| |_/\___|_|  \___|\__,_|_|\___||___/
+//================= License ===============================================
+//= This file is part of Hercules.
+//= http://herc.ws - http://github.com/HerculesWS/Hercules
+//=
+//= Copyright (C) 2023 Hercules Dev Team
+//=
+//= Hercules 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/>.
+//=========================================================================
+//= AutoSpell skills configuration
+//=
+//= This file lists the skills available for Sage AutoSpell (Hindsight) skill.
+//=
+//= Notes:
+//= - The maximum number of entries is controlled by MAX_AUTOSPELL_DB (src/map/skill.h)
+//= - Additionally, some official clients have a hard limit on the number of skills
+//=   (this is limited by packet size and can't be changed)
+//= - SkillLevel and SpiritBoost configures the basic behavior of the skills, the requirement
+//=   for the player to have learned the skill at this level is applied on source
+//=========================================================================
+
+autospell_db: (
+/**************************************************************************
+ ************* Entry structure ********************************************
+ **************************************************************************
+{
+	SkillId: Skill ID/Name                             (constant string or int)
+	SkillLevel: Highest usable level                   (int, defaults to 0) (can be grouped by AutoSpell Levels)
+	                                                   (Level 0 would mean not usable in this AutoSpell level)
+	SpiritBoost: Use max level when under Sage Spirit? (boolean, defaults to false)
+},
+**************************************************************************/
+
+{
+	SkillId: "MG_NAPALMBEAT"
+	SkillLevel: 3
+	SpiritBoost: false
+},
+{
+	SkillId: "MG_COLDBOLT"
+	SkillLevel: {
+		// Lv1: 0
+		Lv2: 1
+		Lv3: 2
+		Lv4: 3
+		Lv5: 3
+		Lv6: 3
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: true
+},
+{
+	SkillId: "MG_FIREBOLT"
+	SkillLevel: {
+		// Lv1: 0
+		Lv2: 1
+		Lv3: 2
+		Lv4: 3
+		Lv5: 3
+		Lv6: 3
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: true
+},
+{
+	SkillId: "MG_LIGHTNINGBOLT"
+	SkillLevel: {
+		// Lv1: 0
+		Lv2: 1
+		Lv3: 2
+		Lv4: 3
+		Lv5: 3
+		Lv6: 3
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: true
+},
+{
+	SkillId: "MG_SOULSTRIKE"
+	SkillLevel: {
+		// Lv1 .. Lv4: 0
+		Lv5: 1
+		Lv6: 2
+		Lv7: 3
+		Lv8: 3
+		Lv9: 3
+		Lv10: 3
+	}
+	SpiritBoost: false
+},
+{
+	SkillId: "MG_FIREBALL"
+	SkillLevel: {
+		// Lv1 .. Lv7: 0
+		Lv8: 1
+		Lv9: 2
+		Lv10: 2
+	}
+	SpiritBoost: false
+},
+{
+	SkillId: "MG_FROSTDIVER"
+	SkillLevel: {
+		// Lv1 .. Lv9: 0
+		Lv10: 1
+	}
+	SpiritBoost: false
+},
+)
diff --git a/src/common/HPMDataCheck.h b/src/common/HPMDataCheck.h
index a6c91dd1455..ad7debe4358 100644
--- a/src/common/HPMDataCheck.h
+++ b/src/common/HPMDataCheck.h
@@ -1185,6 +1185,7 @@ HPExport const struct s_HPMDataCheck HPMDataCheck[] = {
 		#define MAP_SEARCHSTORE_H
 	#endif // MAP_SEARCHSTORE_H
 	#ifdef MAP_SKILL_H
+		{ "s_autospell_db", sizeof(struct s_autospell_db), SERVER_TYPE_MAP },
 		{ "s_skill_abra_db", sizeof(struct s_skill_abra_db), SERVER_TYPE_MAP },
 		{ "s_skill_arrow_db", sizeof(struct s_skill_arrow_db), SERVER_TYPE_MAP },
 		{ "s_skill_changematerial_db", sizeof(struct s_skill_changematerial_db), SERVER_TYPE_MAP },
diff --git a/src/map/skill.c b/src/map/skill.c
index e0c8fe751b7..e4f0ba79a90 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -16489,7 +16489,7 @@ static bool skill_items_required(struct map_session_data *sd, int skill_id, int
 
 /**
  * Checks conditions for a skill to be executed. This check happens after the cast time was completed.
- * 
+ *
  * @param sd The character who cast the skill.
  * @param skill_id The skill's ID.
  * @param skill_lv The skill's level.
@@ -16692,7 +16692,7 @@ static int skill_check_condition_castend(struct map_session_data *sd, uint16 ski
 
 /**
  * Checks conditions for a skill to be executed. This check happens after the cast time was completed.
- * 
+ *
  * @param sd The character who cast the skill.
  * @param skill_id The skill's ID.
  * @param skill_lv The skill's level.
@@ -21769,7 +21769,7 @@ static void skill_validate_max_level(struct config_setting_t *conf, struct s_ski
  * @param conf The libconfig settings block which contains the skill's data.
  * @param sk The s_skill_db struct where the description should be set it.
  * @param inherited Whether this record is an inherited entry (thus sk already has a valid value)
- * 
+ *
  **/
 static void skill_validate_description(struct config_setting_t *conf, struct s_skill_db *sk, bool inherited)
 {
@@ -22016,7 +22016,7 @@ static void skill_validate_skillinfo(struct config_setting_t *conf, struct s_ski
 			{ "RangeModByResearchTrap", INF2_RANGE_RESEARCHTRAP },
 			{ "AllowPlagiarism", INF2_ALLOW_PLAGIARIZE },
 		};
-		
+
 		while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) {
 			const char *skill_info = config_setting_name(tt);
 			bool on = libconfig->setting_get_bool_real(tt);
@@ -24724,7 +24724,7 @@ static bool skill_read_skilldb(const char *filename)
 		idb_iput(loaded_ids_db, tmp_db.nameid, true);
 		count++;
 	}
-	
+
 	db_destroy(loaded_ids_db);
 
 	libconfig->destroy(&skilldb);
@@ -24732,6 +24732,223 @@ static bool skill_read_skilldb(const char *filename)
 	return true;
 }
 
+/**
+ * Reads a AutoSpell skill's Id (SkillId) when reading the autospell DB.
+ *
+ * @param conf The libconfig settings block which contains the skill's data.
+ * @param sk The autospell_skill struct where the id should be set.
+ */
+static void skill_read_autospell_skill_id(struct config_setting_t *conf, struct s_autospell_db *sk, int index)
+{
+	nullpo_retv(conf);
+	nullpo_retv(sk);
+
+	sk->skill_id = 0;
+
+	const char *skill_name = NULL;
+	int skill_id = 0;
+
+	if (libconfig->setting_lookup_int(conf, "SkillId", &skill_id) == CONFIG_FALSE) {
+		if (libconfig->setting_lookup_string(conf, "SkillId", &skill_name) == CONFIG_FALSE) {
+			ShowError("%s: Invalid AutoSpell db entry \"%d\". SkillId is required. Skipping...\n", __func__, index);
+			return;
+		}
+
+		skill_id = skill->name2id(skill_name);
+	}
+
+	if (skill_id == 0) {
+		if (skill_name != NULL)
+			ShowError("%s: Invalid AutoSpell db entry \"%d\". SkillId \"%s\" doesn't exists. Skipping...\n", __func__, index, skill_name);
+		else
+			ShowError("%s: Invalid AutoSpell db entry \"%d\". SkillId \"%d\" doesn't exists. Skipping...\n", __func__, index, skill_id);
+		return;
+	}
+
+	sk->skill_id = skill_id;
+}
+
+/**
+ * Reads a AutoSpell skill's usable levels (SkillLevel) when reading the autospell DB.
+ *
+ * @param conf The libconfig settings block which contains the skill's data.
+ * @param sk The autospell_skill struct where the level should be set.
+ */
+static void skill_read_autospell_skill_level(struct config_setting_t *conf, struct s_autospell_db *sk)
+{
+	nullpo_retv(conf);
+	nullpo_retv(sk);
+
+	skill->level_set_value(sk->skill_lv, 0);
+
+	struct config_setting_t *t = libconfig->setting_get_member(conf, "SkillLevel");
+
+	if (t != NULL && config_setting_is_group(t)) {
+		for (int i = 0; i < MAX_SKILL_LEVEL; i++) {
+			char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL.
+			snprintf(lv, sizeof(lv), "Lv%d", i + 1);
+
+			int level;
+			if (libconfig->setting_lookup_int(t, lv, &level) == CONFIG_TRUE) {
+				if (level >= 0 && level <= MAX_SKILL_LEVEL)
+					sk->skill_lv[i] = level;
+				else
+					ShowWarning("%s: Invalid SkillLevel %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n",
+						    __func__, level, i + 1, sk->skill_id, conf->file, MAX_SKILL_LEVEL);
+			}
+		}
+
+		return;
+	}
+
+	int level;
+	if (libconfig->setting_lookup_int(conf, "SkillLevel", &level) == CONFIG_TRUE) {
+		if (level >= 0 && level <= MAX_SKILL_LEVEL)
+			skill->level_set_value(sk->skill_lv, level);
+		else
+			ShowWarning("%s: Invalid SkillLevel %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n",
+				    __func__, level, sk->skill_id, conf->file, MAX_SKILL_LEVEL);
+	}
+}
+
+/**
+ * Reads additional field settings via plugins when parsing autospell_db.conf
+ * @param   conf    struct, pointer to the entry configuration
+ * @param   sk      struct, struct, pointer to s_autospell_db
+ */
+static void skill_read_autospell_additional_fields(struct config_setting_t *conf, struct s_autospell_db *sk)
+{
+	// Does nothing like a boss. *cough* plugins *cough*
+}
+
+/**
+ * Compares two autospell db entries and sort it as the database expects:
+ * 1. autospell_level = 0 (blank entries) goes to the end
+ * 2. entries are sorted by autospell_level in ascending order
+ *
+ * @param entry1 first entry to compare
+ * @param entry2 second entry to compare
+ * @returns
+ * - < 0 if entry1 should go before entry2 ;
+ * - 0 if entry1 and entry2 are equivalent in sorting ;
+ * - > 0 if entry2 should go before entry1
+ */
+static int skill_autospell_db_entry_compare(const void *entry1, const void *entry2)
+{
+	nullpo_ret(entry1);
+	nullpo_ret(entry2);
+
+	struct s_autospell_db *entry1_ = (struct s_autospell_db *) entry1;
+	struct s_autospell_db *entry2_ = (struct s_autospell_db *) entry2;
+
+	// autospell_level == 0 is always at the end of the list, because they are unused entries
+	if (entry1_->autospell_level == 0 && entry2_->autospell_level != 0)
+		return 1;
+
+	if (entry2_->autospell_level == 0 && entry1_->autospell_level != 0)
+		return -1;
+
+	return entry1_->autospell_level - entry2_->autospell_level;
+}
+
+/**
+ * Reads autospell_db.conf into skill->dbs->autospell_db
+ * @param filename file to be loaded
+ * @returns true if the configuration was read (even if with some errors), false otherwise
+ */
+static bool skill_read_autospell_db(const char *filename)
+{
+	nullpo_retr(false, filename);
+
+	char filepath[256];
+
+	libconfig->format_db_path(filename, filepath, sizeof(filepath));
+
+	if (!exists(filepath)) {
+		ShowError("%s: Can't find file %s! Abort reading AutoSpell skills...\n", __func__, filepath);
+		return false;
+	}
+
+	struct config_t autospelldb;
+
+	if (libconfig->load_file(&autospelldb, filepath) == 0)
+		return false; // Libconfig error report.
+
+	struct config_setting_t *sk = libconfig->setting_get_member(autospelldb.root, "autospell_db");
+	if (sk == NULL) {
+		ShowError("%s: AutoSpell DB could not be loaded! Please check %s.\n", __func__, filepath);
+		libconfig->destroy(&autospelldb);
+		return false;
+	}
+
+	struct config_setting_t *conf;
+	int index = 0;
+	int count = 0;
+
+	// Map int -> bool
+	struct DBMap *loaded_skills_db = idb_alloc(DB_OPT_BASE);
+
+	while ((conf = libconfig->setting_get_elem(sk, index++)) != NULL) {
+		struct s_autospell_db tmp_db = {0};
+
+		skill->read_autospell_skill_id(conf, &tmp_db, index);
+		if (tmp_db.skill_id == 0)
+			continue;
+
+		const char *skill_name = skill->dbs->db[skill->get_index(tmp_db.skill_id)].name;
+
+		if (idb_exists(loaded_skills_db, tmp_db.skill_id)) {
+			ShowError("%s: Invalid AutoSpell db entry \"%d\". Skill \"%s\" (id: %d) duplicated. Skipping...\n", __func__, index, skill_name, tmp_db.skill_id);
+			continue;
+		}
+
+		skill->read_autospell_skill_level(conf, &tmp_db);
+
+		tmp_db.spirit_boost = false;
+		if (libconfig->setting_lookup_bool_real(conf, "SpiritBoost", &tmp_db.spirit_boost) == CONFIG_FALSE)
+			tmp_db.spirit_boost = false;
+
+		skill->read_autospell_additional_fields(conf, &tmp_db);
+
+		int min_level = 0;
+		ARR_FIND(0, MAX_SKILL_LEVEL, min_level, (tmp_db.skill_lv[min_level] != 0));
+		if (min_level == MAX_SKILL_LEVEL) {
+			ShowError("%s: Invalid AutoSpell db entry \"%d\". Skill \"%s\" (id: %d) is never usable (SkillLevel is always 0). Skipping...\n",
+			    __func__, index, skill_name, tmp_db.skill_id);
+			continue;
+		}
+
+		tmp_db.autospell_level = min_level + 1;
+
+		if (count >= MAX_AUTOSPELL_DB) {
+			ShowWarning("%s: Could not add skill \"%s\" (Id: \"%d\") to AutoSpell DB. Limit reached (see MAX_AUTOSPELL_DB). Skipping...\n",
+			    __func__, skill_name, tmp_db.skill_id);
+			continue;
+		}
+
+		skill->dbs->autospell_db[count] = tmp_db;
+		idb_iput(loaded_skills_db, tmp_db.skill_id, true);
+
+		count++;
+	}
+
+#if PACKETVER_MAIN_NUM < 20181128 && PACKETVER_RE_NUM < 20181031
+	if (count > 7) {
+		ShowWarning("%s: Your current packet version only supports up to 7 autospell skills, but your autospell db contains \"%d\" skills. Some skills may not be shown.\n", __func__, count);
+		ShowWarning("%s:    Update your packet version or reduce the number of skills to fix this warning.\n", __func__);
+	}
+#endif
+
+	db_destroy(loaded_skills_db);
+	libconfig->destroy(&autospelldb);
+
+	qsort(skill->dbs->autospell_db, MAX_AUTOSPELL_DB, sizeof(struct s_autospell_db), skill->autospell_db_entry_compare);
+
+	ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filepath);
+
+	return true;
+}
+
 /*===============================
  * DB reading.
  * produce_db.txt
@@ -24774,7 +24991,7 @@ static void skill_readdb(bool minimal)
 		struct s_skill_db *db = &skill->dbs->db[i];
 		if (db->nameid == 0)
 			continue;
-		
+
 		if (skill->name2id(db->name) != 0) {
 			ShowError("%s: Duplicated skill name %s found for Skill ID %d (Other Skill ID: %d). Skipping name...",
 				__func__, db->name, db->nameid, skill->name2id(db->name));
@@ -24798,6 +25015,7 @@ static void skill_readdb(bool minimal)
 	sv->readdb(map->db_path, "magicmushroom_db.txt",         ',',   1,                        1, MAX_SKILL_MAGICMUSHROOM_DB, skill->parse_row_magicmushroomdb);
 	sv->readdb(map->db_path, "skill_improvise_db.txt",       ',',   2,                        2,     MAX_SKILL_IMPROVISE_DB, skill->parse_row_improvisedb);
 	sv->readdb(map->db_path, "skill_changematerial_db.txt",  ',',   4,                    4+2*5,       MAX_SKILL_PRODUCE_DB, skill->parse_row_changematerialdb);
+	skill->read_autospell_db(DBPATH "autospell_db.conf");
 }
 
 static void skill_reload(void)
@@ -25161,6 +25379,13 @@ void skill_defaults(void)
 	skill->validate_status_change = skill_validate_status_change;
 	skill->validate_additional_fields = skill_validate_additional_fields;
 	skill->read_skilldb = skill_read_skilldb;
+	/* AutoSpell DB Libconfig */
+	skill->read_autospell_skill_id = skill_read_autospell_skill_id;
+	skill->read_autospell_skill_level = skill_read_autospell_skill_level;
+	skill->read_autospell_additional_fields = skill_read_autospell_additional_fields;
+	skill->autospell_db_entry_compare = skill_autospell_db_entry_compare;
+	skill->read_autospell_db = skill_read_autospell_db;
+	/* db reading helpers */
 	skill->config_set_level = skill_config_set_level;
 	skill->level_set_value = skill_level_set_value;
 	/* */
diff --git a/src/map/skill.h b/src/map/skill.h
index 6725a43c849..72db5d5fb88 100644
--- a/src/map/skill.h
+++ b/src/map/skill.h
@@ -77,6 +77,7 @@ struct status_change_entry;
 
 #define MAX_SKILL_SPELLBOOK_DB     17
 #define MAX_SKILL_MAGICMUSHROOM_DB 23
+#define MAX_AUTOSPELL_DB           7
 
 //Walk intervals at which chase-skills are attempted to be triggered.
 #define WALK_SKILL_INTERVAL 5
@@ -1810,6 +1811,14 @@ enum skill_enabled_npc_flags {
  * Structures
  **/
 
+/** Information about a possible skill for AutoSpell */
+struct s_autospell_db {
+	int autospell_level; //< Minimum AutoSpell level to show this skill
+	int skill_id; //< Skill Id
+	int skill_lv[MAX_SKILL_LEVEL]; //< Maximum usable skill level at each AutoSpell level
+	bool spirit_boost; //< Whether Sage's Spirit boosts this skill to maximum level
+};
+
 /** A container holding all required items. **/
 struct skill_required_item_data {
 	struct {
@@ -2016,6 +2025,10 @@ BEGIN_ZEROED_BLOCK; // This block will be zeroed in skill_defaults() as well as
 	struct s_skill_improvise_db improvise_db[MAX_SKILL_IMPROVISE_DB];
 	struct s_skill_changematerial_db changematerial_db[MAX_SKILL_PRODUCE_DB];
 	struct s_skill_spellbook_db spellbook_db[MAX_SKILL_SPELLBOOK_DB];
+	/**
+	 * Skills for AutoSpell. entries with autospell_level = 0 are unused (and always at the end)
+	 */
+	struct s_autospell_db autospell_db[MAX_AUTOSPELL_DB];
 END_ZEROED_BLOCK;
 	struct s_skill_unit_layout unit_layout[MAX_SKILL_UNIT_LAYOUT];
 };
@@ -2292,6 +2305,11 @@ struct skill_interface {
 	void (*validate_status_change) (struct config_setting_t *conf, struct s_skill_db *sk, bool inherited);
 	void (*validate_additional_fields) (struct config_setting_t *conf, struct s_skill_db *sk, bool inherited);
 	bool (*read_skilldb) (const char *filename);
+	void (*read_autospell_skill_id) (struct config_setting_t *conf, struct s_autospell_db *sk, int index);
+	void (*read_autospell_skill_level) (struct config_setting_t *conf, struct s_autospell_db *sk);
+	void (*read_autospell_additional_fields) (struct config_setting_t *conf, struct s_autospell_db *sk);
+	int (*autospell_db_entry_compare) (const void *entry1, const void *entry2);
+	bool (*read_autospell_db) (const char *filename);
 	void (*config_set_level) (struct config_setting_t *conf, int *arr);
 	void (*level_set_value) (int *arr, int value);
 	bool (*parse_row_producedb) (char* split[], int columns, int current);
diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc
index 29fb7952d5e..05fad566533 100644
--- a/src/plugins/HPMHooking/HPMHooking.Defs.inc
+++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc
@@ -8748,6 +8748,16 @@ typedef void (*HPMHOOK_pre_skill_validate_additional_fields) (struct config_sett
 typedef void (*HPMHOOK_post_skill_validate_additional_fields) (struct config_setting_t *conf, struct s_skill_db *sk, bool inherited);
 typedef bool (*HPMHOOK_pre_skill_read_skilldb) (const char **filename);
 typedef bool (*HPMHOOK_post_skill_read_skilldb) (bool retVal___, const char *filename);
+typedef void (*HPMHOOK_pre_skill_read_autospell_skill_id) (struct config_setting_t **conf, struct s_autospell_db **sk, int *index);
+typedef void (*HPMHOOK_post_skill_read_autospell_skill_id) (struct config_setting_t *conf, struct s_autospell_db *sk, int index);
+typedef void (*HPMHOOK_pre_skill_read_autospell_skill_level) (struct config_setting_t **conf, struct s_autospell_db **sk);
+typedef void (*HPMHOOK_post_skill_read_autospell_skill_level) (struct config_setting_t *conf, struct s_autospell_db *sk);
+typedef void (*HPMHOOK_pre_skill_read_autospell_additional_fields) (struct config_setting_t **conf, struct s_autospell_db **sk);
+typedef void (*HPMHOOK_post_skill_read_autospell_additional_fields) (struct config_setting_t *conf, struct s_autospell_db *sk);
+typedef int (*HPMHOOK_pre_skill_autospell_db_entry_compare) (const void **entry1, const void **entry2);
+typedef int (*HPMHOOK_post_skill_autospell_db_entry_compare) (int retVal___, const void *entry1, const void *entry2);
+typedef bool (*HPMHOOK_pre_skill_read_autospell_db) (const char **filename);
+typedef bool (*HPMHOOK_post_skill_read_autospell_db) (bool retVal___, const char *filename);
 typedef void (*HPMHOOK_pre_skill_config_set_level) (struct config_setting_t **conf, int **arr);
 typedef void (*HPMHOOK_post_skill_config_set_level) (struct config_setting_t *conf, int *arr);
 typedef void (*HPMHOOK_pre_skill_level_set_value) (int **arr, int *value);
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
index 080837f4a02..2b53f20ac81 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
@@ -6670,6 +6670,16 @@ struct {
 	struct HPMHookPoint *HP_skill_validate_additional_fields_post;
 	struct HPMHookPoint *HP_skill_read_skilldb_pre;
 	struct HPMHookPoint *HP_skill_read_skilldb_post;
+	struct HPMHookPoint *HP_skill_read_autospell_skill_id_pre;
+	struct HPMHookPoint *HP_skill_read_autospell_skill_id_post;
+	struct HPMHookPoint *HP_skill_read_autospell_skill_level_pre;
+	struct HPMHookPoint *HP_skill_read_autospell_skill_level_post;
+	struct HPMHookPoint *HP_skill_read_autospell_additional_fields_pre;
+	struct HPMHookPoint *HP_skill_read_autospell_additional_fields_post;
+	struct HPMHookPoint *HP_skill_autospell_db_entry_compare_pre;
+	struct HPMHookPoint *HP_skill_autospell_db_entry_compare_post;
+	struct HPMHookPoint *HP_skill_read_autospell_db_pre;
+	struct HPMHookPoint *HP_skill_read_autospell_db_post;
 	struct HPMHookPoint *HP_skill_config_set_level_pre;
 	struct HPMHookPoint *HP_skill_config_set_level_post;
 	struct HPMHookPoint *HP_skill_level_set_value_pre;
@@ -14209,6 +14219,16 @@ struct {
 	int HP_skill_validate_additional_fields_post;
 	int HP_skill_read_skilldb_pre;
 	int HP_skill_read_skilldb_post;
+	int HP_skill_read_autospell_skill_id_pre;
+	int HP_skill_read_autospell_skill_id_post;
+	int HP_skill_read_autospell_skill_level_pre;
+	int HP_skill_read_autospell_skill_level_post;
+	int HP_skill_read_autospell_additional_fields_pre;
+	int HP_skill_read_autospell_additional_fields_post;
+	int HP_skill_autospell_db_entry_compare_pre;
+	int HP_skill_autospell_db_entry_compare_post;
+	int HP_skill_read_autospell_db_pre;
+	int HP_skill_read_autospell_db_post;
 	int HP_skill_config_set_level_pre;
 	int HP_skill_config_set_level_post;
 	int HP_skill_level_set_value_pre;
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
index a126a441a61..85de30f642b 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
@@ -3411,6 +3411,11 @@ struct HookingPointData HookingPoints[] = {
 	{ HP_POP(skill->validate_status_change, HP_skill_validate_status_change) },
 	{ HP_POP(skill->validate_additional_fields, HP_skill_validate_additional_fields) },
 	{ HP_POP(skill->read_skilldb, HP_skill_read_skilldb) },
+	{ HP_POP(skill->read_autospell_skill_id, HP_skill_read_autospell_skill_id) },
+	{ HP_POP(skill->read_autospell_skill_level, HP_skill_read_autospell_skill_level) },
+	{ HP_POP(skill->read_autospell_additional_fields, HP_skill_read_autospell_additional_fields) },
+	{ HP_POP(skill->autospell_db_entry_compare, HP_skill_autospell_db_entry_compare) },
+	{ HP_POP(skill->read_autospell_db, HP_skill_read_autospell_db) },
 	{ HP_POP(skill->config_set_level, HP_skill_config_set_level) },
 	{ HP_POP(skill->level_set_value, HP_skill_level_set_value) },
 	{ HP_POP(skill->parse_row_producedb, HP_skill_parse_row_producedb) },
diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
index 65a53b020ec..7bd2e0252c2 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
@@ -89115,6 +89115,138 @@ bool HP_skill_read_skilldb(const char *filename) {
 	}
 	return retVal___;
 }
+void HP_skill_read_autospell_skill_id(struct config_setting_t *conf, struct s_autospell_db *sk, int index) {
+	int hIndex = 0;
+	if (HPMHooks.count.HP_skill_read_autospell_skill_id_pre > 0) {
+		void (*preHookFunc) (struct config_setting_t **conf, struct s_autospell_db **sk, int *index);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_skill_id_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_read_autospell_skill_id_pre[hIndex].func;
+			preHookFunc(&conf, &sk, &index);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return;
+		}
+	}
+	{
+		HPMHooks.source.skill.read_autospell_skill_id(conf, sk, index);
+	}
+	if (HPMHooks.count.HP_skill_read_autospell_skill_id_post > 0) {
+		void (*postHookFunc) (struct config_setting_t *conf, struct s_autospell_db *sk, int index);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_skill_id_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_read_autospell_skill_id_post[hIndex].func;
+			postHookFunc(conf, sk, index);
+		}
+	}
+	return;
+}
+void HP_skill_read_autospell_skill_level(struct config_setting_t *conf, struct s_autospell_db *sk) {
+	int hIndex = 0;
+	if (HPMHooks.count.HP_skill_read_autospell_skill_level_pre > 0) {
+		void (*preHookFunc) (struct config_setting_t **conf, struct s_autospell_db **sk);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_skill_level_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_read_autospell_skill_level_pre[hIndex].func;
+			preHookFunc(&conf, &sk);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return;
+		}
+	}
+	{
+		HPMHooks.source.skill.read_autospell_skill_level(conf, sk);
+	}
+	if (HPMHooks.count.HP_skill_read_autospell_skill_level_post > 0) {
+		void (*postHookFunc) (struct config_setting_t *conf, struct s_autospell_db *sk);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_skill_level_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_read_autospell_skill_level_post[hIndex].func;
+			postHookFunc(conf, sk);
+		}
+	}
+	return;
+}
+void HP_skill_read_autospell_additional_fields(struct config_setting_t *conf, struct s_autospell_db *sk) {
+	int hIndex = 0;
+	if (HPMHooks.count.HP_skill_read_autospell_additional_fields_pre > 0) {
+		void (*preHookFunc) (struct config_setting_t **conf, struct s_autospell_db **sk);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_additional_fields_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_read_autospell_additional_fields_pre[hIndex].func;
+			preHookFunc(&conf, &sk);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return;
+		}
+	}
+	{
+		HPMHooks.source.skill.read_autospell_additional_fields(conf, sk);
+	}
+	if (HPMHooks.count.HP_skill_read_autospell_additional_fields_post > 0) {
+		void (*postHookFunc) (struct config_setting_t *conf, struct s_autospell_db *sk);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_additional_fields_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_read_autospell_additional_fields_post[hIndex].func;
+			postHookFunc(conf, sk);
+		}
+	}
+	return;
+}
+int HP_skill_autospell_db_entry_compare(const void *entry1, const void *entry2) {
+	int hIndex = 0;
+	int retVal___ = 0;
+	if (HPMHooks.count.HP_skill_autospell_db_entry_compare_pre > 0) {
+		int (*preHookFunc) (const void **entry1, const void **entry2);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_db_entry_compare_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_autospell_db_entry_compare_pre[hIndex].func;
+			retVal___ = preHookFunc(&entry1, &entry2);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return retVal___;
+		}
+	}
+	{
+		retVal___ = HPMHooks.source.skill.autospell_db_entry_compare(entry1, entry2);
+	}
+	if (HPMHooks.count.HP_skill_autospell_db_entry_compare_post > 0) {
+		int (*postHookFunc) (int retVal___, const void *entry1, const void *entry2);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_db_entry_compare_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_autospell_db_entry_compare_post[hIndex].func;
+			retVal___ = postHookFunc(retVal___, entry1, entry2);
+		}
+	}
+	return retVal___;
+}
+bool HP_skill_read_autospell_db(const char *filename) {
+	int hIndex = 0;
+	bool retVal___ = false;
+	if (HPMHooks.count.HP_skill_read_autospell_db_pre > 0) {
+		bool (*preHookFunc) (const char **filename);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_db_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_read_autospell_db_pre[hIndex].func;
+			retVal___ = preHookFunc(&filename);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return retVal___;
+		}
+	}
+	{
+		retVal___ = HPMHooks.source.skill.read_autospell_db(filename);
+	}
+	if (HPMHooks.count.HP_skill_read_autospell_db_post > 0) {
+		bool (*postHookFunc) (bool retVal___, const char *filename);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_read_autospell_db_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_read_autospell_db_post[hIndex].func;
+			retVal___ = postHookFunc(retVal___, filename);
+		}
+	}
+	return retVal___;
+}
 void HP_skill_config_set_level(struct config_setting_t *conf, int *arr) {
 	int hIndex = 0;
 	if (HPMHooks.count.HP_skill_config_set_level_pre > 0) {

From 1f5212e1dc9297c65e883b6ae8e2831ea0d48a3d Mon Sep 17 00:00:00 2001
From: "Guilherme G. Menaldo" <guilherme.menaldo@outlook.com>
Date: Thu, 17 Aug 2023 23:18:46 -0300
Subject: [PATCH 2/4] refactor SA_AUTOSPELL spell selection

- split spell selection logic into smaller functions
- move spell list building to skill.c instead of clif
- use config file for autospell list
---
 src/map/clif.c                                |  51 ++++----
 src/map/clif.h                                |   2 +-
 src/map/skill.c                               | 115 ++++++++++++------
 src/map/skill.h                               |   2 +
 src/plugins/HPMHooking/HPMHooking.Defs.inc    |   8 +-
 .../HPMHooking_map.HPMHooksCore.inc           |   8 ++
 .../HPMHooking_map.HookingPoints.inc          |   2 +
 .../HPMHooking/HPMHooking_map.Hooks.inc       |  64 +++++++++-
 8 files changed, 178 insertions(+), 74 deletions(-)

diff --git a/src/map/clif.c b/src/map/clif.c
index 8d63c6d0088..da604f3f2fb 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -8007,46 +8007,43 @@ static void clif_pet_food(struct map_session_data *sd, int foodid, int fail)
 	WFIFOSET(fd, sizeof(struct PACKET_ZC_FEED_PET));
 }
 
-/// Presents a list of skills that can be auto-spelled (ZC_AUTOSPELLLIST).
-/// 01cd { <skill id>.L }*7
-static void clif_autospell(struct map_session_data *sd, uint16 skill_lv)
+/**
+ * Presents a list of skills that can be auto-spelled (ZC_AUTOSPELLLIST).
+ *
+ * 01cd { <skill id>.L }*7
+ * 0afb <packet len>.W { <skills>.L }*
+ *
+ * @param sd player who will receive the list
+ * @param skill_lv autospell skill level
+ * @param skill_ids_list list of available skills to choose from
+ * @param list_len length of skill_ids_list
+ */
+static void clif_autospell(struct map_session_data *sd, uint16 skill_lv, int *skill_ids_list, int list_len)
 {
 #if PACKETVER_MAIN_NUM >= 20090406 || defined(PACKETVER_RE) || defined(PACKETVER_ZERO) || PACKETVER_SAK_NUM >= 20080618
 	nullpo_retv(sd);
 
-	int fd = sd->fd;
 #if PACKETVER_MAIN_NUM >= 20181128 || PACKETVER_RE_NUM >= 20181031
-	// reserve space for 7 skills
-	WFIFOHEAD(fd, sizeof(struct PACKET_ZC_AUTOSPELLLIST) + 4 * 7);
+	const int len = sizeof(struct PACKET_ZC_AUTOSPELLLIST) + sizeof(int) * list_len;
 #else
-	WFIFOHEAD(fd, sizeof(struct PACKET_ZC_AUTOSPELLLIST));
+	const int len = sizeof(struct PACKET_ZC_AUTOSPELLLIST);
+	if (list_len > 7) {
+		ShowError("%s: AutoSpell list too big for current client. Limit: %d, received: %d. Truncating list...\n", __func__, 7, list_len);
+		list_len = 7;
+	}
 #endif
+
+	int fd = sd->fd;
+	WFIFOHEAD(fd, len);
+
 	struct PACKET_ZC_AUTOSPELLLIST *p = WFIFOP(fd, 0);
 	memset(p, 0, sizeof(struct PACKET_ZC_AUTOSPELLLIST));
-	p->packetType = HEADER_ZC_AUTOSPELLLIST;
-	int index = 0;
-
-	if (skill_lv > 0 && pc->checkskill(sd, MG_NAPALMBEAT) > 0)
-		p->skills[index++] = MG_NAPALMBEAT;
-	if (skill_lv > 1 && pc->checkskill(sd, MG_COLDBOLT) > 0)
-		p->skills[index++] = MG_COLDBOLT;
-	if (skill_lv > 1 && pc->checkskill(sd, MG_FIREBOLT) > 0)
-		p->skills[index++] = MG_FIREBOLT;
-	if (skill_lv > 1 && pc->checkskill(sd, MG_LIGHTNINGBOLT) > 0)
-		p->skills[index++] = MG_LIGHTNINGBOLT;
-	if (skill_lv > 4 && pc->checkskill(sd, MG_SOULSTRIKE) > 0)
-		p->skills[index++] = MG_SOULSTRIKE;
-	if (skill_lv > 7 && pc->checkskill(sd, MG_FIREBALL) > 0)
-		p->skills[index++] = MG_FIREBALL;
-	if (skill_lv > 9 && pc->checkskill(sd, MG_FROSTDIVER) > 0)
-		p->skills[index++] = MG_FROSTDIVER;
 
+	p->packetType = HEADER_ZC_AUTOSPELLLIST;
 #if PACKETVER_MAIN_NUM >= 20181128 || PACKETVER_RE_NUM >= 20181031
-	const int len = sizeof(struct PACKET_ZC_AUTOSPELLLIST) + index * 4;
 	p->packetLength = len;
-#else
-	const int len = sizeof(struct PACKET_ZC_AUTOSPELLLIST);
 #endif
+	memcpy(p->skills, skill_ids_list, sizeof(int) * list_len);
 	WFIFOSET(fd, len);
 
 	sd->menuskill_id = SA_AUTOSPELL;
diff --git a/src/map/clif.h b/src/map/clif.h
index f1c9e826603..01213081c44 100644
--- a/src/map/clif.h
+++ b/src/map/clif.h
@@ -1091,7 +1091,7 @@ struct clif_interface {
 	void (*skill_mapinfomessage) (struct map_session_data *sd, int type);
 	void (*skill_produce_mix_list) (struct map_session_data *sd, int skill_id, int trigger);
 	void (*cooking_list) (struct map_session_data *sd, int trigger, uint16 skill_id, int qty, int list_type);
-	void (*autospell) (struct map_session_data *sd,uint16 skill_lv);
+	void (*autospell) (struct map_session_data *sd, uint16 skill_lv, int *skill_ids_list, int list_len);
 	void (*combo_delay) (struct block_list *bl,int wait);
 	void (*status_change) (struct block_list *bl, int relevant_bl, int type, int flag, int total_tick, int val1, int val2, int val3);
 	void (*status_change_sub) (struct block_list *bl, int type, int relevant_bl, int flag, int tick, int total_tick, int val1, int val2, int val3);
diff --git a/src/map/skill.c b/src/map/skill.c
index e4f0ba79a90..9e78e4bcf5d 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -8776,43 +8776,8 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
 			sc_start(src, bl, type, 100, skill_lv, skill->get_time(skill_id, skill_lv), skill_id);
 			break;
 		case SA_AUTOSPELL:
-			clif->skill_nodamage(src,bl,skill_id,skill_lv,1);
-			if(sd){
-				sd->state.workinprogress = 3;
-				clif->autospell(sd,skill_lv);
-			}else {
-				int maxlv=1,spellid=0;
-				static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT };
-				if(skill_lv >= 10) {
-					spellid = MG_FROSTDIVER;
-#if 0
-					if (tsc && tsc->data[SC_SOULLINK] && tsc->data[SC_SOULLINK]->val2 == SA_SAGE)
-						maxlv = 10;
-					else
-#endif // 0
-						maxlv = skill_lv - 9;
-				}
-				else if(skill_lv >=8) {
-					spellid = MG_FIREBALL;
-					maxlv = skill_lv - 7;
-				}
-				else if(skill_lv >=5) {
-					spellid = MG_SOULSTRIKE;
-					maxlv = skill_lv - 4;
-				}
-				else if(skill_lv >=2) {
-					int i = rnd() % ARRAYLENGTH(spellarray);
-					spellid = spellarray[i];
-					maxlv = skill_lv - 1;
-				}
-				else if(skill_lv > 0) {
-					spellid = MG_NAPALMBEAT;
-					maxlv = 3;
-				}
-				if(spellid > 0)
-					sc_start4(src,src,SC_AUTOSPELL,100,skill_lv,spellid,maxlv,0,
-						skill->get_time(SA_AUTOSPELL, skill_lv), SA_AUTOSPELL);
-			}
+			clif->skill_nodamage(src, bl, skill_id, skill_lv, 1);
+			skill->autospell_select_spell(src, skill_lv);
 			break;
 
 		case BS_GREED:
@@ -17834,8 +17799,80 @@ static void skill_weaponrefine(struct map_session_data *sd, int idx)
 }
 
 /*==========================================
- *
+ * Auto Spell / Hindsight
  *------------------------------------------*/
+
+/**
+ * Prepares list and request player to choose the spell they want to use (Auto Spell skill)
+ * @param sd player casting the skill
+ * @param skill_lv Auto Spell level
+ */
+static void skill_autospell_select_spell_pc(struct map_session_data *sd, int skill_lv)
+{
+	nullpo_retv(sd);
+
+	int *skill_ids;
+	CREATE(skill_ids, int, MAX_AUTOSPELL_DB);
+
+	int valid_len = 0;
+
+	for (int i = 0; i < MAX_AUTOSPELL_DB; ++i) {
+		const struct s_autospell_db *sk = &skill->dbs->autospell_db[i];
+		if (sk->autospell_level == 0)
+			break;
+
+		if (skill_lv >= sk->autospell_level && pc->checkskill(sd, sk->skill_id) > 0) {
+			skill_ids[valid_len] = sk->skill_id;
+			valid_len++;
+		}
+	}
+
+	sd->state.workinprogress = 3;
+	clif->autospell(sd, skill_lv, skill_ids, valid_len);
+
+	aFree(skill_ids);
+}
+
+/**
+ * Auto Spell skill spell selection step.
+ * @param bl unit casting the skill
+ * @param skill_lv skill level
+ */
+static void skill_autospell_select_spell(struct block_list *bl, int skill_lv)
+{
+	nullpo_retv(bl);
+
+	if (bl->type == BL_PC) {
+		skill->autospell_select_spell_pc(BL_CAST(BL_PC, bl), skill_lv);
+		return;
+	}
+
+	int lower_idx = -1;
+	int upper_idx = 0;
+	int highest_autospell_tier = 0;
+	while (upper_idx < MAX_AUTOSPELL_DB
+	    && skill->dbs->autospell_db[upper_idx].autospell_level > 0
+	    && skill->dbs->autospell_db[upper_idx].autospell_level <= skill_lv) {
+		if (highest_autospell_tier != skill->dbs->autospell_db[upper_idx].autospell_level) {
+			lower_idx = upper_idx;
+			highest_autospell_tier = skill->dbs->autospell_db[upper_idx].autospell_level;
+		}
+
+		upper_idx++;
+	}
+
+	if (lower_idx == -1)
+		return; // No skill available
+
+	int skill_idx = lower_idx;
+	if ((upper_idx - lower_idx) > 1)
+		skill_idx += rnd() % (upper_idx - lower_idx);
+
+	const struct s_autospell_db *sk = &skill->dbs->autospell_db[skill_idx];
+	sc_start4(bl, bl, SC_AUTOSPELL, 100, skill_lv, sk->skill_id, sk->skill_lv[skill_lv - 1], 0,
+		skill->get_time(SA_AUTOSPELL, skill_lv), SA_AUTOSPELL);
+}
+
 static int skill_autospell(struct map_session_data *sd, uint16 skill_id)
 {
 	uint16 skill_lv;
@@ -25242,6 +25279,8 @@ void skill_defaults(void)
 	skill->repairweapon = skill_repairweapon;
 	skill->identify = skill_identify;
 	skill->weaponrefine = skill_weaponrefine;
+	skill->autospell_select_spell = skill_autospell_select_spell;
+	skill->autospell_select_spell_pc = skill_autospell_select_spell_pc;
 	skill->autospell = skill_autospell;
 	skill->calc_heal = skill_calc_heal;
 	skill->check_cloaking = skill_check_cloaking;
diff --git a/src/map/skill.h b/src/map/skill.h
index 72db5d5fb88..a61f12feed6 100644
--- a/src/map/skill.h
+++ b/src/map/skill.h
@@ -2168,6 +2168,8 @@ struct skill_interface {
 	void (*repairweapon) (struct map_session_data *sd, int idx);
 	void (*identify) (struct map_session_data *sd,int idx);
 	void (*weaponrefine) (struct map_session_data *sd,int idx);
+	void (*autospell_select_spell) (struct block_list *bl, int skill_lv);
+	void (*autospell_select_spell_pc) (struct map_session_data *sd, int skill_lv);
 	int (*autospell) (struct map_session_data *md,uint16 skill_id);
 	int (*calc_heal) (struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal);
 	bool (*check_cloaking) (struct block_list *bl, struct status_change_entry *sce);
diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc
index 05fad566533..7880a6de392 100644
--- a/src/plugins/HPMHooking/HPMHooking.Defs.inc
+++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc
@@ -1572,8 +1572,8 @@ typedef void (*HPMHOOK_pre_clif_skill_produce_mix_list) (struct map_session_data
 typedef void (*HPMHOOK_post_clif_skill_produce_mix_list) (struct map_session_data *sd, int skill_id, int trigger);
 typedef void (*HPMHOOK_pre_clif_cooking_list) (struct map_session_data **sd, int *trigger, uint16 *skill_id, int *qty, int *list_type);
 typedef void (*HPMHOOK_post_clif_cooking_list) (struct map_session_data *sd, int trigger, uint16 skill_id, int qty, int list_type);
-typedef void (*HPMHOOK_pre_clif_autospell) (struct map_session_data **sd, uint16 *skill_lv);
-typedef void (*HPMHOOK_post_clif_autospell) (struct map_session_data *sd, uint16 skill_lv);
+typedef void (*HPMHOOK_pre_clif_autospell) (struct map_session_data **sd, uint16 *skill_lv, int **skill_ids_list, int *list_len);
+typedef void (*HPMHOOK_post_clif_autospell) (struct map_session_data *sd, uint16 skill_lv, int *skill_ids_list, int list_len);
 typedef void (*HPMHOOK_pre_clif_combo_delay) (struct block_list **bl, int *wait);
 typedef void (*HPMHOOK_post_clif_combo_delay) (struct block_list *bl, int wait);
 typedef void (*HPMHOOK_pre_clif_status_change) (struct block_list **bl, int *relevant_bl, int *type, int *flag, int *total_tick, int *val1, int *val2, int *val3);
@@ -8474,6 +8474,10 @@ typedef void (*HPMHOOK_pre_skill_identify) (struct map_session_data **sd, int *i
 typedef void (*HPMHOOK_post_skill_identify) (struct map_session_data *sd, int idx);
 typedef void (*HPMHOOK_pre_skill_weaponrefine) (struct map_session_data **sd, int *idx);
 typedef void (*HPMHOOK_post_skill_weaponrefine) (struct map_session_data *sd, int idx);
+typedef void (*HPMHOOK_pre_skill_autospell_select_spell) (struct block_list **bl, int *skill_lv);
+typedef void (*HPMHOOK_post_skill_autospell_select_spell) (struct block_list *bl, int skill_lv);
+typedef void (*HPMHOOK_pre_skill_autospell_select_spell_pc) (struct map_session_data **sd, int *skill_lv);
+typedef void (*HPMHOOK_post_skill_autospell_select_spell_pc) (struct map_session_data *sd, int skill_lv);
 typedef int (*HPMHOOK_pre_skill_autospell) (struct map_session_data **md, uint16 *skill_id);
 typedef int (*HPMHOOK_post_skill_autospell) (int retVal___, struct map_session_data *md, uint16 skill_id);
 typedef int (*HPMHOOK_pre_skill_calc_heal) (struct block_list **src, struct block_list **target, uint16 *skill_id, uint16 *skill_lv, bool *heal);
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
index 2b53f20ac81..b7d000591cb 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
@@ -6396,6 +6396,10 @@ struct {
 	struct HPMHookPoint *HP_skill_identify_post;
 	struct HPMHookPoint *HP_skill_weaponrefine_pre;
 	struct HPMHookPoint *HP_skill_weaponrefine_post;
+	struct HPMHookPoint *HP_skill_autospell_select_spell_pre;
+	struct HPMHookPoint *HP_skill_autospell_select_spell_post;
+	struct HPMHookPoint *HP_skill_autospell_select_spell_pc_pre;
+	struct HPMHookPoint *HP_skill_autospell_select_spell_pc_post;
 	struct HPMHookPoint *HP_skill_autospell_pre;
 	struct HPMHookPoint *HP_skill_autospell_post;
 	struct HPMHookPoint *HP_skill_calc_heal_pre;
@@ -13945,6 +13949,10 @@ struct {
 	int HP_skill_identify_post;
 	int HP_skill_weaponrefine_pre;
 	int HP_skill_weaponrefine_post;
+	int HP_skill_autospell_select_spell_pre;
+	int HP_skill_autospell_select_spell_post;
+	int HP_skill_autospell_select_spell_pc_pre;
+	int HP_skill_autospell_select_spell_pc_post;
 	int HP_skill_autospell_pre;
 	int HP_skill_autospell_post;
 	int HP_skill_calc_heal_pre;
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
index 85de30f642b..6b14068905b 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
@@ -3274,6 +3274,8 @@ struct HookingPointData HookingPoints[] = {
 	{ HP_POP(skill->repairweapon, HP_skill_repairweapon) },
 	{ HP_POP(skill->identify, HP_skill_identify) },
 	{ HP_POP(skill->weaponrefine, HP_skill_weaponrefine) },
+	{ HP_POP(skill->autospell_select_spell, HP_skill_autospell_select_spell) },
+	{ HP_POP(skill->autospell_select_spell_pc, HP_skill_autospell_select_spell_pc) },
 	{ HP_POP(skill->autospell, HP_skill_autospell) },
 	{ HP_POP(skill->calc_heal, HP_skill_calc_heal) },
 	{ HP_POP(skill->check_cloaking, HP_skill_check_cloaking) },
diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
index 7bd2e0252c2..95d6ff13665 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
@@ -12125,14 +12125,14 @@ void HP_clif_cooking_list(struct map_session_data *sd, int trigger, uint16 skill
 	}
 	return;
 }
-void HP_clif_autospell(struct map_session_data *sd, uint16 skill_lv) {
+void HP_clif_autospell(struct map_session_data *sd, uint16 skill_lv, int *skill_ids_list, int list_len) {
 	int hIndex = 0;
 	if (HPMHooks.count.HP_clif_autospell_pre > 0) {
-		void (*preHookFunc) (struct map_session_data **sd, uint16 *skill_lv);
+		void (*preHookFunc) (struct map_session_data **sd, uint16 *skill_lv, int **skill_ids_list, int *list_len);
 		*HPMforce_return = false;
 		for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_autospell_pre; hIndex++) {
 			preHookFunc = HPMHooks.list.HP_clif_autospell_pre[hIndex].func;
-			preHookFunc(&sd, &skill_lv);
+			preHookFunc(&sd, &skill_lv, &skill_ids_list, &list_len);
 		}
 		if (*HPMforce_return) {
 			*HPMforce_return = false;
@@ -12140,13 +12140,13 @@ void HP_clif_autospell(struct map_session_data *sd, uint16 skill_lv) {
 		}
 	}
 	{
-		HPMHooks.source.clif.autospell(sd, skill_lv);
+		HPMHooks.source.clif.autospell(sd, skill_lv, skill_ids_list, list_len);
 	}
 	if (HPMHooks.count.HP_clif_autospell_post > 0) {
-		void (*postHookFunc) (struct map_session_data *sd, uint16 skill_lv);
+		void (*postHookFunc) (struct map_session_data *sd, uint16 skill_lv, int *skill_ids_list, int list_len);
 		for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_autospell_post; hIndex++) {
 			postHookFunc = HPMHooks.list.HP_clif_autospell_post[hIndex].func;
-			postHookFunc(sd, skill_lv);
+			postHookFunc(sd, skill_lv, skill_ids_list, list_len);
 		}
 	}
 	return;
@@ -85337,6 +85337,58 @@ void HP_skill_weaponrefine(struct map_session_data *sd, int idx) {
 	}
 	return;
 }
+void HP_skill_autospell_select_spell(struct block_list *bl, int skill_lv) {
+	int hIndex = 0;
+	if (HPMHooks.count.HP_skill_autospell_select_spell_pre > 0) {
+		void (*preHookFunc) (struct block_list **bl, int *skill_lv);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_select_spell_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_autospell_select_spell_pre[hIndex].func;
+			preHookFunc(&bl, &skill_lv);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return;
+		}
+	}
+	{
+		HPMHooks.source.skill.autospell_select_spell(bl, skill_lv);
+	}
+	if (HPMHooks.count.HP_skill_autospell_select_spell_post > 0) {
+		void (*postHookFunc) (struct block_list *bl, int skill_lv);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_select_spell_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_autospell_select_spell_post[hIndex].func;
+			postHookFunc(bl, skill_lv);
+		}
+	}
+	return;
+}
+void HP_skill_autospell_select_spell_pc(struct map_session_data *sd, int skill_lv) {
+	int hIndex = 0;
+	if (HPMHooks.count.HP_skill_autospell_select_spell_pc_pre > 0) {
+		void (*preHookFunc) (struct map_session_data **sd, int *skill_lv);
+		*HPMforce_return = false;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_select_spell_pc_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_autospell_select_spell_pc_pre[hIndex].func;
+			preHookFunc(&sd, &skill_lv);
+		}
+		if (*HPMforce_return) {
+			*HPMforce_return = false;
+			return;
+		}
+	}
+	{
+		HPMHooks.source.skill.autospell_select_spell_pc(sd, skill_lv);
+	}
+	if (HPMHooks.count.HP_skill_autospell_select_spell_pc_post > 0) {
+		void (*postHookFunc) (struct map_session_data *sd, int skill_lv);
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_select_spell_pc_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_autospell_select_spell_pc_post[hIndex].func;
+			postHookFunc(sd, skill_lv);
+		}
+	}
+	return;
+}
 int HP_skill_autospell(struct map_session_data *md, uint16 skill_id) {
 	int hIndex = 0;
 	int retVal___ = 0;

From a8c998bd0f8d7ffbecca2075b9f3fceb42dd9d45 Mon Sep 17 00:00:00 2001
From: "Guilherme G. Menaldo" <guilherme.menaldo@outlook.com>
Date: Thu, 17 Aug 2023 23:44:39 -0300
Subject: [PATCH 3/4] refactor SA_AUTOSPELL selected skill processing

- replace if chain with array lookup/config file
- renamed skill_autospell to skill_autospell_spell_selected
---
 src/map/clif.c                                |  2 +-
 src/map/skill.c                               | 59 +++++++++----------
 src/map/skill.h                               |  2 +-
 src/plugins/HPMHooking/HPMHooking.Defs.inc    |  4 +-
 .../HPMHooking_map.HPMHooksCore.inc           |  8 +--
 .../HPMHooking_map.HookingPoints.inc          |  2 +-
 .../HPMHooking/HPMHooking_map.Hooks.inc       | 16 ++---
 7 files changed, 46 insertions(+), 47 deletions(-)

diff --git a/src/map/clif.c b/src/map/clif.c
index da604f3f2fb..1355772a433 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -14196,7 +14196,7 @@ static void clif_parse_AutoSpell(int fd, struct map_session_data *sd)
 	if( !skill_id )
 		return;
 
-	skill->autospell(sd, skill_id);
+	skill->autospell_spell_selected(sd, skill_id);
 	clif_menuskill_clear(sd);
 }
 
diff --git a/src/map/skill.c b/src/map/skill.c
index 9e78e4bcf5d..cdf2d7026ec 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -17873,42 +17873,41 @@ static void skill_autospell_select_spell(struct block_list *bl, int skill_lv)
 		skill->get_time(SA_AUTOSPELL, skill_lv), SA_AUTOSPELL);
 }
 
-static int skill_autospell(struct map_session_data *sd, uint16 skill_id)
+/**
+ * Initiates AutoSpell effect on player based on the skill they chose.
+ *
+ * // @FIXME: Why this always returns 0? Does it even make sense?
+ * @param sd player casting the skill
+ * @param skill_id selected skill
+ * @returns always returns 0
+ */
+static int skill_autospell_spell_selected(struct map_session_data *sd, uint16 skill_id)
 {
-	uint16 skill_lv;
-	int maxlv=1,lv;
-
 	nullpo_ret(sd);
 
-	skill_lv = sd->menuskill_val;
-	lv=pc->checkskill(sd,skill_id);
+	uint16 autospell_lv = sd->menuskill_val;
+	int skill_lv = pc->checkskill(sd, skill_id);
 
-	if(!skill_lv || !lv) return 0; // Player must learn the skill before doing auto-spell [Lance]
+	if(autospell_lv == 0 || skill_lv == 0)
+		return 0; // Player must learn the skill before doing auto-spell [Lance]
 
-	if(skill_id==MG_NAPALMBEAT) maxlv=3;
-	else if(skill_id==MG_COLDBOLT || skill_id==MG_FIREBOLT || skill_id==MG_LIGHTNINGBOLT){
-		if (sd->sc.data[SC_SOULLINK] && sd->sc.data[SC_SOULLINK]->val2 == SL_SAGE)
-			maxlv =10; //Soul Linker bonus. [Skotlex]
-		else if(skill_lv==2) maxlv=1;
-		else if(skill_lv==3) maxlv=2;
-		else if(skill_lv>=4) maxlv=3;
-	}
-	else if(skill_id==MG_SOULSTRIKE){
-		if(skill_lv==5) maxlv=1;
-		else if(skill_lv==6) maxlv=2;
-		else if(skill_lv>=7) maxlv=3;
-	}
-	else if(skill_id==MG_FIREBALL){
-		if(skill_lv==8) maxlv=1;
-		else if(skill_lv>=9) maxlv=2;
-	}
-	else if(skill_id==MG_FROSTDIVER) maxlv=1;
-	else return 0;
+	int skill_idx;
+	ARR_FIND(0, MAX_AUTOSPELL_DB, skill_idx, skill->dbs->autospell_db[skill_idx].skill_id == skill_id);
+	if (skill_idx == MAX_AUTOSPELL_DB)
+		return 0; // Not an AutoSpell skill (exploit attempt?)
+
+	const struct s_autospell_db *sk = &skill->dbs->autospell_db[skill_idx];
+	if (sk->autospell_level > autospell_lv)
+		return 0; // Don't have enough level to use
+
+	int max_lv = sk->skill_lv[autospell_lv - 1];
+	if (sk->spirit_boost && sd->sc.data[SC_SOULLINK] != NULL && sd->sc.data[SC_SOULLINK]->val2 == SL_SAGE)
+		max_lv = skill->dbs->db[skill->get_index(skill_id)].max; // Soul Linker bonus. [Skotlex]
 
-	if(maxlv > lv)
-		maxlv = lv;
+	if (max_lv > skill_lv)
+		max_lv = skill_lv;
 
-	sc_start4(&sd->bl,&sd->bl,SC_AUTOSPELL,100,skill_lv,skill_id,maxlv,0,
+	sc_start4(&sd->bl, &sd->bl, SC_AUTOSPELL, 100, skill_lv, skill_id, max_lv, 0,
 		skill->get_time(SA_AUTOSPELL, skill_lv), SA_AUTOSPELL);
 	return 0;
 }
@@ -25281,7 +25280,7 @@ void skill_defaults(void)
 	skill->weaponrefine = skill_weaponrefine;
 	skill->autospell_select_spell = skill_autospell_select_spell;
 	skill->autospell_select_spell_pc = skill_autospell_select_spell_pc;
-	skill->autospell = skill_autospell;
+	skill->autospell_spell_selected = skill_autospell_spell_selected;
 	skill->calc_heal = skill_calc_heal;
 	skill->check_cloaking = skill_check_cloaking;
 	skill->check_cloaking_end = skill_check_cloaking_end;
diff --git a/src/map/skill.h b/src/map/skill.h
index a61f12feed6..5002617d1b1 100644
--- a/src/map/skill.h
+++ b/src/map/skill.h
@@ -2170,7 +2170,7 @@ struct skill_interface {
 	void (*weaponrefine) (struct map_session_data *sd,int idx);
 	void (*autospell_select_spell) (struct block_list *bl, int skill_lv);
 	void (*autospell_select_spell_pc) (struct map_session_data *sd, int skill_lv);
-	int (*autospell) (struct map_session_data *md,uint16 skill_id);
+	int (*autospell_spell_selected) (struct map_session_data *md, uint16 skill_id);
 	int (*calc_heal) (struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal);
 	bool (*check_cloaking) (struct block_list *bl, struct status_change_entry *sce);
 	int (*check_cloaking_end) (struct block_list *bl, va_list ap);
diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc
index 7880a6de392..4fc5b5ac6ef 100644
--- a/src/plugins/HPMHooking/HPMHooking.Defs.inc
+++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc
@@ -8478,8 +8478,8 @@ typedef void (*HPMHOOK_pre_skill_autospell_select_spell) (struct block_list **bl
 typedef void (*HPMHOOK_post_skill_autospell_select_spell) (struct block_list *bl, int skill_lv);
 typedef void (*HPMHOOK_pre_skill_autospell_select_spell_pc) (struct map_session_data **sd, int *skill_lv);
 typedef void (*HPMHOOK_post_skill_autospell_select_spell_pc) (struct map_session_data *sd, int skill_lv);
-typedef int (*HPMHOOK_pre_skill_autospell) (struct map_session_data **md, uint16 *skill_id);
-typedef int (*HPMHOOK_post_skill_autospell) (int retVal___, struct map_session_data *md, uint16 skill_id);
+typedef int (*HPMHOOK_pre_skill_autospell_spell_selected) (struct map_session_data **md, uint16 *skill_id);
+typedef int (*HPMHOOK_post_skill_autospell_spell_selected) (int retVal___, struct map_session_data *md, uint16 skill_id);
 typedef int (*HPMHOOK_pre_skill_calc_heal) (struct block_list **src, struct block_list **target, uint16 *skill_id, uint16 *skill_lv, bool *heal);
 typedef int (*HPMHOOK_post_skill_calc_heal) (int retVal___, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, bool heal);
 typedef bool (*HPMHOOK_pre_skill_check_cloaking) (struct block_list **bl, struct status_change_entry **sce);
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
index b7d000591cb..fcbcb1af46d 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
@@ -6400,8 +6400,8 @@ struct {
 	struct HPMHookPoint *HP_skill_autospell_select_spell_post;
 	struct HPMHookPoint *HP_skill_autospell_select_spell_pc_pre;
 	struct HPMHookPoint *HP_skill_autospell_select_spell_pc_post;
-	struct HPMHookPoint *HP_skill_autospell_pre;
-	struct HPMHookPoint *HP_skill_autospell_post;
+	struct HPMHookPoint *HP_skill_autospell_spell_selected_pre;
+	struct HPMHookPoint *HP_skill_autospell_spell_selected_post;
 	struct HPMHookPoint *HP_skill_calc_heal_pre;
 	struct HPMHookPoint *HP_skill_calc_heal_post;
 	struct HPMHookPoint *HP_skill_check_cloaking_pre;
@@ -13953,8 +13953,8 @@ struct {
 	int HP_skill_autospell_select_spell_post;
 	int HP_skill_autospell_select_spell_pc_pre;
 	int HP_skill_autospell_select_spell_pc_post;
-	int HP_skill_autospell_pre;
-	int HP_skill_autospell_post;
+	int HP_skill_autospell_spell_selected_pre;
+	int HP_skill_autospell_spell_selected_post;
 	int HP_skill_calc_heal_pre;
 	int HP_skill_calc_heal_post;
 	int HP_skill_check_cloaking_pre;
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
index 6b14068905b..f8374cbb049 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
@@ -3276,7 +3276,7 @@ struct HookingPointData HookingPoints[] = {
 	{ HP_POP(skill->weaponrefine, HP_skill_weaponrefine) },
 	{ HP_POP(skill->autospell_select_spell, HP_skill_autospell_select_spell) },
 	{ HP_POP(skill->autospell_select_spell_pc, HP_skill_autospell_select_spell_pc) },
-	{ HP_POP(skill->autospell, HP_skill_autospell) },
+	{ HP_POP(skill->autospell_spell_selected, HP_skill_autospell_spell_selected) },
 	{ HP_POP(skill->calc_heal, HP_skill_calc_heal) },
 	{ HP_POP(skill->check_cloaking, HP_skill_check_cloaking) },
 	{ HP_POP(skill->check_cloaking_end, HP_skill_check_cloaking_end) },
diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
index 95d6ff13665..d0cbea1c529 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
@@ -85389,14 +85389,14 @@ void HP_skill_autospell_select_spell_pc(struct map_session_data *sd, int skill_l
 	}
 	return;
 }
-int HP_skill_autospell(struct map_session_data *md, uint16 skill_id) {
+int HP_skill_autospell_spell_selected(struct map_session_data *md, uint16 skill_id) {
 	int hIndex = 0;
 	int retVal___ = 0;
-	if (HPMHooks.count.HP_skill_autospell_pre > 0) {
+	if (HPMHooks.count.HP_skill_autospell_spell_selected_pre > 0) {
 		int (*preHookFunc) (struct map_session_data **md, uint16 *skill_id);
 		*HPMforce_return = false;
-		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_pre; hIndex++) {
-			preHookFunc = HPMHooks.list.HP_skill_autospell_pre[hIndex].func;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_spell_selected_pre; hIndex++) {
+			preHookFunc = HPMHooks.list.HP_skill_autospell_spell_selected_pre[hIndex].func;
 			retVal___ = preHookFunc(&md, &skill_id);
 		}
 		if (*HPMforce_return) {
@@ -85405,12 +85405,12 @@ int HP_skill_autospell(struct map_session_data *md, uint16 skill_id) {
 		}
 	}
 	{
-		retVal___ = HPMHooks.source.skill.autospell(md, skill_id);
+		retVal___ = HPMHooks.source.skill.autospell_spell_selected(md, skill_id);
 	}
-	if (HPMHooks.count.HP_skill_autospell_post > 0) {
+	if (HPMHooks.count.HP_skill_autospell_spell_selected_post > 0) {
 		int (*postHookFunc) (int retVal___, struct map_session_data *md, uint16 skill_id);
-		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_post; hIndex++) {
-			postHookFunc = HPMHooks.list.HP_skill_autospell_post[hIndex].func;
+		for (hIndex = 0; hIndex < HPMHooks.count.HP_skill_autospell_spell_selected_post; hIndex++) {
+			postHookFunc = HPMHooks.list.HP_skill_autospell_spell_selected_post[hIndex].func;
 			retVal___ = postHookFunc(retVal___, md, skill_id);
 		}
 	}

From 17d3acd3681f34c800e26e645a57d5da4c7cc1d5 Mon Sep 17 00:00:00 2001
From: "Guilherme G. Menaldo" <guilherme.menaldo@outlook.com>
Date: Sun, 8 Oct 2023 17:44:24 -0300
Subject: [PATCH 4/4] fix a possible buffer overflow when using custom
 MAX_SKILL_LEVEL

---
 src/map/skill.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/map/skill.c b/src/map/skill.c
index cdf2d7026ec..83f362ef1fa 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -21645,7 +21645,7 @@ static void skill_config_set_level(struct config_setting_t *conf, int *arr)
 	if (config_setting_is_group(conf)) {
 		for (i=0; i<MAX_SKILL_LEVEL; i++) {
 			char level[6]; // enough to contain "Lv100" in case of custom MAX_SKILL_LEVEL
-			sprintf(level, "Lv%d", i+1);
+			snprintf(level, sizeof(level), "Lv%d", i + 1);
 			libconfig->setting_lookup_int(conf, level, &arr[i]);
 		}
 	} else if (config_setting_is_array(conf)) {