Introducing the Account/Guild/Party Bounded Items System - Suggestion tid:70372

- Thanks for Xantara for providing initial diff as well as Lighta for guild bound help
- Adds script commands 'getitembound', 'getitembound2', and 'countbound'
- Adds at commands @itembound and @itembound2
- Adds permission pc_can_traded_bounded
- Documentation updated

Don't forget to run the SQL upgrade file!

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@17351 54d463be-8e91-2dee-dedb-b68131a5f0ec
This commit is contained in:
akinari1087 2013-06-08 22:47:10 +00:00
parent e8dcdf9ecb
commit 4742adac12
26 changed files with 518 additions and 81 deletions

View File

@ -310,6 +310,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
// Guild Castles Number
// --------------------
//299: ?? Castles

View File

@ -644,6 +644,22 @@ 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
---------------------------------------
@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
---------------------------------------
@produce <equip name/ID> <element> <# of Very's>
Creates a weapon with the given parameters.

View File

@ -172,3 +172,9 @@ Allows player to use the client command /check (displays character status).
Allows player to use the client command /changemaptype.
---------------------------------------
*can_trade_bounded
Allows player to do normal item actions (sell, trade, store, vend, auction, mail) with bounded items.
---------------------------------------

View File

@ -2687,6 +2687,7 @@ recreate these items perfectly if they are destroyed. Here's what you get:
@inventorylist_card4[] if the character owns an item made by a specific
craftsman.
@inventorylist_expire[] - expire time (Unix time stamp). 0 means never expires.
@inventorylist_bound[] - whether it is bound to the character
@inventorylist_count - the number of items in these lists.
This could be handy to save/restore a character's inventory, since no other
@ -4248,6 +4249,46 @@ command, creating a pet which is the same, but simultaneously exists in 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 will give an amount of specified items to the invoking character.
If an optional account ID is specified, and the target character is currently
online, items will be created in their inventory instead. If they are not
online, nothing will happen.
It works essentially the same as 'getitem', except that items created using
this command will bound the item to the player's account. Depending on the bound type issued,
the character may not be able to trade or store these items, however all bounded types
cannot be sold, vended, auctioned, or mailed.
Bound Types:
1 - Account Bound
2 - Guild Bound
3 - Party Bound
---------------------------------------
*getitembound2 <item id>,<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>,<bound_type>{,<account ID>};
*getitembound2 "<item name>",<amount>,<identify>,<refine>,<attribute>,<card1>,<card2>,<card3>,<card4>,<bound_type>{,<account ID>};
This command will give an amount of specified items to the invoking character.
If an optional account ID is specified, and the target character is currently
online, items will be created in their inventory instead. If they are not
online, nothing will happen.
It works essentially the same as 'getitem2', except that items created using
this command will bound the item to the player's account. Depending on the bound type issued,
the character may not be able to trade or store these items, however all bounded types
cannot be sold, vended, auctioned, or mailed.
Bound Types:
1 - Account Bound
2 - Guild Bound
3 - Party Bound
---------------------------------------
*getnameditem <item id>,<character name|character ID>;
@ -4389,6 +4430,30 @@ Check 'getitem2' to understand the arguments of the function.
---------------------------------------
*countbound({<bound_type>})
This function will return a number of bounded items on the character.
Optionally, you may count a specific type of bounded item on the character.
countbound will also build an array of items in the form of @bound_items.
Using countbound without a type counts all types of bounded items.
Available types are:
1 - Account Bound
2 - Guild Bound
3 - Party Bound
Example:
mes "[Bound Counter]";
mes "I see you currently have "+countbound+" bounded items.";
next;
mes "The list of bounded items include:";
for(.@i = 0; .@i<getarraysize(@bound_items); .@i++)
mes getitemname(@bound_items[.@i]);
close;
---------------------------------------
*groupranditem <group id>;
Returns the item_id of a random item picked from the group specified. The

View File

@ -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(3) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `char_id` (`char_id`)
@ -340,6 +341,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(3) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `guild_id` (`guild_id`)
@ -404,6 +406,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(3) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `char_id` (`char_id`)
@ -665,6 +668,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(3) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)

View File

