From 818ff109f3eea1715dc1477795bd1094510a4de4 Mon Sep 17 00:00:00 2001 From: Lemongrass3110 Date: Mon, 24 Dec 2018 14:10:29 +0100 Subject: [PATCH] Initial Release of Equipment Switch (#3548) Thanks to everyone who contributed to this release in any way, be it donations, information or testing. Merry Christmas :-) --- conf/battle/feature.conf | 4 + db/pre-re/skill_cast_db.txt | 3 + db/pre-re/skill_castnodex_db.txt | 2 + db/pre-re/skill_db.txt | 2 + db/pre-re/skill_require_db.txt | 2 + db/re/skill_cast_db.txt | 4 + db/re/skill_castnodex_db.txt | 2 + db/re/skill_db.txt | 2 + db/re/skill_require_db.txt | 2 + sql-files/main.sql | 1 + sql-files/upgrades/upgrade_20181224.sql | 2 + src/char/char.cpp | 45 ++-- src/common/mmo.hpp | 1 + src/map/atcommand.cpp | 2 + src/map/battle.cpp | 8 + src/map/battle.hpp | 1 + src/map/clif.cpp | 252 ++++++++++++++++++++-- src/map/clif.hpp | 9 + src/map/clif_packetdb.hpp | 22 +- src/map/mail.cpp | 4 + src/map/mail.hpp | 7 +- src/map/pc.cpp | 270 +++++++++++++++++++++--- src/map/pc.hpp | 8 +- src/map/skill.cpp | 18 +- src/map/skill.hpp | 3 + src/map/trade.cpp | 5 + 26 files changed, 590 insertions(+), 91 deletions(-) create mode 100644 sql-files/upgrades/upgrade_20181224.sql diff --git a/conf/battle/feature.conf b/conf/battle/feature.conf index acef55071a..e2c06cb382 100644 --- a/conf/battle/feature.conf +++ b/conf/battle/feature.conf @@ -67,6 +67,10 @@ feature.roulette: on // Requires: 2015-05-13aRagexe or later feature.achievement: on +// Equipment Switch (Note 1) +// Requires: 2017-02-08bRagexeRE or later +feature.equipswitch: on + // Homunculues Autofeeding (Note 1) // Requires: 2017-09-20bRagexeRE or later feature.homunculus_autofeed: on diff --git a/db/pre-re/skill_cast_db.txt b/db/pre-re/skill_cast_db.txt index e39f30356e..6ab220c709 100644 --- a/db/pre-re/skill_cast_db.txt +++ b/db/pre-re/skill_cast_db.txt @@ -1752,6 +1752,9 @@ //-- ALL_FULL_THROTTLE 5014,0,500,0,10000:15000:20000:25000:30000,10000,1800000 +//-- ALL_EQSWITCH +5067,0,0,0,0,0,10000,60 + //===== Homunculus Skills ================== //-- HLIF_HEAL 8001,0,2000,0,0,0,0 diff --git a/db/pre-re/skill_castnodex_db.txt b/db/pre-re/skill_castnodex_db.txt index c1d1faa457..524958968c 100644 --- a/db/pre-re/skill_castnodex_db.txt +++ b/db/pre-re/skill_castnodex_db.txt @@ -56,6 +56,8 @@ 2536,7,7 //ALL_GUARDIAN_RECALL 2537,0,7 //ALL_ODINS_POWER +5067,7,7 //ALL_EQSWITCH + // Mercenary Skills 8214,7 //MA_CHARGEARROW 8215,7 //MA_SHARPSHOOTING diff --git a/db/pre-re/skill_db.txt b/db/pre-re/skill_db.txt index 56b4cb73b7..aa4fe053a9 100644 --- a/db/pre-re/skill_db.txt +++ b/db/pre-re/skill_db.txt @@ -1401,6 +1401,8 @@ 5064,0,0,0,0,0,0,1,0,no,0,0,0,none,0,0x0, WE_ONEFOREVER,One Forever 5065,0,0,0,0,0,0,1,0,no,0,0,0,none,0,0x0, WE_CHEERUP,Cheer Up +5067,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0,0x0, ALL_EQSWITCH,Equip Switch + // New Arch Bishop Skills 5072,0,0,0,0,0,0,1,0,no,0,0,0,none,0,0x0, AB_VITUPERATUM,Vituperatum 5073,0,0,0,0,0,0,1,0,no,0,0,0,none,0,0x0, AB_CONVENIO,Convenio diff --git a/db/pre-re/skill_require_db.txt b/db/pre-re/skill_require_db.txt index 9b70a2c2ca..88b63f762c 100644 --- a/db/pre-re/skill_require_db.txt +++ b/db/pre-re/skill_require_db.txt @@ -961,6 +961,8 @@ //3036,0,0,1,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //BA_POEMBRAGI2 //3037,0,0,1,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //DC_FORTUNEKISS2 +5067,0,0,0,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //ALL_EQSWITCH + 8001,0,0,13:16:19:22:25,0,0,0,99,0,0,none,0,0,545,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_HEAL 8002,0,0,20:25:30:35:40,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_AVOID 8004,0,0,100,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_CHANGE diff --git a/db/re/skill_cast_db.txt b/db/re/skill_cast_db.txt index 79ea5ad65b..797eddc995 100644 --- a/db/re/skill_cast_db.txt +++ b/db/re/skill_cast_db.txt @@ -1850,6 +1850,10 @@ 5064,1500,0,0,0,0,0,1500 //-- WE_CHEERUP 5065,1500,0,0,60000,0,0,1500 + +//-- ALL_EQSWITCH +5067,0,0,0,0,0,10000,60 + //========================================== //===== Homunculus Skills ================== diff --git a/db/re/skill_castnodex_db.txt b/db/re/skill_castnodex_db.txt index 893ade68dd..7889cddb87 100644 --- a/db/re/skill_castnodex_db.txt +++ b/db/re/skill_castnodex_db.txt @@ -61,6 +61,8 @@ 2537,0,7 //ALL_ODINS_POWER 3035,7,7 //ECLAGE_RECALL +5067,7,7 //ALL_EQSWITCH + // Mercenary Skills 8214,7 //MA_CHARGEARROW 8215,7 //MA_SHARPSHOOTING diff --git a/db/re/skill_db.txt b/db/re/skill_db.txt index 21b50e1a30..85f247052a 100644 --- a/db/re/skill_db.txt +++ b/db/re/skill_db.txt @@ -1455,6 +1455,8 @@ 5064,3,6,16,0,0x1,0,1,1,yes,0,0x4,0,none,0,0, WE_ONEFOREVER,One Forever 5065,1,6,4,0,0x3,3,1,1,yes,0,0x4,0,none,0,0, WE_CHEERUP,Cheer Up +5067,0,0,4,0,0x1,0,1,0,no,0,0,0,none,0,0x0, ALL_EQSWITCH,Equip Switch + // New Arch Bishop Skills 5072,9,6,1,0,0x3,1:1:1:2:2,5,0,yes,0,0,0,magic,0,0x0, AB_VITUPERATUM,Vituperatum 5073,0,6,4,0,0x1,0,1,0,yes,0,0,0,none,0,0x0, AB_CONVENIO,Convenio diff --git a/db/re/skill_require_db.txt b/db/re/skill_require_db.txt index f805f046f6..dbeb921acc 100644 --- a/db/re/skill_require_db.txt +++ b/db/re/skill_require_db.txt @@ -1049,6 +1049,8 @@ 5064,0,0,100,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //WE_ONEFOREVER 5065,0,0,50,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //WE_CHEERUP +5067,0,0,0,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //ALL_EQSWITCH + // New Arch Bishop Skills 5072,0,0,144:120:106:92:78,0,0,0,99,0,0,none,0,0,717,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //AB_VITUPERATUM## 5073,0,0,70,0,0,0,99,0,0,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //AB_CONVENIO## diff --git a/sql-files/main.sql b/sql-files/main.sql index 55e355de4b..474be46fe2 100644 --- a/sql-files/main.sql +++ b/sql-files/main.sql @@ -721,6 +721,7 @@ CREATE TABLE IF NOT EXISTS `inventory` ( `favorite` tinyint(3) unsigned NOT NULL default '0', `bound` tinyint(3) unsigned NOT NULL default '0', `unique_id` bigint(20) unsigned NOT NULL default '0', + `equip_switch` int(11) unsigned NOT NULL default '0', PRIMARY KEY (`id`), KEY `char_id` (`char_id`) ) ENGINE=MyISAM; diff --git a/sql-files/upgrades/upgrade_20181224.sql b/sql-files/upgrades/upgrade_20181224.sql new file mode 100644 index 0000000000..8742efe16b --- /dev/null +++ b/sql-files/upgrades/upgrade_20181224.sql @@ -0,0 +1,2 @@ +alter table `inventory` + add column `equip_switch` int(11) unsigned NOT NULL default '0' after `unique_id`; diff --git a/src/char/char.cpp b/src/char/char.cpp index 855b3544fb..591759c813 100644 --- a/src/char/char.cpp +++ b/src/char/char.cpp @@ -564,8 +564,8 @@ int char_memitemdata_to_sql(const struct item items[], int max, int id, enum sto StringBuf_Init(&buf); StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`, `unique_id`"); if (tableswitch == TABLE_INVENTORY) { - StringBuf_Printf(&buf, ", `favorite`"); - offset = 1; + StringBuf_Printf(&buf, ", `favorite`, `equip_switch`"); + offset = 2; } for( i = 0; i < MAX_SLOTS; ++i ) @@ -597,8 +597,10 @@ int char_memitemdata_to_sql(const struct item items[], int max, int id, enum sto SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL); SqlStmt_BindColumn(stmt, 8, SQLDT_UINT, &item.bound, 0, NULL, NULL); SqlStmt_BindColumn(stmt, 9, SQLDT_UINT64, &item.unique_id, 0, NULL, NULL); - if (tableswitch == TABLE_INVENTORY) + if (tableswitch == TABLE_INVENTORY){ SqlStmt_BindColumn(stmt, 10, SQLDT_CHAR, &item.favorite, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 11, SQLDT_UINT, &item.equipSwitch, 0, NULL, NULL); + } for( i = 0; i < MAX_SLOTS; ++i ) SqlStmt_BindColumn(stmt, 10+offset+i, SQLDT_USHORT, &item.card[i], 0, NULL, NULL); for( i = 0; i < MAX_ITEM_RDM_OPT; ++i ) { @@ -632,23 +634,23 @@ int char_memitemdata_to_sql(const struct item items[], int max, int id, enum sto if( j == MAX_SLOTS && k == MAX_ITEM_RDM_OPT && - items[i].amount == item.amount && - items[i].equip == item.equip && - items[i].identify == item.identify && - items[i].refine == item.refine && - items[i].attribute == item.attribute && - items[i].expire_time == item.expire_time && - items[i].bound == item.bound && - (tableswitch != TABLE_INVENTORY || items[i].favorite == item.favorite) ) + items[i].amount == item.amount && + items[i].equip == item.equip && + items[i].identify == item.identify && + items[i].refine == item.refine && + items[i].attribute == item.attribute && + items[i].expire_time == item.expire_time && + items[i].bound == item.bound && + (tableswitch != TABLE_INVENTORY || (items[i].favorite == item.favorite && items[i].equipSwitch == item.equipSwitch)) ) ; //Do nothing. else { // update all fields. StringBuf_Clear(&buf); - StringBuf_Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u', `bound`='%d', `unique_id`='%" PRIu64 "'", + StringBuf_Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%u', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u', `bound`='%d', `unique_id`='%" PRIu64 "'", tablename, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].bound, items[i].unique_id); if (tableswitch == TABLE_INVENTORY) - StringBuf_Printf(&buf, ", `favorite`='%d'", items[i].favorite); + StringBuf_Printf(&buf, ", `favorite`='%d', `equip_switch`='%u'", items[i].favorite, items[i].equipSwitch); for( j = 0; j < MAX_SLOTS; ++j ) StringBuf_Printf(&buf, ", `card%d`=%hu", j, items[i].card[j]); for( j = 0; j < MAX_ITEM_RDM_OPT; ++j ) { @@ -683,7 +685,7 @@ int char_memitemdata_to_sql(const struct item items[], int max, int id, enum sto StringBuf_Clear(&buf); StringBuf_Printf(&buf, "INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`, `unique_id`", tablename, selectoption); if (tableswitch == TABLE_INVENTORY) - StringBuf_Printf(&buf, ", `favorite`"); + StringBuf_Printf(&buf, ", `favorite`, `equip_switch`"); for( j = 0; j < MAX_SLOTS; ++j ) StringBuf_Printf(&buf, ", `card%d`", j); for( j = 0; j < MAX_ITEM_RDM_OPT; ++j ) { @@ -706,10 +708,10 @@ int char_memitemdata_to_sql(const struct item items[], int max, int id, enum sto else found = true; - StringBuf_Printf(&buf, "('%d', '%hu', '%d', '%d', '%d', '%d', '%d', '%u', '%d', '%" PRIu64 "'", + StringBuf_Printf(&buf, "('%d', '%hu', '%d', '%u', '%d', '%d', '%d', '%u', '%d', '%" PRIu64 "'", id, items[i].nameid, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].bound, items[i].unique_id); if (tableswitch == TABLE_INVENTORY) - StringBuf_Printf(&buf, ", '%d'", items[i].favorite); + StringBuf_Printf(&buf, ", '%d', '%u'", items[i].favorite, items[i].equipSwitch); for( j = 0; j < MAX_SLOTS; ++j ) StringBuf_Printf(&buf, ", '%hu'", items[i].card[j]); for( j = 0; j < MAX_ITEM_RDM_OPT; ++j ) { @@ -789,8 +791,8 @@ bool char_memitemdata_from_sql(struct s_storage* p, int max, int id, enum storag StringBuf_Init(&buf); StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`expire_time`,`bound`,`unique_id`"); if (tableswitch == TABLE_INVENTORY) { - StringBuf_Printf(&buf, ", `favorite`"); - offset = 1; + StringBuf_Printf(&buf, ", `favorite`, `equip_switch`"); + offset = 2; } for( j = 0; j < MAX_SLOTS; ++j ) StringBuf_Printf(&buf, ",`card%d`", j); @@ -821,8 +823,10 @@ bool char_memitemdata_from_sql(struct s_storage* p, int max, int id, enum storag SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL); SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &item.bound, 0, NULL, NULL); SqlStmt_BindColumn(stmt, 9, SQLDT_ULONGLONG, &item.unique_id, 0, NULL, NULL); - if (tableswitch == TABLE_INVENTORY) + if (tableswitch == TABLE_INVENTORY){ SqlStmt_BindColumn(stmt, 10, SQLDT_CHAR, &item.favorite, 0, NULL, NULL); + SqlStmt_BindColumn(stmt, 11, SQLDT_UINT, &item.equipSwitch, 0, NULL, NULL); + } for( i = 0; i < MAX_SLOTS; ++i ) SqlStmt_BindColumn(stmt, 10+offset+i, SQLDT_USHORT, &item.card[i], 0, NULL, NULL); for( i = 0; i < MAX_ITEM_RDM_OPT; ++i ) { @@ -2514,7 +2518,8 @@ bool char_checkdb(void){ } //checking inventory_db if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`char_id`,`nameid`,`amount`,`equip`,`identify`,`refine`," - "`attribute`,`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`,`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`,`expire_time`,`favorite`,`bound`,`unique_id`" + "`attribute`,`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`,`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`,`expire_time`,`bound`,`unique_id`" + ",`favorite`,`equip_switch`" " FROM `%s` LIMIT 1;", schema_config.inventory_db) ){ Sql_ShowDebug(sql_handle); return false; diff --git a/src/common/mmo.hpp b/src/common/mmo.hpp index 00fba05c16..ae14d6952c 100644 --- a/src/common/mmo.hpp +++ b/src/common/mmo.hpp @@ -255,6 +255,7 @@ struct item { unsigned int expire_time; char favorite, bound; uint64 unique_id; + unsigned int equipSwitch; // location(s) where item is equipped for equip switching (using enum equip_pos for bitmasking) }; //Equip position constants diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 21ddf70495..4e6bf89d38 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -5524,6 +5524,7 @@ ACMD_FUNC(dropall) if( type == -1 || type == (uint8)item_data->type ) { if( sd->inventory.u.items_inventory[i].equip != 0 ) pc_unequipitem(sd, i, 3); + pc_equipswitch_remove(sd, i); if(pc_dropitem(sd, i, sd->inventory.u.items_inventory[i].amount)) count += sd->inventory.u.items_inventory[i].amount; else count2 += sd->inventory.u.items_inventory[i].amount; @@ -5556,6 +5557,7 @@ ACMD_FUNC(storeall) if (sd->inventory.u.items_inventory[i].amount) { if(sd->inventory.u.items_inventory[i].equip != 0) pc_unequipitem(sd, i, 3); + pc_equipswitch_remove(sd, i); storage_storageadd(sd, &sd->storage, i, sd->inventory.u.items_inventory[i].amount); } } diff --git a/src/map/battle.cpp b/src/map/battle.cpp index d9db6ebd58..bd93370fbe 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -8524,6 +8524,7 @@ static const struct _battle_data { { "rental_transaction", &battle_config.rental_transaction, 1, 0, 1, }, { "min_shop_buy", &battle_config.min_shop_buy, 1, 0, INT_MAX, }, { "min_shop_sell", &battle_config.min_shop_sell, 0, 0, INT_MAX, }, + { "feature.equipswitch", &battle_config.feature_equipswitch, 1, 0, 1, }, #include "../custom/battle_config_init.inc" }; @@ -8654,6 +8655,13 @@ void battle_adjust_conf() } #endif +#if PACKETVER < 20170208 + if (battle_config.feature_equipswitch) { + ShowWarning("conf/battle/feature.conf equip switch is enabled but it requires PACKETVER 2017-02-08 or newer, disabling...\n"); + battle_config.feature_equipswitch = 0; + } +#endif + #if PACKETVER < 20170920 if( battle_config.feature_homunculus_autofeed ){ ShowWarning("conf/battle/feature.conf homunculus autofeeding is enabled but it requires PACKETVER 2017-09-20 or newer, disabling...\n"); diff --git a/src/map/battle.hpp b/src/map/battle.hpp index eb75174330..6d82d25231 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -654,6 +654,7 @@ struct Battle_Config int rental_transaction; int min_shop_buy; int min_shop_sell; + int feature_equipswitch; #include "../custom/battle_config_struct.inc" }; diff --git a/src/map/clif.cpp b/src/map/clif.cpp index f4e13e879c..883142845a 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -9430,6 +9430,7 @@ void clif_refresh(struct map_session_data *sd) clif_changemap(sd,sd->bl.m,sd->bl.x,sd->bl.y); clif_inventorylist(sd); + clif_equipswitch_list(sd); if(pc_iscarton(sd)) { clif_cartlist(sd); clif_updatestatus(sd,SP_CARTINFO); @@ -10328,6 +10329,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) // item clif_inventorylist(sd); // inventory list first, otherwise deleted items in pc_checkitem show up as 'unknown item' pc_checkitem(sd); + clif_equipswitch_list(sd); // cart if(pc_iscarton(sd)) { @@ -12186,28 +12188,19 @@ static void clif_parse_UseSkillToPos_mercenary(struct mercenary_data *md, struct unit_skilluse_pos(&md->bl, x, y, skill_id, skill_lv); } - -/// Request to use a targeted skill. -/// 0113 .W .W .L (CZ_USE_SKILL) -/// 0438 .W .W .L (CZ_USE_SKILL2) -/// There are various variants of this packet, some of them have padding between fields. -void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) -{ - uint16 skill_id, skill_lv; - int inf,target_id; +void clif_parse_skill_toid( struct map_session_data* sd, uint16 skill_id, uint16 skill_lv, int target_id ){ t_tick tick = gettick(); - struct s_packet_db* info = &packet_db[RFIFOW(fd,0)]; - - skill_lv = RFIFOW(fd,info->pos[0]); - skill_id = RFIFOW(fd,info->pos[1]); - target_id = RFIFOL(fd,info->pos[2]); if( skill_lv < 1 ) skill_lv = 1; //No clue, I have seen the client do this with guild skills :/ [Skotlex] - inf = skill_get_inf(skill_id); + int inf = skill_get_inf(skill_id); if (inf&INF_GROUND_SKILL || !inf) return; //Using a ground/passive skill on a target? WRONG. + // Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] + if (battle_config.idletime_option&IDLE_USESKILLTOID) + sd->idletime = last_tick; + if( SKILL_CHK_HOMUN(skill_id) ) { clif_parse_UseSkillToId_homun(sd->hd, sd, tick, skill_id, skill_lv, target_id); return; @@ -12218,10 +12211,6 @@ void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) return; } - // Whether skill fails or not is irrelevant, the char ain't idle. [Skotlex] - if (battle_config.idletime_option&IDLE_USESKILLTOID) - sd->idletime = last_tick; - if( sd->npc_id ){ if( pc_hasprogress( sd, WIP_DISABLE_SKILLITEM ) || !sd->npc_item_flag || !( inf & INF_SELF_SKILL ) ){ #ifdef RENEWAL @@ -12271,7 +12260,7 @@ void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) } else if( sd->menuskill_id != SA_AUTOSPELL ) return; //Can't use skills while a menu is open. } - + if( sd->skillitem == skill_id ) { if( skill_lv != sd->skillitemlv ) skill_lv = sd->skillitemlv; @@ -12288,7 +12277,9 @@ void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) else skill_lv = 0; } else { - skill_lv = min(pc_checkskill(sd, skill_id),skill_lv); //never trust client + if( skill_id != ALL_EQSWITCH ){ + skill_lv = min(pc_checkskill(sd, skill_id),skill_lv); //never trust client + } } pc_delinvincibletimer(sd); @@ -12297,6 +12288,17 @@ void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) unit_skilluse_id(&sd->bl, target_id, skill_id, skill_lv); } + +/// Request to use a targeted skill. +/// 0113 .W .W .L (CZ_USE_SKILL) +/// 0438 .W .W .L (CZ_USE_SKILL2) +/// There are various variants of this packet, some of them have padding between fields. +void clif_parse_UseSkillToId( int fd, struct map_session_data *sd ){ + struct s_packet_db* info = &packet_db[RFIFOW(fd, 0)]; + + clif_parse_skill_toid( sd, RFIFOW(fd, info->pos[1]), RFIFOW(fd, info->pos[0]), RFIFOL(fd, info->pos[2]) ); +} + /*========================================== * Client tells server he'd like to use AoE skill id 'skill_id' of level 'skill_lv' on 'x','y' location *------------------------------------------*/ @@ -12802,6 +12804,10 @@ void clif_parse_MoveToKafra(int fd, struct map_session_data *sd) item_amount = RFIFOL(fd,info->pos[1]); if (item_index < 0 || item_index >= MAX_INVENTORY || item_amount < 1) return; + if( sd->inventory.u.items_inventory[item_index].equipSwitch ){ + clif_msg( sd, C_ITEM_EQUIP_SWITCH ); + return; + } if (sd->state.storage_flag == 1) storage_storageadd(sd, &sd->storage, item_index, item_amount); @@ -12847,6 +12853,13 @@ void clif_parse_MoveToKafraFromCart(int fd, struct map_session_data *sd){ if (!pc_iscarton(sd)) return; + if (idx < 0 || idx >= MAX_INVENTORY || amount < 1) + return; + if( sd->inventory.u.items_inventory[idx].equipSwitch ){ + clif_msg( sd, C_ITEM_EQUIP_SWITCH ); + return; + } + if (sd->state.storage_flag == 1) storage_storageaddfromcart(sd, &sd->storage, idx, amount); else if (sd->state.storage_flag == 2) @@ -15809,7 +15822,11 @@ void clif_parse_Mail_setattach(int fd, struct map_session_data *sd){ flag = mail_setitem(sd, idx, amount); - clif_Mail_setattachment(sd,idx,amount,flag); + if( flag == MAIL_ATTACH_EQUIPSWITCH ){ + clif_msg( sd, C_ITEM_EQUIP_SWITCH ); + }else{ + clif_Mail_setattachment(sd,idx,amount,flag); + } } /// Remove an item from a mail @@ -20604,6 +20621,197 @@ void clif_parse_camerainfo( int fd, struct map_session_data* sd ){ is_atcommand( fd, sd, command, 1 ); } +/// Send the full list of items in the equip switch window +/// 0a9b .W { .W .L }* +void clif_equipswitch_list( struct map_session_data* sd ){ +#if PACKETVER >= 20170208 + int fd = sd->fd; + int offset, i, position; + + WFIFOW(fd, 0) = 0xa9b; + for( i = 0, offset = 4, position = 0; i < EQI_MAX; i++ ){ + short index = sd->equip_switch_index[i]; + + if( index >= 0 && !( position & equip_bitmask[i] ) ){ + WFIFOW(fd, offset) = index + 2; + WFIFOL(fd, offset + 2) = sd->inventory.u.items_inventory[index].equipSwitch; + position |= sd->inventory.u.items_inventory[index].equipSwitch; + offset += 6; + } + } + WFIFOW(fd, 2) = offset; + WFIFOSET(fd, offset); +#endif +} + +/// Acknowledgement for removing an equip to the equip switch window +/// 0a9a .W .L .W +void clif_equipswitch_remove( struct map_session_data* sd, uint16 index, uint32 pos, bool failed ){ +#if PACKETVER >= 20170208 + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0xa9a)); + WFIFOW(fd, 0) = 0xa9a; + WFIFOW(fd, 2) = index + 2; + WFIFOL(fd, 4) = pos; + WFIFOW(fd, 8) = failed; + WFIFOSET(fd, packet_len(0xa9a)); +#endif +} + +/// Request to remove an equip from the equip switch window +/// 0a99 .W .L <= 20170502 +/// 0a99 .W +void clif_parse_equipswitch_remove( int fd, struct map_session_data* sd ){ +#if PACKETVER >= 20170208 + uint16 index = RFIFOW(fd, 2) - 2; + bool removed = false; + + if( !battle_config.feature_equipswitch ){ + return; + } + + // Check if the index is valid + if( index >= MAX_INVENTORY ){ + return; + } + + pc_equipswitch_remove( sd, index ); +#endif +} + +/// Acknowledgement for adding an equip to the equip switch window +/// 0a98 .W .L .L <= 20170502 +/// 0a98 .W .L .W +void clif_equipswitch_add( struct map_session_data* sd, uint16 index, uint32 pos, bool failed ){ +#if PACKETVER >= 20170208 + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0xa98)); + WFIFOW(fd, 0) = 0xa98; + WFIFOW(fd, 2) = index + 2; + WFIFOL(fd, 4) = pos; +#if PACKETVER <= 20170502 + WFIFOL(fd, 8) = failed; +#else + WFIFOW(fd, 8) = failed; +#endif + WFIFOSET(fd,packet_len(0xa98)); +#endif +} + +/// Request to add an equip to the equip switch window +/// 0a97 .W .L +void clif_parse_equipswitch_add( int fd, struct map_session_data* sd ){ +#if PACKETVER >= 20170208 + uint16 index = RFIFOW(fd, 2) - 2; + uint32 position = RFIFOL(fd, 4); + + if( !battle_config.feature_equipswitch ){ + return; + } + + if( index >= MAX_INVENTORY || sd->inventory_data[index] == nullptr ){ + return; + } + + if( sd->state.trading || sd->npc_shopid ){ + clif_equipswitch_add( sd, index, position, true ); + return; + } + + if( sd->inventory_data[index]->type == IT_AMMO ){ + position = EQP_AMMO; + } + + pc_equipitem( sd, index, position, true ); +#endif +} + +/// Acknowledgement packet for the full equip switch +/// 0a9d .W +void clif_equipswitch_reply( struct map_session_data* sd, bool failed ){ +#if PACKETVER >= 20170208 + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0xa9d)); + WFIFOW(fd, 0) = 0xa9d; + WFIFOW(fd, 2) = failed; + WFIFOSET(fd, packet_len(0xa9d)); +#endif +} + +/// Request to do a full equip switch +/// 0a9c +void clif_parse_equipswitch_request( int fd, struct map_session_data* sd ){ +#if PACKETVER >= 20170208 + int i; + t_tick tick = gettick(); + uint16 skill_id = ALL_EQSWITCH, skill_lv = 1; + + if( DIFF_TICK(tick, sd->equipswitch_tick) < 0 ) { + // Client will not let you send a request + return; + } + + sd->equipswitch_tick = tick + skill_get_cooldown( skill_id, skill_lv ); + + if( !battle_config.feature_equipswitch ){ + return; + } + + ARR_FIND( 0, EQI_MAX, i, sd->equip_switch_index[i] >= 0 ); + + if( i == EQI_MAX ){ + // client will show: "There is no item to replace." and should not even come here + clif_equipswitch_reply( sd, false ); + return; + } + + if( pc_issit(sd) && !pc_setstand( sd, false ) ){ + return; + } + + clif_parse_skill_toid( sd, skill_id, skill_lv, sd->bl.id ); +#endif +} + +/// Request to do a single equip switch +/// 0ace .W +void clif_parse_equipswitch_request_single( int fd, struct map_session_data* sd ){ +#if PACKETVER >= 20170502 + uint16 index = RFIFOW(fd, 2) - 2; + + if( !battle_config.feature_equipswitch ){ + return; + } + + // Check if the index is valid + if( index >= MAX_INVENTORY ){ + return; + } + + // Check if the item was already added to equip switch + if( sd->inventory.u.items_inventory[index].equipSwitch ){ + if( sd->npc_id ){ +#ifdef RENEWAL + if( pc_hasprogress( sd, WIP_DISABLE_SKILLITEM ) ){ + clif_msg( sd, WORK_IN_PROGRESS ); + return; + } +#endif + if( !sd->npc_item_flag ){ + return; + } + } + + pc_equipswitch( sd, index ); + }else{ + pc_equipitem( sd, index, pc_equippoint(sd, index), true ); + } +#endif +} + /*========================================== * Main client packet processing function *------------------------------------------*/ diff --git a/src/map/clif.hpp b/src/map/clif.hpp index 85e9b46ceb..949920edbd 100644 --- a/src/map/clif.hpp +++ b/src/map/clif.hpp @@ -520,6 +520,9 @@ enum clif_messages : uint16_t { GUILD_MASTER_WOE = 0xb93, /// <"Currently in WoE hours, unable to delegate Guild leader" GUILD_MASTER_DELAY = 0xb94, /// <"You have to wait for one day before delegating a new Guild leader" MSG_ATTENDANCE_DISABLED = 0xd92, + + // Unofficial names + C_ITEM_EQUIP_SWITCH = 0xbc7, }; enum e_personalinfo : uint8_t { @@ -1104,4 +1107,10 @@ void clif_guild_storage_log( struct map_session_data* sd, std::vector= 20160928 - parseable_packet(0x0A97,8,clif_parse_dull,0); - parseable_packet(0x0A99,4,clif_parse_dull,0); - parseable_packet(0x0A9C,2,clif_parse_dull,0); -#endif - // 2016-10-26bRagexeRE #if PACKETVER >= 20161026 packet(0x0AA5,-1); #endif +// 2017-02-08bRagexeRE +#if PACKETVER >= 20170208 + parseable_packet(0x0A97,8,clif_parse_equipswitch_add,2,4); + packet(0x0A98,12); + parseable_packet(0x0A99,8,clif_parse_equipswitch_remove,2,4,6); + packet(0x0A9A,10); + packet(0x0A9B,-1); + parseable_packet(0x0A9C,2,clif_parse_equipswitch_request,0); + packet(0x0A9D,4); +#endif + // 2017-03-15cRagexeRE #if PACKETVER >= 20170315 packet(0xac7,156); @@ -2367,7 +2371,9 @@ packet(0x0A44,-1); packet(0x0AB2,7); packet(0x0ABD,10); - parseable_packet(0x0ACE,4,clif_parse_dull,0); + packet(0x0A98,10); + parseable_packet(0x0A99,4,clif_parse_equipswitch_remove,2,4); + parseable_packet(0x0ACE,4,clif_parse_equipswitch_request_single,0); #endif // 2017-08-30bRagexeRE diff --git a/src/map/mail.cpp b/src/map/mail.cpp index c31f4ddf0d..5de7fd3ae0 100644 --- a/src/map/mail.cpp +++ b/src/map/mail.cpp @@ -146,6 +146,10 @@ enum mail_attach_result mail_setitem(struct map_session_data *sd, short idx, uin if( idx < 0 || idx >= MAX_INVENTORY || sd->inventory_data[idx] == nullptr ) return MAIL_ATTACH_ERROR; + if( sd->inventory.u.items_inventory[idx].equipSwitch ){ + return MAIL_ATTACH_EQUIPSWITCH; + } + #if PACKETVER < 20150513 i = 0; // Remove existing item diff --git a/src/map/mail.hpp b/src/map/mail.hpp index 45afa099d4..f6638f9357 100644 --- a/src/map/mail.hpp +++ b/src/map/mail.hpp @@ -12,13 +12,16 @@ enum mail_attach_result { MAIL_ATTACH_WEIGHT = 1, MAIL_ATTACH_ERROR = 2, MAIL_ATTACH_SPACE = 3, - MAIL_ATTACH_UNTRADEABLE = 4 + MAIL_ATTACH_UNTRADEABLE = 4, #else MAIL_ATTACH_WEIGHT = 1, MAIL_ATTACH_ERROR = 1, MAIL_ATTACH_SPACE = 1, - MAIL_ATTACH_UNTRADEABLE = 1 + MAIL_ATTACH_UNTRADEABLE = 1, #endif + + // Unofficial + MAIL_ATTACH_EQUIPSWITCH = 99, }; void mail_clear(struct map_session_data *sd); diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 076e0ac5c4..6f651eff39 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -537,6 +537,10 @@ bool pc_can_sell_item(struct map_session_data *sd, struct item *item, enum npc_s if (!battle_config.rental_transaction && item->expire_time) return false; // Cannot Sell Rental Items + if( item->equipSwitch ){ + return false; + } + switch (shoptype) { case NPCTYPE_SHOP: if (item->bound && battle_config.allow_bound_sell&ISR_BOUND_SELLABLE && ( @@ -771,8 +775,10 @@ void pc_setequipindex(struct map_session_data *sd) nullpo_retv(sd); - for (i = 0; i < EQI_MAX; i++) + for (i = 0; i < EQI_MAX; i++){ sd->equip_index[i] = -1; + sd->equip_switch_index[i] = -1; + } for (i = 0; i < MAX_INVENTORY; i++) { if (sd->inventory.u.items_inventory[i].nameid <= 0) @@ -797,6 +803,13 @@ void pc_setequipindex(struct map_session_data *sd) sd->weapontype2 = 0; } } + if (sd->inventory.u.items_inventory[i].equipSwitch) { + for (uint8 j = 0; j < EQI_MAX; j++) { + if (sd->inventory.u.items_inventory[i].equipSwitch & equip_bitmask[j]) { + sd->equip_switch_index[j] = i; + } + } + } } pc_calcweapontype(sd); } @@ -1220,6 +1233,7 @@ bool pc_authok(struct map_session_data *sd, uint32 login_id2, time_t expiration_ memset(&sd->storage, 0, sizeof(struct s_storage)); memset(&sd->premiumStorage, 0, sizeof(struct s_storage)); memset(&sd->equip_index, -1, sizeof(sd->equip_index)); + memset(&sd->equip_switch_index, -1, sizeof(sd->equip_switch_index)); if( pc_isinvisible(sd) && !pc_can_use_command( sd, "hide", COMMAND_ATCOMMAND ) ){ sd->status.option &= ~OPTION_INVISIBLE; @@ -4586,6 +4600,8 @@ char pc_additem(struct map_session_data *sd,struct item *item,int amount,e_log_p sd->inventory.u.items_inventory[i].equip = 0; if( item->favorite ) sd->inventory.u.items_inventory[i].favorite = 0; + if( item->equipSwitch ) + sd->inventory.u.items_inventory[i].equipSwitch = 0; sd->inventory.u.items_inventory[i].amount = amount; sd->inventory_data[i] = id; @@ -4682,6 +4698,9 @@ bool pc_dropitem(struct map_session_data *sd,int n,int amount) ) return false; + if( sd->inventory.u.items_inventory[n].equipSwitch ) + return false; + if( map_getmapflag(sd->bl.m, MF_NODROP) ) { clif_displaymessage (sd->fd, msg_txt(sd,271)); @@ -5122,6 +5141,7 @@ unsigned char pc_cart_additem(struct map_session_data *sd,struct item *item,int clif_cart_additem(sd,i,amount,0); } sd->cart.u.items_cart[i].favorite = 0; // clear + sd->cart.u.items_cart[i].equipSwitch = 0; log_pick_pc(sd, log_type, amount, &sd->cart.u.items_cart[i]); sd->cart_weight += w; @@ -5173,6 +5193,11 @@ void pc_putitemtocart(struct map_session_data *sd,int idx,int amount) if( item_data->nameid == 0 || amount < 1 || item_data->amount < amount || sd->state.vending ) return; + if( item_data->equipSwitch ){ + clif_msg( sd, C_ITEM_EQUIP_SWITCH ); + return; + } + if( (flag = pc_cart_additem(sd,item_data,amount,LOG_TYPE_NONE)) == 0 ) pc_delitem(sd,idx,amount,0,5,LOG_TYPE_NONE); else { @@ -5801,15 +5826,21 @@ static void pc_checkallowskill(struct map_session_data *sd) * -1 : Nothing equipped * idx : (this index could be used in inventory to found item_data) *------------------------------------------*/ -short pc_checkequip(struct map_session_data *sd,int pos) +short pc_checkequip(struct map_session_data *sd,int pos, bool checkall) { uint8 i; nullpo_retr(-1, sd); for(i=0;iequip_index[i] == -1 ){ + // Check all if any match is found + continue; + } + return sd->equip_index[i]; + } } return -1; @@ -9756,20 +9787,29 @@ int pc_load_combo(struct map_session_data *sd) { * Equip item on player sd at req_pos from inventory index n * return: false - fail; true - success *------------------------------------------*/ -bool pc_equipitem(struct map_session_data *sd,short n,int req_pos) +bool pc_equipitem(struct map_session_data *sd,short n,int req_pos,bool equipswitch) { int i, pos, flag = 0, iflag; struct item_data *id; uint8 res = ITEM_EQUIP_ACK_OK; + short* equip_index; nullpo_retr(false,sd); if( n < 0 || n >= MAX_INVENTORY ) { - clif_equipitemack(sd,0,0,ITEM_EQUIP_ACK_FAIL); + if( equipswitch ){ + clif_equipswitch_add( sd, n, req_pos, true ); + }else{ + clif_equipitemack(sd,0,0,ITEM_EQUIP_ACK_FAIL); + } return false; } if( DIFF_TICK(sd->canequip_tick,gettick()) > 0 ) { - clif_equipitemack(sd,n,0,ITEM_EQUIP_ACK_FAIL); + if( equipswitch ){ + clif_equipswitch_add( sd, n, req_pos, true ); + }else{ + clif_equipitemack(sd,n,0,ITEM_EQUIP_ACK_FAIL); + } return false; } @@ -9777,25 +9817,44 @@ bool pc_equipitem(struct map_session_data *sd,short n,int req_pos) return false; pos = pc_equippoint(sd,n); //With a few exceptions, item should go in all specified slots. - if(battle_config.battle_log) + if(battle_config.battle_log && !equipswitch) ShowInfo("equip %hu (%d) %x:%x\n",sd->inventory.u.items_inventory[n].nameid,n,id?id->equip:0,req_pos); if((res = pc_isequip(sd,n))) { - clif_equipitemack(sd,n,0,res); // fail + if( equipswitch ){ + clif_equipswitch_add( sd, n, req_pos, true ); + }else{ + clif_equipitemack(sd,n,0,res); // fail + } + return false; + } + + if( equipswitch && id->type == IT_AMMO ){ + clif_equipswitch_add( sd, n, req_pos, true ); return false; } if (!(pos&req_pos) || sd->inventory.u.items_inventory[n].equip != 0 || sd->inventory.u.items_inventory[n].attribute==1 ) { // [Valaris] - clif_equipitemack(sd,n,0,ITEM_EQUIP_ACK_FAIL); // fail + if( equipswitch ){ + clif_equipswitch_add( sd, n, req_pos, true ); + }else{ + clif_equipitemack(sd,n,0,ITEM_EQUIP_ACK_FAIL); // fail + } return false; } if( sd->sc.count && (sd->sc.data[SC_BERSERK] || sd->sc.data[SC_SATURDAYNIGHTFEVER] || sd->sc.data[SC_KYOUGAKU] || (sd->sc.data[SC_PYROCLASTIC] && sd->inventory_data[n]->type == IT_WEAPON)) ) { - clif_equipitemack(sd,n,0,ITEM_EQUIP_ACK_FAIL); //Fail + if( equipswitch ){ + clif_equipswitch_add( sd, n, req_pos, true ); + }else{ + clif_equipitemack(sd,n,0,ITEM_EQUIP_ACK_FAIL); //Fail + } return false; } - if (id->flag.bindOnEquip && !sd->inventory.u.items_inventory[n].bound) { + equip_index = equipswitch ? sd->equip_switch_index : sd->equip_index; + + if ( !equipswitch && id->flag.bindOnEquip && !sd->inventory.u.items_inventory[n].bound) { sd->inventory.u.items_inventory[n].bound = (char)battle_config.default_bind_on_equip; clif_notify_bindOnEquip(sd,n); } @@ -9803,52 +9862,72 @@ bool pc_equipitem(struct map_session_data *sd,short n,int req_pos) if(pos == EQP_ACC) { //Accesories should only go in one of the two, pos = req_pos&EQP_ACC; if (pos == EQP_ACC) //User specified both slots.. - pos = sd->equip_index[EQI_ACC_R] >= 0 ? EQP_ACC_L : EQP_ACC_R; + pos = equip_index[EQI_ACC_R] >= 0 ? EQP_ACC_L : EQP_ACC_R; } if(pos == EQP_ARMS && id->equip == EQP_HAND_R) { //Dual wield capable weapon. pos = (req_pos&EQP_ARMS); if (pos == EQP_ARMS) //User specified both slots, pick one for them. - pos = sd->equip_index[EQI_HAND_R] >= 0 ? EQP_HAND_L : EQP_HAND_R; + pos = equip_index[EQI_HAND_R] >= 0 ? EQP_HAND_L : EQP_HAND_R; } if(pos == EQP_SHADOW_ACC) { // Shadow System pos = req_pos&EQP_SHADOW_ACC; if (pos == EQP_SHADOW_ACC) - pos = sd->equip_index[EQI_SHADOW_ACC_L] >= 0 ? EQP_SHADOW_ACC_R : EQP_SHADOW_ACC_L; + pos = equip_index[EQI_SHADOW_ACC_L] >= 0 ? EQP_SHADOW_ACC_R : EQP_SHADOW_ACC_L; } if(pos == EQP_SHADOW_ARMS && id->equip == EQP_SHADOW_WEAPON) { pos = (req_pos&EQP_SHADOW_ARMS); if( pos == EQP_SHADOW_ARMS ) - pos = (sd->equip_index[EQI_SHADOW_WEAPON] >= 0 ? EQP_SHADOW_SHIELD : EQP_SHADOW_WEAPON); + pos = (equip_index[EQI_SHADOW_WEAPON] >= 0 ? EQP_SHADOW_SHIELD : EQP_SHADOW_WEAPON); } if (pos&EQP_HAND_R && battle_config.use_weapon_skill_range&BL_PC) { //Update skill-block range database when weapon range changes. [Skotlex] - i = sd->equip_index[EQI_HAND_R]; + i = equip_index[EQI_HAND_R]; if (i < 0 || !sd->inventory_data[i]) //No data, or no weapon equipped flag = 1; else flag = id->range != sd->inventory_data[i]->range; } - for(i=0;iequip_index[i] >= 0) //Slot taken, remove item from there. - pc_unequipitem(sd,sd->equip_index[i],2 | 4); + if( equipswitch ){ + for( i = 0; i < EQI_MAX; i++ ){ + if( pos&equip_bitmask[i] ){ + // If there was already an item assigned to this slot + if( sd->equip_switch_index[i] >= 0 ){ + pc_equipswitch_remove( sd, sd->equip_switch_index[i] ); + } - sd->equip_index[i] = n; + // Assign the new index to it + sd->equip_switch_index[i] = n; + } } - } - if(pos==EQP_AMMO) { - clif_arrowequip(sd,n); - clif_arrow_fail(sd,3); - } - else - clif_equipitemack(sd,n,pos,ITEM_EQUIP_ACK_OK); + sd->inventory.u.items_inventory[n].equipSwitch = pos; + clif_equipswitch_add( sd, n, pos, false ); + return true; + }else{ + for(i=0;iequip_index[i] >= 0) //Slot taken, remove item from there. + pc_unequipitem(sd,sd->equip_index[i],2 | 4); - sd->inventory.u.items_inventory[n].equip = pos; + sd->equip_index[i] = n; + } + } + + pc_equipswitch_remove(sd, n); + + if(pos==EQP_AMMO) { + clif_arrowequip(sd,n); + clif_arrow_fail(sd,3); + } + else + clif_equipitemack(sd,n,pos,ITEM_EQUIP_ACK_OK); + + sd->inventory.u.items_inventory[n].equip = pos; + } if(pos & EQP_HAND_R) { if(id) @@ -10152,13 +10231,114 @@ bool pc_unequipitem(struct map_session_data *sd, int n, int flag) { return true; } +int pc_equipswitch( struct map_session_data* sd, int index ){ + // Get the target equip mask + int position = sd->inventory.u.items_inventory[index].equipSwitch; + + // Get the currently equipped item + short equippedItem = pc_checkequip( sd, position, true ); + + // No item equipped at the target + if( equippedItem == -1 ){ + // Remove it from the equip switch + pc_equipswitch_remove( sd, index ); + + pc_equipitem( sd, index, position ); + + return position; + }else{ + std::map unequipped; + int unequipped_position = 0; + + // Unequip all items that interfere + for( int i = 0; i < EQI_MAX; i++ ){ + int unequip_index = sd->equip_index[i]; + + if( unequip_index >= 0 && position & equip_bitmask[i] ){ + struct item* unequip_item = &sd->inventory.u.items_inventory[unequip_index]; + + // Store the unequipped index and position mask for later + unequipped[unequip_index] = unequip_item->equip; + + // Keep the position for later + unequipped_position |= unequip_item->equip; + + // Unequip the item + pc_unequipitem( sd, unequip_index, 0 ); + } + } + + int all_position = position | unequipped_position; + + // Equip everything that is hit by the mask + for( int i = 0; i < EQI_MAX; i++ ){ + int exchange_index = sd->equip_switch_index[i]; + + if( exchange_index >= 0 && all_position & equip_bitmask[i] ){ + struct item* exchange_item = &sd->inventory.u.items_inventory[exchange_index]; + + // Store the target position + int exchange_position = exchange_item->equipSwitch; + + // Remove the item from equip switch + pc_equipswitch_remove( sd, exchange_index ); + + // Equip the item at the destinated position + pc_equipitem( sd, exchange_index, exchange_position ); + } + } + + // Place all unequipped items into the equip switch window + for( std::pair pair : unequipped ){ + int unequipped_index = pair.first; + int unequipped_position = pair.second; + + // Rebuild the index cache + for( int i = 0; i < EQI_MAX; i++ ){ + if( unequipped_position & equip_bitmask[i] ){ + sd->equip_switch_index[i] = unequipped_index; + } + } + + // Set the correct position mask + sd->inventory.u.items_inventory[unequipped_index].equipSwitch = unequipped_position; + + // Notify the client + clif_equipswitch_add( sd, unequipped_index, unequipped_position, false ); + } + + return all_position; + } +} + +void pc_equipswitch_remove( struct map_session_data* sd, int index ){ + struct item* item = &sd->inventory.u.items_inventory[index]; + + if( !item->equipSwitch ){ + return; + } + + for( int i = 0; i < EQI_MAX; i++ ){ + // If a match is found + if( sd->equip_switch_index[i] == index ){ + // Remove it from the slot + sd->equip_switch_index[i] = -1; + } + } + + // Send out one packet for all slots using the current item's mask + clif_equipswitch_remove( sd, index, item->equipSwitch, false ); + + item->equipSwitch = 0; +} + /*========================================== * Checking if player (sd) has an invalid item * and is unequiped on map load (item_noequip) *------------------------------------------*/ void pc_checkitem(struct map_session_data *sd) { int i, calc_flag = 0; - struct item it; + struct item* it; nullpo_retv(sd); @@ -10168,13 +10348,13 @@ void pc_checkitem(struct map_session_data *sd) { pc_check_available_item(sd, ITMCHK_NONE); // Check for invalid(ated) items. for( i = 0; i < MAX_INVENTORY; i++ ) { - it = sd->inventory.u.items_inventory[i]; + it = &sd->inventory.u.items_inventory[i]; - if( it.nameid == 0 ) + if( it->nameid == 0 ) continue; - if( !it.equip ) + if( !it->equip ) continue; - if( it.equip&~pc_equippoint(sd,i) ) { + if( it->equip&~pc_equippoint(sd,i) ) { pc_unequipitem(sd, i, 2); calc_flag = 1; continue; @@ -10187,6 +10367,28 @@ void pc_checkitem(struct map_session_data *sd) { } } + for( i = 0; i < MAX_INVENTORY; i++ ) { + it = &sd->inventory.u.items_inventory[i]; + + if( it->nameid == 0 ) + continue; + if( !it->equipSwitch ) + continue; + if( it->equipSwitch&~pc_equippoint(sd,i) || + ( !pc_has_permission(sd, PC_PERM_USE_ALL_EQUIPMENT) && !battle_config.allow_equip_restricted_item && itemdb_isNoEquip(sd->inventory_data[i], sd->bl.m) ) ){ + + for( int j = 0; j < EQI_MAX; j++ ){ + if( sd->equip_switch_index[j] == i ){ + sd->equip_switch_index[j] = -1; + } + } + + sd->inventory.u.items_inventory[i].equipSwitch = 0; + + continue; + } + } + if( calc_flag && sd->state.active ) { pc_checkallowskill(sd); status_calc_pc(sd,SCO_NONE); diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 5d44c00bdc..aa5b271b2f 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -318,6 +318,7 @@ struct map_session_data { struct item_data* inventory_data[MAX_INVENTORY]; // direct pointers to itemdb entries (faster than doing item_id lookups) short equip_index[EQI_MAX]; + short equip_switch_index[EQI_MAX]; unsigned int weight,max_weight,add_max_weight; int cart_weight,cart_num,cart_weight_max; int fd; @@ -367,6 +368,7 @@ struct map_session_data { t_tick canskill_tick; // used to prevent abuse from no-delay ACT files t_tick cansendmail_tick; // [Mail System Flood Protection] t_tick ks_floodprotect_tick; // [Kill Steal Protection] + t_tick equipswitch_tick; // Equip switch struct s_item_delay { unsigned short nameid; @@ -1007,7 +1009,7 @@ void pc_setinventorydata(struct map_session_data *sd); int pc_get_skillcooldown(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv); uint8 pc_checkskill(struct map_session_data *sd,uint16 skill_id); -short pc_checkequip(struct map_session_data *sd,int pos); +short pc_checkequip(struct map_session_data *sd,int pos,bool checkall=false); bool pc_checkequip2(struct map_session_data *sd, unsigned short nameid, int min, int max); void pc_scdata_received(struct map_session_data *sd); @@ -1123,8 +1125,10 @@ int pc_resetstate(struct map_session_data*); int pc_resetskill(struct map_session_data*, int); int pc_resetfeel(struct map_session_data*); int pc_resethate(struct map_session_data*); -bool pc_equipitem(struct map_session_data *sd, short n, int req_pos); +bool pc_equipitem(struct map_session_data *sd, short n, int req_pos, bool equipswitch=false); bool pc_unequipitem(struct map_session_data*,int,int); +int pc_equipswitch( struct map_session_data* sd, int index ); +void pc_equipswitch_remove( struct map_session_data* sd, int index ); void pc_checkitem(struct map_session_data*); void pc_check_available_item(struct map_session_data *sd, uint8 type); int pc_useitem(struct map_session_data*,int); diff --git a/src/map/skill.cpp b/src/map/skill.cpp index 7c4c2e4d95..dfa6c09ede 100755 --- a/src/map/skill.cpp +++ b/src/map/skill.cpp @@ -676,7 +676,9 @@ bool skill_isNotOk(uint16 skill_id, struct map_session_data *sd) if (skill_id == AL_TELEPORT && sd->skillitem == skill_id && sd->skillitemlv > 2) return false; // Teleport lv 3 bypasses this check.[Inkfish] - if (map_getmapflag(m, MF_NOSKILL)) + struct map_data *mapdata = map_getmapdata(m); + + if (mapdata->flag[MF_NOSKILL] && skill_id != ALL_EQSWITCH) return true; // Epoque: @@ -701,8 +703,6 @@ bool skill_isNotOk(uint16 skill_id, struct map_session_data *sd) if( sd->skillitem == skill_id && !sd->skillitem_keep_requirement ) return false; - struct map_data *mapdata = map_getmapdata(m); - skill_nocast = skill_get_nocast(skill_id); // Check skill restrictions [Celest] if( (skill_nocast&1 && !mapdata_flag_vs2(mapdata)) || @@ -10988,6 +10988,18 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui } break; + case ALL_EQSWITCH: + if( sd ){ + clif_equipswitch_reply( sd, false ); + + for( int i = 0, position = 0; i < EQI_MAX; i++ ){ + if( sd->equip_switch_index[i] >= 0 && !( position & equip_bitmask[i] ) ){ + position |= pc_equipswitch( sd, sd->equip_switch_index[i] ); + } + } + } + break; + case AB_VITUPERATUM: if (flag&1) clif_skill_nodamage(src, bl, skill_id, skill_lv, sc_start(src, bl, type, 100, skill_lv, skill_get_time(skill_id, skill_lv))); diff --git a/src/map/skill.hpp b/src/map/skill.hpp index 884e427ee1..3c3f2b3f86 100644 --- a/src/map/skill.hpp +++ b/src/map/skill.hpp @@ -414,6 +414,7 @@ int skill_get_castcancel( uint16 skill_id ); int skill_get_maxcount( uint16 skill_id ,uint16 skill_lv ); int skill_get_blewcount( uint16 skill_id ,uint16 skill_lv ); int skill_get_unit_flag( uint16 skill_id ); +int skill_get_cooldown( uint16 skill_id, uint16 skill_lv ); int skill_get_unit_target( uint16 skill_id ); int skill_get_inf3( uint16 skill_id ); // Accessor for skill requirements @@ -1857,6 +1858,8 @@ enum e_skill { WE_ONEFOREVER, WE_CHEERUP, + ALL_EQSWITCH = 5067, + AB_VITUPERATUM = 5072, AB_CONVENIO, diff --git a/src/map/trade.cpp b/src/map/trade.cpp index 05bc7320d6..a047129344 100644 --- a/src/map/trade.cpp +++ b/src/map/trade.cpp @@ -397,6 +397,11 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount) return; } + if( item->equipSwitch ){ + clif_msg(sd, C_ITEM_EQUIP_SWITCH); + return; + } + if (item->bound) sd->state.isBoundTrading |= (1<bound);