Browse Source

- Item Rental System.
Script Usage:
- rentitem <itemid>,<seconds>;
- rentitem <itemname>,<seconds>;

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@13370 54d463be-8e91-2dee-dedb-b68131a5f0ec

zephyrus 16 năm trước cách đây
mục cha
commit
c830a56da3

+ 2 - 1
npc/guild/payg_cas05.txt

@@ -50,7 +50,8 @@ pay_gld,208,268,4	script	Bamboo Grove Hill#f5-3::BambooGroveHill2	722,{
 			if (select("Return to the guild castle.:Quit.") == 1) {
 				if (getcharid(2) == GetCastleData("payg_cas05",1)) {
 					warp "payg_cas05",276,227;
-					//warp "payg_cas05",243;27;					end;
+					//warp "payg_cas05",243;27;
+					end;
 				}
 			}
 			close;

+ 4 - 0
sql-files/main.sql

@@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS `cart_inventory` (
   `card1` int(11) NOT NULL default '0',
   `card2` int(11) NOT NULL default '0',
   `card3` int(11) NOT NULL default '0',
+  `expire_time` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`id`),
   KEY `char_id` (`char_id`)
 ) ENGINE=MyISAM;
@@ -309,6 +310,7 @@ CREATE TABLE IF NOT EXISTS `guild_storage` (
   `card1` smallint(11) NOT NULL default '0',
   `card2` smallint(11) NOT NULL default '0',
   `card3` smallint(11) NOT NULL default '0',
+  `expire_time` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`id`),
   KEY `guild_id` (`guild_id`)
 ) ENGINE=MyISAM;
@@ -369,6 +371,7 @@ CREATE TABLE IF NOT EXISTS `inventory` (
   `card1` smallint(11) NOT NULL default '0',
   `card2` smallint(11) NOT NULL default '0',
   `card3` smallint(11) NOT NULL default '0',
+  `expire_time` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`id`),
   KEY `char_id` (`char_id`)
 ) ENGINE=MyISAM;
@@ -634,6 +637,7 @@ CREATE TABLE IF NOT EXISTS `storage` (
   `card1` smallint(11) NOT NULL default '0',
   `card2` smallint(11) NOT NULL default '0',
   `card3` smallint(11) NOT NULL default '0',
+  `expire_time` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY  (`id`),
   KEY `account_id` (`account_id`)
 ) ENGINE=MyISAM;

+ 6 - 0
sql-files/upgrade_svn13370_inventory.sql

@@ -0,0 +1,6 @@
+-- Creates expire_time column on Inventory
+
+ALTER TABLE `inventory` ADD COLUMN `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `cart_inventory` ADD COLUMN `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `storage` ADD COLUMN `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `guild_storage` ADD COLUMN `expire_time` INT(11) UNSIGNED NOT NULL DEFAULT '0';

+ 41 - 34
src/char_sql/char.c

@@ -529,6 +529,7 @@ int mmo_char_tosql(int char_id, struct mmo_charstatus* p)
 		(p->sword_calls != cp->sword_calls) || (p->sword_faith != cp->sword_faith) )
 	{
 		mercenary_owner_tosql(char_id, p);
+		strcat(save_status, " mercenary");
 	}
 
 	//memo points
@@ -697,7 +698,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`");
+	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);
 	StringBuf_Printf(&buf, " FROM `%s` WHERE `%s`='%d'", tablename, selectoption, id);
@@ -712,15 +713,16 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
 		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, 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);
 	for( j = 0; j < MAX_SLOTS; ++j )
-		SqlStmt_BindColumn(stmt, 7+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
+		SqlStmt_BindColumn(stmt, 8+j, SQLDT_SHORT, &item.card[j], 0, NULL, NULL);
 
 	// bit array indicating which inventory items have already been matched
 	flag = (bool*) aCallocA(max, sizeof(bool));
@@ -746,14 +748,15 @@ int memitemdata_to_sql(const struct item items[], int max, int id, int tableswit
 				    items[i].equip == item.equip &&
 				    items[i].identify == item.identify &&
 				    items[i].refine == item.refine &&
-				    items[i].attribute == item.attribute )
+				    items[i].attribute == item.attribute &&
+				    items[i].expire_time == item.expire_time )
 				;	//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'",
-						tablename, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute);
+					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);
 					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);