@ -0,0 +1,4 @@
ALTER TABLE `inventory` ADD COLUMN `bound` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `favorite`;
ALTER TABLE `cart_inventory` ADD COLUMN `bound` TINYINT(3) UNSIGNED NOT NULL default '0' AFTER `expire_time`;
ALTER TABLE `storage` ADD COLUMN `bound` TINYINT(3) 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`;

View File

@ -775,7 +775,7 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
// it significantly reduces cpu load on the database server.
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`");
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `%s`='%d'", tablename, selectoption, id);
@ -798,8 +798,9 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &item.refine, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 8, SQLDT_UINT, &item.bound, 0, NULL, NULL);
for( j = 0; j < MAX_SLOTS; ++j )
SqlStmt_BindColumn(stmt, 8+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
SqlStmt_BindColumn(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));
@ -826,14 +827,15 @@ 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.
StringBuf_Clear(&buf);
StringBuf_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);
StringBuf_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 )
StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
@ -861,7 +863,7 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
SqlStmt_Free(stmt);
StringBuf_Clear(&buf);
StringBuf_Printf(&buf, "INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `unique_id`", tablename, selectoption);
StringBuf_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 )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_AppendStr(&buf, ") VALUES ");
@ -879,8 +881,8 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
else
found = true;
StringBuf_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);
StringBuf_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 )
StringBuf_Printf(&buf, ", '%d'", items[i].card[j]);
StringBuf_AppendStr(&buf, ")");
@ -919,7 +921,7 @@ int inventory_to_sql(const struct item items[], int max, int id) {
// it significantly reduces cpu load on the database server.
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`");
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `bound`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`='%d'", inventory_db, id);
@ -943,8 +945,9 @@ int inventory_to_sql(const struct item items[], int max, int id) {
SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &item.favorite, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 9, SQLDT_CHAR, &item.bound, 0, NULL, NULL);
for( j = 0; j < MAX_SLOTS; ++j )
SqlStmt_BindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
SqlStmt_BindColumn(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));
@ -970,13 +973,14 @@ 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.
StringBuf_Clear(&buf);
StringBuf_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);
StringBuf_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 )
StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]);
StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id);
@ -1001,7 +1005,7 @@ int inventory_to_sql(const struct item items[], int max, int id) {
SqlStmt_Free(stmt);
StringBuf_Clear(&buf);
StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `unique_id`", inventory_db);
StringBuf_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 )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_AppendStr(&buf, ") VALUES ");
@ -1018,8 +1022,8 @@ int inventory_to_sql(const struct item items[], int max, int id) {
else
found = true;
StringBuf_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);
StringBuf_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 )
StringBuf_Printf(&buf, ", '%d'", items[i].card[j]);
StringBuf_AppendStr(&buf, ")");
@ -1282,7 +1286,7 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
//read inventory
//`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `expire_time`, `favorite`, `unique_id`)
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `unique_id`");
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `favorite`, `bound`, `unique_id`");
for( i = 0; i < MAX_SLOTS; ++i )
StringBuf_Printf(&buf, ", `card%d`", i);
StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", inventory_db, MAX_INVENTORY);
@ -1299,10 +1303,11 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &tmp_item.attribute, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &tmp_item.expire_time, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &tmp_item.favorite, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 9, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 9, SQLDT_CHAR, &tmp_item.bound, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt,10, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_SLOTS; ++i )
if( SQL_ERROR == SqlStmt_BindColumn(stmt, 10+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
if( SQL_ERROR == SqlStmt_BindColumn(stmt, 11+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_INVENTORY && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i )
@ -1313,7 +1318,7 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
//read cart
//`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, expire_time`, `unique_id`)
StringBuf_Clear(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `unique_id`");
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`, `unique_id`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", cart_db, MAX_CART);
@ -1329,10 +1334,11 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &tmp_item.refine, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &tmp_item.attribute, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &tmp_item.expire_time, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 8, SQLDT_CHAR, &tmp_item.bound, 0, NULL, NULL)
|| SQL_ERROR == SqlStmt_BindColumn(stmt, 9, SQLDT_ULONGLONG, &tmp_item.unique_id, 0, NULL, NULL) )
SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_SLOTS; ++i )
if( SQL_ERROR == SqlStmt_BindColumn(stmt, 9+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
if( SQL_ERROR == SqlStmt_BindColumn(stmt, 10+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
SqlStmt_ShowDebug(stmt);
for( i = 0; i < MAX_CART && SQL_SUCCESS == SqlStmt_NextRow(stmt); ++i )
@ -2962,7 +2968,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))
{

View File

@ -63,6 +63,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++)
{
@ -183,6 +184,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++ )
{

View File

@ -38,7 +38,7 @@ int storage_fromsql(int account_id, struct storage_data* p)
// storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`}
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`expire_time`,`unique_id`");
StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`expire_time`,`bound`,`unique_id`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ",`card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `account_id`='%d' ORDER BY `nameid`", storage_db, account_id);
@ -59,10 +59,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);
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;
@ -95,7 +96,7 @@ 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`}
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`unique_id`");
StringBuf_AppendStr(&buf, "SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`bound`,`unique_id`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ",`card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `guild_id`='%d' ORDER BY `nameid`", guild_storage_db, guild_id);
@ -115,11 +116,12 @@ 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);
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;
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;
@ -158,18 +160,19 @@ 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));
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;
}
@ -200,7 +203,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;
}
@ -234,6 +237,126 @@ int mapif_parse_SaveGuildStorage(int fd)
return 0;
}
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]
//------------------------------------------------
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);
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`, `bound`");
for( j = 0; j < MAX_SLOTS; ++j )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`='%d'",inventory_db,char_id);
stmt = SqlStmt_Malloc(sql_handle);
if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
|| SQL_ERROR == SqlStmt_Execute(stmt) )
{
SqlStmt_ShowDebug(stmt);
SqlStmt_Free(stmt);
StringBuf_Destroy(&buf);
return 1;
}
SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &item.id, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT, &item.nameid, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT, &item.amount, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &item.equip, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR, &item.identify, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR, &item.refine, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR, &item.attribute, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL);
SqlStmt_BindColumn(stmt, 8, SQLDT_UINT, &item.bound, 0, NULL, NULL);
for( j = 0; j < MAX_SLOTS; ++j )
SqlStmt_BindColumn(stmt, 9+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ) {
if(item.bound == 2) {
memcpy(&items[i],&item,sizeof(struct item));
i++;
}
}
if(!i) //No items found - No need to continue
return 0;
//First we delete the character's items
StringBuf_Clear(&buf);
StringBuf_Printf(&buf, "DELETE FROM `%s` WHERE",inventory_db);
for(j=0; j<i; j++) {
if( found )
StringBuf_AppendStr(&buf, " OR");
else
found = true;
StringBuf_Printf(&buf, " `id`=%d",items[j].id);
}
stmt = SqlStmt_Malloc(sql_handle);
if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
|| SQL_ERROR == SqlStmt_Execute(stmt) )
{
SqlStmt_ShowDebug(stmt);
SqlStmt_Free(stmt);
StringBuf_Destroy(&buf);
return 1;
}
//Now let's update the guild storage with those deleted items
found = false;
StringBuf_Clear(&buf);
StringBuf_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 )
StringBuf_Printf(&buf, ", `card%d`", j);
StringBuf_AppendStr(&buf, ") VALUES ");
for( j = 0; j < i; ++j ) {
if( found )
StringBuf_AppendStr(&buf, ",");
else
found = true;
StringBuf_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 )
StringBuf_Printf(&buf, ", '%d'", items[j].card[s]);
StringBuf_AppendStr(&buf, ")");
}
stmt = SqlStmt_Malloc(sql_handle);
if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
|| SQL_ERROR == SqlStmt_Execute(stmt) )
{
SqlStmt_ShowDebug(stmt);
SqlStmt_Free(stmt);
StringBuf_Destroy(&buf);
return 1;
}
//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;
}
int inter_storage_parse_frommap(int fd)
{
@ -241,6 +364,7 @@ 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;
case 0x3056: mapif_parse_itembound_retrieve(fd); break;
default:
return 0;
}

