Ver código fonte

Introducing new Store Taxes
* Replacement of Vending Tax config
* Added tax for Buying Store
* Allow multiple taxes & minimum transactions to support 2015 clients or newer

Cydh Ramdh 7 anos atrás
pai
commit
77fec5825e

+ 3 - 0
conf/battle/misc.conf

@@ -180,3 +180,6 @@ mail_delay: 1000
 
 // Hides items from the player's favorite tab from being sold to a NPC. (Note 1)
 hide_fav_sell: no
+
+// Display tax info given from conf/tax.yml (Note 1)
+display_tax_info: yes

+ 28 - 0
conf/import-tmpl/tax.yml

@@ -0,0 +1,28 @@
+## Taxes for selling something (vending)
+## The zeny that you received will be reduced after tax
+#Selling:
+#  # Tax applies in total transaction
+#  In_Total:
+#  - Minimal_Value: 0
+#    Tax: 0
+#
+#  # Tax by selling entry
+#  Each_Entry:
+#  # 10% if >= 100,000,001
+#  - Minimal_Value: 100000001
+#    Tax: 1000
+#
+## Taxes for buying something (buyingstore)
+## The zeny that you must be paid will be increased after tax
+## but player who sells still get the value without tax
+#Buying:
+#  # Tax applies in total transaction
+#  In_Total:
+#  - Minimal_Value: 0
+#    Tax: 0
+#
+#  # Tax by buying entry
+#  Each_Entry:
+#  # 10% if >= 100,000,001
+#  - Minimal_Value: 100000001
+#    Tax: 1000

+ 3 - 0
conf/map_athena.conf

@@ -122,6 +122,9 @@ charhelp_txt: conf/charhelp.txt
 // Load channel config from
 channel_conf: conf/channels.conf
 
+// Load Store tax config from
+tax_conf: conf/tax.yml
+
 // Maps:
 import: conf/maps_athena.conf
 

+ 8 - 1
conf/msg_conf/map_msg.conf

@@ -831,7 +831,14 @@
 // Achievements
 772: Achievements are disabled.
 
-//773-899 free
+// Tax:
+773: [ Tax Information ]
+774: %s : %u %c %.2f%% => %u
+775: [ Total Transaction Tax ]
+776: Tax: %.2f%% Minimal Transaction: %u
+777: %s : %.0f => %.0f
+
+//778-899 free
 
 //------------------------------------
 // More atcommands message

+ 52 - 0
conf/tax.yml

@@ -0,0 +1,52 @@
+# Taxes for selling something (vending)
+# The zeny that you received will be reduced after tax
+Selling:
+  # Tax applies in total transaction
+  In_Total:
+  - Minimal_Value: 0
+    Tax: 0
+
+  # Tax by selling entry
+  Each_Entry:
+  # 10% if >= 100,000,001
+  - Minimal_Value: 100000001
+    Tax: 1000
+  # 8% if >= 10,000,001
+  - Minimal_Value: 10000001
+    Tax: 800
+  # 6% if >= 1,000,001
+  - Minimal_Value: 1000001
+    Tax: 600
+  # 4% if >= 100,001
+  - Minimal_Value: 100001
+    Tax: 400
+  # 2% if >= 10,001
+  - Minimal_Value: 10001
+    Tax: 200
+
+# Taxes for buying something (buyingstore)
+# The zeny that you must be paid will be increased after tax
+# but player who sells still get the value without tax
+Buying:
+  # Tax applies in total transaction
+  In_Total:
+  - Minimal_Value: 0
+    Tax: 0
+
+  # Tax by buying entry
+  Each_Entry:
+  # 10% if >= 100,000,001
+  - Minimal_Value: 100000001
+    Tax: 1000
+  # 8% if >= 10,000,001
+  - Minimal_Value: 10000001
+    Tax: 800
+  # 6% if >= 1,000,001
+  - Minimal_Value: 1000001
+    Tax: 600
+  # 4% if >= 100,001
+  - Minimal_Value: 100001
+    Tax: 400
+  # 2% if >= 10,001
+  - Minimal_Value: 10001
+    Tax: 200

+ 1 - 0
src/map/battle.cpp

