...
 
Commits (15)
......@@ -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
# Taxes for selling something (Vending)
# Zeny received for the seller will be reduced after taxes from the total selling price but the buyer pays just the total selling price
Selling:
# Taxes for buying something (Buyingstore)
# Zeny received for the seller will be the total selling price but the buyer must pay taxes for the total selling price
Buying:
......@@ -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
......
......@@ -831,7 +831,15 @@
// Achievements
772: Achievements are disabled.
//773-899 free
// Tax:
776: [ Tax Information ]
777: %s : %u %c %.2f%% => %u
778: [ Total Transaction Tax ]
779: Tax: %.2f%% Minimal Transaction: %u
780: %s : %.0f => %.0f
781: Tax database has been reloaded.
//781-899 free
//------------------------------------
// More atcommands message
......
# Taxes for selling something (Vending)
# Zeny received for the seller will be reduced after taxes from the total selling price but the buyer pays just the total selling price
Selling:
# Tax applied for total transaction
InTotal:
- MinimalValue: 0
Tax: 0
# Tax by selling entry
EachEntry:
# 10% if >= 100,000,001
- MinimalValue: 100000001
Tax: 1000
# 8% if >= 10,000,001
- MinimalValue: 10000001
Tax: 800
# 6% if >= 1,000,001
- MinimalValue: 1000001
Tax: 600
# 4% if >= 100,001
- MinimalValue: 100001
Tax: 400
# 2% if >= 10,001
- MinimalValue: 10001
Tax: 200
# Taxes for buying something (Buyingstore)
# Zeny received for the seller will be the total selling price but the buyer must pay taxes for the total selling price
Buying:
# Tax applied for total transaction
InTotal:
- MinimalValue: 0
Tax: 0
# Tax by buying entry
EachEntry:
# 10% if >= 100,000,001
- MinimalValue: 100000001
Tax: 1000
# 8% if >= 10,000,001
- MinimalValue: 10000001
Tax: 800
# 6% if >= 1,000,001
- MinimalValue: 1000001
Tax: 600
# 4% if >= 100,001
- MinimalValue: 100001
Tax: 400
# 2% if >= 10,001
- MinimalValue: 10001
Tax: 200
......@@ -48,6 +48,7 @@
#include "mob.hpp"
#include "achievement.hpp"
#include "clan.hpp"
#include "tax.hpp"
#define ATCOMMAND_LENGTH 50
#define ACMD_FUNC(x) static int atcommand_ ## x (const int fd, struct map_session_data* sd, const char* command, const char* message)
......@@ -3946,6 +3947,9 @@ ACMD_FUNC(reload) {
} else if (strstr(command, "achievementdb") || strncmp(message, "achievementdb", 4) == 0) {
achievement_db_reload();
clif_displaymessage(fd, msg_txt(sd,771)); // Achievement database has been reloaded.
} else if (strstr(command, "taxdb") || strncmp(message, "taxdb", 3) == 0) {
tax_db_reload();
clif_displaymessage(fd, msg_txt(sd,781)); // Tax database has been reloaded.
}
return 0;
......@@ -10109,6 +10113,7 @@ void atcommand_basecommands(void) {
ACMD_DEF2("reloadmsgconf", reload),
ACMD_DEF2("reloadinstancedb", reload),
ACMD_DEF2("reloadachievementdb",reload),
ACMD_DEF2("reloadtaxdb",reload),
ACMD_DEF(partysharelvl),
ACMD_DEF(mapinfo),
ACMD_DEF(dye),
......
......@@ -8510,6 +8510,7 @@ static const struct _battle_data {
{ "feature.homunculus_autofeed", &battle_config.feature_homunculus_autofeed, 1, 0, 1, },
{ "feature.homunculus_autofeed_rate", &battle_config.feature_homunculus_autofeed_rate,30, 0, 100, },
{ "summoner_trait", &battle_config.summoner_trait, 3, 0, 3, },
{ "display_tax_info", &battle_config.display_tax_info, 0, 0, 1, },
#include "../custom/battle_config_init.inc"
};
......
......@@ -644,6 +644,7 @@ struct Battle_Config
int feature_homunculus_autofeed;
int feature_homunculus_autofeed_rate;
int summoner_trait;
int display_tax_info;
#include "../custom/battle_config_struct.inc"
};
......
......@@ -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);
......@@ -221,6 +224,18 @@ int8 buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha
return 7;
}
taxdata = tax_get(TAX_BUYING);
tax_buyingstore_vat(sd); // Calculate value after taxes
if (battle_config.display_tax_info && taxdata->total.size()) {
clif_displaymessage(sd->fd, msg_txt(sd, 778)); // [ Total Transaction Tax ]
for (const auto &tax : taxdata->total) {
memset(msg, '\0', CHAT_SIZE_MAX);
sprintf(msg, msg_txt(sd, 779), 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 +330,43 @@ void buyingstore_open(struct map_session_data* sd, uint32 account_id)
clif_buyingstore_itemlist(sd, pl_sd);
}
/**
* Calculates taxes for Buyingstore purchases.
* @param sd: Player data.
* @param itemlist: List of sold items { <index>.W, <nameid>.W, <amount>.W }*.
* @param count: Item list count.
* @return Taxed price
*/
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 +376,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 +419,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 +482,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 +504,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 +523,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, 780), itemdb_jname(nameid), (double)zeny, (double)zeny_paid); // %s : %.0f => %.0f
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 ) {
......
......@@ -15,9 +15,10 @@ struct map_session_data;
struct s_buyingstore_item
{
int price;
unsigned short amount;
unsigned short nameid;
int price; ///< Value
unsigned short amount; ///< Amount of items in Buyingstore
unsigned short nameid; ///< Item ID
size_t price_vat; ///< Value after tax
};
struct s_buyingstore
......
......@@ -7218,7 +7218,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;
......@@ -7229,7 +7229,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));
}
......
......@@ -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];
......@@ -737,7 +747,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);
......
......@@ -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')" />
......
......@@ -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>
......
......@@ -48,6 +48,7 @@
#include "battle.hpp"
#include "log.hpp"
#include "mob.hpp"
#include "tax.hpp"
char default_codepage[32] = "";
......@@ -3884,6 +3885,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
......@@ -4424,6 +4427,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);
......@@ -4756,6 +4760,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();
......
// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
#include "tax.hpp"
#include <algorithm>
#include <string.h>
#include <yaml-cpp/yaml.h>
#include "../common/cbasetypes.h"
#include "../common/nullpo.h"
#include "../common/showmsg.h"
#include "battle.hpp"
#include "buyingstore.hpp"
#include "clif.hpp"
#include "pc.hpp"
#include "vending.hpp"
static struct s_tax TaxDB[TAX_MAX];
std::string tax_conf = "conf/store_tax.yml";
/**
* Returns the tax rate of a given amount.
* @param entry: Tax data.
* @param price: Value of item.
* @return Tax rate
*/
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;
}
/**
* Returns the tax type based on selling or buying.
* @param type: Tax type.
* @return Tax data.
*/
struct s_tax *tax_get(enum e_tax_type type) {
if (type < TAX_SELLING || type >= TAX_MAX)
return NULL;
return &TaxDB[type];
}
/**
* Calculates the value after tax for Vendors.
* @param sd: Player data
*/
void tax_vending_vat(struct map_session_data *sd) {
nullpo_retv(sd);
if (battle_config.display_tax_info)
clif_displaymessage(sd->fd, msg_txt(sd, 776)); // [ Tax Information ]
for (int i = 0; i < ARRAYLENGTH(sd->vending); i++) {
char msg[CHAT_SIZE_MAX];
unsigned short tax;
s_tax *taxdata;
taxdata = tax_get(TAX_SELLING);
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, 777), itemdb_jname(sd->cart.u.items_cart[sd->vending[i].index].nameid), sd->vending[i].value, '-', tax / 100., sd->vending[i].value_vat); // %s : %u %c %.2f%% => %u
clif_displaymessage(sd->fd, msg);
}
}
}
/**
* Calculates the value after tax for Buyingstores.
* @param sd: Player data
*/
void tax_buyingstore_vat(struct map_session_data *sd) {
nullpo_retv(sd);
if (battle_config.display_tax_info)
clif_displaymessage(sd->fd, msg_txt(sd, 776)); // [ Tax Information ]
for (int i = 0; i < ARRAYLENGTH(sd->buyingstore.items); i++) {
char msg[CHAT_SIZE_MAX];
unsigned short tax;
s_tax *taxdata;
taxdata = tax_get(TAX_BUYING);
tax = taxdata->get_tax(taxdata->each, sd->buyingstore.items[i].price);
sd->buyingstore.items[i].price_vat = (size_t)(sd->buyingstore.items[i].price + sd->buyingstore.items[i].price / 10000. * tax);
if (battle_config.display_tax_info) {
memset(msg, '\0', CHAT_SIZE_MAX);
sprintf(msg, msg_txt(sd, 777), itemdb_jname(sd->buyingstore.items[i].nameid), sd->buyingstore.items[i].price, '+', tax / 100., sd->buyingstore.items[i].price_vat); // %s : %u %c %.2f%% => %u
clif_displaymessage(sd->fd, msg);
}
}
}
/**
* Reads and parses an entry from the tax database.
* @param node: YAML node containing the entry.
* @param taxdata: Tax data.
* @param count: The sequential index of the current entry.
* @param source: The source YAML file.
*/
static void tax_readdb_sub(const YAML::Node &node, struct s_tax *taxdata, int *count, const std::string &source) {
if (node["InTotal"].IsDefined()) {
for (const auto &tax : node["InTotal"]) {
if (tax["MinimalValue"].IsDefined() && tax["Tax"].IsDefined()) {
struct s_tax_entry entry;
entry.minimal = tax["MinimalValue"].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["EachEntry"].IsDefined()) {
for (const auto &tax : node["EachEntry"]) {
if (tax["MinimalValue"].IsDefined() && tax["Tax"].IsDefined()) {
struct s_tax_entry entry;
entry.minimal = tax["MinimalValue"].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)++;
}
}
}
}
/**
* Loads taxes from the tax database.
*/
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());
}
/**
* Reload value after taxes for players with a Vending/Buyingstore shop.
*/
void tax_reload_vat(void) {
struct map_session_data *sd = NULL;
struct s_mapiterator *iter = NULL;
iter = mapit_getallusers();
for (sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter)) {
if (sd->state.vending)
tax_vending_vat(sd);
else if (sd->state.buyingstore)
tax_buyingstore_vat(sd);
}
mapit_free(iter);
}
/**
* Sets the tax database file name from the map_athena.conf
*/
void tax_set_conf(const std::string filename) {
tax_conf = filename;
}
/**
* Reloads the tax database
*/
void tax_db_reload(void) {
do_final_tax();
do_init_tax();
tax_reload_vat();
}
/**
* Initializes the tax database
*/
void do_init_tax(void) {
tax_readdb();
}
/**
* Finalizes the tax database
*/
void do_final_tax(void) {
for (int i = 0; i < TAX_MAX; i++) {
TaxDB[i].total.clear();
TaxDB[i].each.clear();
}
}
// 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 <s_tax_entry> each;
std::vector <s_tax_entry> total;
unsigned short get_tax(const std::vector <s_tax_entry>, double);
};
struct s_tax *tax_get(enum e_tax_type type);
void tax_vending_vat(struct map_session_data *sd);
void tax_buyingstore_vat(struct map_session_data *sd);
void tax_readdb(void);
void tax_reload_vat(void);
void tax_set_conf(const std::string filename);
void tax_db_reload(void);
void do_init_tax(void);
void do_final_tax(void);
#endif /* _TAX_HPP_ */
......@@ -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
......@@ -97,17 +99,42 @@ void vending_vendinglistreq(struct map_session_data* sd, int id)
}
/**
* Calculates taxes for vending
* @param sd: Vender
* @param zeny: Total amount to tax
* @return Total amount after taxes
* Return the total amount after taxes
* @param vsd: Vendor player
* @param data: Item data
* @param count: Number of different items
* @return Total price 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 +149,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 +158,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 +178,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 +198,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 +223,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 +244,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, 780), itemdb_jname(vsd->cart.u.items_cart[idx].nameid), (double)vsd->vending[vend_list[i]].value * amount, z_gained); // %s : %.0f => %.0f
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 +280,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 +339,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)) {
......@@ -346,6 +388,7 @@ 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);
i++; // item successfully added
}
......@@ -357,6 +400,18 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
return 5;
}
taxdata = tax_get(TAX_SELLING);
tax_vending_vat(sd); // Calculate value after taxes
if (battle_config.display_tax_info && taxdata->total.size()) {
clif_displaymessage(sd->fd, msg_txt(sd, 778)); // [ Total Transaction Tax ]
for (const auto &tax : taxdata->total) {
memset(msg, '\0', CHAT_SIZE_MAX);
sprintf(msg, msg_txt(sd, 779), 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;
......@@ -687,7 +742,7 @@ static int vending_autotrader_free(DBKey key, DBData *data, va_list ap) {
}
/**
* Initialise the vending module
* Destroy the vending module
* called in map::do_init
*/
void do_final_vending(void)
......@@ -697,7 +752,7 @@ void do_final_vending(void)
}
/**
* Destory the vending module
* Initialize the vending module
* called in map::do_final
*/
void do_init_vending(void)
......
......@@ -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; ///< Amount of the item for vending
unsigned int value; ///< Price
size_t value_vat; ///< Value after tax
};
DBMap * vending_getdb();
......