View File

@ -51,7 +51,7 @@ 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]
-1,-1,10,10, 0,-1,12, 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,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-

View File

@ -204,7 +204,7 @@ struct item {
char attribute;
short card[MAX_SLOTS];
unsigned int expire_time;
char favorite;
char favorite, bound;
uint64 unique_id;
};

View File

@ -1112,11 +1112,12 @@ ACMD_FUNC(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>)
*------------------------------------------*/
ACMD_FUNC(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;
@ -1124,7 +1125,13 @@ ACMD_FUNC(item)
memset(item_name, '\0', sizeof(item_name));
if (!message || !*message || (
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_displaymessage(fd, msg_txt(sd,295)); // Please enter an item name or ID (usage: @item <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
)) {
@ -1142,6 +1149,11 @@ ACMD_FUNC(item)
return -1;
}
if( bound < 0 || bound > 3 ) {
clif_displaymessage(fd, msg_txt(sd,298)); // Invalid bound type
return -1;
}
item_id = item_data->nameid;
get_count = number;
//Check if it's stackable.
@ -1154,6 +1166,7 @@ ACMD_FUNC(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);
@ -1173,17 +1186,23 @@ ACMD_FUNC(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;
nullpo_retr(-1, sd);
memset(item_name, '\0', sizeof(item_name));
if (!message || !*message || (
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_displaymessage(fd, msg_txt(sd,296)); // Please enter all parameters (usage: @item2 <item name/ID> <quantity>
clif_displaymessage(fd, msg_txt(sd,297)); // <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4> <bound_type>).
return -1;
} 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_displaymessage(fd, msg_txt(sd,984)); // Please enter all parameters (usage: @item2 <item name/ID> <quantity>
clif_displaymessage(fd, msg_txt(sd,985)); // <identify_flag> <refine> <attribute> <card1> <card2> <card3> <card4>).
return -1;
@ -1192,6 +1211,11 @@ ACMD_FUNC(item2)
if (number <= 0)
number = 1;
if( bound < 0 || bound > 3 ) {
clif_displaymessage(fd, msg_txt(sd,298)); // Invalid bound type
return -1;
}
item_id = 0;
if ((item_data = itemdb_searchname(item_name)) != NULL ||
(item_data = itemdb_exists(atoi(item_name))) != NULL)
@ -1228,6 +1252,7 @@ ACMD_FUNC(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);
}
@ -9012,6 +9037,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),

View File

@ -311,7 +311,7 @@ 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)) )
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;