@@ -8492,6 +8492,7 @@ static const struct _battle_data {
 	{ "allow_bound_sell",                   &battle_config.allow_bound_sell,                0,      0,      0x3,            },
 	{ "event_refine_chance",                &battle_config.event_refine_chance,             0,      0,      1,              },
 	{ "autoloot_adjust",                    &battle_config.autoloot_adjust,                 0,      0,      1,              },
+	{ "display_tax_info",                   &battle_config.display_tax_info,                0,      0,      1,              },
 
 #include "../custom/battle_config_init.inc"
 };

+ 1 - 0
src/map/battle.hpp

@@ -637,6 +637,7 @@ struct Battle_Config
 	int allow_bound_sell;
 	int event_refine_chance;
 	int autoloot_adjust;
+	int display_tax_info;
 
 #include "../custom/battle_config_struct.inc"
 };

+ 72 - 5
src/map/buyingstore.cpp

@@ -20,6 +20,7 @@
 #include "pc.hpp"  // struct map_session_data
 #include "chrif.hpp"
 #include "npc.hpp"
+#include "tax.hpp"
 
 //Autotrader
 static DBMap *buyingstore_autotrader_db; /// Holds autotrader info: char_id -> struct s_autotrader
@@ -117,7 +118,9 @@ int8 buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha
 {
 	unsigned int i, weight, listidx;
 	char message_sql[MESSAGE_SIZE*2];
+	char msg[CHAT_SIZE_MAX];
 	StringBuf buf;
+	s_tax *taxdata;
 
 	nullpo_retr(1, sd);
 
@@ -160,10 +163,16 @@ int8 buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha
 
 	weight = sd->weight;
 
+	taxdata = tax_get(TAX_BUYING);
+	if (battle_config.display_tax_info) {
+		clif_displaymessage(sd->fd, msg_txt(sd, 773)); // [ Tax Information ]
+	}
+
 	// check item list
 	for( i = 0; i < count; i++ )
 	{// itemlist: <name id>.W <amount>.W <price>.L
 		unsigned short nameid, amount;
+		unsigned short tax;
 		int price, idx;
 		struct item_data* id;
 
@@ -205,6 +214,14 @@ int8 buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha
 		sd->buyingstore.items[i].nameid = nameid;
 		sd->buyingstore.items[i].amount = amount;
 		sd->buyingstore.items[i].price  = price;
+		tax = taxdata->get_tax(taxdata->each, price);
+		sd->buyingstore.items[i].price_vat = (size_t)(price + price / 10000. * tax);
+
+		if (battle_config.display_tax_info) {
+			memset(msg, '\0', CHAT_SIZE_MAX);
+			sprintf(msg, msg_txt(sd, 774), itemdb_jname(nameid), price, '+', tax / 100., sd->buyingstore.items[i].price_vat); // %s : %u %c %.2f%% => %u
+			clif_displaymessage(sd->fd, msg);
+		}
 	}
 
 	if( i != count )
@@ -221,6 +238,15 @@ int8 buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha
 		return 7;
 	}
 
+	if (battle_config.display_tax_info && taxdata->total.size()) {
+		clif_displaymessage(sd->fd, msg_txt(sd, 775)); // [ Total Transaction Tax ]
+		for (const auto &tax : taxdata->total) {
+			memset(msg, '\0', CHAT_SIZE_MAX);
+			sprintf(msg, msg_txt(sd, 776), tax.tax / 100., tax.minimal); // Tax: %.2f%% Minimal Transaction: %u
+			clif_displaymessage(sd->fd, msg);
+		}
+	}
+
 	// success
 	sd->state.buyingstore = true;
 	sd->buyer_id = buyingstore_getuid();
@@ -315,6 +341,36 @@ void buyingstore_open(struct map_session_data* sd, uint32 account_id)
 	clif_buyingstore_itemlist(sd, pl_sd);
 }
 
