Index: conf/groups.conf =================================================================== --- conf/groups.conf (revision 17294) +++ conf/groups.conf (working copy) @@ -266,6 +266,7 @@ log_commands: true permissions: { can_trade: true + can_trade_bounded: true can_party: true all_skill: false all_equipment: false Index: conf/msg_conf/map_msg.conf =================================================================== --- conf/msg_conf/map_msg.conf (revision 17294) +++ conf/msg_conf/map_msg.conf (working copy) @@ -453,6 +453,13 @@ 463: Message configuration has been reloaded. 464: ---- Available languages: +// 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. + // Messages of others (not for GM commands) // ---------------------------------------- @@ -712,11 +719,11 @@ 981: Please enter color and message (usage: @kamic ). 982: Invalid color. -// @item -983: Please enter an item name or ID (usage: @item ). +// @item / @itembound +983: Please enter an item name or ID (usage: @%s ). -// @item2 -984: Please enter all parameters (usage: @item2 +// @item2 / @itembound2 +984: Please enter all parameters (usage: @%s 985: ). // @baselevelup Index: doc/atcommands.txt =================================================================== --- doc/atcommands.txt (revision 17294) +++ doc/atcommands.txt (working copy) @@ -629,6 +629,21 @@ --------------------------------------- +@itembound {} + +Creates the specified item and bounds it to the account. +If an amount is given for @itembound, that number will be created. + +--------------------------------------- + +@itembound2 + +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 + +--------------------------------------- + @produce <# of Very's> Creates a weapon with the given parameters. Index: doc/permissions.txt =================================================================== --- doc/permissions.txt (revision 17294) +++ doc/permissions.txt (working copy) @@ -31,5 +31,6 @@ 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...). channel_admin : Ability to modify channel settings regardless of ownership and join password-protected channels without a password. \ No newline at end of file Index: doc/script_commands.txt =================================================================== --- doc/script_commands.txt (revision 17294) +++ doc/script_commands.txt (working copy) @@ -2687,6 +2687,7 @@ craftsman. @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 only way to @@ -4226,6 +4227,42 @@ --------------------------------------- +*getitembound ,{,}; +*getitembound "",{,}; + +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. They will not be able +to trade, sell, drop, nor auction the item. + +--------------------------------------- + +*getitembound2 ,,,,,,,,{,}; +*getitembound2 "",,,,,,,,{,}; + +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. They will not be able +to trade, sell, drop, nor auction the item. + +--------------------------------------- + +*equipisbounded ; + +This command will check if the item in the specified equipment slot is bounded +to the player's account or not. It will return 1 if bounded, 0 otherwise. +For a list of equipment slots see 'getequipid'. + +--------------------------------------- + *getnameditem ,; *getnameditem "",; Index: sql-files/main.sql =================================================================== --- sql-files/main.sql (revision 17294) +++ sql-files/main.sql (working copy) @@ -43,6 +43,7 @@ `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`) @@ -404,6 +405,7 @@ `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`) @@ -665,6 +667,7 @@ `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`) Index: sql-files/upgrades/upgrade_svn17099_itembound.sql =================================================================== --- sql-files/upgrades/upgrade_svn17099_itembound.sql (revision 0) +++ sql-files/upgrades/upgrade_svn17099_itembound.sql (working copy) @@ -0,0 +1,3 @@ +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`; Index: src/char/char.c =================================================================== --- src/char/char.c (revision 17294) +++ src/char/char.c (working copy) @@ -778,6 +778,8 @@ StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`"); for( j = 0; j < MAX_SLOTS; ++j ) StringBuf_Printf(&buf, ", `card%d`", j); + if( tableswitch != TABLE_GUILD_STORAGE ) + StringBuf_AppendStr(&buf, ", `bound`"); StringBuf_Printf(&buf, " FROM `%s` WHERE `%s`='%d'", tablename, selectoption, id); stmt = SqlStmt_Malloc(sql_handle); @@ -800,6 +802,8 @@ SqlStmt_BindColumn(stmt, 7, SQLDT_UINT, &item.expire_time, 0, NULL, NULL); for( j = 0; j < MAX_SLOTS; ++j ) SqlStmt_BindColumn(stmt, 8+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL); + if( tableswitch != TABLE_GUILD_STORAGE ) + SqlStmt_BindColumn(stmt, 8+MAX_SLOTS, SQLDT_CHAR, &item.bound, 0, NULL, NULL); // bit array indicating which inventory items have already been matched flag = (bool*) aCalloc(max, sizeof(bool)); @@ -826,7 +830,8 @@ 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 && + (tableswitch != TABLE_GUILD_STORAGE && items[i].bound == item.bound) ) ; //Do nothing. else { @@ -836,6 +841,8 @@ 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 ) StringBuf_Printf(&buf, ", `card%d`=%d", j, items[i].card[j]); + if( tableswitch != TABLE_GUILD_STORAGE ) + StringBuf_Printf(&buf, ", `bound`=%d", items[i].bound); StringBuf_Printf(&buf, " WHERE `id`='%d' LIMIT 1", item.id); if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ) @@ -864,6 +871,8 @@ StringBuf_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 ) StringBuf_Printf(&buf, ", `card%d`", j); + if( tableswitch != TABLE_GUILD_STORAGE ) + StringBuf_AppendStr(&buf, ", `bound`"); StringBuf_AppendStr(&buf, ") VALUES "); found = false; @@ -883,8 +892,10 @@ 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 ) StringBuf_Printf(&buf, ", '%d'", items[i].card[j]); + if( tableswitch != TABLE_GUILD_STORAGE ) + StringBuf_Printf(&buf, ", '%d'", items[i].bound); StringBuf_AppendStr(&buf, ")"); - + updateLastUid(items[i].unique_id); // Unique Non Stackable Item ID } dbUpdateUid(sql_handle); // Unique Non Stackable Item ID @@ -919,7 +930,7 @@ // 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 +954,9 @@ 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 +982,14 @@ 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 +1014,7 @@ 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 +1031,8 @@ 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, ")"); @@ -1270,7 +1283,7 @@ //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); @@ -1287,10 +1300,11 @@ || 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 ) @@ -1299,9 +1313,9 @@ 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`) + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, expire_time`, `bound`, `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); @@ -1317,10 +1331,11 @@ || 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 ) Index: src/char/int_mail.c =================================================================== --- src/char/int_mail.c (revision 17294) +++ src/char/int_mail.c (working copy) @@ -63,6 +63,7 @@ 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 @@ 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++ ) { Index: src/char/int_storage.c =================================================================== --- src/char/int_storage.c (revision 17294) +++ src/char/int_storage.c (working copy) @@ -38,7 +38,7 @@ // 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 @@ 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; @@ -117,6 +118,7 @@ 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; + item->bound = 0; for( j = 0; j < MAX_SLOTS; ++j ) { Sql_GetData(sql_handle, 8+j, &data, NULL); item->card[j] = atoi(data); Index: src/common/mmo.h =================================================================== --- src/common/mmo.h (revision 17294) +++ src/common/mmo.h (working copy) @@ -204,6 +204,7 @@ short card[MAX_SLOTS]; unsigned int expire_time; char favorite; + char bound; uint64 unique_id; }; Index: src/map/atcommand.c =================================================================== --- src/map/atcommand.c (revision 17294) +++ src/map/atcommand.c (working copy) @@ -1113,23 +1113,26 @@ /*========================================== * @item command (usage: @item ) (modified by [Yor] for pet_egg) + * @itembound command (usage: @itembound ) *------------------------------------------*/ 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; nullpo_retr(-1, sd); memset(item_name, '\0', sizeof(item_name)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); if (!message || !*message || ( sscanf(message, "\"%99[^\"]\" %d", item_name, &number) < 1 && sscanf(message, "%99s %d", item_name, &number) < 1 )) { - clif_displaymessage(fd, msg_txt(sd,983)); // Please enter an item name or ID (usage: @item ). + sprintf(atcmd_output, msg_txt(sd,983), command+1); // Please enter an item name or ID (usage: @%s ). + clif_displaymessage(fd, atcmd_output); return -1; } @@ -1142,12 +1145,23 @@ clif_displaymessage(fd, msg_txt(sd,19)); // Invalid item ID or name. return -1; } + + if( !strcmpi(command+1,"itembound") ) + bound = 1; item_id = item_data->nameid; get_count = number; //Check if it's stackable. - if (!itemdb_isstackable2(item_data)) + if (!itemdb_isstackable2(item_data)) { + if( bound && (item_data->type == IT_PETEGG || item_data->type == IT_PETARMOR) ) { + clif_displaymessage(fd, msg_txt(sd,498)); // Cannot create bounded pet eggs or pet armors. + return -1; + } get_count = 1; + } else if( bound ) { + clif_displaymessage(fd, msg_txt(sd,499)); // Cannot create bounded stackable items. + return -1; + } for (i = 0; i < number; i += get_count) { // if not pet egg @@ -1155,6 +1169,7 @@ 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); @@ -1167,25 +1182,27 @@ } /*========================================== - * + * @item2 and @itembound2 command *------------------------------------------*/ 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)); + memset(atcmd_output, '\0', sizeof(atcmd_output)); 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 + sprintf(atcmd_output, msg_txt(sd,984), command+1); // Please enter all parameters (usage: @%s + clif_displaymessage(fd, atcmd_output); clif_displaymessage(fd, msg_txt(sd,985)); // ). return -1; } @@ -1203,8 +1220,13 @@ 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) { + 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_displaymessage(fd, msg_txt(sd,498)); // Cannot create bounded pet eggs or pet armors. + return -1; + } loop = number; get_count = 1; if (item_data->type == IT_PETEGG) { @@ -1216,6 +1238,10 @@ if (refine > MAX_REFINE) refine = MAX_REFINE; } else { + if( bound ) { + clif_displaymessage(fd, msg_txt(sd,499)); // Cannot create bounded stackable items. + return -1; + } identify = 1; refine = attr = 0; } @@ -1229,6 +1255,7 @@ 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); } @@ -9241,6 +9268,8 @@ ACMD_DEF(unloadnpcfile), ACMD_DEF(cart), ACMD_DEF(mount2), + ACMD_DEF2("itembound", item), + ACMD_DEF2("itembound2", item2), ACMD_DEF(join), ACMD_DEF(channel), ACMD_DEF(fontcolor), Index: src/map/buyingstore.c =================================================================== --- src/map/buyingstore.c (revision 17294) +++ src/map/buyingstore.c (working copy) @@ -312,7 +312,7 @@ 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; Index: src/map/clif.c =================================================================== --- src/map/clif.c (revision 17294) +++ src/map/clif.c (working copy) @@ -1788,8 +1788,8 @@ 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 val=sd->inventory_data[i]->value_sell; if( val < 0 ) @@ -2180,7 +2180,7 @@ WFIFOL(fd,23)=sd->status.inventory[n].expire_time; #endif #if PACKETVER >= 20071002 - WFIFOW(fd,27)=0; // unknown + WFIFOW(fd,27)=sd->status.inventory[n].bound ? 2 : 0; #endif } @@ -2300,7 +2300,7 @@ clif_addcards(WBUFP(bufe, ne*se+16), &sd->status.inventory[i]); #if PACKETVER >= 20071002 WBUFL(bufe,ne*se+24)=sd->status.inventory[i].expire_time; - WBUFW(bufe,ne*se+28)=0; //Unknown + WBUFW(bufe,ne*se+28)=sd->status.inventory[i].bound ? 2 : 0; #endif #if PACKETVER >= 20100629 if (sd->inventory_data[i]->equip&EQP_VISIBLE) @@ -2392,7 +2392,7 @@ clif_addcards(WBUFP(buf, n*cmd+16), &sd->status.inventory[i]); #if PACKETVER >= 20071002 WBUFL(buf,n*cmd+24)=sd->status.inventory[i].expire_time; - WBUFW(buf,n*cmd+28)=0; //Unknown + WBUFW(buf,n*cmd+28)=sd->status.inventory[i].bound ? 2 : 0; #endif #if PACKETVER >= 20100629 if (sd->inventory_data[i]->equip&EQP_VISIBLE) @@ -2449,7 +2449,7 @@ clif_addcards(WBUFP(bufe, ne*cmd+16), &items[i]); #if PACKETVER >= 20071002 WBUFL(bufe,ne*cmd+24)=items[i].expire_time; - WBUFW(bufe,ne*cmd+28)=0; //Unknown + WBUFW(bufe,ne*cmd+28)=items[i].bound ? 2 : 0; #endif ne++; } @@ -2529,7 +2529,7 @@ clif_addcards(WBUFP(bufe, ne*cmd+16), &sd->status.cart[i]); #if PACKETVER >= 20071002 WBUFL(bufe,ne*cmd+24)=sd->status.cart[i].expire_time; - WBUFW(bufe,ne*cmd+28)=0; //Unknown + WBUFW(bufe,ne*cmd+28)=sd->status.cart[i].bound ? 2 : 0; #endif ne++; } @@ -8743,7 +8743,7 @@ clif_addcards(WBUFP(buf, n*s+55), &tsd->status.inventory[i]); // Expiration date stuff, if all of those are set to 0 then the client doesn't show anything related (6 bytes) WBUFL(buf, n*s+63) = tsd->status.inventory[i].expire_time; - WBUFW(buf, n*s+67) = 0; + WBUFW(buf, n*s+67) = tsd->status.inventory[i].bound ? 2 : 0; #if PACKETVER >= 20100629 if (tsd->inventory_data[i]->equip&EQP_VISIBLE) WBUFW(buf, n*s+69) = tsd->inventory_data[i]->look; @@ -14059,10 +14059,12 @@ clif_Auction_setitem(sd->fd, idx, true); return; } - - 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 + + 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)) || + (sd->status.inventory[idx].bound && !pc_can_give_bounded_items(sd)) ) { // Quest Item or something else clif_Auction_setitem(sd->fd, idx, true); return; } Index: src/map/mail.c =================================================================== --- src/map/mail.c (revision 17294) +++ src/map/mail.c (working copy) @@ -78,8 +78,10 @@ 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; Index: src/map/pc.c =================================================================== --- src/map/pc.c (revision 17294) +++ src/map/pc.c (working copy) @@ -509,6 +509,14 @@ 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. *------------------------------------------*/ @@ -3883,7 +3891,7 @@ { // 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; @@ -4430,6 +4438,7 @@ { ARR_FIND( 0, MAX_CART, i, 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] ); }; @@ -7865,7 +7874,7 @@ *------------------------------------------*/ 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; Index: src/map/pc.h =================================================================== --- src/map/pc.h (revision 17294) +++ src/map/pc.h (working copy) @@ -708,6 +708,7 @@ 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 ) Index: src/map/pc_groups.h =================================================================== --- src/map/pc_groups.h (revision 17294) +++ src/map/pc_groups.h (working copy) @@ -44,6 +44,7 @@ PC_PERM_DISABLE_PVP = 0x080000, PC_PERM_DISABLE_CMD_DEAD = 0x100000, PC_PERM_CHANNEL_ADMIN = 0x200000, + PC_PERM_TRADE_BOUNDED = 0x400000, }; static const struct { Index: src/map/script.c =================================================================== --- src/map/script.c (revision 17294) +++ src/map/script.c (working copy) @@ -6554,6 +6554,7 @@ 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)) ) { @@ -11475,6 +11476,7 @@ 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]; @@ -11548,6 +11550,7 @@ 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]; @@ -12211,6 +12214,7 @@ 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++; } } @@ -17575,6 +17579,172 @@ return 0; } +/*========================================== + * getitembound ,{,}; + * getitembound "",{,}; + *------------------------------------------*/ +BUILDIN_FUNC(getitembound) +{ + int nameid, amount, i, flag; + struct item it; + struct script_data *data; + TBL_PC *sd; + + data = script_getdata(st,2); + get_val(st,data); + if( data_isstring(data) ) { // "" + const char *name = conv_str(st,data); + struct item_data *item_data = itemdb_searchname(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) ) { // + nameid = 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 useles iteration + + memset(&it,0,sizeof(it)); + it.nameid = nameid; + it.bound = 1; + it.identify = 1; + + if( script_hasdata(st,4) ) + sd = map_id2sd(script_getnum(st,4)); // 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; +} + +/*========================================== + * getitembound2 ,,,,,,,,{,}; + * getitembound2 "",,,,,,,,{,}; + *------------------------------------------*/ +BUILDIN_FUNC(getitembound2) +{ + int nameid, amount, i, flag; + int iden, ref, attr, c1, c2, c3, c4; + struct item_data *item_data; + struct item item_tmp; + struct script_data *data; + TBL_PC *sd; + + if( script_hasdata(st,11) ) + sd = map_id2sd(script_getnum(st,11)); // + else + sd = script_rid2sd(st); // Attached player + + if( sd == NULL ) // no target + return 0; + + data = script_getdata(st,2); + get_val(st,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 + nameid = 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 || (item_data = itemdb_exists(nameid)) == NULL || itemdb_isstackable2(item_data) ) + return 0; + + memset(&item_tmp,0,sizeof(item_tmp)); + + if( item_data->type == IT_WEAPON || item_data->type == IT_ARMOR ) + ref = cap_value(ref,0,MAX_REFINE); + else if( item_data->type == IT_PETEGG ) { + ShowError("getitembound2: invalid item type. Pet Egg cannot be set as rental items.\n"); + return 1; + } else { // Should not happen + iden = 1; + ref = attr = 0; + } + + item_tmp.nameid = nameid; + item_tmp.identify = iden; + 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 = 1; + + for( i = 0; i < amount; i++ ) { + if( (flag = pc_additem(sd, &item_tmp, 1, LOG_TYPE_SCRIPT)) ) { + clif_additem(sd, 0, 0, flag); + if( pc_candrop(sd,&item_tmp) ) + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,0,0,0,0); + } + } + + return 0; +} + +/*========================================== + * equipisbounded ; + *------------------------------------------*/ +BUILDIN_FUNC(equipisbounded) +{ + int i = -1, num; + TBL_PC *sd; + + if( (sd = script_rid2sd(st)) == NULL ) + return 0; + + num = script_getnum(st,2); + if( num > 0 && num <= ARRAYLENGTH(equip) ) + i = pc_checkequip(sd,equip[num-1]); + + if( i >= 0 && sd->status.inventory[i].bound ) + script_pushint(st,1); + else + script_pushint(st,0); + + return 0; +} + + // declarations that were supposed to be exported from npc_chat.c #ifdef PCRE_SUPPORT BUILDIN_FUNC(defpattern); @@ -18031,6 +18201,12 @@ BUILDIN_DEF(bindatcmd, "ss??"), BUILDIN_DEF(unbindatcmd, "s"), BUILDIN_DEF(useatcmd, "s"), + /** + * Item bound + **/ + BUILDIN_DEF(getitembound,"vi?"), + BUILDIN_DEF(getitembound2,"viiiiiiii?"), + BUILDIN_DEF(equipisbounded,"i"), //Quest Log System [Inkfish] BUILDIN_DEF(setquest, "i"), Index: src/map/storage.c =================================================================== --- src/map/storage.c (revision 17294) +++ src/map/storage.c (working copy) @@ -120,7 +120,8 @@ 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++); @@ -442,7 +443,7 @@ return 1; } - if( !itemdb_canguildstore(item_data, pc_get_group_level(sd)) || item_data->expire_time ) + 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_displaymessage (sd->fd, msg_txt(sd,264)); return 1; Index: src/map/trade.c =================================================================== --- src/map/trade.c (revision 17294) +++ src/map/trade.c (working copy) @@ -368,6 +368,12 @@ return; } + if( item->bound && !pc_can_give_bounded_items(sd) ) { // Account Bound + clif_displaymessage(sd->fd, msg_txt(sd, 497)); // Can't distribute an account bounded item + 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 Index: src/map/vending.c =================================================================== --- src/map/vending.c (revision 17294) +++ src/map/vending.c (working copy) @@ -275,6 +275,7 @@ || !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;