View File

@ -1790,8 +1790,11 @@ 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;
if( val < 0 )
@ -2194,7 +2197,7 @@ void clif_additem(struct map_session_data *sd, int n, int amount, int fail)
WFIFOL(fd,offs+23)=sd->status.inventory[n].expire_time;
#endif
#if PACKETVER >= 20071002
WFIFOW(fd,offs+27)=0; // HireExpireDate
WFIFOW(fd,offs+27)=sd->status.inventory[n].bound ? 2 : 0;
#endif
}
@ -2300,7 +2303,7 @@ void clif_item_sub(unsigned char *buf, int n, int idx, struct item *i, struct it
clif_addcards(WBUFP(buf, n+12), i); //8B
#if PACKETVER >= 20071002
WBUFL(buf,n+20)=i->expire_time;
WBUFW(buf,n+24)=0; //Unknown
WBUFW(buf,n+24)=i->bound ? 2 : 0;
#endif
#if PACKETVER >= 20100629
WBUFW(buf,n+26)= (id->equip&EQP_VISIBLE)?id->look:0;
@ -14199,6 +14202,11 @@ void clif_parse_Auction_register(int fd, struct map_session_data *sd)
}
// Auction checks...
if( sd->status.inventory[sd->auction.index].bound && !pc_can_give_bounded_items(sd) ) {
clif_displaymessage(sd->fd, msg_txt(sd,293));
clif_Auction_message(fd, 2); // The auction has been canceled
return;
}
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;

View File

@ -106,6 +106,7 @@ void guild_flag_remove(struct npc_data *nd);
void guild_flags_clear(void);
void guild_guildaura_refresh(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv);
void guild_retrieveitembound(int char_id,int aid,int guild_id);
void do_final_guild(void);

View File

