summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIbrahim Zidan <brahem@aotsw.com>2019-04-17 07:29:18 +0200
committerIbrahim Zidan <brahem@aotsw.com>2019-05-05 23:40:39 +0200
commit8fa081b6b4ad26851f3ab8f485ba76f3e4338762 (patch)
treec45282c318a70a4da40762ce220406abd1f21ed7 /src
parent5a0c9f2f74070e9d1e6f4fec503b3687b5f1ef88 (diff)
downloadhercules-8fa081b6b4ad26851f3ab8f485ba76f3e4338762.tar.gz
hercules-8fa081b6b4ad26851f3ab8f485ba76f3e4338762.tar.bz2
hercules-8fa081b6b4ad26851f3ab8f485ba76f3e4338762.tar.xz
hercules-8fa081b6b4ad26851f3ab8f485ba76f3e4338762.zip
Implement Refinery UI
Signed-off-by: Ibrahim Zidan <brahem@aotsw.com>
Diffstat (limited to 'src')
-rw-r--r--src/common/mmo.h8
-rw-r--r--src/map/atcommand.c17
-rw-r--r--src/map/battle.c14
-rw-r--r--src/map/battle.h2
-rw-r--r--src/map/clif.c87
-rw-r--r--src/map/clif.h6
-rw-r--r--src/map/itemdb.h1
-rw-r--r--src/map/map.c1
-rw-r--r--src/map/packets.h6
-rw-r--r--src/map/packets_struct.h70
-rw-r--r--src/map/pc.h5
-rw-r--r--src/map/refine.c388
-rw-r--r--src/map/refine.h35
-rw-r--r--src/map/refine.p.h66
-rw-r--r--src/map/script.c20
15 files changed, 719 insertions, 7 deletions
diff --git a/src/common/mmo.h b/src/common/mmo.h
index aafa54008..eb74d62b3 100644
--- a/src/common/mmo.h
+++ b/src/common/mmo.h
@@ -1366,6 +1366,10 @@ enum questinfo_type {
#define MAX_ITEMLIST MAX_STORAGE
#endif
+#ifndef MAX_REFINE_REQUIREMENTS
+ #define MAX_REFINE_REQUIREMENTS 4
+#endif
+
// sanity checks...
#if MAX_ZENY > INT_MAX
#error MAX_ZENY is too big
@@ -1379,4 +1383,8 @@ enum questinfo_type {
#error MAX_SKILL has been replaced by MAX_SKILL_DB. Please update your custom definitions.
#endif
+#if MAX_REFINE_REQUIREMENTS > 4
+#error MAX_REFINE_REQUIREMENTS is bigger than allowed, this is a hardcoded limit in the client
+#endif
+
#endif /* COMMON_MMO_H */
diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index e97822e69..09303912b 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -9829,6 +9829,22 @@ ACMD(camerainfo)
return true;
}
+ACMD(refineryui)
+{
+#if PACKETVER_MAIN_NUM >= 20161005 || PACKETVER_RE_NUM >= 20161005 || defined(PACKETVER_ZERO)
+ if (battle_config.enable_refinery_ui == 0) {
+ clif->message(fd, msg_fd(fd, 453));
+ return false;
+ }
+
+ clif->OpenRefineryUI(sd);
+ return true;
+#else
+ clif->message(fd, msg_fd(fd, 453));
+ return false;
+#endif
+}
+
/**
* Fills the reference of available commands in atcommand DBMap
**/
@@ -10112,6 +10128,7 @@ static void atcommand_basecommands(void)
ACMD_DEF(reloadclans),
ACMD_DEF(setzone),
ACMD_DEF(camerainfo),
+ ACMD_DEF(refineryui),
};
int i;
diff --git a/src/map/battle.c b/src/map/battle.c
index b06de267d..2f33e2ea9 100644
--- a/src/map/battle.c
+++ b/src/map/battle.c
@@ -7419,6 +7419,8 @@ static const struct battle_data {
{ "ping_time", &battle_config.ping_time, 20, 0, 99999999, },
{ "option_drop_max_loop", &battle_config.option_drop_max_loop, 10, 1, 100000, },
{ "drop_connection_on_quit", &battle_config.drop_connection_on_quit, 0, 0, 1, },
+ {"features/enable_refinery_ui", &battle_config.enable_refinery_ui, 1, 0, 1, },
+ {"features/replace_refine_npcs", &battle_config.replace_refine_npcs, 1, 0, 1, },
};
static bool battle_set_value_sub(int index, int value)
@@ -7544,6 +7546,18 @@ static void battle_adjust_conf(void)
}
#endif
+#if !(PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO))
+ if (battle_config.enable_refinery_ui == 1) {
+ ShowWarning("conf/map/battle/feature.conf refinery ui is enabled but it requires PACKETVER 2016-11-09 RagexeRE/2016-11-30 Ragexe or newer, disabling...\n");
+ battle_config.enable_refinery_ui = 0;
+ }
+
+ if (battle_config.replace_refine_npcs == 1) {
+ ShowWarning("conf/map/battle/feature.conf replace refine npcs is enabled but it requires PACKETVER 2016-11-09 RagexeRE/2016-11-30 Ragexe or newer, disabling...\n");
+ battle_config.replace_refine_npcs = 0;
+ }
+#endif
+
#ifndef CELL_NOSTACK
if (battle_config.custom_cell_stack_limit != 1)
ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");
diff --git a/src/map/battle.h b/src/map/battle.h
index 8743274ee..1640a4e7f 100644
--- a/src/map/battle.h
+++ b/src/map/battle.h
@@ -587,6 +587,8 @@ struct Battle_Config {
int option_drop_max_loop;
int drop_connection_on_quit;
+ int enable_refinery_ui;
+ int replace_refine_npcs;
};
/* criteria for battle_config.idletime_critera */
diff --git a/src/map/clif.c b/src/map/clif.c
index 299c69a1c..b8a54166a 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -49,6 +49,7 @@
#include "map/pet.h"
#include "map/quest.h"
#include "map/rodex.h"
+#include "map/refine.h"
#include "map/script.h"
#include "map/skill.h"
#include "map/status.h"
@@ -22492,6 +22493,87 @@ static void clif_parse_ResetCooldown(int fd, struct map_session_data *sd)
atcommand->exec(fd, sd, cmd, true);
}
+static void clif_OpenRefineryUI(struct map_session_data *sd)
+{
+#if PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO)
+ nullpo_retv(sd);
+
+ if (battle_config.enable_refinery_ui == 0)
+ return;
+
+ struct PACKET_ZC_REFINE_OPEN_WINDOW p;
+ p.packetType = HEADER_ZC_REFINE_OPEN_WINDOW;
+ clif->send(&p, sizeof(p), &sd->bl, SELF);
+
+ sd->state.refine_ui = 1;
+#endif
+}
+
+static void clif_parse_AddItemRefineryUI(int fd, struct map_session_data *sd) __attribute__((nonnull(2)));
+static void clif_parse_AddItemRefineryUI(int fd, struct map_session_data *sd)
+{
+#if PACKETVER_MAIN_NUM >= 20161005 || PACKETVER_RE_NUM >= 20161005 || defined(PACKETVER_ZERO)
+ if (battle_config.enable_refinery_ui == 0)
+ return;
+
+ const struct PACKET_CZ_REFINE_ADD_ITEM *p = RFIFO2PTR(fd);
+ refine->refinery_add_item(sd, p->index - 2);
+#endif
+}
+
+static void clif_AddItemRefineryUIAck(struct map_session_data *sd, int item_index, struct s_refine_requirement *req)
+{
+#if PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO)
+ nullpo_retv(sd);
+ nullpo_retv(req);
+ Assert_retv(item_index >= 0 && item_index < sd->status.inventorySize);
+
+ if (battle_config.enable_refinery_ui == 0)
+ return;
+
+ char buf[sizeof(struct PACKET_ZC_REFINE_ADD_ITEM) + sizeof(struct PACKET_ZC_REFINE_ADD_ITEM_SUB) * MAX_REFINE_REQUIREMENTS];
+ struct PACKET_ZC_REFINE_ADD_ITEM *p = (struct PACKET_ZC_REFINE_ADD_ITEM *)buf;
+
+ p->packetType = HEADER_ZC_REFINE_ADD_ITEM;
+ p->packtLength = sizeof(*p) + sizeof(p->req[0]) * req->req_count;
+ p->itemIndex = item_index + 2;
+ p->blacksmithBlessing = req->blacksmith_blessing;
+
+ int weapon_level = itemdb_wlv(sd->status.inventory[item_index].nameid);
+ for (int i = 0; i < req->req_count; ++i) {
+ p->req[i].chance = refine->get_refine_chance(weapon_level, sd->status.inventory[item_index].refine, req->req[i].type);
+ p->req[i].itemId = req->req[i].nameid;
+ p->req[i].zeny = req->req[i].cost;
+ }
+
+ clif->send(p, p->packtLength, &sd->bl, SELF);
+#endif
+}
+
+static void clif_parse_RefineryUIRefine(int fd, struct map_session_data *sd) __attribute__((nonnull(2)));
+static void clif_parse_RefineryUIRefine(int fd, struct map_session_data *sd)
+{
+#if PACKETVER_MAIN_NUM >= 20161005 || PACKETVER_RE_NUM >= 20161005 || defined(PACKETVER_ZERO)
+ if (battle_config.enable_refinery_ui == 0)
+ return;
+
+ const struct PACKET_CZ_REFINE_ITEM_REQUEST *p = RFIFO2PTR(fd);
+ refine->refinery_refine_request(sd, p->index - 2, p->itemId, (p->blacksmithBlessing == 1) ? true : false);
+#endif
+}
+
+static void clif_parse_RefineryUIClose(int fd, struct map_session_data *sd) __attribute__((nonnull(2)));
+static void clif_parse_RefineryUIClose(int fd, struct map_session_data *sd)
+{
+#if PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO)
+ if (battle_config.enable_refinery_ui == 0)
+ return;
+
+ sd->state.refine_ui = 0;
+ return;
+#endif
+}
+
/*==========================================
* Main client packet processing function
*------------------------------------------*/
@@ -23705,4 +23787,9 @@ void clif_defaults(void)
clif->pResetCooldown = clif_parse_ResetCooldown;
clif->loadConfirm = clif_loadConfirm;
clif->send_selforarea = clif_send_selforarea;
+ clif->OpenRefineryUI = clif_OpenRefineryUI;
+ clif->pAddItemRefineryUI = clif_parse_AddItemRefineryUI;
+ clif->AddItemRefineryUIAck = clif_AddItemRefineryUIAck;
+ clif->pRefineryUIClose = clif_parse_RefineryUIClose;
+ clif->pRefineryUIRefine = clif_parse_RefineryUIRefine;
}
diff --git a/src/map/clif.h b/src/map/clif.h
index aaf053274..0515fbd05 100644
--- a/src/map/clif.h
+++ b/src/map/clif.h
@@ -55,6 +55,7 @@ struct skill_unit;
struct unit_data;
struct view_data;
struct achievement_data; // map/achievement.h
+struct s_refine_requirement;
enum clif_messages;
enum rodex_add_item;
@@ -1611,6 +1612,11 @@ struct clif_interface {
void (*pResetCooldown) (int fd, struct map_session_data *sd);
void (*loadConfirm) (struct map_session_data *sd);
void (*send_selforarea) (int fd, struct block_list *bl, const void *buf, int len);
+ void (*OpenRefineryUI) (struct map_session_data *sd);
+ void (*pAddItemRefineryUI) (int fd, struct map_session_data *sd);
+ void (*AddItemRefineryUIAck) (struct map_session_data *sd, int item_index, struct s_refine_requirement *req);
+ void (*pRefineryUIClose) (int fd, struct map_session_data *sd);
+ void (*pRefineryUIRefine) (int fd, struct map_session_data *sd);
};
#ifdef HERCULES_CORE
diff --git a/src/map/itemdb.h b/src/map/itemdb.h
index 315787993..e032def0c 100644
--- a/src/map/itemdb.h
+++ b/src/map/itemdb.h
@@ -130,6 +130,7 @@ enum item_itemid {
ITEMID_INDIGO_PTS = 6361,
ITEMID_YELLOW_WISH_PTS = 6362,
ITEMID_LIME_GREEN_PTS = 6363,
+ ITEMID_BLACKSMITH_BLESSING = 6635,
ITEMID_STONE = 7049,
ITEMID_FIRE_BOTTLE = 7135,
ITEMID_ACID_BOTTLE = 7136,
diff --git a/src/map/map.c b/src/map/map.c
index d34d421d7..2b95ec27a 100644
--- a/src/map/map.c
+++ b/src/map/map.c
@@ -6409,6 +6409,7 @@ static void map_load_defaults(void)
npc_chat_defaults();
rodex_defaults();
stylist_defaults();
+ refine_defaults();
}
/**
* --run-once handler
diff --git a/src/map/packets.h b/src/map/packets.h
index 99404cbe3..bffec4f43 100644
--- a/src/map/packets.h
+++ b/src/map/packets.h
@@ -1805,6 +1805,12 @@ packet(0x96e,clif->ackmergeitems);
packet(0x0a88,clif->pResetCooldown);
#endif
+#if PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO)
+ packet(0x0aa1, clif->pAddItemRefineryUI);
+ packet(0x0aa3, clif->pRefineryUIRefine);
+ packet(0x0aa4, clif->pRefineryUIClose);
+#endif
+
// 2017-02-28aRagexeRE
#if PACKETVER >= 20170228
// new packets
diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h
index 45683596f..33b7759ce 100644
--- a/src/map/packets_struct.h
+++ b/src/map/packets_struct.h
@@ -3319,6 +3319,76 @@ struct PACKET_ZC_SE_PC_BUY_CASHITEM_RESULT {
DEFINE_PACKET_HEADER(ZC_SE_PC_BUY_CASHITEM_RESULT, 0x0849);
#endif
+#if PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO)
+struct PACKET_ZC_REFINE_OPEN_WINDOW {
+ int16 packetType;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_REFINE_OPEN_WINDOW, 0x0aa0);
+#endif
+
+#if PACKETVER_MAIN_NUM >= 20161005 || PACKETVER_RE_NUM >= 20161005 || defined(PACKETVER_ZERO)
+struct PACKET_CZ_REFINE_ADD_ITEM {
+ int16 packetType;
+ int16 index;
+};
+DEFINE_PACKET_HEADER(CZ_REFINE_ADD_ITEM, 0x0aa1);
+#endif
+
+#if PACKETVER_MAIN_NUM >= 20161130 || PACKETVER_RE_NUM >= 20161109 || defined(PACKETVER_ZERO)
+struct PACKET_ZC_REFINE_ADD_ITEM_SUB {
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+ uint32 itemId;
+#else
+ uint16 itemId;
+#endif
+ int8 chance;
+ int32 zeny;
+} __attribute__((packed));
+
+struct PACKET_ZC_REFINE_ADD_ITEM {
+ int16 packetType;
+ int16 packtLength;
+ int16 itemIndex;
+ int8 blacksmithBlessing;
+ struct PACKET_ZC_REFINE_ADD_ITEM_SUB req[];
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_REFINE_ADD_ITEM, 0x0aa2);
+#endif
+
+#if PACKETVER_MAIN_NUM >= 20161005 || PACKETVER_RE_NUM >= 20161005 || defined(PACKETVER_ZERO)
+struct PACKET_CZ_REFINE_ITEM_REQUEST {
+ int16 packetType;
+ int16 index;
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+ uint32 itemId;
+#else
+ uint16 itemId;
+#endif
+ int8 blacksmithBlessing;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(CZ_REFINE_ITEM_REQUEST, 0x0aa3);
+
+struct PACKET_CZ_REFINE_WINDOW_CLOSE {
+ int16 packetType;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(CZ_REFINE_WINDOW_CLOSE, 0x0aa4);
+#endif
+
+#if PACKETVER_MAIN_NUM >= 20170906 || PACKETVER_RE_NUM >= 20170830 || defined(PACKETVER_ZERO)
+struct PACKET_ZC_REFINE_STATUS {
+ int16 packetType;
+ char name[NAME_LENGTH];
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+ uint32 itemId;
+#else
+ uint16 itemId;
+#endif
+ int8 refine_level;
+ int8 status;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_REFINE_STATUS, 0x0ada);
+#endif
+
#if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute
#pragma pack(pop)
#endif // not NetBSD < 6 / Solaris
diff --git a/src/map/pc.h b/src/map/pc.h
index b2069d4df..42c9d204e 100644
--- a/src/map/pc.h
+++ b/src/map/pc.h
@@ -237,6 +237,7 @@ struct map_session_data {
unsigned int standalone : 1;/* [Ind/Hercules <3] */
unsigned int loggingout : 1;
unsigned int warp_clean : 1;
+ unsigned int refine_ui : 1;
} state;
struct {
unsigned char no_weapon_damage, no_magic_damage, no_misc_damage;
@@ -660,10 +661,10 @@ END_ZEROED_BLOCK;
#define pc_issit(sd) ( (sd)->vd.dead_sit == 2 )
#define pc_isidle(sd) ( (sd)->chat_id != 0 || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(sockt->last_tick, (sd)->idletime) >= battle->bc->idle_no_share )
#define pc_istrading(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->state.trading )
-#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->chat_id != 0 || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag || (sd)->state.prevend )
+#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->chat_id != 0 || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag || (sd)->state.prevend || (sd)->state.refine_ui == 1)
/* equals pc_cant_act except it doesn't check for chat rooms */
-#define pc_cant_act2(sd) ( (sd)->npc_id || (sd)->state.buyingstore || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag || (sd)->state.prevend )
+#define pc_cant_act2(sd) ( (sd)->npc_id || (sd)->state.buyingstore || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag || (sd)->state.prevend || (sd)->state.refine_ui == 1)
#define pc_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) )
#define pc_setchatid(sd,n) ( (sd)->chat_id = (n) )
diff --git a/src/map/refine.c b/src/map/refine.c
index 5d0517d7d..96d37141d 100644
--- a/src/map/refine.c
+++ b/src/map/refine.c
@@ -22,14 +22,19 @@
#include "refine.p.h"
#include "common/cbasetypes.h"
-#include "common/mmo.h"
#include "common/nullpo.h"
+#include "common/random.h"
#include "common/showmsg.h"
#include "common/strlib.h"
+#include "common/utils.h"
+#include "map/itemdb.h"
#include "map/map.h"
+#include "map/pc.h"
+#include "map/script.h"
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
/** @file
* Implementation of the refine interface.
@@ -40,6 +45,151 @@ static struct refine_interface_private refine_p;
static struct refine_interface_dbs refine_dbs;
struct refine_interface *refine;
+/// @copydoc refine_interface::refinery_refine_request()
+static void refine_refinery_refine_request(struct map_session_data *sd, int item_index, int material_id, bool use_blacksmith_blessing)
+{
+ nullpo_retv(sd);
+
+ if (item_index < 0 || item_index >= sd->status.inventorySize)
+ return;
+
+ if (!refine->p->is_refinable(sd, item_index))
+ return;
+
+ int weapon_level = itemdb_wlv(sd->status.inventory[item_index].nameid),
+ refine_level = sd->status.inventory[item_index].refine,
+ i = 0;
+ const struct s_refine_requirement *req = &refine->p->dbs->refine_info[weapon_level].refine_requirements[refine_level];
+ ARR_FIND(0, req->req_count, i, req->req[i].nameid == material_id);
+
+ if (i == req->req_count)
+ return;
+
+ if (use_blacksmith_blessing && req->blacksmith_blessing == 0)
+ return;
+
+ if (sd->status.zeny < req->req[i].cost)
+ return;
+
+ if (use_blacksmith_blessing) {
+ int count = 0;
+ for (int k = 0; k < sd->status.inventorySize; ++k) {
+ if (sd->status.inventory[k].nameid == ITEMID_BLACKSMITH_BLESSING)
+ count += sd->status.inventory[k].amount;
+ }
+
+ if (count < req->blacksmith_blessing)
+ return;
+ }
+
+ int idx;
+ if ((idx = pc->search_inventory(sd, req->req[i].nameid)) == INDEX_NOT_FOUND)
+ return;
+
+ if (use_blacksmith_blessing) {
+ int amount = req->blacksmith_blessing;
+ for (int k = 0; k < sd->status.inventorySize; ++k) {
+ if (sd->status.inventory[k].nameid != ITEMID_BLACKSMITH_BLESSING)
+ continue;
+
+ int delamount = (amount < sd->status.inventory[k].amount) ? amount : sd->status.inventory[k].amount;
+ if (pc->delitem(sd, k, delamount, 0, DELITEM_NORMAL, LOG_TYPE_REFINE) != 0)
+ break;
+
+ amount -= delamount;
+ if (amount == 0)
+ break;
+ }
+ }
+
+ if (pc->delitem(sd, idx, 1, 0, DELITEM_NORMAL, LOG_TYPE_REFINE) != 0)
+ return;
+
+ if (pc->payzeny(sd, req->req[i].cost, LOG_TYPE_REFINE, NULL) != 0)
+ return;
+
+ int refine_chance = refine->get_refine_chance(weapon_level, refine_level, req->req[i].type);
+ if (rnd() % 100 >= refine_chance) {
+ clif->misceffect(&sd->bl, 2);
+
+ int failure_behabior = (use_blacksmith_blessing) ? REFINE_FAILURE_BEHAVIOR_KEEP : req->req[i].failure_behavior;
+ switch (failure_behabior) {
+ case REFINE_FAILURE_BEHAVIOR_KEEP:
+ clif->refine(sd->fd, 1, 0, sd->status.inventory[item_index].refine);
+ refine->refinery_add_item(sd, item_index);
+ break;
+ case REFINE_FAILURE_BEHAVIOR_DOWNGRADE:
+ sd->status.inventory[item_index].refine -= 1;
+ sd->status.inventory[item_index].refine = cap_value(sd->status.inventory[item_index].refine, 0, MAX_REFINE);
+ clif->refine(sd->fd, 2, item_index, sd->status.inventory[item_index].refine);
+ logs->pick_pc(sd, LOG_TYPE_REFINE, 1, &sd->status.inventory[item_index], sd->inventory_data[item_index]);
+ refine->refinery_add_item(sd, item_index);
+ break;
+ case REFINE_FAILURE_BEHAVIOR_DESTROY:
+ default:
+ clif->refine(sd->fd, 1, item_index, sd->status.inventory[item_index].refine);
+ pc->delitem(sd, item_index, 1, 0, DELITEM_FAILREFINE, LOG_TYPE_REFINE);
+ break;
+ }
+ } else {
+ sd->status.inventory[item_index].refine += 1;
+ sd->status.inventory[item_index].refine = cap_value(sd->status.inventory[item_index].refine, 0, MAX_REFINE);
+
+ clif->misceffect(&sd->bl, 3);
+ clif->refine(sd->fd, 0, item_index, sd->status.inventory[item_index].refine);
+ logs->pick_pc(sd, LOG_TYPE_REFINE, 1, &sd->status.inventory[item_index], sd->inventory_data[item_index]);
+ refine->refinery_add_item(sd, item_index);
+ }
+}
+
+/// @copydoc refine_interface::refinery_add_item()
+static void refine_refinery_add_item(struct map_session_data *sd, int item_index)
+{
+ nullpo_retv(sd);
+
+ if (item_index < 0 || item_index >= sd->status.inventorySize)
+ return;
+
+ if (!refine->p->is_refinable(sd, item_index))
+ return;
+
+ int weapon_level = itemdb_wlv(sd->status.inventory[item_index].nameid);
+ int refine_level = sd->status.inventory[item_index].refine;
+ clif->AddItemRefineryUIAck(sd, item_index, &refine->p->dbs->refine_info[weapon_level].refine_requirements[refine_level]);
+}
+
+/// @copydoc refine_interface_private::is_refinable()
+static bool refine_is_refinable(struct map_session_data *sd, int item_index)
+{
+ nullpo_retr(false, sd);
+ Assert_retr(false, item_index >= 0 && item_index < sd->status.inventorySize);
+
+ if (sd->status.inventory[item_index].nameid == 0)
+ return false;
+
+ struct item_data *itd = itemdb->search(sd->status.inventory[item_index].nameid);
+
+ if (itd == &itemdb->dummy)
+ return false;
+
+ if (itd->type != IT_WEAPON && itd->type != IT_ARMOR)
+ return false;
+
+ if (itd->flag.no_refine == 1)
+ return false;
+
+ if (sd->status.inventory[item_index].identify == 0)
+ return false;
+
+ if (sd->status.inventory[item_index].refine >= MAX_REFINE || sd->status.inventory[item_index].expire_time > 0)
+ return false;
+
+ if ((sd->status.inventory[item_index].attribute & ATTR_BROKEN) != 0)
+ return false;
+
+ return true;
+}
+
/// @copydoc refine_interface::get_randombonus_max()
static int refine_get_randombonus_max(enum refine_type equipment_type, int refine_level)
{
@@ -72,6 +222,221 @@ static int refine_get_refine_chance(enum refine_type wlv, int refine_level, enum
return refine->p->dbs->refine_info[wlv].chance[type][refine_level];
}
+/// @copydoc refine_interface_private::failure_behavior_string2enum()
+static bool refine_failure_behavior_string2enum(const char *str, enum refine_ui_failure_behavior *result)
+{
+ nullpo_retr(false, str);
+ nullpo_retr(false, result);
+
+ if (strcasecmp(str, "Destroy") == 0)
+ *result = REFINE_FAILURE_BEHAVIOR_DESTROY;
+ else if (strcasecmp(str, "Keep") == 0)
+ *result = REFINE_FAILURE_BEHAVIOR_KEEP;
+ else if (strcasecmp(str, "Downgrade") == 0)
+ *result = REFINE_FAILURE_BEHAVIOR_DOWNGRADE;
+ else
+ return false;
+
+ return true;
+}
+
+/// @copydoc refine_interface_private::readdb_refinery_ui_settings_items()
+static bool refine_readdb_refinery_ui_settings_items(const struct config_setting_t *elem, struct s_refine_requirement *req, const char *name, const char *source)
+{
+ nullpo_retr(false, elem);
+ nullpo_retr(false, req);
+ nullpo_retr(false, name);
+ nullpo_retr(false, source);
+ Assert_retr(false, req->req_count < MAX_REFINE_REQUIREMENTS);
+
+ const char *aegis_name = config_setting_name(elem);
+ struct item_data *itd;
+
+ if ((itd = itemdb->search_name(aegis_name)) == NULL) {
+ ShowWarning("refine_readdb_requirements_items: Invalid item '%s' passed to requirements of '%s' in \"%s\" skipping...\n", aegis_name, name, source);
+ return false;
+ }
+
+ for (int i = 0; i < req->req_count; ++i) {
+ if (req->req[i].nameid == itd->nameid) {
+ ShowWarning("refine_readdb_requirements_items: Duplicated item '%s' passed to requirements of '%s' in \"%s\" skipping...\n", aegis_name, name, source);
+ return false;
+ }
+ }
+
+ const char *type_string = NULL;
+ if (libconfig->setting_lookup_string(elem, "Type", &type_string) == CONFIG_FALSE) {
+ ShowWarning("refine_readdb_requirements_items: no type passed to item '%s' of requirements of '%s' in \"%s\" skipping...\n", aegis_name, name, source);
+ return false;
+ }
+
+ int type;
+ if (!script->get_constant(type_string, &type)) {
+ ShowWarning("refine_readdb_requirements_items: invalid type '%s' passed to item '%s' of requirements of '%s' in \"%s\" skipping...\n", type_string, aegis_name, name, source);
+ return false;
+ }
+
+ int cost = 0;
+ if (libconfig->setting_lookup_int(elem, "Cost", &cost) == CONFIG_TRUE) {
+ if (cost < 1) {
+ ShowWarning("refine_readdb_requirements_items: invalid cost value %d passed to item '%s' of requirements of '%s' in \"%s\" defaulting to 0...\n", cost, aegis_name, name, source);
+ cost = 0;
+ }
+ }
+
+ enum refine_ui_failure_behavior behavior = REFINE_FAILURE_BEHAVIOR_DESTROY;
+ const char *behavior_string = NULL;
+ if (libconfig->setting_lookup_string(elem, "FailureBehavior", &behavior_string) != CONFIG_FALSE) {
+ if (!refine->p->failure_behavior_string2enum(behavior_string, &behavior)) {
+ ShowWarning("refine_readdb_requirements_items: invalid failure behavior value %s passed to item '%s' of requirements of '%s' in \"%s\" defaulting to 'Destroy'...\n", behavior_string, aegis_name, name, source);
+ }
+ }
+
+ req->req[req->req_count].nameid = itd->nameid;
+ req->req[req->req_count].type = type;
+ req->req[req->req_count].cost = cost;
+ req->req[req->req_count].failure_behavior = behavior;
+ req->req_count++;
+
+ return true;
+}
+
+/// @copydoc refine_interface_private::readdb_refinery_ui_settings_sub()
+static bool refine_readdb_refinery_ui_settings_sub(const struct config_setting_t *elem, int type, const char *name, const char *source)
+{
+ nullpo_retr(false, elem);
+ nullpo_retr(false, name);
+ nullpo_retr(false, source);
+ Assert_retr(0, type >= REFINE_TYPE_ARMOR && type < REFINE_TYPE_MAX);
+
+ struct config_setting_t *level_t;
+ bool levels[MAX_REFINE] = {0};
+
+ if ((level_t = libconfig->setting_get_member(elem, "Level")) == NULL) {
+ ShowWarning("refine_readdb_requirements_sub: a requirements element missing level field for entry '%s' in \"%s\" skipping...\n", name, source);
+ return false;
+ }
+
+ if (config_setting_is_scalar(level_t)) {
+ if (!config_setting_is_number(level_t)) {
+ ShowWarning("refine_readdb_requirements_sub: expected 'Level' field to be an integer '%s' in \"%s\" skipping...\n", name, source);
+ return false;
+ }
+
+ int refine_level = libconfig->setting_get_int(level_t);
+ if (refine_level < 1 || refine_level > MAX_REFINE) {
+ ShowWarning("refine_readdb_requirements_sub: Invalid 'Level' given value %d expected a value between %d and %d '%s' in \"%s\" skipping...\n", refine_level, 1, MAX_REFINE, name, source);
+ return false;
+ }
+
+ levels[refine_level - 1] = true;
+ } else if (config_setting_is_aggregate(level_t)) {
+ if (libconfig->setting_length(level_t) != 2) {
+ ShowWarning("refine_readdb_requirements_sub: invalid length for Level array, expected 2 found %d for entry '%s' in \"%s\" skipping...\n", libconfig->setting_length(level_t), name, source);
+ return false;
+ }
+
+ int levels_range[2];
+ const struct config_setting_t *level_entry = NULL;
+ int i = 0,
+ k = 0;
+ while ((level_entry = libconfig->setting_get_elem(level_t, i++)) != NULL) {
+ if (!config_setting_is_number(level_entry)) {
+ ShowWarning("refine_readdb_requirements_sub: expected 'Level' array field to be an integer '%s' in \"%s\" skipping...\n", name, source);
+ return false;
+ }
+
+ levels_range[k] = libconfig->setting_get_int(level_entry);
+ if (levels_range[k] < 1 || levels_range[k] > MAX_REFINE) {
+ ShowWarning("refine_readdb_requirements_sub: Invalid 'Level' given value %d expected a value between %d and %d in entry'%s' in \"%s\" skipping...\n", levels_range[k], 1, MAX_REFINE, name, source);
+ return false;
+ }
+
+ ++k;
+ }
+
+ if (!(levels_range[0] < levels_range[1])) {
+ ShowWarning("refine_readdb_requirements_sub: Invalid 'Level' range was given low %d high %d in entry'%s' in \"%s\" skipping...\n", levels_range[0], levels_range[1], name, source);
+ return false;
+ }
+
+ for (i = levels_range[0] - 1; i < levels_range[1]; ++i) {
+ levels[i] = true;
+ }
+ }
+
+ struct s_refine_requirement req = {0};
+ if (libconfig->setting_lookup_int(elem, "BlacksmithBlessing", &req.blacksmith_blessing) == CONFIG_TRUE) {
+ if (req.blacksmith_blessing < 1 || req.blacksmith_blessing > INT8_MAX) {
+ ShowWarning("refine_readdb_requirements_sub: Invalid 'BlacksmithBlessing' amount was given value %d expected a value between %d and %d in entry'%s' in \"%s\" defaulting to 0...\n", req.blacksmith_blessing, 1, INT8_MAX, name, source);
+ req.blacksmith_blessing = 0;
+ }
+ }
+
+ struct config_setting_t *items_t;
+ if ((items_t = libconfig->setting_get_member(elem, "Items")) == NULL) {
+ ShowWarning("refine_readdb_requirements_sub: a requirements element missing Items element for entry '%s' in \"%s\" skipping...\n", name, source);
+ return false;
+ }
+
+ if (libconfig->setting_length(items_t) < 1) {
+ ShowWarning("refine_readdb_requirements_sub: an Items element containing no items passed for entry '%s' in \"%s\" skipping...\n", name, source);
+ return false;
+ }
+
+ int loaded_items = 0;
+ for (int i = 0; i < libconfig->setting_length(items_t); ++i) {
+ if (req.req_count >= MAX_REFINE_REQUIREMENTS) {
+ ShowWarning("refine_readdb_requirements_sub: Too many items passed to requirements maximum possible items is %d entry '%s' in \"%s\" skipping...\n", MAX_REFINE_REQUIREMENTS, name, source);
+ continue;
+ }
+
+ struct config_setting_t *item_t = libconfig->setting_get_elem(items_t, i);
+
+ if (!refine->p->readdb_refinery_ui_settings_items(item_t, &req, name, source))
+ continue;
+
+ loaded_items++;
+ }
+
+ if (loaded_items == 0) {
+ ShowWarning("refine_readdb_requirements_sub: no valid items for requirements is passed for entry '%s' in \"%s\" skipping...\n", name, source);
+ return false;
+ }
+
+ for (int i = 0; i < MAX_REFINE; ++i) {
+ if (!levels[i])
+ continue;
+
+ refine->p->dbs->refine_info[type].refine_requirements[i] = req;
+ }
+
+ return true;
+}
+
+/// @copydoc refine_interface_private::readdb_refinery_ui_settings()
+static int refine_readdb_refinery_ui_settings(const struct config_setting_t *r, int type, const char *name, const char *source)
+{
+ nullpo_retr(0, r);
+ nullpo_retr(0, name);
+ nullpo_retr(0, source);
+ Assert_retr(0, type >= REFINE_TYPE_ARMOR && type < REFINE_TYPE_MAX);
+
+ int i = 0;
+ const struct config_setting_t *elem = NULL;
+ while ((elem = libconfig->setting_get_elem(r, i++)) != NULL) {
+ refine->p->readdb_refinery_ui_settings_sub(elem, type, name, source);
+ }
+
+ int retval = 0;
+ for (i = 0; i < MAX_REFINE; ++i) {
+ if (refine->p->dbs->refine_info[type].refine_requirements[i].req_count > 0)
+ retval++;
+ }
+
+ return retval;
+}
+
/// @copydoc refine_interface_private::readdb_refine_libconfig_sub()
static int refine_readdb_refine_libconfig_sub(struct config_setting_t *r, const char *name, const char *source)
{
@@ -92,6 +457,18 @@ static int refine_readdb_refine_libconfig_sub(struct config_setting_t *r, const
ShowError("status_readdb_refine_libconfig_sub: Out of range level for entry '%s' in \"%s\", skipping.\n", name, source);
return 0;
}
+
+ struct config_setting_t *refinery_ui_settings;
+ if ((refinery_ui_settings = libconfig->setting_get_member(r, "RefineryUISettings")) == NULL) {
+ ShowWarning("status_readdb_refine_libconfig_sub: Missing Requirements for entry '%s' in \"%s\", skipping.\n", name, source);
+ return 0;
+ }
+
+ if (refine->p->readdb_refinery_ui_settings(refinery_ui_settings, type, name, source) != MAX_REFINE) {
+ ShowWarning("status_readdb_refine_libconfig_sub: Not all refine levels have requrements entry for entry '%s' in \"%s\", skipping.\n", name, source);
+ return 0;
+ }
+
if (!libconfig->setting_lookup_int(r, "StatsPerLevel", &bonus_per_level)) {
ShowWarning("status_readdb_refine_libconfig_sub: Missing StatsPerLevel for entry '%s' in \"%s\", skipping.\n", name, source);
return 0;
@@ -191,6 +568,8 @@ static int refine_readdb_refine_libconfig_sub(struct config_setting_t *r, const
/// @copydoc refine_interface_private::readdb_refine_libconfig()
static int refine_readdb_refine_libconfig(const char *filename)
{
+ nullpo_retr(0, filename);
+
bool duplicate[REFINE_TYPE_MAX];
struct config_t refine_db_conf;
struct config_setting_t *r;
@@ -244,9 +623,16 @@ void refine_defaults(void)
refine->p->readdb_refine_libconfig = refine_readdb_refine_libconfig;
refine->p->readdb_refine_libconfig_sub = refine_readdb_refine_libconfig_sub;
+ refine->p->failure_behavior_string2enum = refine_failure_behavior_string2enum;
+ refine->p->readdb_refinery_ui_settings_items = refine_readdb_refinery_ui_settings_items;
+ refine->p->readdb_refinery_ui_settings_sub = refine_readdb_refinery_ui_settings_sub;
+ refine->p->readdb_refinery_ui_settings = refine_readdb_refinery_ui_settings;
+ refine->p->is_refinable = refine_is_refinable;
refine->init = refine_init;
refine->final = refine_final;
+ refine->refinery_refine_request = refine_refinery_refine_request;
+ refine->refinery_add_item = refine_refinery_add_item;
refine->get_refine_chance = refine_get_refine_chance;
refine->get_bonus = refine_get_bonus;
refine->get_randombonus_max = refine_get_randombonus_max;
diff --git a/src/map/refine.h b/src/map/refine.h
index 850f6143a..100d2c6b2 100644
--- a/src/map/refine.h
+++ b/src/map/refine.h
@@ -25,6 +25,7 @@
* Refine Interface.
**/
#include "common/hercules.h"
+#include "common/mmo.h"
/* Defines */
/**
@@ -60,6 +61,24 @@ enum refine_chance_type {
REFINE_CHANCE_TYPE_MAX
};
+enum refine_ui_failure_behavior {
+ REFINE_FAILURE_BEHAVIOR_DESTROY,
+ REFINE_FAILURE_BEHAVIOR_KEEP,
+ REFINE_FAILURE_BEHAVIOR_DOWNGRADE
+};
+
+/* Structure */
+struct s_refine_requirement {
+ int blacksmith_blessing;
+ int req_count;
+ struct {
+ int nameid;
+ int cost;
+ enum refine_chance_type type;
+ enum refine_ui_failure_behavior failure_behavior;
+ } req[MAX_REFINE_REQUIREMENTS];
+};
+
/**
* Refine Interface
**/
@@ -101,6 +120,22 @@ struct refine_interface {
* @return returns the bonus from refine db
**/
int(*get_randombonus_max) (enum refine_type equipment_type, int refine_level);
+
+ /**
+ * Validates and send Item addition packet to the client for refinery UI
+ * @param sd player session data.
+ * @param item_index the requested item index in inventory.
+ **/
+ void (*refinery_add_item) (struct map_session_data *sd, int item_index);
+
+ /**
+ * Processes an refine request through Refinery UI
+ * @param sd player session data
+ * @param item_index the index of the requested item
+ * @param material_id the refine material chosen by player
+ * @param use_blacksmith_blessing sets either if blacksmith blessing is requested to be used or not
+ **/
+ void (*refinery_refine_request) (struct map_session_data *sd, int item_index, int material_id, bool use_blacksmith_blessing);
};
#ifdef HERCULES_CORE
diff --git a/src/map/refine.p.h b/src/map/refine.p.h
index d47944315..e1a24f6b7 100644
--- a/src/map/refine.p.h
+++ b/src/map/refine.p.h
@@ -28,11 +28,11 @@
#include "refine.h"
#include "common/conf.h"
-/* Structures */
struct s_refine_info {
- int chance[REFINE_CHANCE_TYPE_MAX][MAX_REFINE]; // success chance
- int bonus[MAX_REFINE]; // cumulative fixed bonus damage
- int randombonus_max[MAX_REFINE]; // cumulative maximum random bonus damage
+ int chance[REFINE_CHANCE_TYPE_MAX][MAX_REFINE]; //< success chance
+ int bonus[MAX_REFINE]; //< cumulative fixed bonus damage
+ int randombonus_max[MAX_REFINE]; //< cumulative maximum random bonus damage
+ struct s_refine_requirement refine_requirements[MAX_REFINE]; //< The requirements used for refinery UI
};
struct refine_interface_dbs {
@@ -66,6 +66,64 @@ struct refine_interface_private {
* @return The number of found entries.
**/
int (*readdb_refine_libconfig) (const char *filename);
+
+ /**
+ * Converts refine database failure behvaior string to enum refine_ui_failure_behavior
+ * @param str the string to convert
+ * @param result pointer to where the converted value will be held
+ * @return true on success, false otherwise.
+ **/
+ bool (*failure_behavior_string2enum) (const char *str, enum refine_ui_failure_behavior *result);
+
+ /**
+ * Processes a refine_db.conf RefineryUISettings items entry.
+ *
+ * @param elem Libconfig setting entry. It is expected to be valid and it
+ * won't be freed (it is care of the caller to do so if
+ * necessary)
+ * @param req a pointer to requirements struct to fill with the item data
+ * @param name the current element name
+ * @param source Source of the entry (file name), to be displayed in case of
+ * validation errors.
+ * @return true on success, false otherwise.
+ **/
+ bool (*readdb_refinery_ui_settings_items) (const struct config_setting_t *elem, struct s_refine_requirement *req, const char *name, const char *source);
+
+ /**
+ * Processes a refine_db.conf RefineryUISettings entry.
+ *
+ * @param elem Libconfig setting entry. It is expected to be valid and it
+ * won't be freed (it is care of the caller to do so if
+ * necessary)
+ * @param type the type index in refine database to fill the data
+ * @param name the current element name
+ * @param source Source of the entry (file name), to be displayed in case of
+ * validation errors.
+ * @return true on success, false otherwise.
+ **/
+ bool (*readdb_refinery_ui_settings_sub) (const struct config_setting_t *elem, int type, const char *name, const char *source);
+
+ /**
+ * Reads a refine_db.conf RefineryUISettings entry and sends it to be processed.
+ *
+ * @param r Libconfig setting entry. It is expected to be valid and it
+ * won't be freed (it is care of the caller to do so if
+ * necessary)
+ * @param type the type index in refine database to fill the data
+ * @param name the current element name
+ * @param source Source of the entry (file name), to be displayed in case of
+ * validation errors.
+ * @return true on success, false otherwise.
+ **/
+ int (*readdb_refinery_ui_settings) (const struct config_setting_t *r, int type, const char *name, const char *source);
+
+ /**
+ * Checks if a given item in player's inventory is refineable.
+ * @param sd player session data.
+ * @param item_index the item index in player's inventory.
+ * @return true if item is refineable, false otherwise.
+ **/
+ bool (*is_refinable) (struct map_session_data *sd, int item_index);
};
#endif
diff --git a/src/map/script.c b/src/map/script.c
index 1cc1d3ad2..5843ac292 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -25286,6 +25286,25 @@ static BUILDIN(closeroulette)
return true;
}
+static BUILDIN(openrefineryui)
+{
+ struct map_session_data *sd = script_rid2sd(st);
+
+ if (sd == NULL) {
+ script_pushint(st, 0);
+ return true;
+ }
+
+ if (battle_config.enable_refinery_ui == 0) {
+ script_pushint(st, 0);
+ return true;
+ }
+
+ clif->OpenRefineryUI(sd);
+ script_pushint(st, 1);
+ return true;
+}
+
/**
* Adds a built-in script function.
*
@@ -26039,6 +26058,7 @@ static void script_parse_builtin(void)
BUILDIN_DEF(getInventorySize, ""),
BUILDIN_DEF(closeroulette, ""),
+ BUILDIN_DEF(openrefineryui, ""),
};
int i, len = ARRAYLENGTH(BUILDIN);
RECREATE(script->buildin, char *, script->buildin_count + len); // Pre-alloc to speed up