Explorar el Código

Initial release of the guild storage log (#3365)

Thanks to @aleos89 and @Everade
Lemongrass3110 hace 6 años
padre
commit
55acdb9863

+ 1 - 0
conf/inter_athena.conf

@@ -149,6 +149,7 @@ vending_table: vendings
 vending_items_table: vending_items
 market_table: market
 roulette_table: db_roulette
+guild_storage_log: guild_storage_log
 
 // Use SQL item_db, mob_db and mob_skill_db for the map server? (yes/no)
 use_sql_db: no

+ 26 - 0
doc/script_commands.txt

@@ -5502,6 +5502,32 @@ Return values:
 
 ---------------------------------------
 
+*guildopenstorage_log({<char id>})
+
+Opens the guild storage log window for the attached character or the given character id.
+
+Possible return values:
+GUILDSTORAGE_LOG_FINAL_SUCCESS	Window was opened successfully.
+GUILDSTORAGE_LOG_EMPTY			Window was not opened, because no entries exist.
+GUILDSTORAGE_LOG_FAILED			Some database error occurred.
+
+---------------------------------------
+
+*guild_has_permission(<permission>{,<char id>})
+
+Checks if the attached player or the player with the given character id has the given permission(s).
+Permission can be a bitmask and allows to use multiple values at the same time.
+Returns true if the player has all of the given permissions or false if the player does at least
+miss one of the given permissions or is not in a guild at all.
+
+Available permissions are:
+GUILD_PERM_INVITE	If a player is allowed to invite other players.
+GUILD_PERM_EXPEL	If a player is allowed to expel other guild members.
+GUILD_PERM_STORAGE	If a player is allowed to access the guild storage.
+GUILD_PERM_ALL		A combination of all permissions above.
+
+---------------------------------------
+
 *guildchangegm(<guild id>,<new master's name>)
 
 This function will change the Guild Master of a guild. The ID is the guild's

+ 111 - 0
npc/re/merchants/guild_warehouse.txt

@@ -0,0 +1,111 @@
+//===== rAthena Script =======================================
+//= Guild Warehouse Manager
+//===== Description: =========================================
+//= [Walkthrough Conversion]
+//= Gives players access to their guild storage and log.
+//===== Changelogs: ==========================================
+//= 1.0 First Version. [Lemongrass]
+//============================================================
+
+-	script	GuildWarehouse	-1,{
+	mes "[Warehouse Manager]";
+	mes "How are you? We are specialized in guild warehouses. This is our ^0000cdstory^000000:";
+	mes "Why can't guild members share a storage? We started off with that simple question.";
+	next;
+	mes "[Warehouse Manager]";
+	mes "You can open the warehouse for 1000 Zeny or look up the usage history of the guild.";
+	mes "How can I help you?";
+	next;
+	if( select( "Open guild warehouse:View warehouse usage history" ) == 1 ){
+		.@guildid = getcharid( 2 );
+
+		if( .@guildid == 0 ){
+			mes "[Warehouse Manager]";
+			mes "The guild storage is only available for guild members.";
+			close;
+		}
+
+		if( getgdskilllv( .@guildid, "GD_GUILD_STORAGE" ) == 0 || !guild_has_permission( GUILD_PERM_STORAGE ) ){
+			mes "[Warehouse Manager]";
+			mes "It seems that it is not yet possible for your guild to use the guild warehouse or you do not have access permissions for the warehouse.";
+			mes "Please come back after checking the guild skill and the permission to access the warehouse.";
+			close;
+		}
+
+		if( Zeny < 1000 ){
+			mes "[Warehouse Manager]";
+			mes "I am afraid you do not have enough money to settle the fee.";
+			mes "The fee is 1000 Zeny.";
+			close;
+		}
+
+		mes "[Warehouse Manager]";
+		mes "I will open the guild storage for you then. Have a memorable time!";
+		close2;
+
+		if( Zeny < 1000 ){
+			// Cheat prevention
+			end;
+		}
+
+		if( guildopenstorage() == GSTORAGE_OPEN ){
+			Zeny -= 1000;
+			end;
+		}else{
+			mes "[Warehouse Manager]";
+			mes "I am afraid someone else is using the warehouse right now.";
+			mes "Please try again after a while.";
+			close;
+		}
+	}else{
+		.@guildid = getcharid( 2 );
+
+		if( .@guildid == 0 ){
+			mes "[Warehouse Manager]";
+			mes "The guild storage is only available for guild members.";
+			close;
+		}
+
+		if( getgdskilllv( .@guildid, "GD_GUILD_STORAGE" ) == 0 || !guild_has_permission( GUILD_PERM_STORAGE ) ){
+			mes "[Warehouse Manager]";
+			mes "It seems that it is not yet possible for your guild to use the guild warehouse or you do not have access permissions for the warehouse.";
+			mes "Please come back after checking the guild skill and the permission to access the warehouse.";
+			close;
+		}
+
+		mes "[Warehouse Manager]";
+		mes "I will show you the usage history of the warehouse. Usage history will be retained for up to 3 months.";
+		mes "Have a memorable time!";
+		close2;
+		guildopenstorage_log();
+		end;
+	}
+}
+
+alberta,114,65,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#alberta	896
+aldebaran,146,122,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#aldebaran	896
+amatsu,100,156,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#amatsu	896
+ayothaya,203,173,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#ayothaya	896
+brasilis,204,227,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#brasilis	896
+comodo,204,153,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#comodo	896
+dewata,196,193,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#dewata	896
+einbech,182,124,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#einbech	896
+einbroch,238,203,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#einbroch	896
+geffen,128,68,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#geffen	896
+gonryun,164,127,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#gonryun	896
+harboro1,288,212,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#harboro1	896
+hugel,91,158,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#hugel	896
+izlude,133,149,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#izlude	896
+lighthalzen,162,102,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#lighthalzen	896
+louyang,210,111,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#louyang	896
+malaya,238,206,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#malaya	896
+morocc,168,107,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#morocc	896
+moscovia,211,200,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#moscovia	896
+niflheim,200,184,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#niflheim	896
+payon,180,106,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#payon	896
+prontera,150,191,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#prontera	896
+rachel,123,145,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#rachel	896
+umbala,106,160,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#umbala	896
+veins,205,130,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#veins	896
+xmas,155,140,5	duplicate(GuildWarehouse)	Guild Warehouse Manager#xmas	896
+yuno,176,187,3	duplicate(GuildWarehouse)	Guild Warehouse Manager#yuno	896

+ 1 - 0
npc/re/scripts_athena.conf

@@ -101,6 +101,7 @@ npc: npc/re/merchants/enchan_mora.txt
 npc: npc/re/merchants/enchan_rockridge.txt
 npc: npc/re/merchants/flute.txt
 npc: npc/re/merchants/gld_mission_exchange.txt
+npc: npc/re/merchants/guild_warehouse.txt
 npc: npc/re/merchants/hd_refiner.txt
 npc: npc/re/merchants/HorrorToyFactory_merchants.txt
 npc: npc/re/merchants/inn.txt

+ 41 - 0
sql-files/main.sql

@@ -580,6 +580,47 @@ CREATE TABLE IF NOT EXISTS `guild_storage` (
   KEY `guild_id` (`guild_id`)
 ) ENGINE=MyISAM;
 
+--
+-- Table structure for table `guild_storage_log`
+--
+
+CREATE TABLE IF NOT EXISTS `guild_storage_log` (
+  `id` int(11) NOT NULL auto_increment,
+  `guild_id` int(11) unsigned NOT NULL default '0',
+  `time` datetime NOT NULL,
+  `char_id` int(11) NOT NULL default '0',
+  `name` varchar(24) NOT NULL default '',
+  `nameid` smallint(5) unsigned NOT NULL default '0',
+  `amount` int(11) NOT NULL default '1',
+  `identify` smallint(6) NOT NULL default '0',
+  `refine` tinyint(3) unsigned NOT NULL default '0',
+  `attribute` tinyint(4) unsigned NOT NULL default '0',
+  `card0` smallint(5) unsigned NOT NULL default '0',
+  `card1` smallint(5) unsigned NOT NULL default '0',
+  `card2` smallint(5) unsigned NOT NULL default '0',
+  `card3` smallint(5) unsigned NOT NULL default '0',
+  `option_id0` smallint(5) NOT NULL default '0',
+  `option_val0` smallint(5) NOT NULL default '0',
+  `option_parm0` tinyint(3) NOT NULL default '0',
+  `option_id1` smallint(5) NOT NULL default '0',
+  `option_val1` smallint(5) NOT NULL default '0',
+  `option_parm1` tinyint(3) NOT NULL default '0',
+  `option_id2` smallint(5) NOT NULL default '0',
+  `option_val2` smallint(5) NOT NULL default '0',
+  `option_parm2` tinyint(3) NOT NULL default '0',
+  `option_id3` smallint(5) NOT NULL default '0',
+  `option_val3` smallint(5) NOT NULL default '0',
+  `option_parm3` tinyint(3) NOT NULL default '0',
+  `option_id4` smallint(5) NOT NULL default '0',
+  `option_val4` smallint(5) NOT NULL default '0',
+  `option_parm4` tinyint(3) NOT NULL default '0',
+  `expire_time` int(11) unsigned NOT NULL default '0',
+  `unique_id` bigint(20) unsigned NOT NULL default '0',
+  `bound` tinyint(1) unsigned NOT NULL default '0',
+  PRIMARY KEY  (`id`),
+  INDEX (`guild_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=1;
+
 --
 -- Table structure for table `homunculus`
 --

+ 40 - 0
sql-files/upgrades/upgrade_20181010.sql

@@ -0,0 +1,40 @@
+--
+-- Table structure for table `guild_storage_log`
+--
+
+CREATE TABLE IF NOT EXISTS `guild_storage_log` (
+  `id` int(11) NOT NULL auto_increment,
+  `guild_id` int(11) unsigned NOT NULL default '0',
+  `time` datetime NOT NULL,
+  `char_id` int(11) NOT NULL default '0',
+  `name` varchar(24) NOT NULL default '',
+  `nameid` smallint(5) unsigned NOT NULL default '0',
+  `amount` int(11) NOT NULL default '1',
+  `identify` smallint(6) NOT NULL default '0',
+  `refine` tinyint(3) unsigned NOT NULL default '0',
+  `attribute` tinyint(4) unsigned NOT NULL default '0',
+  `card0` smallint(5) unsigned NOT NULL default '0',
+  `card1` smallint(5) unsigned NOT NULL default '0',
+  `card2` smallint(5) unsigned NOT NULL default '0',
+  `card3` smallint(5) unsigned NOT NULL default '0',
+  `option_id0` smallint(5) NOT NULL default '0',
+  `option_val0` smallint(5) NOT NULL default '0',
+  `option_parm0` tinyint(3) NOT NULL default '0',
+  `option_id1` smallint(5) NOT NULL default '0',
+  `option_val1` smallint(5) NOT NULL default '0',
+  `option_parm1` tinyint(3) NOT NULL default '0',
+  `option_id2` smallint(5) NOT NULL default '0',
+  `option_val2` smallint(5) NOT NULL default '0',
+  `option_parm2` tinyint(3) NOT NULL default '0',
+  `option_id3` smallint(5) NOT NULL default '0',
+  `option_val3` smallint(5) NOT NULL default '0',
+  `option_parm3` tinyint(3) NOT NULL default '0',
+  `option_id4` smallint(5) NOT NULL default '0',
+  `option_val4` smallint(5) NOT NULL default '0',
+  `option_parm4` tinyint(3) NOT NULL default '0',
+  `expire_time` int(11) unsigned NOT NULL default '0',
+  `unique_id` bigint(20) unsigned NOT NULL default '0',
+  `bound` tinyint(1) unsigned NOT NULL default '0',
+  PRIMARY KEY  (`id`),
+  INDEX (`guild_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=1;

+ 1 - 2
src/common/mmo.hpp

@@ -705,13 +705,12 @@ struct guild_castle {
 enum e_guild_permission {
 	GUILD_PERM_INVITE	= 0x001,
 	GUILD_PERM_EXPEL	= 0x010,
-#if PACKETVER >= 20140205
 	GUILD_PERM_STORAGE	= 0x100,
+#if PACKETVER >= 20140205
 	GUILD_PERM_ALL		= GUILD_PERM_INVITE|GUILD_PERM_EXPEL|GUILD_PERM_STORAGE,
 #else
 	GUILD_PERM_ALL		= GUILD_PERM_INVITE|GUILD_PERM_EXPEL,
 #endif
-	GUILD_PERM_MASK		= GUILD_PERM_ALL,
 	GUILD_PERM_DEFAULT	= GUILD_PERM_ALL,
 };
 

+ 47 - 0
src/map/clif.cpp

@@ -20501,6 +20501,53 @@ void clif_parse_private_airship_request( int fd, struct map_session_data* sd ){
 #endif
 }
 
+/// Sends out the usage history of the guild storage
+/// 09DA <size>.W <result>.W <count>.W { <id>.L <item id>.W <amount>.L <action>.B <refine>.L <unique id>.Q <identify>.B <item type>.W
+///      { <card item id>.W }*4 <name>.24B <time>.24B <attribute>.B }*count (ZC_ACK_GUILDSTORAGE_LOG)
+void clif_guild_storage_log( struct map_session_data* sd, std::vector<struct guild_log_entry>& log, enum e_guild_storage_log result ){
+#if PACKETVER >= 20140205
+	nullpo_retv( sd );
+
+	int fd = sd->fd;
+	int size = 8;
+	int sub = 83;
+
+	if( result == GUILDSTORAGE_LOG_FINAL_SUCCESS ){
+		size += log.size() * sub;
+	}else{
+		log.clear();
+	}
+
+	WFIFOHEAD(fd, size);
+	WFIFOW(fd, 0) = 0x9DA;
+	WFIFOW(fd, 2) = size;
+	WFIFOW(fd, 4) = result;
+	WFIFOW(fd, 6) = (uint16)log.size();
+
+	if( result == GUILDSTORAGE_LOG_FINAL_SUCCESS ){
+		for (int offset = 8, i = 0; i < log.size(); i++, offset += sub) {
+			struct guild_log_entry& entry = log[i];
+			uint16 viewid = itemdb_viewid( entry.item.nameid );
+
+			WFIFOL(fd, offset) = entry.id;
+			WFIFOW(fd, offset + 4) = viewid > 0 ? viewid : entry.item.nameid;
+			WFIFOL(fd, offset + 6) = (uint16)(entry.amount > 0 ? entry.amount : (entry.amount * -1));
+			WFIFOB(fd, offset + 10) = entry.amount > 0; // action = true(put), false(get)
+			WFIFOL(fd, offset + 11) = entry.item.refine;
+			WFIFOQ(fd, offset + 15) = entry.item.unique_id;
+			WFIFOB(fd, offset + 23) = entry.item.identify;
+			WFIFOW(fd, offset + 24) = itemtype(entry.item.nameid);
+			clif_addcards(WFIFOP(fd, offset + 26), &entry.item);
+			safestrncpy(WFIFOCP(fd, offset + 34), entry.name, NAME_LENGTH);
+			safestrncpy(WFIFOCP(fd, offset + 34 + NAME_LENGTH), entry.time, NAME_LENGTH);
+			WFIFOB(fd, offset + 34 + 2 * NAME_LENGTH) = entry.item.attribute;
+		}
+	}
+
+	WFIFOSET(fd, size);
+#endif
+}
+
 /*==========================================
  * Main client packet processing function
  *------------------------------------------*/

+ 6 - 0
src/map/clif.hpp

@@ -4,6 +4,8 @@
 #ifndef CLIF_HPP
 #define CLIF_HPP
 
+#include <vector>
+
 #include <stdarg.h>
 
 #include "../common/cbasetypes.hpp"
@@ -35,6 +37,8 @@ struct party_booking_ad_info;
 struct sale_item_data;
 struct mail_message;
 struct achievement;
+struct guild_log_entry;
+enum e_guild_storage_log : uint16;
 
 enum e_PacketDBVersion { // packet DB
 	MIN_PACKET_DB  = 0x064,
@@ -1095,4 +1099,6 @@ void clif_attendence_response( struct map_session_data *sd, int32 data );
 
 void clif_weight_limit( struct map_session_data* sd );
 
+void clif_guild_storage_log( struct map_session_data* sd, std::vector<struct guild_log_entry>& log, enum e_guild_storage_log result );
+
 #endif /* CLIF_HPP */

+ 5 - 0
src/map/clif_packetdb.hpp

@@ -2188,6 +2188,11 @@
 	packet(0x09DF,7); // ZC_ACK_WHISPER02
 #endif
 
+// 2014-02-05bRagexeRE
+#if PACKETVER >= 20140205
+	packet(0x09DA,-1);
+#endif
+
 // 2014-10-16Ragexe
 #if PACKETVER >= 20141016
 	packet(0x09DF,7);

+ 3 - 0
src/map/map.cpp

@@ -87,6 +87,7 @@ char vendings_table[32] = "vendings";
 char vending_items_table[32] = "vending_items";
 char market_table[32] = "market";
 char roulette_table[32] = "db_roulette";
+char guild_storage_log_table[32] = "guild_storage_log";
 
 // log database
 char log_db_ip[32] = "127.0.0.1";
@@ -4141,6 +4142,8 @@ int inter_config_read(const char *cfgName)
 			strcpy(market_table, w2);
 		else if (strcmpi(w1, "sales_table") == 0)
 			strcpy(sales_table, w2);
+		else if (strcmpi(w1, "guild_storage_log") == 0)
+			strcpy(guild_storage_log_table, w2);
 		else
 		//Map Server SQL DB
 		if(strcmpi(w1,"map_server_ip")==0)

+ 1 - 0
src/map/map.hpp

@@ -1173,6 +1173,7 @@ extern char vendings_table[32];
 extern char vending_items_table[32];
 extern char market_table[32];
 extern char roulette_table[32];
+extern char guild_storage_log_table[32];
 
 void do_shutdown(void);
 

+ 57 - 0
src/map/script.cpp

@@ -9969,6 +9969,61 @@ BUILDIN_FUNC(guildopenstorage)
 	return SCRIPT_CMD_SUCCESS;
 }
 
+BUILDIN_FUNC(guildopenstorage_log){
+#if PACKETVER < 20140205
+	ShowError( "buildin_guildopenstorage_log: This command requires PACKETVER 2014-02-05 or newer.\n" );
+	return SCRIPT_CMD_FAILURE;
+#else
+	struct map_session_data* sd;
+
+	if( !script_charid2sd( 2, sd ) ){
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	script_pushint( st, storage_guild_log_read( sd ) );
+
+	return SCRIPT_CMD_SUCCESS;
+#endif
+}
+
+BUILDIN_FUNC(guild_has_permission){
+	struct map_session_data* sd;
+
+	if( !script_charid2sd( 3, sd ) ){
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	int permission = script_getnum(st,2);
+
+	if( permission == 0 ){
+		ShowError( "buildin_guild_has_permission: No permission given.\n" );
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if( ( permission & GUILD_PERM_ALL ) == 0 ){
+		ShowError( "buildin_guild_has_permission: Invalid permission '%d'.\n", permission );
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if( !sd->guild ){
+		script_pushint( st, false );
+
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	int position = guild_getposition(sd);
+	
+	if( position < 0 || ( sd->guild->position[position].mode&permission ) != permission ){
+		script_pushint( st, false );
+
+		return SCRIPT_CMD_SUCCESS;
+	}
+	
+	script_pushint( st, true );
+
+	return SCRIPT_CMD_SUCCESS;
+}
+
 /*==========================================
  * Make player use a skill trought item usage
  *------------------------------------------*/
@@ -24083,6 +24138,8 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(gettimestr,"si?"),
 	BUILDIN_DEF(openstorage,""),
 	BUILDIN_DEF(guildopenstorage,""),
+	BUILDIN_DEF(guildopenstorage_log,"?"),
+	BUILDIN_DEF(guild_has_permission,"i?"),
 	BUILDIN_DEF(itemskill,"vi?"),
 	BUILDIN_DEF(produce,"i"),
 	BUILDIN_DEF(cooking,"i"),

+ 11 - 0
src/map/script_constants.hpp

@@ -7306,6 +7306,17 @@
 	export_constant(SKILLDMG_MAX);
 	export_constant(SKILLDMG_CASTER);
 
+	/* guild permissions */
+	export_constant(GUILD_PERM_INVITE);
+	export_constant(GUILD_PERM_EXPEL);
+	export_constant(GUILD_PERM_STORAGE);
+	export_constant(GUILD_PERM_ALL);
+
+	/* guild storage log */
+	export_constant(GUILDSTORAGE_LOG_FINAL_SUCCESS);
+	export_constant(GUILDSTORAGE_LOG_EMPTY);
+	export_constant(GUILDSTORAGE_LOG_FAILED);
+
 	#undef export_constant
 	#undef export_constant2
 	#undef export_parameter

+ 120 - 0
src/map/storage.cpp

@@ -627,6 +627,117 @@ char storage_guild_storageopen(struct map_session_data* sd)
 	return GSTORAGE_OPEN;
 }
 
+void storage_guild_log( struct map_session_data* sd, struct item* item, int16 amount ){
+	int i;
+	SqlStmt* stmt = SqlStmt_Malloc(mmysql_handle);
+	StringBuf buf;
+	StringBuf_Init(&buf);
+
+	StringBuf_Printf(&buf, "INSERT INTO `%s` (`time`, `guild_id`, `char_id`, `name`, `nameid`, `amount`, `identify`, `refine`, `attribute`, `unique_id`, `bound`", guild_storage_log_table);
+	for (i = 0; i < MAX_SLOTS; ++i)
+		StringBuf_Printf(&buf, ", `card%d`", i);
+	for (i = 0; i < MAX_ITEM_RDM_OPT; ++i) {
+		StringBuf_Printf(&buf, ", `option_id%d`", i);
+		StringBuf_Printf(&buf, ", `option_val%d`", i);
+		StringBuf_Printf(&buf, ", `option_parm%d`", i);
+	}
+	StringBuf_Printf(&buf, ") VALUES(NOW(),'%u','%u', '%s', '%d', '%d','%d','%d','%d','%" PRIu64 "','%d'",
+		sd->status.guild_id, sd->status.char_id, sd->status.name, item->nameid, amount, item->identify, item->refine,item->attribute, item->unique_id, item->bound);
+
+	for (i = 0; i < MAX_SLOTS; i++)
+		StringBuf_Printf(&buf, ",'%d'", item->card[i]);
+	for (i = 0; i < MAX_ITEM_RDM_OPT; i++)
+		StringBuf_Printf(&buf, ",'%d','%d','%d'", item->option[i].id, item->option[i].value, item->option[i].param);
+	StringBuf_Printf(&buf, ")");
+
+	if (SQL_SUCCESS != SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf)) || SQL_SUCCESS != SqlStmt_Execute(stmt))
+		SqlStmt_ShowDebug(stmt);
+
+	SqlStmt_Free(stmt);
+	StringBuf_Destroy(&buf);
+}
+
+enum e_guild_storage_log storage_guild_log_read_sub( struct map_session_data* sd, std::vector<struct guild_log_entry>& log, uint32 max ){
+	StringBuf buf;
+	int j;
+
+	StringBuf_Init(&buf);
+
+	StringBuf_AppendStr(&buf, "SELECT `id`, `time`, `name`, `amount`");
+	StringBuf_AppendStr(&buf, " , `nameid`, `identify`, `refine`, `attribute`, `expire_time`, `bound`, `unique_id`");
+	for (j = 0; j < MAX_SLOTS; ++j)
+		StringBuf_Printf(&buf, ", `card%d`", j);
+	for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
+		StringBuf_Printf(&buf, ", `option_id%d`", j);
+		StringBuf_Printf(&buf, ", `option_val%d`", j);
+		StringBuf_Printf(&buf, ", `option_parm%d`", j);
+	}
+	StringBuf_Printf(&buf, " FROM `%s` WHERE `guild_id`='%u'", guild_storage_log_table, sd->status.guild_id );
+	StringBuf_Printf(&buf, " ORDER BY `time` DESC LIMIT %u", max);
+
+	SqlStmt* stmt = SqlStmt_Malloc(mmysql_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 GUILDSTORAGE_LOG_FAILED;
+	}
+
+	struct guild_log_entry entry;
+
+	// General data
+	SqlStmt_BindColumn(stmt, 0, SQLDT_UINT,      &entry.id,               0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 1, SQLDT_STRING,    &entry.time, sizeof(entry.time), NULL, NULL);
+	SqlStmt_BindColumn(stmt, 2, SQLDT_STRING,    &entry.name, sizeof(entry.name), NULL, NULL);
+	SqlStmt_BindColumn(stmt, 3, SQLDT_SHORT,     &entry.amount,           0, NULL, NULL);
+
+	// Item data
+	SqlStmt_BindColumn(stmt, 4, SQLDT_USHORT,    &entry.item.nameid,      0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 5, SQLDT_CHAR,      &entry.item.identify,    0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 6, SQLDT_CHAR,      &entry.item.refine,      0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 7, SQLDT_CHAR,      &entry.item.attribute,   0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 8, SQLDT_UINT,      &entry.item.expire_time, 0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 9, SQLDT_UINT,      &entry.item.bound,       0, NULL, NULL);
+	SqlStmt_BindColumn(stmt, 10, SQLDT_UINT64,    &entry.item.unique_id,   0, NULL, NULL);
+	for( j = 0; j < MAX_SLOTS; ++j )
+		SqlStmt_BindColumn(stmt, 11+j, SQLDT_USHORT, &entry.item.card[j], 0, NULL, NULL);
+	for( j = 0; j < MAX_ITEM_RDM_OPT; ++j ) {
+		SqlStmt_BindColumn(stmt, 11+MAX_SLOTS+j*3, SQLDT_SHORT, &entry.item.option[j].id, 0, NULL, NULL);
+		SqlStmt_BindColumn(stmt, 11+MAX_SLOTS+j*3+1, SQLDT_SHORT, &entry.item.option[j].value, 0, NULL, NULL);
+		SqlStmt_BindColumn(stmt, 11+MAX_SLOTS+j*3+2, SQLDT_CHAR, &entry.item.option[j].param, 0, NULL, NULL);
+	}
+
+	log.reserve(max);
+
+	while( SQL_SUCCESS == SqlStmt_NextRow(stmt) ){
+		log.push_back( entry );
+	}
+
+	Sql_FreeResult(mmysql_handle);
+	StringBuf_Destroy(&buf);
+	SqlStmt_Free(stmt);
+
+	if( log.empty() ){
+		return GUILDSTORAGE_LOG_EMPTY;
+	}
+
+	return GUILDSTORAGE_LOG_FINAL_SUCCESS;
+}
+
+enum e_guild_storage_log storage_guild_log_read( struct map_session_data* sd ){
+	std::vector<struct guild_log_entry> log;
+
+	// ( 65535(maximum packet size) - 8(header) ) / 83 (entry size) = 789 (-1 for safety)
+	enum e_guild_storage_log ret = storage_guild_log_read_sub( sd, log, 788 );
+
+	clif_guild_storage_log( sd, log, ret );
+
+	return ret;
+}
+
 /**
  * Attempt to add an item in guild storage, then refresh it
  * @param sd : player attempting to open the guild_storage
@@ -671,6 +782,9 @@ bool storage_guild_additem(struct map_session_data* sd, struct s_storage* stor,
 				stor->u.items_guild[i].amount += amount;
 				clif_storageitemadded(sd,&stor->u.items_guild[i],i,amount);
 				stor->dirty = true;
+
+				storage_guild_log( sd, &stor->u.items_guild[i], amount );
+
 				return true;
 			}
 		}
@@ -687,6 +801,9 @@ bool storage_guild_additem(struct map_session_data* sd, struct s_storage* stor,
 	clif_storageitemadded(sd,&stor->u.items_guild[i],i,amount);
 	clif_updatestorageamount(sd, stor->amount, stor->max_amount);
 	stor->dirty = true;
+
+	storage_guild_log( sd, &stor->u.items_guild[i], amount );
+
 	return true;
 }
 
@@ -752,6 +869,9 @@ bool storage_guild_delitem(struct map_session_data* sd, struct s_storage* stor,
 	if(!stor->u.items_guild[n].nameid || stor->u.items_guild[n].amount < amount)
 		return false;
 
+	// Log before removing it
+	storage_guild_log( sd, &stor->u.items_guild[n], -amount );
+
 	stor->u.items_guild[n].amount -= amount;
 
 	if(!stor->u.items_guild[n].amount) {

+ 19 - 0
src/map/storage.hpp

@@ -4,7 +4,10 @@
 #ifndef STORAGE_HPP
 #define STORAGE_HPP
 
+#include <vector>
+
 #include "../common/cbasetypes.hpp"
+#include "../common/mmo.hpp"
 
 struct s_storage;
 struct item;
@@ -30,6 +33,21 @@ enum e_guild_storage_flags : uint8 {
 	GSTORAGE_NO_PERMISSION
 };
 
+enum e_guild_storage_log : uint16 {
+	GUILDSTORAGE_LOG_SUCCESS,
+	GUILDSTORAGE_LOG_FINAL_SUCCESS,
+	GUILDSTORAGE_LOG_EMPTY,
+	GUILDSTORAGE_LOG_FAILED,
+};
+
+struct guild_log_entry{
+	uint32 id;
+	char name[NAME_LENGTH];
+	char time[NAME_LENGTH];
+	struct item item;
+	int16 amount;
+};
+
 const char *storage_getName(uint8 id);
 bool storage_exists(uint8 id);
 
@@ -51,6 +69,7 @@ struct s_storage* guild2storage(int guild_id);
 struct s_storage* guild2storage2(int guild_id);
 void storage_guild_delete(int guild_id);
 char storage_guild_storageopen(struct map_session_data *sd);
+enum e_guild_storage_log storage_guild_log_read( struct map_session_data* sd );
 bool storage_guild_additem(struct map_session_data *sd,struct s_storage *stor,struct item *item_data,int amount);
 bool storage_guild_additem2(struct s_storage* stor, struct item* item, int amount);
 bool storage_guild_delitem(struct map_session_data *sd,struct s_storage *stor,int n,int amount);