@@ -775,7 +778,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`", tablename, selectoption);
+	StringBuf_Printf(&buf, "INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`", tablename, selectoption);
 	for( j = 0; j < MAX_SLOTS; ++j )
 		StringBuf_Printf(&buf, ", `card%d`", j);
 	StringBuf_AppendStr(&buf, ") VALUES ");
@@ -793,8 +796,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'",
-			id, items[i].nameid, items[i].amount, items[i].equip, items[i].identify, items[i].refine, items[i].attribute);
+		StringBuf_Printf(&buf, "('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%u'",
+			id, items[i].nameid, 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, ", '%d'", items[i].card[j]);
 		StringBuf_AppendStr(&buf, ")");
@@ -1017,7 +1020,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`)
 	StringBuf_Init(&buf);
-	StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`");
+	StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `expire_time`");
 	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);
@@ -1025,26 +1028,28 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
 	if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
 	||	SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT,    &tmp_item.id, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT,  &tmp_item.nameid, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT,  &tmp_item.amount, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &tmp_item.equip, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR,   &tmp_item.identify, 0, NULL, NULL)
-	||	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, 0, SQLDT_INT,       &tmp_item.id, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT,     &tmp_item.nameid, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT,     &tmp_item.amount, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT,    &tmp_item.equip, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR,      &tmp_item.identify, 0, NULL, NULL)
+	||	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) )
 		SqlStmt_ShowDebug(stmt);
 	for( i = 0; i < MAX_SLOTS; ++i )
-		if( SQL_ERROR == SqlStmt_BindColumn(stmt, 7+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
+		if( SQL_ERROR == SqlStmt_BindColumn(stmt, 8+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 )
 		memcpy(&p->inventory[i], &tmp_item, sizeof(tmp_item));
+
 	strcat(t_msg, " inventory");
 
 	//read cart
 	//`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`)
 	StringBuf_Clear(&buf);
-	StringBuf_AppendStr(&buf, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`");
+	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);
 	StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id`=? LIMIT %d", cart_db, MAX_CART);
@@ -1052,16 +1057,17 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
 	if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
 	||	SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0)
 	||	SQL_ERROR == SqlStmt_Execute(stmt)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT,    &tmp_item.id, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT,  &tmp_item.nameid, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT,  &tmp_item.amount, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT, &tmp_item.equip, 0, NULL, NULL)
-	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR,   &tmp_item.identify, 0, NULL, NULL)
-	||	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, 0, SQLDT_INT,         &tmp_item.id, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_SHORT,       &tmp_item.nameid, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_SHORT,       &tmp_item.amount, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_USHORT,      &tmp_item.equip, 0, NULL, NULL)
+	||	SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_CHAR,        &tmp_item.identify, 0, NULL, NULL)
+	||	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) )
 		SqlStmt_ShowDebug(stmt);
 	for( i = 0; i < MAX_SLOTS; ++i )
-		if( SQL_ERROR == SqlStmt_BindColumn(stmt, 7+i, SQLDT_SHORT, &tmp_item.card[i], 0, NULL, NULL) )
+		if( SQL_ERROR == SqlStmt_BindColumn(stmt, 8+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 )
@@ -1125,11 +1131,12 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything
 			ShowWarning("mmo_char_fromsql: ignoring invalid hotkey (hotkey=%d,type=%u,id=%u,lv=%u) of character %s (AID=%d,CID=%d)\n", hotkey_num, tmp_hotkey.type, tmp_hotkey.id, tmp_hotkey.lv, p->name, p->account_id, p->char_id);
 	}
 	strcat(t_msg, " hotkeys");
+#endif
 
 	/* Mercenary Owner DataBase */
 	mercenary_owner_fromsql(char_id, p);
+	strcat(t_msg, " mercenary");
 
-#endif
 
 	if (save_log) ShowInfo("Loaded char (%d - %s): %s\n", char_id, p->name, t_msg);	//ok. all data load successfuly!
 	SqlStmt_Free(stmt);

+ 1 - 0
src/common/mmo.h