+static unsigned short buyinstore_tax_intotal(struct map_session_data* sd, const uint8* itemlist, int count) {
+	s_tax *tax = tax_get(TAX_BUYING);
+
+	double total = 0;
+	int i;
+
+	if (tax->total.size() == 0)
+		return 0;
+
+	for (i = 0; i < count; i++) {
+		unsigned short nameid, amount, listidx;
+		int index;
+
+		index = RBUFW(itemlist, i * 6 + 0) - 2;
+		nameid = RBUFW(itemlist, i * 6 + 2);
+		amount = RBUFW(itemlist, i * 6 + 4);
+
+		if (amount <= 0)
+			continue;
+
+		ARR_FIND(0, sd->buyingstore.slots, listidx, sd->buyingstore.items[listidx].nameid == nameid);
+		if (listidx == sd->buyingstore.slots || sd->buyingstore.items[listidx].amount == 0)
+			continue;
+
+		total += ((double)sd->buyingstore.items[listidx].price * amount);
+	}
+
+	return tax->get_tax(tax->total, total);
+}
+
 /**
 * Start transaction
 * @param sd Player/Seller
@@ -324,7 +380,7 @@ void buyingstore_open(struct map_session_data* sd, uint32 account_id)
 */
 void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count)
 {
-	int zeny = 0;
+	size_t zeny = 0, zeny_paid = 0;
 	unsigned int i, weight, listidx, k;
 	struct map_session_data* pl_sd;
 
@@ -367,6 +423,7 @@ void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned
 		pl_sd->buyingstore.zenylimit = pl_sd->status.zeny;
 	}
 	weight = pl_sd->weight;
+	int tax_total = buyinstore_tax_intotal(pl_sd, itemlist, count);
 
 	// check item list
 	for( i = 0; i < count; i++ )
@@ -429,7 +486,9 @@ void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned
 		}
 		weight+= amount*sd->inventory_data[index]->weight;
 
-		if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny )
+		zeny_paid += amount * pl_sd->buyingstore.items[listidx].price_vat;
+		zeny_paid += (size_t)(zeny_paid / 10000. * tax_total);
+		if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit - zeny_paid)
 		{// buyer does not have enough zeny
 			clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid);
 			return;
@@ -449,6 +508,8 @@ void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned
 
 		ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid );
 		zeny = amount*pl_sd->buyingstore.items[listidx].price;
+		zeny_paid = amount*pl_sd->buyingstore.items[listidx].price_vat;
+		zeny_paid = zeny_paid + (size_t)(zeny_paid / 10000. * tax_total);
 
 		// move item
 		pc_additem(pl_sd, &sd->inventory.u.items_inventory[index], amount, LOG_TYPE_BUYING_STORE);
@@ -466,13 +527,19 @@ void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned
 		}
 
 		// pay up
-		pc_payzeny(pl_sd, zeny, LOG_TYPE_BUYING_STORE, sd);
+		pc_payzeny(pl_sd, zeny_paid, LOG_TYPE_BUYING_STORE, sd);
 		pc_getzeny(sd, zeny, LOG_TYPE_BUYING_STORE, pl_sd);
-		pl_sd->buyingstore.zenylimit-= zeny;
+		pl_sd->buyingstore.zenylimit-= zeny_paid;
+
+		if (battle_config.display_tax_info) {
+			char msg[CHAT_SIZE_MAX];
+			sprintf(msg, msg_txt(sd, 777), itemdb_jname(nameid), (double)zeny, (double)zeny_paid);
+			clif_displaymessage(pl_sd->fd, msg);
+		}
 
 		// notify clients
 		clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price);
-		clif_buyingstore_update_item(pl_sd, nameid, amount, sd->status.char_id, zeny);
+		clif_buyingstore_update_item(pl_sd, nameid, amount, sd->status.char_id, zeny_paid);
 	}
 
 	if( save_settings&CHARSAVE_VENDING ) {

+ 1 - 0
src/map/buyingstore.hpp

@@ -18,6 +18,7 @@ struct s_buyingstore_item
 	int price;
 	unsigned short amount;
 	unsigned short nameid;
+	size_t price_vat;
 };
 
 struct s_buyingstore

+ 2 - 2
src/map/clif.cpp