@ -39,7 +39,7 @@ static const int packet_len_table[]={
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,-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
@ -1002,15 +1002,19 @@ int 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 1;
sd=map_id2sd( RFIFOL(fd,4) );
if(sd==NULL){
ShowError("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4));
return 1;
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 1;
}
}
gstor=guild2storage(guild_id);
if(!gstor) {
@ -1018,21 +1022,23 @@ int intif_parse_LoadGuildStorage(int fd)
return 1;
}
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);
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 1;
}
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);
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 1;
}
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));
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 1;
}
memcpy(gstor,RFIFOP(fd,12),sizeof(struct guild_storage));
storage_guild_storageopen(sd);
memcpy(gstor,RFIFOP(fd,13),sizeof(struct guild_storage));
if( flag )
storage_guild_storageopen(sd);
return 0;
}
@ -2152,6 +2158,31 @@ void intif_parse_MessageToFD(int fd) {
return;
}
/*==========================================
* Item Bound System
*------------------------------------------*/
void intif_itembound_req(int char_id,int aid,int guild_id) {
struct guild_storage *gstor = guild2storage2(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(char_fd,6);
gstor = guild2storage2(guild_id);
if(gstor) gstor->lock = 0; //Unlock now that operation is completed
}
//-----------------------------------------------------------------
// Communication from the inter server
// Return a 0 (false) if there were any errors.
@ -2235,6 +2266,9 @@ int intif_parse(int fd)
case 0x3854: intif_parse_Auction_message(fd); break;
case 0x3855: intif_parse_Auction_bid(fd); break;
//Bound items
case 0x3856: intif_parse_itembound_ack(fd); break;
// Mercenary System
case 0x3870: intif_parse_mercenary_received(fd); break;
case 0x3871: intif_parse_mercenary_deleted(fd); break;

View File

@ -60,6 +60,7 @@ int intif_guild_notice(int guild_id, const char *mes1, const char *mes2);
int intif_guild_emblem(int guild_id, int len, const char *data);
int intif_guild_castle_dataload(int num, int *castle_ids);
int intif_guild_castle_datasave(int castle_id, int index, int value);
void intif_itembound_req(int char_id, int aid, int guild_id);
int intif_create_pet(int account_id, int char_id, short pet_type, short pet_lv, short pet_egg_id,
short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name);

View File

@ -78,8 +78,9 @@ unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount) {
return 1;
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)) )
if( !pc_can_give_items(sd) || sd->status.inventory[idx].expire_time
|| !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;

View File

@ -552,12 +552,10 @@ int party_member_withdraw(int party_id, int account_id, int char_id)
struct map_session_data* sd = map_id2sd(account_id);
struct party_data* p = party_search(party_id);
if( p )
{
if( p ) {
int i;
ARR_FIND( 0, MAX_PARTY, i, p->party.member[i].account_id == account_id && p->party.member[i].char_id == char_id );
if( i < MAX_PARTY )
{
if( i < MAX_PARTY ) {
clif_party_withdraw(p,sd,account_id,p->party.member[i].name,0x0);
memset(&p->party.member[i], 0, sizeof(p->party.member[0]));
memset(&p->data[i], 0, sizeof(p->data[0]));
@ -566,8 +564,12 @@ 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 )
{
if( sd && sd->status.party_id == party_id && sd->status.char_id == char_id ) {
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);
sd->status.party_id = 0;
clif_charnameupdate(sd); //Update name display [Skotlex]
//TODO: hp bars should be cleared too

View File

@ -509,6 +509,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.
*------------------------------------------*/
@ -914,7 +922,8 @@ 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;
int i, j;
int idxlist[MAX_INVENTORY];
unsigned long tick = gettick();
uint32 ip = session[sd->fd]->client_addr;
@ -1091,6 +1100,12 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim
**/
pc_itemcd_do(sd,true);
// 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);
}
// Request all registries (auth is considered completed whence they arrive)
intif_request_registry(sd,7);
return true;
@ -3878,7 +3893,7 @@ 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;
@ -4418,7 +4433,7 @@ 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)) )
if( !itemdb_cancartstore(item_data, pc_get_group_level(sd)) || ((item_data->bound == 2 || item_data->bound == 3) && !pc_can_give_bounded_items(sd)))
{ // Check item trade restrictions [Skotlex]
clif_displaymessage (sd->fd, msg_txt(sd,264));
return 1;
@ -4431,7 +4446,7 @@ 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].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] );
};
@ -4566,6 +4581,25 @@ int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount)
return 1;
}
/*==========================================
* Bound Item Check
* Type:
* 1 Account Bound
* 2 Guild Bound
* 3 Party 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
*------------------------------------------*/
@ -7880,7 +7914,7 @@ int pc_setmadogear(TBL_PC* sd, int flag)
*------------------------------------------*/
int pc_candrop(struct map_session_data *sd, struct item *item)
{
if( item && item->expire_time )
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;

View File

@ -705,6 +705,7 @@ int pc_get_group_level(struct map_session_data *sd);
int pc_get_group_id(struct map_session_data *sd);
int pc_getrefinebonus(int lv,int type);
bool pc_can_give_items(struct map_session_data *sd);
bool pc_can_give_bounded_items(struct map_session_data *sd);
bool pc_can_use_command(struct map_session_data *sd, const char *command, AtCommandType type);
#define pc_has_permission(sd, permission) ( ((sd)->permissions&permission) != 0 )
@ -748,6 +749,9 @@ int pc_additem(struct map_session_data*,struct item*,int,e_log_pick_type);
int pc_getzeny(struct map_session_data*,int, enum e_log_pick_type, struct map_session_data*);
int pc_delitem(struct map_session_data*,int,int,int,short,e_log_pick_type);
//Bound items
int pc_bound_chk(TBL_PC *sd,int type,int *idxlist);
// Special Shop System
int pc_paycash( struct map_session_data *sd, int price, int points, e_log_pick_type type );
int pc_getcash( struct map_session_data *sd, int cash, int points, e_log_pick_type type );

View File

@ -44,6 +44,7 @@ enum e_pc_permission {
PC_PERM_DISABLE_PVP = 0x080000,
PC_PERM_DISABLE_CMD_DEAD = 0x100000,
PC_PERM_CHANNEL_ADMIN = 0x200000,
PC_PERM_TRADE_BOUNDED = 0x400000,
};
static const struct {
@ -72,6 +73,7 @@ static const struct {
{ "disable_pvp", PC_PERM_DISABLE_PVP },
{ "disable_commands_when_dead", PC_PERM_DISABLE_CMD_DEAD },
{ "channel_admin", PC_PERM_CHANNEL_ADMIN },
{ "can_trade_bounded", PC_PERM_TRADE_BOUNDED },
};
#endif // _PC_GROUPS_H_

View File

@ -6270,6 +6270,13 @@ BUILDIN_FUNC(checkweight2){
/*==========================================
* getitem <item id>,<amount>{,<account ID>};
* getitem "<item name>",<amount>{,<account ID>};
*
* getitembound <item id>,<amount>,<type>{,<account ID>};
* getitembound "<item id>",<amount>,<type>{,<account ID>};
* Type:
* 1 - Account Bound
* 2 - Guild Bound
* 3 - Party Bound
*------------------------------------------*/
BUILDIN_FUNC(getitem)
{
@ -6280,8 +6287,7 @@ BUILDIN_FUNC(getitem)
data=script_getdata(st,2);
get_val(st,data);
if( data_isstring(data) )
{// "<item name>"
if( data_isstring(data) ) {// "<item name>"
const char *name=conv_str(st,data);
struct item_data *item_data = itemdb_searchname(name);
if( item_data == NULL ){
@ -6289,8 +6295,7 @@ BUILDIN_FUNC(getitem)
return 1; //No item created.
}
nameid=item_data->nameid;
} else if( data_isint(data) )
{// <item id>
} else if( data_isint(data) ) {// <item id>
nameid=conv_num(st,data);
//Violet Box, Blue Box, etc - random item pick
if( nameid < 0 ) {
@ -6317,7 +6322,18 @@ BUILDIN_FUNC(getitem)
else
it.identify=itemdb_isidentified(nameid);
if( script_hasdata(st,4) )
if( !strcmp(script_getfuncname(st),"getitembound") ) {
char bound = script_getnum(st,4);
if( bound < 1 || bound > 3) { //Not a correct bound type
ShowError("script_getitembound: Not a correct bound type! Type=%d\n",bound);
return 1;
}
it.bound = bound;
if( script_hasdata(st,5) )
sd=map_id2sd(script_getnum(st,5));
else
sd=script_rid2sd(st); // Attached player
} else if( script_hasdata(st,4) )
sd=map_id2sd(script_getnum(st,4)); // <Account ID>
else
sd=script_rid2sd(st); // Attached player
@ -6355,12 +6371,23 @@ BUILDIN_FUNC(getitem2)
{
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;
if( script_hasdata(st,11) )
if( !strcmp(script_getfuncname(st),"getitembound2") ) {
bound = script_getnum(st,11);
if( bound < 1 || bound > 3) { //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
} else if( script_hasdata(st,11) )
sd=map_id2sd(script_getnum(st,11)); // <Account ID>
else
sd=script_rid2sd(st); // Attached player
@ -6370,14 +6397,14 @@ BUILDIN_FUNC(getitem2)
data=script_getdata(st,2);
get_val(st,data);
if( data_isstring(data) ){
if( data_isstring(data) ) {
const char *name=conv_str(st,data);
struct item_data *item_data = itemdb_searchname(name);
if( item_data )
nameid=item_data->nameid;
else
nameid=UNKNOWN_ITEM_ID;
}else
} else
nameid=conv_num(st,data);
amount=script_getnum(st,3);
@ -6422,6 +6449,7 @@ BUILDIN_FUNC(getitem2)
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))
@ -6496,6 +6524,7 @@ BUILDIN_FUNC(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)) )
{
@ -11371,6 +11400,7 @@ BUILDIN_FUNC(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];
@ -11444,6 +11474,7 @@ BUILDIN_FUNC(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];
@ -12107,6 +12138,7 @@ BUILDIN_FUNC(getinventorylist)
pc_setreg(sd,reference_uid(add_str(card_var), j),sd->status.inventory[i].card[k]);
}
pc_setreg(sd,reference_uid(add_str("@inventorylist_expire"), j),sd->status.inventory[i].expire_time);
pc_setreg(sd,reference_uid(add_str("@inventorylist_bound"), j),sd->status.inventory[i].bound);
j++;
}
}
@ -17660,6 +17692,40 @@ BUILDIN_FUNC(sit)
return 0;
}
/*==========================================
* 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_FUNC(countbound)
{
int i, type, j=0, k=0;
TBL_PC *sd;
if( (sd = script_rid2sd(st)) == NULL )
return 0;
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(add_str("@bound_items"), k),sd->status.inventory[i].nameid);
k++;
j += sd->status.inventory[i].amount;
}
}
script_pushint(st,j);
return 0;
}
// declarations that were supposed to be exported from npc_chat.c
#ifdef PCRE_SUPPORT
BUILDIN_FUNC(defpattern);
@ -18125,5 +18191,10 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(checkquest, "i?"),
BUILDIN_DEF(changequest, "ii"),
BUILDIN_DEF(showevent, "ii"),
//Bound items [Xantara] & [Akinari]
BUILDIN_DEF2(getitem,"getitembound","vii?"),
BUILDIN_DEF2(getitem2,"getitembound2","viiiiiiiii?"),
BUILDIN_DEF(countbound, "?"),
{NULL,NULL,NULL},
};

View File

@ -120,7 +120,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++);
@ -154,6 +155,11 @@ static int storage_additem(struct map_session_data* sd, struct item* item_data,
return 1;
}
if( (item_data->bound == 2 || item_data->bound == 3) && !pc_can_give_bounded_items(sd) ) {
clif_displaymessage(sd->fd, msg_txt(sd,294));
return 1;
}
if( itemdb_isstackable2(data) )
{//Stackable
for( i = 0; i < MAX_STORAGE; i++ )
@ -448,6 +454,11 @@ int guild_storage_additem(struct map_session_data* sd, struct guild_storage* sto
return 1;
}
if( (item_data->bound == 1 || item_data->bound == 3) && !pc_can_give_bounded_items(sd) ) {
clif_displaymessage(sd->fd, msg_txt(sd,294));
return 1;
}
if(itemdb_isstackable2(data)){ //Stackable
for(i=0;i<MAX_GUILD_STORAGE;i++){
if(compare_item(&stor->items[i], item_data)) {

View File

@ -368,6 +368,12 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount)
return;
}
if( ((item->bound == 1 || item->bound == 3) || (item->bound == 2 && sd->status.guild_id != target_sd->status.guild_id)) && !pc_can_give_bounded_items(sd) ) { // Item Bound
clif_displaymessage(sd->fd, msg_txt(sd,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

View File

@ -267,6 +267,7 @@ 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
|| (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;