@@ -155,6 +155,7 @@ struct item {
 	char refine;
 	char attribute;
 	short card[MAX_SLOTS];
+	unsigned int expire_time;
 };
 
 struct point {

+ 37 - 8
src/map/clif.c

@@ -1419,17 +1419,22 @@ int clif_selllist(struct map_session_data *sd)
 	fd=sd->fd;
 	WFIFOHEAD(fd, MAX_INVENTORY * 10 + 4);
 	WFIFOW(fd,0)=0xc7;
-	for(i=0;i<MAX_INVENTORY;i++) {
-		if(sd->status.inventory[i].nameid > 0 && sd->inventory_data[i]) {
-			if (!itemdb_cansell(&sd->status.inventory[i], pc_isGM(sd)))
+	for( i = 0; i < MAX_INVENTORY; i++ )
+	{
+		if( sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] )
+		{
+			if( !itemdb_cansell(&sd->status.inventory[i], pc_isGM(sd)) )
 				continue;
 
+			if( sd->status.inventory[i].expire_time )
+				continue; // Cannot Sell Rental Items
+
 			val=sd->inventory_data[i]->value_sell;
-			if (val < 0)
+			if( val < 0 )
 				continue;
 			WFIFOW(fd,4+c*10)=i+2;
 			WFIFOL(fd,6+c*10)=val;
-			if (!sd->inventory_data[i]->flag.value_notoc)
+			if( !sd->inventory_data[i]->flag.value_notoc )
 				val=pc_modifysellvalue(sd,val);
 			WFIFOL(fd,10+c*10)=val;
 			c++;
@@ -11992,12 +11997,12 @@ void clif_parse_Auction_setitem(int fd, struct map_session_data *sd)
 		return;
 	}
 	
-	if( !pc_candrop(sd, &sd->status.inventory[idx]) || !sd->status.inventory[idx].identify )
+	if( !pc_candrop(sd, &sd->status.inventory[idx]) || !sd->status.inventory[idx].identify || sd->status.inventory[idx].expire_time )
 	{ // Quest Item or something else
 		clif_Auction_setitem(sd->fd, idx, true);
 		return;
 	}
-
+	
 	sd->auction.index = idx;
 	sd->auction.amount = amount;
 	clif_Auction_setitem(fd, idx + 2, false);
@@ -12207,7 +12212,10 @@ void clif_parse_cashshop_buy(int fd, struct map_session_data *sd)
 	amount = RFIFOW(fd,4);
 	points = RFIFOL(fd,6); // Not Implemented. Should be 0
 
-	fail = npc_cashshop_buy(sd, nameid, amount, points);
+	if( sd->state.trading || !sd->npc_shopid )
+		fail = 1;
+	else
+		fail = npc_cashshop_buy(sd, nameid, amount, points);
 
 	WFIFOHEAD(fd,12);
 	WFIFOW(fd,0) = 0x289;
@@ -12636,6 +12644,27 @@ void clif_mercenary_message(int fd, int message)
 	WFIFOSET(fd,4);
 }
 
+/*------------------------------------------
+ * Rental System Messages
+ *------------------------------------------*/
+void clif_rental_time(int fd, int nameid, int seconds)
+{ // '<ItemName>' item will disappear in <seconds/60> minutes.
+	WFIFOHEAD(fd,8);
+	WFIFOW(fd,0) = 0x0298;
+	WFIFOW(fd,2) = nameid;
+	WFIFOL(fd,4) = seconds;
+	WFIFOSET(fd,8);
+}
+
+void clif_rental_expired(int fd, int nameid)
+{ // '<ItemName>' item has been deleted from the Inventory
+	WFIFOHEAD(fd,6);
+	WFIFOW(fd,0) = 0x0299;
+	WFIFOW(fd,2) = 0;
+	WFIFOW(fd,4) = nameid;
+	WFIFOSET(fd,6);
+}
+
 /*==========================================
  * ƒpƒPƒbƒgƒfƒoƒbƒO
  *------------------------------------------*/

+ 4 - 0
src/map/clif.h

@@ -445,4 +445,8 @@ void clif_mercenary_skillblock(struct map_session_data *sd);
 void clif_mercenary_message(int fd, int message);
 void clif_mercenary_updatestatus(struct map_session_data *sd, int type);
 
+// RENTAL SYSTEM
+void clif_rental_time(int fd, int nameid, int seconds);
+void clif_rental_expired(int fd, int nameid);
+
 #endif /* _CLIF_H_ */

+ 7 - 7
src/map/mail.c

@@ -69,16 +69,14 @@ int mail_removezeny(struct map_session_data *sd, short flag)
 
 unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount)
 {
-	if (idx == 0)
+	if( idx == 0 )
 	{ // Zeny Transfer
-		if( amount < 0 )
-			return 0;
+		if( amount < 0 || !pc_can_give_items(pc_isGM(sd)) )
+			return 1;
+
 		if( amount > sd->status.zeny )
 			amount = sd->status.zeny;
 
-		if( !pc_can_give_items(pc_isGM(sd)) )
-			amount = 0;
-
 		sd->mail.zeny = amount;
 		// clif_updatestatus(sd, SP_ZENY);
 		return 0;
@@ -94,6 +92,8 @@ unsigned char mail_setitem(struct map_session_data *sd, int idx, int amount)
 			return 1;
 		if( !pc_candrop(sd, &sd->status.inventory[idx]) )
 			return 1;
+		if( sd->status.inventory[idx].expire_time )
+			return 1; // Rental System
 
 		sd->mail.index = idx;
 		sd->mail.nameid = sd->status.inventory[idx].nameid;
@@ -174,7 +174,7 @@ void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg)
 	nullpo_retv(sd);
 	nullpo_retv(msg);
 
-	// Item  recieve (due to failure)
+	// Item recieve (due to failure)
 	if(log_config.enable_logs&0x2000)
 		log_pick_pc(sd, "E", msg->item.nameid, msg->item.amount, &msg->item);
 

+ 103 - 12
src/map/pc.c

@@ -280,6 +280,88 @@ int pc_setrestartvalue(struct map_session_data *sd,int type)
 	return 0;
 }
 
+/*==========================================
+	Rental System
+ *------------------------------------------*/
+static int pc_inventory_rental_end(int tid, unsigned int tick, int id, intptr data)
+{
+	struct map_session_data *sd = map_id2sd(id);
+	if( sd == NULL )
+		return 0;
+	if( tid != sd->rental_timer )
+	{
+		ShowError("pc_inventory_rental_end: invalid timer id.\n");
+		return 0;
+	}
+
+	pc_inventory_rentals(sd);
+	return 1;
+}
+
+int pc_inventory_rental_clear(struct map_session_data *sd)
+{
+	if( sd->rental_timer != INVALID_TIMER )
+	{
+		delete_timer(sd->rental_timer, pc_inventory_rental_end);
+		sd->rental_timer = -1;
+	}
+
+	return 1;
+}
+
+void pc_inventory_rentals(struct map_session_data *sd)
+{
+	int i, c = 0;
+	unsigned int expire_tick, next_tick = UINT_MAX;
+
+	for( i = 0; i < MAX_INVENTORY; i++ )
+	{
+		if( sd->status.inventory[i].nameid == 0 )
+			continue; // Nothing here
+		if( sd->status.inventory[i].expire_time == 0 )
+			continue;
+
+		if( sd->status.inventory[i].expire_time <= time(NULL) )
+		{
+			clif_rental_expired(sd->fd, sd->status.inventory[i].nameid);
+			pc_delitem(sd, i, sd->status.inventory[i].amount, 0);
+		}
+		else
+		{
+			expire_tick = (unsigned int)(sd->status.inventory[i].expire_time - time(NULL)) * 1000;
+			clif_rental_time(sd->fd, sd->status.inventory[i].nameid, (int)(expire_tick / 1000));
+			next_tick = min(expire_tick, next_tick);
+			c++;
+		}
+	}
+
+	if( c > 0 )
+		sd->rental_timer = add_timer(gettick() + next_tick, pc_inventory_rental_end, sd->bl.id, 0);
+	else
+		sd->rental_timer = -1;
+}
+
+void pc_inventory_rental_add(struct map_session_data *sd, int seconds)
+{
+	const struct TimerData * td;
+	int tick = seconds * 1000;
+
+	if( sd == NULL )
+		return;
+
+	if( sd->rental_timer != INVALID_TIMER )
+	{
+		td = get_timer(sd->rental_timer);
+		if( DIFF_TICK(td->tick, gettick()) > tick )
+		{ // Update Timer as this one ends first than the current one
+			pc_inventory_rental_clear(sd);
+			sd->rental_timer = add_timer(gettick() + tick, pc_inventory_rental_end, sd->bl.id, 0);
+		}
+	}
+	else
+		sd->rental_timer = add_timer(gettick() + tick, pc_inventory_rental_end, sd->bl.id, 0);
+}
+
 /*==========================================
 	Determines if the GM can give / drop / trade / vend items
     Args: GM Level (current player GM level)
@@ -732,7 +814,7 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim
 	// アイテムチェック
 	pc_setinventorydata(sd);
 	pc_checkitem(sd);
-	
+
 	status_change_init(&sd->bl);
 	if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && (pc_isGM(sd) >= get_atcommand_level(atcommand_hide)))
 		sd->status.option &= (OPTION_MASK | OPTION_INVISIBLE);
@@ -747,11 +829,13 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim
 	sd->guild_x = -1;
 	sd->guild_y = -1;
 
-	// イベント?係の初期化
-	for(i = 0; i < MAX_EVENTTIMER; i++)
+	// Event Timers
+	for( i = 0; i < MAX_EVENTTIMER; i++ )
 		sd->eventtimer[i] = -1;
+	// Rental Timer
+	sd->rental_timer = -1;
 
-	for (i = 0; i < 3; i++)
+	for( i = 0; i < 3; i++ )
 		sd->hate_mob[i] = -1;
 
 	// 位置の設定
@@ -873,7 +957,7 @@ int pc_reg_received(struct map_session_data *sd)
 	sd->kafraPoints = pc_readaccountreg(sd,"#KAFRAPOINTS");
 
 	if( (sd->class_&MAPID_BASEMASK) == MAPID_TAEKWON )
-	{	//Better check for class rather than skill to prevent "skill resets" from unsetting this
+	{ // Better check for class rather than skill to prevent "skill resets" from unsetting this
 		sd->mission_mobid = pc_readglobalreg(sd,"TK_MISSION_ID");
 		sd->mission_count = pc_readglobalreg(sd,"TK_MISSION_COUNT");
 	}
@@ -940,6 +1024,10 @@ int pc_reg_received(struct map_session_data *sd)
 		sd->state.connect_new = 1;
 		clif_parse_LoadEndAck(sd->fd, sd);
 	}
+
+#ifndef TXT_ONLY
+	pc_inventory_rentals(sd);
+#endif
 	return 1;
 }
 
@@ -2982,12 +3070,14 @@ int pc_dropitem(struct map_session_data *sd,int n,int amount)
 		)
 		return 0;
 
-	if (map[sd->bl.m].flag.nodrop) {
+	if( map[sd->bl.m].flag.nodrop )
+	{
 		clif_displaymessage (sd->fd, msg_txt(271));
 		return 0; //Can't drop items in nodrop mapflag maps.
 	}
 	
-	if (!pc_candrop(sd,&sd->status.inventory[n])) {
+	if( !pc_candrop(sd,&sd->status.inventory[n]) || sd->status.inventory[n].expire_time )
+	{
 		clif_displaymessage (sd->fd, msg_txt(263));
 		return 0;
 	}
@@ -3291,17 +3381,17 @@ int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amoun
 		return 1;
 	data = itemdb_search(item_data->nameid);
 
-	if(!itemdb_cancartstore(item_data, pc_isGM(sd)))
-	{	//Check item trade restrictions	[Skotlex]
+	if( item_data->expire_time || !itemdb_cancartstore(item_data, pc_isGM(sd)) )
+	{ // Check item trade restrictions	[Skotlex]
 		clif_displaymessage (sd->fd, msg_txt(264));
 		return 1;
 	}
 
-	if((w=data->weight*amount) + sd->cart_weight > battle_config.max_cart_weight)
+	if( (w = data->weight*amount) + sd->cart_weight > battle_config.max_cart_weight )
 		return 1;
 
-	i=MAX_CART;
-	if(itemdb_isstackable2(data))
+	i = MAX_CART;
+	if( itemdb_isstackable2(data) )
 	{
 		ARR_FIND( 0, MAX_CART, i,
 			sd->status.cart[i].nameid == item_data->nameid &&
@@ -7562,6 +7652,7 @@ int do_init_pc(void)
 
 	add_timer_func_list(pc_invincible_timer, "pc_invincible_timer");
 	add_timer_func_list(pc_eventtimer, "pc_eventtimer");
+	add_timer_func_list(pc_inventory_rental_end, "pc_inventory_rental_end");
 	add_timer_func_list(pc_calc_pvprank_timer, "pc_calc_pvprank_timer");
 	add_timer_func_list(pc_autosave, "pc_autosave");
 	add_timer_func_list(pc_spiritball_timer, "pc_spiritball_timer");

+ 6 - 0
src/map/pc.h

@@ -340,6 +340,7 @@ struct map_session_data {
 	char away_message[128]; // [LuzZza]
 
 	int cashPoints, kafraPoints;
+	int rental_timer;
 
 	// Auction System [Zephyrus]
 	struct {
@@ -711,6 +712,11 @@ extern int night_timer_tid;
 int map_day_timer(int tid, unsigned int tick, int id, intptr data); // by [yor]
 int map_night_timer(int tid, unsigned int tick, int id, intptr data); // by [yor]
 
+// Rental System
+void pc_inventory_rentals(struct map_session_data *sd);
+int pc_inventory_rental_clear(struct map_session_data *sd);
+void pc_inventory_rental_add(struct map_session_data *sd, int seconds);
+
 //Duel functions // [LuzZza]
 int duel_create(struct map_session_data* sd, const unsigned int maxpl);
 int duel_invite(const unsigned int did, struct map_session_data* sd, struct map_session_data* target_sd);

+ 74 - 2
src/map/script.c

@@ -5238,6 +5238,77 @@ BUILDIN_FUNC(getitem2)
 	return 0;
 }
 
+/*==========================================
+ * rentitem <item id>
+ * rentitem "<item name>"
+ *------------------------------------------*/
+BUILDIN_FUNC(rentitem)
+{
+	struct map_session_data *sd;
+	struct script_data *data;
+	struct item it;
+	int seconds;
+	int nameid = 0, flag;
+
+	data = script_getdata(st,2);
+	get_val(st,data);
+
+	if( (sd = script_rid2sd(st)) == NULL )
+		return 0;
+
+	if( data_isstring(data) )
+	{
+		const char *name = conv_str(st,data);
+		struct item_data *itd = itemdb_searchname(name);
+		if( itd == NULL )
+		{
+			ShowError("buildin_rentitem: Nonexistant item %s requested.\n", name);
+			return 1;
+		}
+		nameid = itd->nameid;
+	}
+	else if( data_isint(data) )
+	{
+		nameid = conv_num(st,data);
+		if( nameid <= 0 || !itemdb_exists(nameid) )
+		{
+			ShowError("buildin_rentitem: Nonexistant item %d requested.\n", nameid);
+			return 1;
+		}
+	}
+	else
+	{
+		ShowError("buildin_rentitem: invalid data type for argument #1 (%d).\n", data->type);
+		return 1;
+	}
+
+	if( itemdb_isstackable(nameid) )
+	{
+		ShowError("buildin_rentitem: invalid rental item %d requested.\n", nameid);
+		return 1;
+	}
+
+	seconds = script_getnum(st,3);
+	memset(&it, 0, sizeof(it));
+	it.nameid = nameid;
+	it.identify = 1;
+	it.expire_time = (unsigned int)(time(NULL) + seconds);
+
+	if( (flag = pc_additem(sd, &it, 1)) )
+	{
+		clif_additem(sd, 0, 0, flag);
+		return 1;
+	}
+
+	clif_rental_time(sd->fd, nameid, seconds);
+	pc_inventory_rental_add(sd, seconds);
+
+	if( log_config.enable_logs&LOG_SCRIPT_TRANSACTIONS )
+		log_pick_pc(sd, "N", nameid, 1, NULL);
+	
+	return 0;
+}
+
 /*==========================================
  * gets an item with someone's name inscribed [Skotlex]
  * getinscribeditem item_num, character_name
@@ -9580,8 +9651,8 @@ BUILDIN_FUNC(mapwarp)	// Added by RoVeRT
 static int buildin_mobcount_sub(struct block_list *bl,va_list ap)	// Added by RoVeRT
 {
 	char *event=va_arg(ap,char *);
-    struct mob_data *md = ((struct mob_data *)bl);
-    if(strcmp(event,md->npc_event)==0 && md->status.hp > 0)
+	struct mob_data *md = ((struct mob_data *)bl);
+	if(strcmp(event,md->npc_event)==0 && md->status.hp > 0)
 		return 1;
 	return 0;
 }
@@ -13186,6 +13257,7 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(deletearray,"r?"),
 	BUILDIN_DEF(getelementofarray,"ri"),
 	BUILDIN_DEF(getitem,"vi?"),
+	BUILDIN_DEF(rentitem,"vi"),
 	BUILDIN_DEF(getitem2,"iiiiiiiii*"),
 	BUILDIN_DEF(getnameditem,"is"),
 	BUILDIN_DEF2(grouprandomitem,"groupranditem","i"),

+ 24 - 11
src/map/storage.c

@@ -112,10 +112,11 @@ int storage_storageopen(struct map_session_data *sd)
 // helper function
 int compare_item(struct item *a, struct item *b)
 {
-	if (a->nameid == b->nameid &&
+	if( a->nameid == b->nameid &&
 		a->identify == b->identify &&
 		a->refine == b->refine &&
-		a->attribute == b->attribute)
+		a->attribute == b->attribute &&
+		a->expire_time == b->expire_time )
 	{
 		int i;
 		for (i = 0; i < MAX_SLOTS && (a->card[i] == b->card[i]); i++);
@@ -215,7 +216,10 @@ int storage_storageadd(struct map_session_data* sd, int index, int amount)
 		return 0;
 
 	if( sd->status.inventory[index].nameid <= 0 )
-		return 0; //No item on that spot
+		return 0; // No item on that spot
+
+	if( sd->status.inventory[index].expire_time )
+		return 0; // Cannot Store Rental Items
 	
 	if( amount < 1 || amount > sd->status.inventory[index].amount )
   		return 0;
@@ -266,6 +270,9 @@ int storage_storageaddfromcart(struct map_session_data* sd, int index, int amoun
 	if( sd->status.cart[index].nameid <= 0 )
 		return 0; //No item there.
 	
+	if( sd->status.inventory[index].expire_time )
+		return 0; // Cannot Store Rental Items
+
 	if( amount < 1 || amount > sd->status.cart[index].amount )
 		return 0;
 
@@ -462,16 +469,19 @@ int storage_guild_storageadd(struct map_session_data* sd, int index, int amount)
 	nullpo_retr(0, sd);
 	nullpo_retr(0, stor=guild2storage2(sd->status.guild_id));
 		
-	if (!stor->storage_status || stor->storage_amount > MAX_GUILD_STORAGE)
+	if( !stor->storage_status || stor->storage_amount > MAX_GUILD_STORAGE )
 		return 0;
 	
-	if(index<0 || index>=MAX_INVENTORY)
+	if( index<0 || index>=MAX_INVENTORY )
 		return 0;
 
-	if(sd->status.inventory[index].nameid <= 0)
+	if( sd->status.inventory[index].nameid <= 0 )
 		return 0;
 	
-	if(amount < 1 || amount > sd->status.inventory[index].amount)
+	if( amount < 1 || amount > sd->status.inventory[index].amount )
+		return 0;
+
+	if( sd->status.inventory[index].expire_time )
 		return 0;
 
 //	log_tostorage(sd, index, 1);
@@ -517,16 +527,19 @@ int storage_guild_storageaddfromcart(struct map_session_data* sd, int index, int
 	nullpo_retr(0, sd);
 	nullpo_retr(0, stor=guild2storage2(sd->status.guild_id));
 
-	if(!stor->storage_status || stor->storage_amount > MAX_GUILD_STORAGE)
+	if( !stor->storage_status || stor->storage_amount > MAX_GUILD_STORAGE )
 		return 0;
 
-	if(index<0 || index>=MAX_CART)
+	if( index < 0 || index >= MAX_CART )
 		return 0;
 
-	if(sd->status.cart[index].nameid <= 0)
+	if( sd->status.cart[index].nameid <= 0 )
 		return 0;
 	
-	if(amount < 1 || amount > sd->status.cart[index].amount)
+	if( amount < 1 || amount > sd->status.cart[index].amount )
+		return 0;
+
+	if( sd->status.inventory[index].expire_time )
 		return 0;
 
 	if(guild_storage_additem(sd,stor,&sd->status.cart[index],amount)==0)

+ 7 - 0
src/map/trade.c

@@ -354,6 +354,13 @@ void trade_tradeadditem(struct map_session_data *sd, short index, short amount)
 		return;
 	}
 
+	if( item->expire_time )
+	{ // Rental System
+		clif_displaymessage (sd->fd, msg_txt(260));
+		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

+ 1 - 0
src/map/unit.c

@@ -1941,6 +1941,7 @@ int unit_free(struct block_list *bl, int clrtype)
 			party_send_logout(sd);
 			guild_send_memberinfoshort(sd,0);
 			pc_cleareventtimer(sd);
+			pc_inventory_rental_clear(sd);
 			pc_delspiritball(sd,sd->spiritball,1);
 
 			if( sd->reg )