summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--conf/messages.conf41
-rw-r--r--doc/atcommands.txt16
-rw-r--r--doc/permissions.txt3
-rw-r--r--doc/script_commands.txt46
-rw-r--r--sql-files/main.sql4
-rw-r--r--sql-files/upgrades/2013-10-31--07-49.sql4
-rw-r--r--src/char/char.c94
-rw-r--r--src/char/int_mail.c2
-rw-r--r--src/char/int_storage.c167
-rw-r--r--src/char/inter.c4
-rw-r--r--src/common/mmo.h6
-rw-r--r--src/config/core.h4
-rw-r--r--src/map/atcommand.c78
-rw-r--r--src/map/buyingstore.c4
-rw-r--r--src/map/clif.c26
-rw-r--r--src/map/guild.c52
-rw-r--r--src/map/guild.h4
-rw-r--r--src/map/intif.c63
-rw-r--r--src/map/intif.h4
-rw-r--r--src/map/mail.c5
-rw-r--r--src/map/packets_struct.h1
-rw-r--r--src/map/party.c9
-rw-r--r--src/map/pc.c59
-rw-r--r--src/map/pc.h7
-rw-r--r--src/map/pc_groups.c1
-rw-r--r--src/map/pc_groups.h3
-rw-r--r--src/map/script.c229
-rw-r--r--src/map/storage.c17
-rw-r--r--src/map/trade.c6
-rw-r--r--src/map/vending.c3
30 files changed, 825 insertions, 137 deletions
diff --git a/conf/messages.conf b/conf/messages.conf
index 7b7a99301..d2e57bb0e 100644
--- a/conf/messages.conf
+++ b/conf/messages.conf
@@ -316,6 +316,13 @@
290: The player is no longer killable.
291: Weather effects will dispell on warp/refresh
292: Killer state reset.
+//Item Bound System
+293: This bounded item cannot be traded to that character.
+294: This bounded item cannot be stored there.
+295: Please enter an item name or ID (usage: @item <item name/ID> <quantity> <bound_type>).
+296: Please enter all parameters (usage: @item2 <item name/ID> <quantity>
+297: <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4> <bound_type>).
+298: Invalid bound type. Valid types are - 1:Account 2:Guild 3:Party 4:Character
// Guild Castles Number
// --------------------
//299: ?? Castles
@@ -455,6 +462,13 @@
// Messages of others (not for GM commands)
// ----------------------------------------
+// Account-Bound Items
+497: You cannot distribute this item - it is an account bounded item!
+
+// @itembound / @itembound2
+498: Cannot create bounded pet eggs or pet armors.
+499: Cannot create bounded stackable items.
+
//500: FREE
501: Your account time limit is: %d-%m-%Y %H:%M:%S.
502: Day Mode is activated
@@ -711,11 +725,13 @@
981: Please enter color and message (usage: @kamic <color> <message>).
982: Invalid color.
-// @item
-983: Please enter an item name or ID (usage: @item <item name/ID> <quantity>).
-// @item2
-984: Please enter all parameters (usage: @item2 <item name/ID> <quantity>
+// @item / @itembound
+983: Please enter an item name or ID (usage: @%s <item name/ID> <quantity>).
+
+
+// @item2 / @itembound2
+984: Please enter all parameters (usage: @%s <item name/ID> <quantity>).
985: <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4>).
// @baselevelup
@@ -1360,7 +1376,8 @@
1361: Already using this font.
// @new_mount
-1362: NOTICE: If you crash with mount your LUA is outdated.
+//1362: NOTICE: If you crash with mount your LUA is outdated.
+1362: ..
1363: You have mounted.
1364: You have released your mount.
@@ -1534,5 +1551,19 @@
//CashShop mapflag
1489: Cash Shop is disabled in this map
+
+// @autoloottype
+1490: You're already autolooting this item type.
+1491: Your autoloottype list has all item types. You can remove some items with @autoloottype -<type name or ID>.
+1492: Autolooting item type: '%s' {%d}
+1493: You're currently not autolooting this item type.
+1494: Removed item type: '%s' {%d} from your autoloottype list.
+1495: To add an item type to the list, use "@aloottype +<type name or ID>". To remove an item type, use "@aloottype -<type name or ID>".
+1496: Type List: healing = 0, usable = 2, etc = 3, weapon = 4, armor = 5, card = 6, petegg = 7, petarmor = 8, ammo = 10
+1497: "@aloottype reset" will clear your autoloottype list.
+1498: Your autoloottype list is empty.
+1499: Item types on your autoloottype list:
+1500: Your autoloottype list has been reset.
+
//Custom translations
import: conf/import/msg_conf.txt
diff --git a/doc/atcommands.txt b/doc/atcommands.txt
index 42b085cd6..a7377b563 100644
--- a/doc/atcommands.txt
+++ b/doc/atcommands.txt
@@ -626,6 +626,22 @@ identify_flag: 0 = unidentified, 1 = identified
attribute: 0 = not broken, 1 = broken
---------------------------------------
+
+@itembound <item name/ID> <amount> <bound_type>
+
+Creates the specified item and bounds it to the account.
+bound_type: 1 = Account, 2 = Guild, 3 = Party, 4 = Character
+
+---------------------------------------
+
+@itembound2 <item name/ID> <quantity> <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4> <bound_type>
+
+Creates an item with the given parameters (the 'cards' can be any item) and bounds it to the account.
+identify_flag: 0 = unidentified, 1 = identified
+attribute: 0 = not broken, 1 = broken
+bound_type: 1 = Account, 2 = Guild, 3 = Party, 4 = Character
+
+---------------------------------------
@produce <equip name/ID> <element> <# of Very's>
diff --git a/doc/permissions.txt b/doc/permissions.txt
index 9760f716c..c2aeba081 100644
--- a/doc/permissions.txt
+++ b/doc/permissions.txt
@@ -3,7 +3,7 @@
//===== By: ==================================================
//= Hercules Dev Team
//===== Current Version: =====================================
-//= 20130528
+//= 20131031
//===== Description: =========================================
//= Player group permissions, configured in /conf/groups.conf.
//============================================================
@@ -31,4 +31,5 @@ show_bossmobs : Ability to see boss mobs with @showmobs.
disable_pvm : Ability to disable Player vs. Monster.
disable_pvp : Ability to disable Player vs. Player.
disable_commands_when_dead : Ability to disable @command usage when dead.
+can_trade_bounded : Ability to trade or otherwise distribute bounded items (drop, storage, vending etc...).
hchsys_admin : Hercules Chat System Admin (Ability to modify channel settings regardless of ownership and join password-protected channels without requiring a password.)
diff --git a/doc/script_commands.txt b/doc/script_commands.txt
index bd0b032f1..23bacec5f 100644
--- a/doc/script_commands.txt
+++ b/doc/script_commands.txt
@@ -2781,6 +2781,7 @@ recreate these items perfectly if they are destroyed. Here's what you get:
@inventorylist_expire[] - expire time (Unix time stamp). 0 means never
expires.
@inventorylist_count - the number of items in these lists.
+@inventorylist_bound - whether it is an account bounded item or not.
This could be handy to save/restore a character's inventory, since no
other command returns such a complete set of data, and could also be the
@@ -4449,6 +4450,51 @@ two eggs, and may hatch from either, although, I'm not sure what kind of a
mess will this really cause.
---------------------------------------
+*getitembound <item id>,<amount>,<bound type>{,<account ID>};
+*getitembound "<item name>",<amount>,<bound type>{,<account ID>};
+
+This command behaves identically to 'getitem', but the items created will be
+bound to the target character as specified by the bound type. All items created
+in this manner cannot be dropped, sold, vended, auctioned, or mailed, and in
+some cases cannot be traded or stored.
+
+Valid bound types are:
+ 1 - Account Bound
+ 2 - Guild Bound
+ 3 - Party Bound
+ 4 - Character Bound
+
+---------------------------------------
+
+*getitembound2 <item id>,<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>,<bound type>;
+*getitembound2 "<item name>",<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>,<bound type>;
+
+This command behaves identically to 'getitem2', but the items created will be
+bound to the target character as specified by the bound type. All items created
+in this manner cannot be dropped, sold, vended, auctioned, or mailed, and in
+some cases cannot be traded or stored.
+
+For a list of bound types see 'getitembound'.
+
+---------------------------------------
+
+*countbound({<bound type>})
+
+This function will return the number of bounded items in the character's
+inventory, and sets an array @bound_items[] containing all item IDs of the
+counted items. If a bound type is specified, only those items will be counted.
+
+For a list of bound types see 'getitembound'.
+
+Example:
+ mes "You currently have "+countbound()+" bounded items.";
+ next;
+ mes "The list of bounded items include:";
+ for(set .@i,0; .@i<getarraysize(@bound_items); set .@i,.@i+1)
+ mes getitemname(@bound_items[.@i]);
+ close;
+
+---------------------------------------
*getnameditem <item id>,<character name|character ID>;
*getnameditem "<item name>",<character name|character ID>;
diff --git a/sql-files/main.sql b/sql-files/main.sql
index 2c91d58b1..3f425d411 100644
--- a/sql-files/main.sql
+++ b/sql-files/main.sql
@@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS `cart_inventory` (
`card2` smallint(11) NOT NULL default '0',
`card3` smallint(11) NOT NULL default '0',
`expire_time` int(11) unsigned NOT NULL default '0',
+ `bound` tinyint(1) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `char_id` (`char_id`)
@@ -342,6 +343,7 @@ CREATE TABLE IF NOT EXISTS `guild_storage` (
`card2` smallint(11) NOT NULL default '0',
`card3` smallint(11) NOT NULL default '0',
`expire_time` int(11) unsigned NOT NULL default '0',
+ `bound` tinyint(1) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `guild_id` (`guild_id`)
@@ -406,6 +408,7 @@ CREATE TABLE IF NOT EXISTS `inventory` (
`card3` smallint(11) NOT NULL default '0',
`expire_time` int(11) unsigned NOT NULL default '0',
`favorite` tinyint(3) unsigned NOT NULL default '0',
+ `bound` tinyint(1) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `char_id` (`char_id`)
@@ -691,6 +694,7 @@ CREATE TABLE IF NOT EXISTS `storage` (
`card2` smallint(11) NOT NULL default '0',
`card3` smallint(11) NOT NULL default '0',
`expire_time` int(11) unsigned NOT NULL default '0',
+ `bound` tinyint(1) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
diff --git a/sql-files/upgrades/2013-10-31--07-49.sql b/sql-files/upgrades/2013-10-31--07-49.sql
new file mode 100644
index 000000000..40e0421c8
--- /dev/null
+++ b/sql-files/upgrades/2013-10-31--07-49.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `inventory` ADD COLUMN `bound` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `favorite`;
+ALTER TABLE `cart_inventory` ADD COLUMN `bound` tinyint(1) UNSIGNED NOT NULL default '0' AFTER `expire_time`;
+ALTER TABLE `storage` ADD COLUMN `bound` tinyint(1) UNSIGNED NOT NULL default '0' AFTER `expire_time`;
+ALTER TABLE `guild_storage` ADD COLUMN `bound` TINYINT(3) UNSIGNED NOT NULL default '0' AFTER `expire_time`; \ No newline at end of file
diff --git a/src/char/char.c b/src/char/char.c
index 4a04c521d..61a1e24e0 100644
--- a/src/char/char.c
+++ b/src/char/char.c
@@ -724,8 +724,8 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
// it significantly reduces cpu load on the database server.
StrBuf->Init(&buf);
- StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`");
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`");
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", `card%d`", j);
StrBuf->Printf(&buf, " FROM `%s` WHERE `%s`='%d'", tablename, selectoption, id);
@@ -747,8 +747,9 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
SQL->StmtBindColumn(stmt, 5, SQLDT_CHAR, &item.refine, 0, NULL, NULL);
SQL->StmtBindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL);
SQL->StmtBindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL);
- for( j = 0; j < MAX_SLOTS; ++j )
- SQL->StmtBindColumn(stmt, 8+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 8, SQLDT_UINT, &item.bound, 0, NULL, NULL);
+ for( j = 0; j < MAX_SLOTS; ++j )
+ SQL->StmtBindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
// bit array indicating which inventory items have already been matched
flag = (bool*) aCalloc(max, sizeof(bool));
@@ -775,17 +776,18 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
items[i].identify == item.identify &&
items[i].refine == item.refine &&
items[i].attribute == item.attribute &&
- items[i].expire_time == item.expire_time )
+ items[i].expire_time == item.expire_time &&
+ items[i].bound == item.bound )
; //Do nothing.
else
{
// update all fields.
StrBuf->Clear(&buf);
- StrBuf->Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u'",
- tablename, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time);
- for( j = 0; j < MAX_SLOTS; ++j )
- StrBuf->Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
- StrBuf->Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
+ StrBuf->Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u', `bound`='%d'",
+ tablename, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].bound);
+ for( j = 0; j < MAX_SLOTS; ++j )for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
+ StrBuf->Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
if( SQL_ERROR == SQL->QueryStr(sql_handle, StrBuf->Value(&buf)) )
{
@@ -810,8 +812,8 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
SQL->StmtFree(stmt);
StrBuf->Clear(&buf);
- StrBuf->Printf(&buf, "INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `unique_id`", tablename, selectoption);
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, "INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`, `unique_id`", tablename, selectoption);
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", `card%d`", j);
StrBuf->AppendStr(&buf, ") VALUES ");
@@ -828,9 +830,9 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
else
found = true;
- StrBuf->Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%u', '%"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].unique_id);
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, "('%d', '%d', '%d', '%d', '%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);
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", '%d'", items[i].card[j]);
StrBuf->AppendStr(&buf, ")");
@@ -868,8 +870,8 @@ int inventory_to_sql(const struct item items[], int max, int id) {
// it significantly reduces cpu load on the database server.
StrBuf->Init(&buf);
- StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`");
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `bound`");
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", `card%d`", j);
StrBuf->Printf(&buf, " FROM `%s` WHERE `char_id`='%d'", inventory_db, id);
@@ -892,8 +894,9 @@ int inventory_to_sql(const struct item items[], int max, int id) {
SQL->StmtBindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL);
SQL->StmtBindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL);
SQL->StmtBindColumn(stmt, 8, SQLDT_CHAR, &item.favorite, 0, NULL, NULL);
- for( j = 0; j < MAX_SLOTS; ++j )
- SQL->StmtBindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 9, SQLDT_CHAR, &item.bound, 0, NULL, NULL);
+ for( j = 0; j < MAX_SLOTS; ++j )
+ SQL->StmtBindColumn(stmt, 10+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
// bit array indicating which inventory items have already been matched
flag = (bool*) aCalloc(max, sizeof(bool));
@@ -919,15 +922,16 @@ int inventory_to_sql(const struct item items[], int max, int id) {
items[i].refine == item.refine &&
items[i].attribute == item.attribute &&
items[i].expire_time == item.expire_time &&
- items[i].favorite == item.favorite )
+ items[i].favorite == item.favorite &&
+ items[i].bound == item.bound )
; //Do nothing.
else {
// update all fields.
StrBuf->Clear(&buf);
- StrBuf->Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u', `favorite`='%d'",
- inventory_db, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].favorite);
- for( j = 0; j < MAX_SLOTS; ++j )
- StrBuf->Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
+ StrBuf->Printf(&buf, "UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d', `refine`='%d',`attribute`='%d', `expire_time`='%u', `favorite`='%d', `bound`='%d'",
+ inventory_db, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute, items[i].expire_time, items[i].favorite, items[i].bound);
+ for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
StrBuf->Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
if( SQL_ERROR == SQL->QueryStr(sql_handle, StrBuf->Value(&buf)) ) {
@@ -950,8 +954,8 @@ int inventory_to_sql(const struct item items[], int max, int id) {
SQL->StmtFree(stmt);
StrBuf->Clear(&buf);
- StrBuf->Printf(&buf, "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `unique_id`", inventory_db);
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `bound`, `unique_id`", inventory_db);
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", `card%d`", j);
StrBuf->AppendStr(&buf, ") VALUES ");
@@ -967,9 +971,9 @@ int inventory_to_sql(const struct item items[], int max, int id) {
else
found = true;
- StrBuf->Printf(&buf, "('%d', '%d', '%d', '%d', '%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].favorite, items[i].unique_id);
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%u', '%d', '%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].favorite, items[i].bound, items[i].unique_id);
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", '%d'", items[i].card[j]);
StrBuf->AppendStr(&buf, ")");
@@ -1229,10 +1233,10 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
strcat(t_msg, " memo");
//read inventory
- //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `expire_time`, `favorite`, `unique_id`)
- StrBuf->Init(&buf);
- StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `unique_id`");
- for( i = 0; i < MAX_SLOTS; ++i )
+ //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `expire_time`, `favorite`, `bound`, `unique_id`)
+ StrBuf->Init(&buf);
+ StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `bound`, `unique_id`");
+ for( i = 0; i < MAX_SLOTS; ++i )
StrBuf->Printf(&buf, ", `card%d`", i);
StrBuf->Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", inventory_db, MAX_INVENTORY);
@@ -1248,10 +1252,11 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
|| SQL_ERROR == SQL->StmtBindColumn(stmt, 6, SQLDT_CHAR, &tmp_item.attribute, 0, NULL, NULL)
|| SQL_ERROR == SQL->StmtBindColumn(stmt, 7, SQLDT_UINT, &tmp_item.expire_time, 0, NULL, NULL)
|| SQL_ERROR == SQL->StmtBindColumn(stmt, 8, SQLDT_CHAR, &tmp_item.favorite, 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 9, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
- SqlStmt_ShowDebug(stmt);
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 9, SQLDT_CHAR, &tmp_item.bound, 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 10, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
+ SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_SLOTS; ++i )
- if( SQL_ERROR == SQL->StmtBindColumn(stmt, 10+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
+ if( SQL_ERROR == SQL->StmtBindColumn(stmt, 11+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_INVENTORY && SQL_SUCCESS == SQL->StmtNextRow(stmt); ++i )
@@ -1260,10 +1265,10 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
strcat(t_msg, " inventory");
//read cart
- //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, expire_time`, `unique_id`)
- StrBuf->Clear(&buf);
- StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `unique_id`");
- for( j = 0; j < MAX_SLOTS; ++j )
+ //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, expire_time`, `bound`, `unique_id`)
+ StrBuf->Clear(&buf);
+ StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`, `unique_id`");
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ", `card%d`", j);
StrBuf->Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", cart_db, MAX_CART);
@@ -1278,10 +1283,11 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
|| SQL_ERROR == SQL->StmtBindColumn(stmt, 5, SQLDT_CHAR, &tmp_item.refine, 0, NULL, NULL)
|| SQL_ERROR == SQL->StmtBindColumn(stmt, 6, SQLDT_CHAR, &tmp_item.attribute, 0, NULL, NULL)
|| SQL_ERROR == SQL->StmtBindColumn(stmt, 7, SQLDT_UINT, &tmp_item.expire_time, 0, NULL, NULL)
- || SQL_ERROR == SQL->StmtBindColumn(stmt, 8, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
- SqlStmt_ShowDebug(stmt);
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 8, SQLDT_CHAR, &tmp_item.bound, 0, NULL, NULL)
+ || SQL_ERROR == SQL->StmtBindColumn(stmt, 9, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
+ SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_SLOTS; ++i )
- if( SQL_ERROR == SQL->StmtBindColumn(stmt, 9+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
+ if( SQL_ERROR == SQL->StmtBindColumn(stmt, 10+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_CART && SQL_SUCCESS == SQL->StmtNextRow(stmt); ++i )
@@ -2947,7 +2953,7 @@ int parse_frommap(int fd)
break;
}
//Check account only if this ain't final save. Final-save goes through because of the char-map reconnect
- if (RFIFOB(fd,12) || (
+ if (RFIFOB(fd,12) || RFIFOB(fd,13) || (
(character = (struct online_char_data*)idb_get(online_char_db, aid)) != NULL &&
character->char_id == cid))
{
diff --git a/src/char/int_mail.c b/src/char/int_mail.c
index e4b88b5bf..b69824d4b 100644
--- a/src/char/int_mail.c
+++ b/src/char/int_mail.c
@@ -64,6 +64,7 @@ static int mail_fromsql(int char_id, struct mail_data* md)
SQL->GetData(sql_handle,14, &data, NULL); item->identify = atoi(data);
SQL->GetData(sql_handle,15, &data, NULL); item->unique_id = strtoull(data, NULL, 10);
item->expire_time = 0;
+ item->bound = 0;
for (j = 0; j < MAX_SLOTS; j++)
{
@@ -184,6 +185,7 @@ static bool mail_loadmessage(int mail_id, struct mail_message* msg)
SQL->GetData(sql_handle,14, &data, NULL); msg->item.identify = atoi(data);
SQL->GetData(sql_handle,15, &data, NULL); msg->item.unique_id = strtoull(data, NULL, 10);
msg->item.expire_time = 0;
+ msg->item.bound = 0;
for( j = 0; j < MAX_SLOTS; j++ )
{
diff --git a/src/char/int_storage.c b/src/char/int_storage.c
index 429b80105..6443aa743 100644
--- a/src/char/int_storage.c
+++ b/src/char/int_storage.c
@@ -39,8 +39,8 @@ int storage_fromsql(int account_id, struct storage_data* p)
// storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`}
StrBuf->Init(&buf);
- StrBuf->AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`expire_time`,`unique_id`");
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`expire_time`,`bound`,`unique_id`");
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ",`card%d`", j);
StrBuf->Printf(&buf, " FROM `%s` WHERE `account_id`='%d' ORDER BY `nameid`", storage_db, account_id);
@@ -60,10 +60,11 @@ int storage_fromsql(int account_id, struct storage_data* p)
SQL->GetData(sql_handle, 5, &data, NULL); item->refine = atoi(data);
SQL->GetData(sql_handle, 6, &data, NULL); item->attribute = atoi(data);
SQL->GetData(sql_handle, 7, &data, NULL); item->expire_time = (unsigned int)atoi(data);
- SQL->GetData(sql_handle, 8, &data, NULL); item->unique_id = strtoull(data, NULL, 10);
- for( j = 0; j < MAX_SLOTS; ++j )
+ SQL->GetData(sql_handle, 8, &data, NULL); item->bound = atoi(data);
+ SQL->GetData(sql_handle, 9, &data, NULL); item->unique_id = strtoull(data, NULL, 10);
+ for( j = 0; j < MAX_SLOTS; ++j )
{
- SQL->GetData(sql_handle, 9+j, &data, NULL); item->card[j] = atoi(data);
+ SQL->GetData(sql_handle, 10+j, &data, NULL); item->card[j] = atoi(data);
}
}
p->storage_amount = i;
@@ -96,8 +97,8 @@ int guild_storage_fromsql(int guild_id, struct guild_storage* p)
// storage {`guild_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`}
StrBuf->Init(&buf);
- StrBuf->AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`unique_id`");
- for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`bound`,`unique_id`");
+ for( j = 0; j < MAX_SLOTS; ++j )
StrBuf->Printf(&buf, ",`card%d`", j);
StrBuf->Printf(&buf, " FROM `%s` WHERE `guild_id`='%d' ORDER BY `nameid`", guild_storage_db, guild_id);
@@ -116,11 +117,13 @@ int guild_storage_fromsql(int guild_id, struct guild_storage* p)
SQL->GetData(sql_handle, 4, &data, NULL); item->identify = atoi(data);
SQL->GetData(sql_handle, 5, &data, NULL); item->refine = atoi(data);
SQL->GetData(sql_handle, 6, &data, NULL); item->attribute = atoi(data);
- SQL->GetData(sql_handle, 7, &data, NULL); item->unique_id = strtoull(data, NULL, 10);
- item->expire_time = 0;
+ SQL->GetData(sql_handle, 7, &data, NULL); item->bound = atoi(data);
+ SQL->GetData(sql_handle, 8, &data, NULL); item->unique_id = strtoull(data, NULL, 10);
+ item->expire_time = 0;
+ item->bound = 0;
for( j = 0; j < MAX_SLOTS; ++j )
{
- SQL->GetData(sql_handle, 8+j, &data, NULL); item->card[j] = atoi(data);
+ SQL->GetData(sql_handle, 9+j, &data, NULL); item->card[j] = atoi(data);
}
}
p->storage_amount = i;
@@ -159,19 +162,20 @@ int inter_guild_storage_delete(int guild_id)
//---------------------------------------------------------
// packet from map server
-int mapif_load_guild_storage(int fd,int account_id,int guild_id)
+int mapif_load_guild_storage(int fd,int account_id,int guild_id, char flag)
{
if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `guild_id` FROM `%s` WHERE `guild_id`='%d'", guild_db, guild_id) )
Sql_ShowDebug(sql_handle);
else if( SQL->NumRows(sql_handle) > 0 )
{// guild exists
- WFIFOHEAD(fd, sizeof(struct guild_storage)+12);
+ WFIFOHEAD(fd, sizeof(struct guild_storage)+13);
WFIFOW(fd,0) = 0x3818;
- WFIFOW(fd,2) = sizeof(struct guild_storage)+12;
+ WFIFOW(fd,2) = sizeof(struct guild_storage)+13;
WFIFOL(fd,4) = account_id;
WFIFOL(fd,8) = guild_id;
- guild_storage_fromsql(guild_id, (struct guild_storage*)WFIFOP(fd,12));
- WFIFOSET(fd, WFIFOW(fd,2));
+ WFIFOB(fd,12) = flag; //1 open storage, 0 don't open
+ guild_storage_fromsql(guild_id, (struct guild_storage*)WFIFOP(fd,13));
+ WFIFOSET(fd, WFIFOW(fd,2));
return 0;
}
// guild does not exist
@@ -201,7 +205,7 @@ int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail)
int mapif_parse_LoadGuildStorage(int fd)
{
RFIFOHEAD(fd);
- mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6));
+ mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6),1);
return 0;
}
@@ -235,6 +239,134 @@ int mapif_parse_SaveGuildStorage(int fd)
return 0;
}
+#ifdef BOUND_ITEMS
+int mapif_itembound_ack(int fd, int aid, int guild_id)
+{
+ WFIFOHEAD(fd,8);
+ WFIFOW(fd,0) = 0x3856;
+ WFIFOL(fd,2) = aid;
+ WFIFOW(fd,6) = guild_id;
+ WFIFOSET(fd,8);
+ return 0;
+}
+
+//------------------------------------------------
+//Guild bound items pull for offline characters [Akinari]
+//Revised by [Mhalicot]
+//------------------------------------------------
+int mapif_parse_itembound_retrieve(int fd)
+{
+ StringBuf buf;
+ SqlStmt* stmt;
+ struct item item;
+ int j, i=0, s;
+ bool found=false;
+ struct item items[MAX_INVENTORY];
+ int char_id = RFIFOL(fd,2);
+ int aid = RFIFOL(fd,6);
+ int guild_id = RFIFOW(fd,10);
+
+ StrBuf->Init(&buf);
+ StrBuf->AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`");
+ for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, ", `card%d`", j);
+ StrBuf->Printf(&buf, " FROM `%s` WHERE `char_id`='%d'",inventory_db,char_id);
+
+ stmt = SQL->StmtMalloc(sql_handle);
+ if( SQL_ERROR == SQL->StmtPrepareStr(stmt, StrBuf->Value(&buf))
+ || SQL_ERROR == SQL->StmtExecute(stmt) )
+ {
+ Sql_ShowDebug(sql_handle);
+ SQL->StmtFree(stmt);
+ StrBuf->Destroy(&buf);
+ return 1;
+ }
+
+ SQL->StmtBindColumn(stmt, 0, SQLDT_INT, &item.id, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 1, SQLDT_SHORT, &item.nameid, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 2, SQLDT_SHORT, &item.amount, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 3, SQLDT_USHORT, &item.equip, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 4, SQLDT_CHAR, &item.identify, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 5, SQLDT_CHAR, &item.refine, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 8, SQLDT_UINT, &item.bound, 0, NULL, NULL);
+ for( j = 0; j < MAX_SLOTS; ++j )
+ SQL->StmtBindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
+
+ while( SQL_SUCCESS == SQL->StmtNextRow(stmt) ) {
+ if(item.bound == 2) {
+ memcpy(&items[i],&item,sizeof(struct item));
+ i++;
+ }
+ }
+ SQL->FreeResult(sql_handle);
+
+ if(!i) { //No items found - No need to continue
+ StrBuf->Destroy(&buf);
+ SQL->StmtFree(stmt);
+ return 0;
+ }
+
+ //First we delete the character's items
+ StrBuf->Clear(&buf);
+ StrBuf->Printf(&buf, "DELETE FROM `%s` WHERE",inventory_db);
+ for(j=0; j<i; j++) {
+ if( found )
+ StrBuf->AppendStr(&buf, " OR");
+ else
+ found = true;
+ StrBuf->Printf(&buf, " `id`=%d",items[j].id);
+ }
+
+ if( SQL_ERROR == SQL->StmtPrepareStr(stmt, StrBuf->Value(&buf))
+ || SQL_ERROR == SQL->StmtExecute(stmt) )
+ {
+ Sql_ShowDebug(sql_handle);
+ SQL->StmtFree(stmt);
+ StrBuf->Destroy(&buf);
+ return 1;
+ }
+
+ //Now let's update the guild storage with those deleted items
+ found = false;
+ StrBuf->Clear(&buf);
+ StrBuf->Printf(&buf, "INSERT INTO `%s` (`guild_id`, `nameid`, `amount`, `identify`, `refine`, `attribute`, `expire_time`, `bound`", guild_storage_db);
+ for( j = 0; j < MAX_SLOTS; ++j )
+ StrBuf->Printf(&buf, ", `card%d`", j);
+ StrBuf->AppendStr(&buf, ") VALUES ");
+
+ for( j = 0; j < i; ++j ) {
+ if( found )
+ StrBuf->AppendStr(&buf, ",");
+ else
+ found = true;
+
+ StrBuf->Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d'",
+ guild_id, items[j].nameid, items[j].amount, items[j].identify, items[j].refine, items[j].attribute, items[j].expire_time, items[j].bound);
+ for( s = 0; s < MAX_SLOTS; ++s )
+ StrBuf->Printf(&buf, ", '%d'", items[j].card[s]);
+ StrBuf->AppendStr(&buf, ")");
+ }
+
+ if( SQL_ERROR == SQL->StmtPrepareStr(stmt, StrBuf->Value(&buf))
+ || SQL_ERROR == SQL->StmtExecute(stmt) )
+ {
+ Sql_ShowDebug(sql_handle);
+ SQL->StmtFree(stmt);
+ StrBuf->Destroy(&buf);
+ return 1;
+ }
+
+ StrBuf->Destroy(&buf);
+ SQL->StmtFree(stmt);
+
+ //Finally reload storage and tell map we're done
+ mapif_load_guild_storage(fd,aid,guild_id,0);
+ mapif_itembound_ack(fd,aid,guild_id);
+ return 0;
+}
+#endif
int inter_storage_parse_frommap(int fd)
{
@@ -242,6 +374,9 @@ int inter_storage_parse_frommap(int fd)
switch(RFIFOW(fd,0)){
case 0x3018: mapif_parse_LoadGuildStorage(fd); break;
case 0x3019: mapif_parse_SaveGuildStorage(fd); break;
+#ifdef BOUND_ITEMS
+ case 0x3056: mapif_parse_itembound_retrieve(fd); break;
+#endif
default:
return 0;
}
diff --git a/src/char/inter.c b/src/char/inter.c
index 040246c31..54672faee 100644
--- a/src/char/inter.c
+++ b/src/char/inter.c
@@ -52,8 +52,8 @@ int inter_recv_packet_length[] = {
-1,10,-1,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, // 3020- Party
-1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1, // 3030-
-1, 9, 0, 0, 0, 0, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040-
- -1,-1,10,10, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3050- Auction System [Zephyrus]
- 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3060- Quest system [Kevin] [Inkfish]
+ -1,-1,10,10, 0,-1, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3050- Auction System [Zephyrus] [Mhalicot]
+ 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3060- Quest system [Kevin] [Inkfish]
-1,10, 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, -1,10, 6,-1, // 3070- Mercenary packets [Zephyrus], Elemental packets [pakpil]
48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3080-
-1,10,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3090- Homunculus packets [albator]
diff --git a/src/common/mmo.h b/src/common/mmo.h
index 5f75f35da..d5bf45adf 100644
--- a/src/common/mmo.h
+++ b/src/common/mmo.h
@@ -48,11 +48,11 @@
// 20120307 - 2012-03-07aRagexeRE+ - 0x970
#ifndef PACKETVER
- #define PACKETVER 20120418
+ #define PACKETVER 20130807
#endif
// Comment the following line if your client is NOT ragexeRE (required because of conflicting packets in ragexe vs ragexeRE).
-#define PACKETVER_RE
+//#define PACKETVER_RE
// Client support for experimental RagexeRE UI present in 2012-04-10 and 2012-04-18
#ifdef PACKETVER_RE
@@ -222,7 +222,7 @@ struct item {
char attribute;
short card[MAX_SLOTS];
unsigned int expire_time;
- char favorite;
+ char favorite, bound;
uint64 unique_id;
};
diff --git a/src/config/core.h b/src/config/core.h
index b5ad1b794..481c35af8 100644
--- a/src/config/core.h
+++ b/src/config/core.h
@@ -61,6 +61,10 @@
/// By enabling it, the system will create an unique id for each new non stackable item created
//#define NSI_UNIQUE_ID
+/// Comment to disable Guild/Party Bound item system
+/// By default, we recover/remove Guild/Party Bound items automatically
+#define BOUND_ITEMS
+
/// Uncomment to enable real-time server stats (in and out data and ram usage). [Ai4rei]
//#define SHOW_SERVER_STATS
diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index 146159c63..2900fde03 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -1081,22 +1081,29 @@ ACMD(heal)
/*==========================================
* @item command (usage: @item <name/id_of_item> <quantity>) (modified by [Yor] for pet_egg)
+ * @itembound command (usage: @itembound <name/id_of_item> <quantity> <bound type>) (revised by [Mhalicot])
*------------------------------------------*/
ACMD(item)
{
char item_name[100];
- int number = 0, item_id, flag = 0;
+ int number = 0, item_id, flag = 0, bound = 0;
struct item item_tmp;
struct item_data *item_data;
int get_count, i;
memset(item_name, '\0', sizeof(item_name));
-
- if (!message || !*message || (
- sscanf(message, "\"%99[^\"]\" %d", item_name, &number) < 1 &&
- sscanf(message, "%99s %d", item_name, &number) < 1
- )) {
- clif->message(fd, msg_txt(983)); // Please enter an item name or ID (usage: @item <item name/ID> <quantity>).
+
+ if (!strcmpi(command+1,"itembound") && (!message || !*message || (
+ sscanf(message, "\"%99[^\"]\" %d %d", item_name, &number, &bound) < 2 &&
+ sscanf(message, "%99s %d %d", item_name, &number, &bound) < 2
+ ))) {
+ clif->message(fd, msg_txt(295)); // Please enter an item name or ID (usage: @itembound <item name/ID> <quantity> <bound_type>).
+ return -1;
+ } else if (!message || !*message || (
+ sscanf(message, "\"%99[^\"]\" %d", item_name, &number) < 1 &&
+ sscanf(message, "%99s %d", item_name, &number) < 1 ))
+ {
+ clif->message(fd, msg_txt(983)); // Please enter an item name or ID (usage: @item <item name/ID> <quantity>).
return false;
}
@@ -1110,11 +1117,24 @@ ACMD(item)
return false;
}
+ if( bound < 0 || bound > 4 ) {
+ clif->message(fd, msg_txt(298)); // Invalid bound type
+ return false;
+ }
+
item_id = item_data->nameid;
get_count = number;
//Check if it's stackable.
- if (!itemdb->isstackable2(item_data))
- get_count = 1;
+ if (!itemdb->isstackable2(item_data)) {
+ if( bound && (item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) ) {
+ clif->message(fd, msg_txt(498)); // Cannot create bounded pet eggs or pet armors.
+ return false;
+ }
+ get_count = 1;
+ } else if( bound ) {
+ clif->message(fd, msg_txt(499)); // Cannot create bounded stackable items.
+ return false;
+ }
for (i = 0; i < number; i += get_count) {
// if not pet egg
@@ -1122,6 +1142,7 @@ ACMD(item)
memset(&item_tmp, 0, sizeof(item_tmp));
item_tmp.nameid = item_id;
item_tmp.identify = 1;
+ item_tmp.bound = bound;
if ((flag = pc->additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND)))
clif->additem(sd, 0, 0, flag);
@@ -1134,21 +1155,27 @@ ACMD(item)
}
/*==========================================
- *
+ * @item2 and @itembound2 command (revised by [Mhalicot])
*------------------------------------------*/
ACMD(item2)
{
struct item item_tmp;
struct item_data *item_data;
char item_name[100];
- int item_id, number = 0;
+ int item_id, number = 0, bound = 0;
int identify = 0, refine = 0, attr = 0;
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
memset(item_name, '\0', sizeof(item_name));
- if (!message || !*message || (
- sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 &&
+ if (!strcmpi(command+1,"itembound2") && (!message || !*message || (
+ sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4, &bound) < 10 &&
+ sscanf(message, "%99s %d %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4, &bound) < 10 ))) {
+ clif->message(fd, msg_txt(296)); // Please enter all parameters (usage: @itembound2 <item name/ID> <quantity>
+ clif->message(fd, msg_txt(297)); // <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4> <bound_type>).
+ return false;
+ } else if ( !message || !*message || (
+ sscanf(message, "\"%99[^\"]\" %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9 &&
sscanf(message, "%99s %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9
)) {
clif->message(fd, msg_txt(984)); // Please enter all parameters (usage: @item2 <item name/ID> <quantity>
@@ -1158,7 +1185,12 @@ ACMD(item2)
if (number <= 0)
number = 1;
-
+
+ if( bound < 0 || bound > 4 ) {
+ clif->message(fd, msg_txt(298)); // Invalid bound type
+ return -1;
+ }
+
item_id = 0;
if ((item_data = itemdb->search_name(item_name)) != NULL ||
(item_data = itemdb->exists(atoi(item_name))) != NULL)
@@ -1169,9 +1201,14 @@ ACMD(item2)
int loop, get_count, i;
loop = 1;
get_count = number;
- if (item_data->type == IT_WEAPON || item_data->type == IT_ARMOR ||
- item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) {
- loop = number;
+ if( !strcmpi(command+1,"itembound2") )
+ bound = 1;
+ if( !itemdb->isstackable2(item_data) ) {
+ if( bound && (item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) ) {
+ clif->message(fd, msg_txt(498)); // Cannot create bounded pet eggs or pet armors.
+ return false;
+ }
+ loop = number;
get_count = 1;
if (item_data->type == IT_PETEGG) {
identify = 1;
@@ -1182,6 +1219,10 @@ ACMD(item2)
if (refine > MAX_REFINE)
refine = MAX_REFINE;
} else {
+ if( bound ) {
+ clif->message(fd, msg_txt(499)); // Cannot create bounded stackable items.
+ return false;
+ }
identify = 1;
refine = attr = 0;
}
@@ -1195,6 +1236,7 @@ ACMD(item2)
item_tmp.card[1] = c2;
item_tmp.card[2] = c3;
item_tmp.card[3] = c4;
+ item_tmp.bound = bound;
if ((flag = pc->additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND)))
clif->additem(sd, 0, 0, flag);
}
@@ -9230,6 +9272,8 @@ void atcommand_basecommands(void) {
ACMD_DEF(heal),
ACMD_DEF(item),
ACMD_DEF(item2),
+ ACMD_DEF2("itembound", item),
+ ACMD_DEF2("itembound2", item2),
ACMD_DEF(itemreset),
ACMD_DEF(clearstorage),
ACMD_DEF(cleargstorage),
diff --git a/src/map/buyingstore.c b/src/map/buyingstore.c
index 2a9e6a88e..44ece49c6 100644
--- a/src/map/buyingstore.c
+++ b/src/map/buyingstore.c
@@ -290,8 +290,8 @@ void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int
return;
}
- if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc->get_group_level(sd), pc->get_group_level(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore->blankslots, sizeof(buyingstore->blankslots)) )
- {// non-tradable item
+ if( sd->status.inventory[index].expire_time || (sd->status.inventory[index].bound && !pc->can_give_bounded_items(sd)) || !itemdb_cantrade(&sd->status.inventory[index], pc->get_group_level(sd), pc->get_group_level(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore->blankslots, sizeof(buyingstore->blankslots)) )
+ {// non-tradable item
clif->buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
return;
}
diff --git a/src/map/clif.c b/src/map/clif.c
index c1e7cb1c9..99f7c87d7 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -1802,10 +1802,13 @@ void clif_selllist(struct map_session_data *sd)
if( !itemdb_cansell(&sd->status.inventory[i], pc->get_group_level(sd)) )
continue;
- if( sd->status.inventory[i].expire_time )
- continue; // Cannot Sell Rental Items
+ if( sd->status.inventory[i].expire_time || (sd->status.inventory[i].bound && !pc->can_give_bounded_items(sd)) )
+ continue; // Cannot Sell Rental Items or Account Bounded Items
+
+ if( sd->status.inventory[i].bound && !pc->can_give_bounded_items(sd))
+ continue; // Don't allow sale of bound items
- val=sd->inventory_data[i]->value_sell;
+ val=sd->inventory_data[i]->value_sell;
if( val < 0 )
continue;
WFIFOW(fd,4+c*10)=i+2;
@@ -2229,7 +2232,7 @@ void clif_additem(struct map_session_data *sd, int n, int amount, int fail) {
p.HireExpireDate = sd->status.inventory[n].expire_time;
#endif
#if PACKETVER >= 20071002
- p.bindOnEquipType = 0; // unused
+ p.bindOnEquipType = sd->status.inventory[n].bound ? 2 : 0;
#endif
}
p.result = (unsigned char)fail;
@@ -2341,7 +2344,7 @@ void clif_item_equip(short idx, struct EQUIPITEM_INFO *p, struct item *i, struct
#endif
#if PACKETVER >= 20080102
- p->bindOnEquipType = 0;
+ p->bindOnEquipType = i->bound ? 2 : 0;
#endif
#if PACKETVER >= 20100629
@@ -2378,6 +2381,7 @@ void clif_item_normal(short idx, struct NORMALITEM_INFO *p, struct item *i, stru
#if PACKETVER >= 20080102
p->HireExpireDate = i->expire_time;
+ p->bindOnEquipType = i->bound ? 2 : 0;
#endif
#if PACKETVER >= 20120925
@@ -15061,8 +15065,9 @@ void clif_parse_Auction_setitem(int fd, struct map_session_data *sd)
if( !pc->can_give_items(sd) || sd->status.inventory[idx].expire_time ||
!sd->status.inventory[idx].identify ||
- !itemdb_canauction(&sd->status.inventory[idx],pc->get_group_level(sd)) ) { // Quest Item or something else
- clif->auction_setitem(sd->fd, idx, true);
+ !itemdb_canauction(&sd->status.inventory[idx],pc->get_group_level(sd)) ||
+ (sd->status.inventory[idx].bound && !pc->can_give_bounded_items(sd)) ) { // Quest Item or something else
+ clif->auction_setitem(sd->fd, idx, true);
return;
}
@@ -15139,9 +15144,10 @@ void clif_parse_Auction_register(int fd, struct map_session_data *sd)
}
// Auction checks...
- if( sd->status.zeny < (auction.hours * battle_config.auction_feeperhour) ) {
- clif->auction_message(fd, 5); // You do not have enough zeny to pay the Auction Fee.
- return;
+ if( sd->status.inventory[sd->auction.index].bound && !pc->can_give_bounded_items(sd) ) {
+ clif->message(sd->fd, msg_txt(293));
+ clif->auction_message(fd, 2); // The auction has been canceled
+ return;
}
if( auction.buynow > battle_config.auction_maximumprice )
diff --git a/src/map/guild.c b/src/map/guild.c
index 0ae45bede..517a49cc4 100644
--- a/src/map/guild.c
+++ b/src/map/guild.c
@@ -865,6 +865,11 @@ int guild_member_withdraw(int guild_id, int account_id, int char_id, int flag, c
online_member_sd = guild->getavailablesd(g);
if(online_member_sd == NULL)
return 0; // noone online to inform
+
+#ifdef BOUND_ITEMS
+ //Guild bound item check
+ guild->retrieveitembound(char_id,account_id,guild_id);
+#endif
if(!flag)
clif->guild_leave(online_member_sd, name, mes);
@@ -899,6 +904,41 @@ int guild_member_withdraw(int guild_id, int account_id, int char_id, int flag, c
return 0;
}
+#ifdef BOUND_ITEMS
+void guild_retrieveitembound(int char_id,int aid,int guild_id)
+{
+ TBL_PC *sd = map->id2sd(aid);
+ if(sd){ //Character is online
+ int idxlist[MAX_INVENTORY];
+ int j,i;
+ j = pc->bound_chk(sd,2,idxlist);
+ if(j) {
+ struct guild_storage *gstor = gstorage->id2storage(guild_id);
+ for(i=0;i<j;i++) { //Loop the matching items, guild_storage_additem takes care of opening storage
+ if(gstor)
+ gstorage->additem(sd,gstor,&sd->status.inventory[idxlist[i]],sd->status.inventory[idxlist[i]].amount);
+ pc->delitem(sd,idxlist[i],sd->status.inventory[idxlist[i]].amount,0,4,LOG_TYPE_GSTORAGE);
+ }
+ gstorage->close(sd); //Close and save the storage
+ }
+ }
+ else { //Character is offline, ask char server to do the job
+ struct guild_storage *gstor = gstorage->id2storage2(guild_id);
+ if(gstor && gstor->storage_status == 1) { //Someone is in guild storage, close them
+ struct s_mapiterator* iter = mapit_getallusers();
+ for( sd = (TBL_PC*)mapit->first(iter); mapit->exists(iter); sd = (TBL_PC*)mapit->next(iter) ) {
+ if(sd->status.guild_id == guild_id && sd->state.storage_flag == 2) {
+ gstorage->close(sd);
+ break;
+ }
+ }
+ mapit->free(iter);
+ }
+ intif->itembound_req(char_id,aid,guild_id);
+ }
+}
+#endif
+
int guild_send_memberinfoshort(struct map_session_data *sd,int online)
{ // cleaned up [LuzZza]
struct guild *g;
@@ -1813,6 +1853,11 @@ int guild_break(struct map_session_data *sd,char *name) {
struct guild *g;
struct unit_data *ud;
int i;
+
+#ifdef BOUND_ITEMS
+ int j;
+ int idxlist[MAX_INVENTORY];
+#endif
nullpo_ret(sd);
@@ -1855,6 +1900,13 @@ int guild_break(struct map_session_data *sd,char *name) {
skill->del_unitgroup(groups[i],ALC_MARK);
}
}
+
+#ifdef BOUND_ITEMS
+ //Guild bound item check - Removes the bound flag
+ j = pc->bound_chk(sd,2,idxlist);
+ for(i=0;i<j;i++)
+ sd->status.inventory[idxlist[i]].bound = 0;
+#endif
intif->guild_break(g->guild_id);
return 1;
diff --git a/src/map/guild.h b/src/map/guild.h
index 348a6c7e4..57148867a 100644
--- a/src/map/guild.h
+++ b/src/map/guild.h
@@ -147,6 +147,10 @@ struct guild_interface {
void (*flags_clear) (void);
/* guild aura */
void (*aura_refresh) (struct map_session_data *sd, uint16 skill_id, uint16 skill_lv);
+ /* item bound [Mhalicot]*/
+#ifdef BOUND_ITEMS
+ void (*retrieveitembound) (int char_id,int aid,int guild_id);
+#endif
/* */
int (*payexp_timer) (int tid, int64 tick, int id, intptr_t data);
TBL_PC* (*sd_check) (int guild_id, int account_id, int char_id);
diff --git a/src/map/intif.c b/src/map/intif.c
index f31ab0f5a..b8b16a356 100644
--- a/src/map/intif.c
+++ b/src/map/intif.c
@@ -982,15 +982,19 @@ void intif_parse_LoadGuildStorage(int fd)
{
struct guild_storage *gstor;
struct map_session_data *sd;
- int guild_id;
+ int guild_id, flag;
guild_id = RFIFOL(fd,8);
+ flag = RFIFOL(fd,12);
if(guild_id <= 0)
return;
sd=map->id2sd( RFIFOL(fd,4) );
- if(sd==NULL){
- ShowError("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4));
- return;
+ if( flag ){ //If flag != 0, we attach a player and open the storage
+ if(sd==NULL){
+ ShowError("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4));
+ return;
+ }
+
}
gstor=gstorage->id2storage(guild_id);
if(!gstor) {
@@ -998,20 +1002,21 @@ void intif_parse_LoadGuildStorage(int fd)
return;
}
if (gstor->storage_status == 1) { // Already open.. lets ignore this update
- ShowWarning("intif_parse_LoadGuildStorage: storage received for a client already open (User %d:%d)\n", sd->status.account_id, sd->status.char_id);
- return;
+ ShowWarning("intif_parse_LoadGuildStorage: storage received for a client already open (User %d:%d)\n", flag?sd->status.account_id:1, flag?sd->status.char_id:1);
+ return;
}
if (gstor->dirty) { // Already have storage, and it has been modified and not saved yet! Exploit! [Skotlex]
- ShowWarning("intif_parse_LoadGuildStorage: received storage for an already modified non-saved storage! (User %d:%d)\n", sd->status.account_id, sd->status.char_id);
- return;
+ ShowWarning("intif_parse_LoadGuildStorage: received storage for an already modified non-saved storage! (User %d:%d)\n", flag?sd->status.account_id:1, flag?sd->status.char_id:1);
+ return;
}
- if( RFIFOW(fd,2)-12 != sizeof(struct guild_storage) ){
- ShowError("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-12 , sizeof(struct guild_storage));
- gstor->storage_status = 0;
+ if( RFIFOW(fd,2)-13 != sizeof(struct guild_storage) ){
+ ShowError("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-13 , sizeof(struct guild_storage));
+ gstor->storage_status = 0;
return;
}
- memcpy(gstor,RFIFOP(fd,12),sizeof(struct guild_storage));
+ memcpy(gstor,RFIFOP(fd,13),sizeof(struct guild_storage));
+ if( flag )
gstorage->open(sd);
}
@@ -2005,7 +2010,31 @@ void intif_parse_MessageToFD(int fd) {
return;
}
+/*==========================================
+ * Item Bound System [Xantara][Mhalicot]
+ *------------------------------------------*/
+#ifdef BOUND_ITEMS
+void intif_itembound_req(int char_id,int aid,int guild_id) {
+ struct guild_storage *gstor = gstorage->id2storage2(guild_id);
+ WFIFOHEAD(inter_fd,12);
+ WFIFOW(inter_fd,0) = 0x3056;
+ WFIFOL(inter_fd,2) = char_id;
+ WFIFOL(inter_fd,6) = aid;
+ WFIFOW(inter_fd,10) = guild_id;
+ WFIFOSET(inter_fd,12);
+ if(gstor)
+ gstor->lock = 1; //Lock for retrieval process
+}
+
+//3856
+void intif_parse_Itembound_ack(int fd) {
+ struct guild_storage *gstor;
+ int guild_id = RFIFOW(fd,6);
+ gstor = gstorage->id2storage2(guild_id);
+ if(gstor) gstor->lock = 0; //Unlock now that operation is completed
+}
+#endif
//-----------------------------------------------------------------
// Communication from the inter server
// Return a 0 (false) if there were any errors.
@@ -2088,7 +2117,10 @@ int intif_parse(int fd)
case 0x3853: intif->pAuctionClose(fd); break;
case 0x3854: intif->pAuctionMessage(fd); break;
case 0x3855: intif->pAuctionBid(fd); break;
-
+ //Bound items
+#ifdef BOUND_ITEMS
+ case 0x3856: intif->pItembound_ack(fd); break;
+#endif
// Mercenary System
case 0x3870: intif->pMercenaryReceived(fd); break;
case 0x3871: intif->pMercenaryDeleted(fd); break;
@@ -2127,8 +2159,8 @@ void intif_defaults(void) {
39,-1,15,15, 14,19, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3820
10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, //0x3830
-1, 0, 0,14, 0, 0, 0, 0, -1,74,-1,11, 11,-1, 0, 0, //0x3840
- -1,-1, 7, 7, 7,11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus]
- -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3860 Quests [Kevin] [Inkfish]
+ -1,-1, 7, 7, 7,11, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] itembound[Akinari]
+ -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3860 Quests [Kevin] [Inkfish]
-1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, 3, 0, //0x3870 Mercenaries [Zephyrus] / Elemental [pakpil]
11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3880
-1,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3890 Homunculus [albator]
@@ -2263,6 +2295,7 @@ void intif_defaults(void) {
intif->pAuctionClose = intif_parse_AuctionClose;
intif->pAuctionMessage = intif_parse_AuctionMessage;
intif->pAuctionBid = intif_parse_AuctionBid;
+ intif->pItembound_ack = intif_parse_Itembound_ack;
intif->pMercenaryReceived = intif_parse_MercenaryReceived;
intif->pMercenaryDeleted = intif_parse_MercenaryDeleted;
intif->pMercenarySaved = intif_parse_MercenarySaved;
diff --git a/src/map/intif.h b/src/map/intif.h
index 768e735de..577d58923 100644
--- a/src/map/intif.h
+++ b/src/map/intif.h
@@ -75,6 +75,9 @@ struct intif_interface {
int (*guild_emblem) (int guild_id, int len, const char *data);
int (*guild_castle_dataload) (int num, int *castle_ids);
int (*guild_castle_datasave) (int castle_id, int index, int value);
+#ifdef BOUND_ITEMS
+ void (*itembound_req) (int char_id, int aid, int guild_id);
+#endif
int (*request_petdata) (int account_id, int char_id, int pet_id);
int (*save_petdata) (int account_id, struct s_pet *p);
int (*delete_petdata) (int pet_id);
@@ -161,6 +164,7 @@ struct intif_interface {
void (*pAuctionClose) (int fd);
void (*pAuctionMessage) (int fd);
void (*pAuctionBid) (int fd);
+ void (*pItembound_ack) (int fd);
void (*pMercenaryReceived) (int fd);
void (*pMercenaryDeleted) (int fd);
void (*pMercenarySaved) (int fd);
diff --git a/src/map/mail.c b/src/map/mail.c
index 2378cbe2a..6b1537d87 100644
--- a/src/map/mail.c
+++ b/src/map/mail.c
@@ -82,8 +82,9 @@ unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount) {
if( amount < 0 || amount > sd->status.inventory[idx].amount )
return 1;
if( !pc->can_give_items(sd) || sd->status.inventory[idx].expire_time ||
- !itemdb_canmail(&sd->status.inventory[idx],pc->get_group_level(sd)) )
- return 1;
+ !itemdb_canmail(&sd->status.inventory[idx],pc->get_group_level(sd)) ||
+ (sd->status.inventory[idx].bound && !pc->can_give_bounded_items(sd)) )
+ return 1;
sd->mail.index = idx;
sd->mail.nameid = sd->status.inventory[idx].nameid;
diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h
index 813aebee0..e6f68ea4f 100644
--- a/src/map/packets_struct.h
+++ b/src/map/packets_struct.h
@@ -212,6 +212,7 @@ struct NORMALITEM_INFO {
#endif
#if PACKETVER >= 20080102
int HireExpireDate;
+ unsigned short bindOnEquipType;
#endif
#if PACKETVER >= 20120925
struct {
diff --git a/src/map/party.c b/src/map/party.c
index ab05c23f7..69b343fb7 100644
--- a/src/map/party.c
+++ b/src/map/party.c
@@ -547,7 +547,14 @@ int party_member_withdraw(int party_id, int account_id, int char_id)
}
if( sd && sd->status.party_id == party_id && sd->status.char_id == char_id ) {
- sd->status.party_id = 0;
+#ifdef BOUND_ITEMS
+ int idxlist[MAX_INVENTORY]; //or malloc to reduce consumtion
+ int j,i;
+ j = pc->bound_chk(sd,3,idxlist);
+ for(i=0;i<j;i++)
+ pc->delitem(sd,idxlist[i],sd->status.inventory[idxlist[i]].amount,0,1,LOG_TYPE_OTHER);
+#endif
+ sd->status.party_id = 0;
clif->charnameupdate(sd); //Update name display [Skotlex]
//TODO: hp bars should be cleared too
if( p->instances )
diff --git a/src/map/pc.c b/src/map/pc.c
index 35d883b6f..ba445b4f4 100644
--- a/src/map/pc.c
+++ b/src/map/pc.c
@@ -568,6 +568,14 @@ bool pc_can_give_items(struct map_session_data *sd)
return pc->has_permission(sd, PC_PERM_TRADE);
}
+/**
+ * Determines if player can give / drop / trade / vend bounded items
+ */
+bool pc_can_give_bounded_items(struct map_session_data *sd)
+{
+ return pc->has_permission(sd, PC_PERM_TRADE_BOUNDED);
+}
+
/*==========================================
* prepares character for saving.
*------------------------------------------*/
@@ -991,6 +999,10 @@ int pc_isequip(struct map_session_data *sd,int n)
*------------------------------------------*/
bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_time, int group_id, struct mmo_charstatus *st, bool changing_mapservers) {
int i;
+#ifdef BOUND_ITEMS
+ int j;
+ int idxlist[MAX_INVENTORY];
+#endif
int64 tick = timer->gettick();
uint32 ip = session[sd->fd]->client_addr;
@@ -1190,7 +1202,15 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim
* Check if player have any item cooldowns on
**/
pc->itemcd_do(sd,true);
-
+
+#ifdef BOUND_ITEMS
+ // Party bound item check
+ if(sd->status.party_id == 0 && (j = pc->bound_chk(sd,3,idxlist))) { // Party was deleted while character offline
+ for(i=0;i<j;i++)
+ pc->delitem(sd,idxlist[i],sd->status.inventory[idxlist[i]].amount,0,1,LOG_TYPE_OTHER);
+ }
+#endif
+
/* [Ind/Hercules] */
sd->sc_display = NULL;
sd->sc_display_count = 0;
@@ -3948,8 +3968,8 @@ int pc_additem(struct map_session_data *sd,struct item *item_data,int amount,e_l
{ // Stackable | Non Rental
for( i = 0; i < MAX_INVENTORY; i++ )
{
- if( sd->status.inventory[i].nameid == item_data->nameid && memcmp(&sd->status.inventory[i].card, &item_data->card, sizeof(item_data->card)) == 0 )
- {
+ if( sd->status.inventory[i].nameid == item_data->nameid && sd->status.inventory[i].bound == item_data->bound && memcmp(&sd->status.inventory[i].card, &item_data->card, sizeof(item_data->card)) == 0 )
+ {
if( amount > MAX_AMOUNT - sd->status.inventory[i].amount || ( data->stack.inventory && amount > data->stack.amount - sd->status.inventory[i].amount ) )
return 5;
sd->status.inventory[i].amount += amount;
@@ -4500,8 +4520,8 @@ int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amoun
return 1;
}
- if( !itemdb_cancartstore(item_data, pc->get_group_level(sd)) )
- { // Check item trade restrictions [Skotlex]
+ if( !itemdb_cancartstore(item_data, pc->get_group_level(sd)) || (item_data->bound > 1 && !pc->can_give_bounded_items(sd)))
+ { // Check item trade restrictions [Skotlex]
clif->message (sd->fd, msg_txt(264));
return 1;/* TODO: there is no official response to this? */
}
@@ -4513,8 +4533,8 @@ int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amoun
if( itemdb->isstackable2(data) && !item_data->expire_time )
{
ARR_FIND( 0, MAX_CART, i,
- sd->status.cart[i].nameid == item_data->nameid &&
- sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] &&
+ sd->status.cart[i].nameid == item_data->nameid && sd->status.cart[i].bound == item_data->bound &&
+ sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] &&
sd->status.cart[i].card[2] == item_data->card[2] && sd->status.cart[i].card[3] == item_data->card[3] );
};
@@ -4647,7 +4667,25 @@ int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount)
return flag;
}
-
+ /*==========================================
+ * Bound Item Check
+ * Type:
+ * 1 Account Bound
+ * 2 Guild Bound
+ * 3 Party Bound
+ * 4 Character Bound
+ *------------------------------------------*/
+int pc_bound_chk(TBL_PC *sd,int type,int *idxlist)
+{
+ int i=0, j=0;
+ for(i=0;i<MAX_INVENTORY;i++){
+ if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount > 0 && sd->status.inventory[i].bound == type) {
+ idxlist[j] = i;
+ j++;
+ }
+ }
+ return j;
+}
/*==========================================
* Display item stolen msg to player sd
*------------------------------------------*/
@@ -7965,8 +8003,8 @@ int pc_setmadogear(TBL_PC* sd, int flag)
*------------------------------------------*/
int pc_candrop(struct map_session_data *sd, struct item *item)
{
- if( item && item->expire_time )
- return 0;
+ if( item && (item->expire_time || (item->bound && !pc->can_give_bounded_items(sd))) )
+ return 0;
if( !pc->can_give_items(sd) ) //check if this GM level can drop items
return 0;
return (itemdb_isdropable(item, pc->get_group_level(sd)));
@@ -10293,6 +10331,7 @@ void pc_defaults(void) {
pc->class2idx = pc_class2idx;
pc->get_group_level = pc_get_group_level;
pc->can_give_items = pc_can_give_items;
+ pc->can_give_bounded_items = pc_can_give_bounded_items;
pc->can_use_command = pc_can_use_command;
pc->has_permission = pc_has_permission;
diff --git a/src/map/pc.h b/src/map/pc.h
index fc37d0ef2..d517d8fcf 100644
--- a/src/map/pc.h
+++ b/src/map/pc.h
@@ -749,7 +749,8 @@ struct pc_interface {
int (*get_group_level) (struct map_session_data *sd);
//int (*getrefinebonus) (int lv,int type); FIXME: This function does not exist, nor it is ever called
bool (*can_give_items) (struct map_session_data *sd);
-
+ bool (*can_give_bounded_items) (struct map_session_data *sd);
+
bool (*can_use_command) (struct map_session_data *sd, const char *command);
bool (*has_permission) (struct map_session_data *sd, enum e_pc_permission permission);
int (*set_group) (struct map_session_data *sd, int group_id);
@@ -788,6 +789,10 @@ struct pc_interface {
int (*additem) (struct map_session_data *sd,struct item *item_data,int amount,e_log_pick_type log_type);
int (*getzeny) (struct map_session_data *sd,int zeny, enum e_log_pick_type type, struct map_session_data *tsd);
int (*delitem) (struct map_session_data *sd,int n,int amount,int type, short reason, e_log_pick_type log_type);
+
+ //Bound items
+ int (*bound_chk) (TBL_PC *sd,int type,int *idxlist);
+
// Special Shop System
int (*paycash) (struct map_session_data *sd, int price, int points);
int (*getcash) (struct map_session_data *sd, int cash, int points);
diff --git a/src/map/pc_groups.c b/src/map/pc_groups.c
index be02b5f15..41bc19cba 100644
--- a/src/map/pc_groups.c
+++ b/src/map/pc_groups.c
@@ -55,6 +55,7 @@ const struct pc_permission_name_table pc_g_permission_name[NUM_PC_PERM] = {
{ "disable_pvp", PC_PERM_DISABLE_PVP },
{ "disable_commands_when_dead", PC_PERM_DISABLE_CMD_DEAD },
{ "hchsys_admin", PC_PERM_HCHSYS_ADMIN },
+ { "can_trade_bounded", PC_PERM_TRADE_BOUNDED },
};
static DBMap* pc_group_db; // id -> GroupSettings
diff --git a/src/map/pc_groups.h b/src/map/pc_groups.h
index 8f350c2b6..63e7acc51 100644
--- a/src/map/pc_groups.h
+++ b/src/map/pc_groups.h
@@ -30,6 +30,7 @@ enum e_pc_permission {
PC_PERM_DISABLE_PVP = 0x080000, // #20
PC_PERM_DISABLE_CMD_DEAD = 0x100000,
PC_PERM_HCHSYS_ADMIN = 0x200000,
+ PC_PERM_TRADE_BOUNDED = 0x400000,
};
/// Total number of PC permissions (without PC_PERM_NONE).
@@ -37,7 +38,7 @@ enum e_pc_permission {
/// so it's possible to apply sizeof to it [C-FAQ 1.24]
/// Whenever adding new permission: 1. add enum entry above, 2. add entry into
/// pc_g_permission_name (in pc.c), 3. increase NUM_PC_PERM below by 1.
-#define NUM_PC_PERM 22
+#define NUM_PC_PERM 23
struct pc_permission_name_table {
const char *name;
diff --git a/src/map/script.c b/src/map/script.c
index d51f27ce9..174d12316 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -5946,6 +5946,7 @@ BUILDIN(rentitem)
it.nameid = nameid;
it.identify = 1;
it.expire_time = (unsigned int)(time(NULL) + seconds);
+ it.bound = 0;
if( (flag = pc->additem(sd, &it, 1, LOG_TYPE_SCRIPT)) )
{
@@ -10850,6 +10851,7 @@ BUILDIN(successremovecards) {
item_tmp.refine = sd->status.inventory[i].refine;
item_tmp.attribute = sd->status.inventory[i].attribute;
item_tmp.expire_time = sd->status.inventory[i].expire_time;
+ item_tmp.bound = sd->status.inventory[i].bound;
for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++)
item_tmp.card[j]=sd->status.inventory[i].card[j];
@@ -10923,7 +10925,8 @@ BUILDIN(failedremovecards) {
item_tmp.refine = sd->status.inventory[i].refine;
item_tmp.attribute = sd->status.inventory[i].attribute;
item_tmp.expire_time = sd->status.inventory[i].expire_time;
-
+ item_tmp.bound = sd->status.inventory[i].bound;
+
for (j = sd->inventory_data[i]->slot; j < MAX_SLOTS; j++)
item_tmp.card[j]=sd->status.inventory[i].card[j];
@@ -11573,7 +11576,8 @@ BUILDIN(getinventorylist)
pc->setreg(sd,reference_uid(script->add_str(card_var), j),sd->status.inventory[i].card[k]);
}
pc->setreg(sd,reference_uid(script->add_str("@inventorylist_expire"), j),sd->status.inventory[i].expire_time);
- j++;
+ pc->setreg(sd,reference_uid(script->add_str("@inventorylist_bound"), j),sd->status.inventory[i].bound);
+ j++;
}
}
pc->setreg(sd,script->add_str("@inventorylist_count"),j);
@@ -17529,6 +17533,219 @@ BUILDIN(bg_join_team) {
return true;
}
+
+/*==============[Mhalicot]==================
+ * getitembound <item id>,<amount>,<type>{,<account ID>};
+ * getitembound "<item id>",<amount>,<type>{,<account ID>};
+ * Type:
+ * 1 - Account Bound
+ * 2 - Guild Bound
+ * 3 - Party Bound
+ * 4 - Character Bound
+ *------------------------------------------*/
+BUILDIN(getitembound)
+{
+ int nameid, amount, i, flag;
+ struct item it;
+ struct script_data *data;
+ char bound = script_getnum(st,4);
+ TBL_PC *sd;
+
+ data = script_getdata(st,2);
+ get_val(st,data);
+ if( data_isstring(data) ) { // "<item name>"
+ const char *name = script->conv_str(st,data);
+ struct item_data *item_data = itemdb->search_name(name);
+ if( item_data == NULL ) {
+ ShowError("buildin_getitembound: Nonexistant item %s requested.\n", name);
+ return 1; //No item created.
+ }
+ nameid = item_data->nameid;
+ } else if( data_isint(data) ) { // <item id>
+ nameid = script->conv_num(st,data);
+ if( nameid <= 0 || !itemdb->exists(nameid) ) {
+ ShowError("buildin_getitembound: Nonexistant item %d requested.\n", nameid);
+ return 1; //No item created.
+ }
+ } else {
+ ShowError("buildin_getitembound: invalid data type for argument #1 (%d).", data->type);
+ return 1;
+ }
+
+ if( itemdb->isstackable(nameid) || itemdb_type(nameid) == IT_PETEGG ) {
+ ShowError("buildin_getitembound: invalid item type. Bound only work for non stackeable items (Item %d).", nameid);
+ return 1;
+ }
+
+ if( (amount = script_getnum(st,3)) <= 0)
+ return 0; //return if amount <=0, skip the useless iteration
+
+ memset(&it,0,sizeof(it));
+ it.nameid = nameid;
+ it.identify = 1;
+ it.bound = bound;
+
+ if( bound < 1 || bound > 4) { //Not a correct bound type
+ ShowError("script_getitembound: Not a correct bound type! Type=%d\n",bound);
+ return 1;
+ }
+
+ if( script_hasdata(st,5) )
+ sd=map->id2sd(script_getnum(st,5)); // Account ID
+ else
+ sd=script->rid2sd(st); // Attached player
+
+ if( sd == NULL ) // no target
+ return 0;
+
+ for( i = 0; i < amount; i++ ) {
+ if( (flag = pc->additem(sd, &it, 1, LOG_TYPE_SCRIPT)) ) {
+ clif->additem(sd, 0, 0, flag);
+ if( pc->candrop(sd,&it) )
+ map->addflooritem(&it,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0);
+ }
+ }
+
+ return 0;
+}
+
+/*==============[Mhalicot]==================
+ * getitembound2 <item id>,<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>,<bound type>;
+ * getitembound2 "<item name>",<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>,<bound type>;
+ *------------------------------------------*/
+BUILDIN(getitembound2)
+{
+ int nameid,amount,get_count,i,flag = 0;
+ int iden,ref,attr,c1,c2,c3,c4;
+ char bound=0;
+ struct item_data *item_data;
+ struct item item_tmp;
+ TBL_PC *sd;
+ struct script_data *data;
+
+ bound = script_getnum(st,11);
+ if( bound < 1 || bound > 4) { //Not a correct bound type
+ ShowError("script_getitembound2: Not a correct bound type! Type=%d\n",bound);
+ return 1;
+ }
+ if( script_hasdata(st,12) )
+ sd=map->id2sd(script_getnum(st,12));
+ else
+ sd=script->rid2sd(st); // Attached player
+
+ if( sd == NULL ) // no target
+ return true;
+
+ data=script_getdata(st,2);
+ script->get_val(st,data);
+ if( data_isstring(data) ){
+ const char *name=script->conv_str(st,data);
+ struct item_data *item_data = itemdb->search_name(name);
+ if( item_data )
+ nameid=item_data->nameid;
+ else
+ nameid=UNKNOWN_ITEM_ID;
+ }else
+ nameid=script->conv_num(st,data);
+
+ amount=script_getnum(st,3);
+ iden=script_getnum(st,4);
+ ref=script_getnum(st,5);
+ attr=script_getnum(st,6);
+ c1=(short)script_getnum(st,7);
+ c2=(short)script_getnum(st,8);
+ c3=(short)script_getnum(st,9);
+ c4=(short)script_getnum(st,10);
+
+ if(nameid<0) { // Invalide nameid
+ nameid = -nameid;
+ flag = 1;
+ }
+
+ if(nameid > 0) {
+ memset(&item_tmp,0,sizeof(item_tmp));
+ item_data=itemdb->exists(nameid);
+ if (item_data == NULL)
+ return -1;
+ if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR){
+ if(ref > MAX_REFINE) ref = MAX_REFINE;
+ }
+ else if(item_data->type==IT_PETEGG) {
+ iden = 1;
+ ref = 0;
+ }
+ else {
+ iden = 1;
+ ref = attr = 0;
+ }
+
+ item_tmp.nameid=nameid;
+ if(!flag)
+ item_tmp.identify=iden;
+ else if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR)
+ item_tmp.identify=0;
+ item_tmp.refine=ref;
+ item_tmp.attribute=attr;
+ item_tmp.card[0]=(short)c1;
+ item_tmp.card[1]=(short)c2;
+ item_tmp.card[2]=(short)c3;
+ item_tmp.card[3]=(short)c4;
+ item_tmp.bound=bound;
+
+ //Check if it's stackable.
+ if (!itemdb->isstackable(nameid))
+ get_count = 1;
+ else
+ get_count = amount;
+
+ for (i = 0; i < amount; i += get_count) {
+ // if not pet egg
+ if (!pet->create_egg(sd, nameid)) {
+ if ((flag = pc->additem(sd, &item_tmp, get_count, LOG_TYPE_SCRIPT))) {
+ clif->additem(sd, 0, 0, flag);
+ if( pc->candrop(sd,&item_tmp) )
+ map->addflooritem(&item_tmp,get_count,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+/*==============[Mhalicot]==================
+ * countbound {<type>};
+ * Creates an array of bounded item IDs
+ * Returns amount of items found
+ * Type:
+ * 1 - Account Bound
+ * 2 - Guild Bound
+ * 3 - Party Bound
+ *------------------------------------------*/
+BUILDIN(countbound)
+{
+ int i, type, j=0, k=0;
+ TBL_PC *sd;
+
+ if( (sd = script->rid2sd(st)) == NULL )
+ return false;
+
+ type = script_hasdata(st,2)?script_getnum(st,2):0;
+
+ for(i=0;i<MAX_INVENTORY;i++){
+ if(sd->status.inventory[i].nameid > 0 && (
+ (!type && sd->status.inventory[i].bound > 0) ||
+ (type && sd->status.inventory[i].bound == type)
+ )) {
+ pc->setreg(sd,reference_uid(script->add_str("@bound_items"), k),sd->status.inventory[i].nameid);
+ k++;
+ j += sd->status.inventory[i].amount;
+ }
+ }
+
+ script_pushint(st,j);
+ return 0;
+}
+
/* bg_match_over( arena_name {, optional canceled } ) */
/* returns 0 when successful, 1 otherwise */
BUILDIN(bg_match_over) {
@@ -18109,7 +18326,13 @@ void script_parse_builtin(void) {
BUILDIN_DEF(bindatcmd, "ss???"),
BUILDIN_DEF(unbindatcmd, "s"),
BUILDIN_DEF(useatcmd, "s"),
-
+ /**
+ * Item bound [Mhalicot\Hercules]
+ **/
+ BUILDIN_DEF(getitembound,"vii?"),
+ BUILDIN_DEF(getitembound2,"viiiiiiiii?"),
+ BUILDIN_DEF(countbound, "?"),
+
//Quest Log System [Inkfish]
BUILDIN_DEF(questinfo, "ii??"),
BUILDIN_DEF(setquest, "i"),
diff --git a/src/map/storage.c b/src/map/storage.c
index cc1100d28..df406257d 100644
--- a/src/map/storage.c
+++ b/src/map/storage.c
@@ -107,7 +107,8 @@ int compare_item(struct item *a, struct item *b)
a->identify == b->identify &&
a->refine == b->refine &&
a->attribute == b->attribute &&
- a->expire_time == b->expire_time )
+ a->expire_time == b->expire_time &&
+ a->bound == b->bound )
{
int i;
for (i = 0; i < MAX_SLOTS && (a->card[i] == b->card[i]); i++);
@@ -140,6 +141,11 @@ int storage_additem(struct map_session_data* sd, struct item* item_data, int amo
return 1;
}
+ if( (item_data->bound > 1) && !pc->can_give_bounded_items(sd) ) {
+ clif->message(sd->fd, msg_txt(294));
+ return 1;
+ }
+
if( itemdb->isstackable2(data) )
{//Stackable
for( i = 0; i < MAX_STORAGE; i++ )
@@ -429,12 +435,17 @@ int guild_storage_additem(struct map_session_data* sd, struct guild_storage* sto
return 1;
}
- if( !itemdb_canguildstore(item_data, pc->get_group_level(sd)) || item_data->expire_time )
- { //Check if item is storable. [Skotlex]
+ if( !itemdb_canguildstore(item_data, pc->get_group_level(sd)) || item_data->expire_time || (item_data->bound && !pc->can_give_bounded_items(sd)) )
+ { //Check if item is storable. [Skotlex]
clif->message (sd->fd, msg_txt(264));
return 1;
}
+ if( (item_data->bound == 1 || item_data->bound > 2) && !pc->can_give_bounded_items(sd) ) {
+ clif->message(sd->fd, msg_txt(294));
+ return 1;
+ }
+
if(itemdb->isstackable2(data)){ //Stackable
for(i=0;i<MAX_GUILD_STORAGE;i++){
if(compare_item(&stor->items[i], item_data)) {
diff --git a/src/map/trade.c b/src/map/trade.c
index 8dd30371b..7085fdda3 100644
--- a/src/map/trade.c
+++ b/src/map/trade.c
@@ -365,6 +365,12 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount)
return;
}
+ if( ((item->bound == 1 || item->bound > 2) || (item->bound == 2 && sd->status.guild_id != target_sd->status.guild_id)) && !pc->can_give_bounded_items(sd) ) { // Item Bound
+ clif->message(sd->fd, msg_txt(293));
+ clif->tradeitemok(sd, index+2, 1);
+ return;
+ }
+
//Locate a trade position
ARR_FIND( 0, 10, trade_i, sd->deal.item[trade_i].index == index || sd->deal.item[trade_i].amount == 0 );
if( trade_i == 10 ) //No space left
diff --git a/src/map/vending.c b/src/map/vending.c
index 7d6d02cfb..be3826754 100644
--- a/src/map/vending.c
+++ b/src/map/vending.c
@@ -257,7 +257,8 @@ void vending_openvending(struct map_session_data* sd, const char* message, const
|| !sd->status.cart[index].identify // unidentified item
|| sd->status.cart[index].attribute == 1 // broken item
|| sd->status.cart[index].expire_time // It should not be in the cart but just in case
- || !itemdb_cantrade(&sd->status.cart[index], pc->get_group_level(sd), pc->get_group_level(sd)) ) // untradeable item
+ || (sd->status.cart[index].bound && !pc->can_give_bounded_items(sd)) // can't trade account bound items and has no permission
+ || !itemdb_cantrade(&sd->status.cart[index], pc->get_group_level(sd), pc->get_group_level(sd)) ) // untradeable item
continue;
sd->vending[i].index = index;