@@ -7199,7 +7199,7 @@ void clif_vendinglist(struct map_session_data* sd, int id, struct s_vending* ven
 ///     5 = "cannot use an npc shop while in a trade"
 ///     6 = Because the store information was incorrect the item was not purchased.
 ///     7 = No sales information.
-void clif_buyvending(struct map_session_data* sd, int index, int amount, int fail)
+void clif_buyvending(struct map_session_data* sd, int index, int amount, enum e_vending_ack ack)
 {
 	int fd;
 
@@ -7210,7 +7210,7 @@ void clif_buyvending(struct map_session_data* sd, int index, int amount, int fai
 	WFIFOW(fd,0) = 0x135;
 	WFIFOW(fd,2) = index+2;
 	WFIFOW(fd,4) = amount;
-	WFIFOB(fd,6) = fail;
+	WFIFOB(fd,6) = ack;
 	WFIFOSET(fd,packet_len(0x135));
 }
 

+ 11 - 1
src/map/clif.hpp

@@ -174,6 +174,16 @@ enum e_bossmap_info {
 	BOSS_INFO_DEAD,
 };
 
+enum e_vending_ack : uint8_t {
+	VENDING_ACK_OK = 0,
+	VENDING_ACK_NOZENY = 1,
+	VENDING_ACK_OVERWEIGHT = 2,
+	VENDING_ACK_NOSTOCK = 4,
+	VENDING_ACK_NOTALKNPC = 5,
+	VENDING_ACK_INVALID = 6,
+	VENDING_ACK_NOITEM = 7,
+};
+
 #define packet_len(cmd) packet_db[cmd].len
 extern struct s_packet_db packet_db[MAX_PACKET_DB+1];
 extern int packet_db_ack[MAX_ACK_FUNC + 1];
@@ -730,7 +740,7 @@ void clif_openvendingreq(struct map_session_data* sd, int num);
 void clif_showvendingboard(struct block_list* bl, const char* message, int fd);
 void clif_closevendingboard(struct block_list* bl, int fd);
 void clif_vendinglist(struct map_session_data* sd, int id, struct s_vending* vending);
-void clif_buyvending(struct map_session_data* sd, int index, int amount, int fail);
+void clif_buyvending(struct map_session_data* sd, int index, int amount, enum e_vending_ack ack);
 void clif_openvending(struct map_session_data* sd, int id, struct s_vending* vending);
 void clif_vendingreport(struct map_session_data* sd, int index, int amount, uint32 char_id, int zeny);
 

+ 3 - 0
src/map/map-server.vcxproj

@@ -204,6 +204,7 @@
     <ClInclude Include="skill.hpp" />
     <ClInclude Include="status.hpp" />
     <ClInclude Include="storage.hpp" />
+    <ClInclude Include="tax.hpp" />
     <ClInclude Include="trade.hpp" />
     <ClInclude Include="unit.hpp" />
     <ClInclude Include="vending.hpp" />
@@ -249,6 +250,7 @@
     <ClCompile Include="skill.cpp" />
     <ClCompile Include="status.cpp" />
     <ClCompile Include="storage.cpp" />
+    <ClCompile Include="tax.cpp" />
     <ClCompile Include="trade.cpp" />
     <ClCompile Include="unit.cpp" />
     <ClCompile Include="vending.cpp" />
@@ -280,6 +282,7 @@
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\map_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\map_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\packet_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\packet_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\script_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\script_conf.txt')" />
+    <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\tax.yml" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\tax.yml')" />
     <Copy SourceFiles="$(SolutionDir)conf\msg_conf\import-tmpl\map_msg_chn_conf.txt" DestinationFolder="$(SolutionDir)conf\msg_conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\msg_conf\import\map_msg_chn_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\msg_conf\import-tmpl\map_msg_eng_conf.txt" DestinationFolder="$(SolutionDir)conf\msg_conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\msg_conf\import\map_msg_eng_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\msg_conf\import-tmpl\map_msg_frn_conf.txt" DestinationFolder="$(SolutionDir)conf\msg_conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\msg_conf\import\map_msg_frn_conf.txt')" />

+ 6 - 0
src/map/map-server.vcxproj.filters

@@ -134,6 +134,9 @@
     <ClInclude Include="storage.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="tax.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="trade.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -259,6 +262,9 @@
     <ClCompile Include="storage.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="tax.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="trade.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>

+ 5 - 0
src/map/map.cpp

@@ -48,6 +48,7 @@
 #include "battle.hpp"
 #include "log.hpp"
 #include "mob.hpp"
+#include "tax.hpp"
 
 char default_codepage[32] = "";
 
@@ -3885,6 +3886,8 @@ int map_config_read(const char *cfgName)
 			console_msg_log = atoi(w2);//[Ind]
 		else if (strcmpi(w1, "console_log_filepath") == 0)
 			safestrncpy(console_log_filepath, w2, sizeof(console_log_filepath));
+		else if (strcmpi(w1, "tax_conf") == 0)
+			tax_set_conf(w2);
 		else if (strcmpi(w1, "import") == 0)
 			map_config_read(w2);
 		else
@@ -4425,6 +4428,7 @@ void do_final(void)
 	do_final_vending();
 	do_final_buyingstore();
 	do_final_path();
+	do_final_tax();
 
 	map_db->destroy(map_db, map_db_final);
 
@@ -4757,6 +4761,7 @@ int do_init(int argc, char *argv[])
 	do_init_elemental();
 	do_init_quest();
 	do_init_achievement();
+	do_init_tax();
 	do_init_npc();
 	do_init_unit();
 	do_init_battleground();

+ 93 - 0
src/map/tax.cpp

@@ -0,0 +1,93 @@
+// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "tax.hpp"
+
+#include <string.h>
+#include <yaml-cpp/yaml.h>
+
+#include "../common/cbasetypes.h"
+#include "../common/showmsg.h"
+
+#include "buyingstore.hpp"
+#include "vending.hpp"
+
+static struct s_tax TaxDB[TAX_MAX];
+std::string tax_conf = "conf/store_tax.yml";
+
+unsigned short s_tax::get_tax(const std::vector <struct s_tax_entry> entry, double price) {
+	const auto &tax = std::find_if(entry.begin(), entry.end(),
+		[&price](const s_tax_entry &e) { return price >= e.minimal; });
+	return tax != entry.end() ? tax->tax : 0;
+}
+
+struct s_tax *tax_get(enum e_tax_type type) {
+	if (type < TAX_SELLING || type >= TAX_MAX)
+		return NULL;
+	return &TaxDB[type];
+}
+
+static void tax_readdb_sub(const YAML::Node &node, struct s_tax *taxdata, int *count, const std::string &source) {
+	if (node["In_Total"].IsDefined()) {
+		for (const auto &tax : node["In_Total"]) {
+			if (tax["Minimal_Value"].IsDefined() && tax["Tax"].IsDefined()) {
+				struct s_tax_entry entry;
+				entry.minimal = tax["Minimal_Value"].as<size_t>();
+				entry.tax = tax["Tax"].as<unsigned short>();
+				taxdata->total.push_back(entry);
+				std::sort(taxdata->total.begin(), taxdata->total.end(),
+					[](const s_tax_entry &a, const s_tax_entry &b) -> bool { return a.minimal > b.minimal; });
+				(*count)++;
+			}
+		}
+	}
+	if (node["Each_Entry"].IsDefined()) {
+		for (const auto &tax : node["Each_Entry"]) {
+			if (tax["Minimal_Value"].IsDefined() && tax["Tax"].IsDefined()) {
+				struct s_tax_entry entry;
+				entry.minimal = tax["Minimal_Value"].as<size_t>();
+				entry.tax = tax["Tax"].as<unsigned short>();
+				taxdata->each.push_back(entry);
+				std::sort(taxdata->each.begin(), taxdata->each.end(),
+					[](const s_tax_entry &a, const s_tax_entry &b) -> bool { return a.minimal > b.minimal; });
+				(*count)++;
+			}
+		}
+	}
+}
+
+void tax_readdb(void) {
+	YAML::Node config;
+
+	try {
+		config = YAML::LoadFile(tax_conf);
+	}
+	catch (...) {
+		ShowError("tax_read_db: Cannot read '" CL_WHITE "%s" CL_RESET "'.\n", tax_conf.c_str());
+		return;
+	}
+
+	int count = 0;
+	if (config["Selling"].IsDefined())
+		tax_readdb_sub(config["Selling"], &TaxDB[TAX_SELLING], &count, tax_conf);
+	if (config["Buying"].IsDefined())
+		tax_readdb_sub(config["Buying"], &TaxDB[TAX_BUYING], &count, tax_conf);
+
+	ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'\n", count, tax_conf.c_str());
+}
+
+void tax_reload_vendors(void) {
+	// reload VAT price on vendors
+}
+
+void tax_set_conf(const std::string filename) {
+	tax_conf = filename;
+}
+
+void do_init_tax(void) {
+	tax_readdb();
+}
+
+void do_final_tax(void) {
+
+}

+ 42 - 0
src/map/tax.hpp

@@ -0,0 +1,42 @@
+// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef _TAX_HPP_
+#define _TAX_HPP_
+
+#include <string>
+#include <vector>
+
+#include "../common/cbasetypes.h"
+
+#pragma once
+
+enum e_tax_type : unsigned char {
+	TAX_SELLING = 0,
+	TAX_BUYING,
+	TAX_MAX,
+};
+
+struct s_tax_entry {
+	size_t minimal;
+	unsigned short tax;
+};
+
+struct s_tax {
+	std::vector <struct s_tax_entry> each;
+	std::vector <struct s_tax_entry> total;
+
+	unsigned short get_tax(const std::vector <struct s_tax_entry>, double);
+};
+
+struct s_tax *tax_get(enum e_tax_type type);
+void tax_reloadall(void);
+
+void tax_readdb(void);
+void tax_reload_vendors(void);
+void tax_set_conf(const std::string filename);
+
+void do_init_tax(void);
+void do_final_tax(void);
+
+#endif /* _TAX_HPP_ */

+ 87 - 27
src/map/vending.cpp

@@ -2,6 +2,9 @@
 // For more information, see LICENCE in the main folder
 
 #include "vending.hpp"
+
+#include <stdlib.h> // atoi
+
 #include "../common/nullpo.h"
 #include "../common/malloc.h" // aMalloc, aFree
 #include "../common/showmsg.h" // ShowInfo
@@ -21,8 +24,7 @@
 #include "battle.hpp"
 #include "log.hpp"
 #include "achievement.hpp"
-
-#include <stdlib.h> // atoi
+#include "tax.hpp"
 
 static uint32 vending_nextid = 0; ///Vending_id counter
 static DBMap *vending_db; ///DB holder the vender : charid -> map_session_data
@@ -96,18 +98,36 @@ void vending_vendinglistreq(struct map_session_data* sd, int id)
 	clif_vendinglist(sd, id, vsd->vending);
 }
 
-/**
- * Calculates taxes for vending
- * @param sd: Vender
- * @param zeny: Total amount to tax
- * @return Total amount after taxes
- */
-static double vending_calc_tax(struct map_session_data *sd, double zeny)
-{
-	if (battle_config.vending_tax && zeny >= battle_config.vending_tax_min)
-		zeny -= zeny * (battle_config.vending_tax / 10000.);
+static unsigned short vending_tax_intotal(struct map_session_data* vsd, const uint8* data, int count) {
+	s_tax *tax = tax_get(TAX_SELLING);
+	double total = 0;
+	int i, vend_list[MAX_VENDING]; // against duplicate packets
+
+	if (tax->total.size() == 0)
+		return 0;
+
+	for (i = 0; i < count; i++) {
+		short amount = *(uint16*)(data + 4 * i + 0);
+		short idx = *(uint16*)(data + 4 * i + 2), j;
+		idx -= 2;
+
+		if (amount <= 0)
+			continue;
+
+		// check of item index in the cart
+		if (idx < 0 || idx >= MAX_CART)
+			continue;
+
+		ARR_FIND(0, vsd->vend_num, j, vsd->vending[j].index == idx);
+		if (j == vsd->vend_num)
+			continue; //picked non-existing item
+		else
+			vend_list[i] = j;
+
+		total += ((double)vsd->vending[j].value * amount);
+	}
 
-	return zeny;
+	return tax->get_tax(tax->total, total);
 }
 
 /**
@@ -122,7 +142,7 @@ static double vending_calc_tax(struct map_session_data *sd, double zeny)
 void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count)
 {
 	int i, j, cursor, w, new_ = 0, blank, vend_list[MAX_VENDING];
-	double z;
+	double z, z_gained = 0;
 	struct s_vending vending[MAX_VENDING]; // against duplicate packets
 	struct map_session_data* vsd = map_id2sd(aid);
 
@@ -131,7 +151,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 		return; // invalid shop
 
 	if( vsd->vender_id != uid ) { // shop has changed
-		clif_buyvending(sd, 0, 0, 6);  // store information was incorrect
+		clif_buyvending(sd, 0, 0, VENDING_ACK_INVALID);  // store information was incorrect
 		return;
 	}
 
@@ -151,6 +171,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 	// some checks
 	z = 0.; // zeny counter
 	w = 0;  // weight counter
+	int tax_total = vending_tax_intotal(vsd, data, count);
 	for( i = 0; i < count; i++ ) {
 		short amount = *(uint16*)(data + 4*i + 0);
 		short idx    = *(uint16*)(data + 4*i + 2);
@@ -170,18 +191,20 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 			vend_list[i] = j;
 
 		z += ((double)vsd->vending[j].value * (double)amount);
+		z_gained += ((double)vsd->vending[j].value_vat * (double)amount);
+		z_gained = z_gained - (z_gained / 10000 * tax_total);
 		if( z > (double)sd->status.zeny || z < 0. || z > (double)MAX_ZENY ) {
-			clif_buyvending(sd, idx, amount, 1); // you don't have enough zeny
+			clif_buyvending(sd, idx, amount, VENDING_ACK_NOZENY); // you don't have enough zeny
 			return;
 		}
-		if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) {
-			clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // too much zeny = overflow
+		if(z_gained + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) {
+			clif_buyvending(sd, idx, vsd->vending[j].amount, VENDING_ACK_NOSTOCK); // too much zeny = overflow
 			return;
 
 		}
 		w += itemdb_weight(vsd->cart.u.items_cart[idx].nameid) * amount;
 		if( w + sd->weight > sd->max_weight ) {
-			clif_buyvending(sd, idx, amount, 2); // you can not buy, because overweight
+			clif_buyvending(sd, idx, amount, VENDING_ACK_OVERWEIGHT); // you can not buy, because overweight
 			return;
 		}
 
@@ -193,7 +216,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 		// here, we check cumulative amounts
 		if( vending[j].amount < amount ) {
 			// send more quantity is not a hack (an other player can have buy items just before)
-			clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // not enough quantity
+			clif_buyvending(sd, idx, vsd->vending[j].amount, VENDING_ACK_NOSTOCK); // not enough quantity
 			return;
 		}
 
@@ -214,19 +237,30 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 
 	pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd);
 	achievement_update_objective(sd, AG_SPEND_ZENY, 1, (int)z);
-	z = vending_calc_tax(sd, z);
-	pc_getzeny(vsd, (int)z, LOG_TYPE_VENDING, sd);
+	pc_getzeny(vsd, (int)z_gained, LOG_TYPE_VENDING, sd);
+
+	if (battle_config.etc_log) {
+		ShowInfo("vending_purchasereq: AID=%u CID=%u gained %u zeny. AID=%u CID=%u paid %u zeny\n",
+			vsd->status.account_id, vsd->status.char_id, (size_t)z_gained,
+			sd->status.account_id, sd->status.char_id, (size_t)z);
+	}
 
 	for( i = 0; i < count; i++ ) {
 		short amount = *(uint16*)(data + 4*i + 0);
 		short idx    = *(uint16*)(data + 4*i + 2);
 		idx -= 2;
-		z = 0.; // zeny counter
 
 		// vending item
 		pc_additem(sd, &vsd->cart.u.items_cart[idx], amount, LOG_TYPE_VENDING);
 		vsd->vending[vend_list[i]].amount -= amount;
-		z += ((double)vsd->vending[i].value * (double)amount);
+		z_gained = ((double)vsd->vending[vend_list[i]].value_vat * (double)amount);
+		z_gained = z_gained - (z_gained / 10000 * tax_total);
+
+		if (battle_config.display_tax_info) {
+			char msg[CHAT_SIZE_MAX];
+			sprintf(msg, msg_txt(sd, 777), itemdb_jname(vsd->cart.u.items_cart[idx].nameid), (double)vsd->vending[vend_list[i]].value * amount, z_gained);
+			clif_displaymessage(vsd->fd, msg);
+		}
 
 		if( vsd->vending[vend_list[i]].amount ) {
 			if( Sql_Query( mmysql_handle, "UPDATE `%s` SET `amount` = %d WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_table, vsd->vending[vend_list[i]].amount, vsd->vender_id, vsd->cart.u.items_cart[idx].id ) != SQL_SUCCESS ) {
@@ -239,8 +273,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 		}
 
 		pc_cart_delitem(vsd, idx, amount, 0, LOG_TYPE_VENDING);
-		z = vending_calc_tax(sd, z);
-		clif_vendingreport(vsd, idx, amount, sd->status.char_id, (int)z);
+		clif_vendingreport(vsd, idx, amount, sd->status.char_id, (int)z_gained);
 
 		//print buyer's name
 		if( battle_config.buyer_name ) {
@@ -299,8 +332,10 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
 	int i, j;
 	int vending_skill_lvl;
 	char message_sql[MESSAGE_SIZE*2];
+	char msg[CHAT_SIZE_MAX];
 	StringBuf buf;
-	
+	s_tax *taxdata;
+
 	nullpo_retr(false,sd);
 
 	if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd)) {
@@ -324,12 +359,18 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
 	if (save_settings&CHARSAVE_VENDING) // Avoid invalid data from saving
 		chrif_save(sd, CSAVE_INVENTORY|CSAVE_CART);
 
+	taxdata = tax_get(TAX_SELLING);
+	if (battle_config.display_tax_info) {
+		clif_displaymessage(sd->fd, msg_txt(sd, 773)); // [ Tax Information ]
+	}
+
 	// filter out invalid items
 	i = 0;
 	for( j = 0; j < count; j++ ) {
 		short index        = *(uint16*)(data + 8*j + 0);
 		short amount       = *(uint16*)(data + 8*j + 2);
 		unsigned int value = *(uint32*)(data + 8*j + 4);
+		unsigned short tax;
 
 		index -= 2; // offset adjustment (client says that the first cart position is 2)
 
@@ -346,6 +387,16 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
 		sd->vending[i].index = index;
 		sd->vending[i].amount = amount;
 		sd->vending[i].value = min(value, (unsigned int)battle_config.vending_max_value);
+
+		tax = taxdata->get_tax(taxdata->each, sd->vending[i].value);
+		sd->vending[i].value_vat = tax ? (size_t)(sd->vending[i].value - sd->vending[i].value / 10000. * tax) : sd->vending[i].value;
+
+		if (battle_config.display_tax_info) {
+			memset(msg, '\0', CHAT_SIZE_MAX);
+			sprintf(msg, msg_txt(sd, 774), itemdb_jname(sd->cart.u.items_cart[index].nameid), sd->vending[i].value, '-', tax / 100., sd->vending[i].value_vat); // // %s : %u %c %.2f%% => %u
+			clif_displaymessage(sd->fd, msg);
+		}
+
 		i++; // item successfully added
 	}
 
@@ -357,6 +408,15 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
 		return 5;
 	}
 
+	if (battle_config.display_tax_info && taxdata->total.size()) {
+		clif_displaymessage(sd->fd, msg_txt(sd, 775)); // [ Total Transaction Tax ]
+		for (const auto &tax : taxdata->total) {
+			memset(msg, '\0', CHAT_SIZE_MAX);
+			sprintf(msg, msg_txt(sd, 776), tax.tax / 100., tax.minimal); // Tax: %.2f%% Minimal Transaction: %u
+			clif_displaymessage(sd->fd, msg);
+		}
+	}
+
 	sd->state.prevend = 0;
 	sd->state.vending = true;
 	sd->state.workinprogress = WIP_DISABLE_NONE;

+ 4 - 4
src/map/vending.hpp

@@ -5,7 +5,6 @@
 #define	_VENDING_HPP_
 
 #include "../common/cbasetypes.h"
-
 #include "../common/db.h"
 
 struct map_session_data;
@@ -13,9 +12,10 @@ struct s_search_store_search;
 struct s_autotrader;
 
 struct s_vending {
-	short index; /// cart index (return item data)
-	short amount; ///amout of the item for vending
-	unsigned int value; ///at wich price
+	short index; ///< Cart index (return item data)
+	short amount; ///< Amout of the item for vending
+	unsigned int value; ///< Price
+	size_t value_vat; ///< Value after tax
 };
 
 DBMap * vending_getdb();