Compare commits

...

38 Commits

Author SHA1 Message Date
Aleos
e0cb658b7f
Merge branch 'master' into feature/store_taxes 2022-09-17 00:13:46 -04:00
Cydh
cc16f46885 Follow up 98cf6920, suggestion leftover 2022-02-10 08:08:09 +07:00
Cydh Ramdh
98cf69206a
Apply suggestions from code review
Co-authored-by: Aleos <aleos89@users.noreply.github.com>
2022-02-10 08:03:36 +07:00
Cydh
a9754cc198 Merge branch 'master' into feature/store_taxes
# Conflicts:
#	src/map/battle.cpp
#	src/map/battle.hpp
#	src/map/clif.hpp
2022-02-05 04:24:55 +07:00
Cydh
5c00d26e42 Fixed yaml2sql compilation error 2021-10-13 21:31:19 +07:00
Cydh
efb25cde9a Follow up e86daf339c025de633bc0f768a9b1c7bd73ac158
* Wrong nodesExist condition
2021-10-13 13:12:49 +07:00
Cydh
ad8501cd1d Merge branch 'master' into feature/store_taxes
# Conflicts:
#	conf/msg_conf/map_msg.conf
#	src/map/battle.cpp
#	src/map/battle.hpp
#	src/map/vending.cpp
2021-10-13 12:35:01 +07:00
aleos
e86daf339c General cleanups and fixes 2021-04-01 09:46:12 -04:00
Aleos
725ae2e899
Merge branch 'master' into feature/store_taxes 2021-04-01 08:34:07 -04:00
Cydh
2d681f2061 Compilation fixes of csv2yaml 2019-11-21 10:39:15 +07:00
Cydh
812895b98a Merge branch 'master' into feature/store_taxes 2019-11-21 10:29:14 +07:00
Cydh
0c4ae23f0d Fixes LGTM alerts 2019-11-20 21:16:41 +07:00
Cydh
b0fdf80961 Follow up and clean up 2019-11-20 20:23:20 +07:00
Cydh
4d4f0eb1b8 Implemented new YML database for Tax Config
* Credits to @aleos89
2019-11-20 18:20:19 +07:00
Aleos
faafa5661e
Merge branch 'master' into feature/store_taxes 2019-04-19 22:26:45 -04:00
Aleos
b1299f54b1
Merge branch 'master' into feature/store_taxes 2019-02-18 15:19:38 -05:00
Aleos
27e1c40818
Merge branch 'master' into feature/store_taxes 2018-08-31 10:54:44 -04:00
Aleos
6ffc505066
Merge branch 'master' into feature/store_taxes 2018-07-26 13:44:40 -04:00
Aleos
eef0561fc1
Merge branch 'master' into feature/store_taxes 2018-06-24 10:36:16 -04:00
Cydh Ramdh
eaa7fdf2d0 Bug fixed & clean up
* A little clean up for 212ce7f
* Removed unused configs: vending_tax and vending_tax_min
* Fixed VAT didn't moved when item cleared from vending list. Thanks to @Badarosk0 @ecdarreola
2018-06-24 06:34:18 +07:00
Lemongrass3110
6eae3bf74b Follow up to e605adb 2018-06-14 22:21:59 +02:00
Lemongrass3110
e605adbd17 Merge branch 'master' into feature/store_taxes
# Conflicts:
#	conf/msg_conf/map_msg.conf
#	src/map/battle.cpp
#	src/map/battle.hpp
#	src/map/vending.cpp
#	src/map/vending.hpp
2018-06-14 22:20:16 +02:00
Aleos
060838b9d8
Merge branch 'master' into feature/store_taxes 2018-05-09 10:39:54 -04:00
aleos89
04606d25d4 Follow up to 3286468
* Should not be pointer type.
2018-04-16 14:07:08 -04:00
aleos89
3286468786 Follow up to 212ce7f
* Corrected the way vectors are cleared.
2018-04-16 14:03:34 -04:00
aleos89
5690891c40 Follow up to 212ce7f
* Added missing includes.
2018-04-16 13:55:48 -04:00
aleos89
e61ea13ad8 Follow up to 212ce7f
* Added missing header defines.
2018-04-16 13:50:23 -04:00
aleos89
fa93bbd90c Follow up to 212ce7f
* Fixed a compile error.
2018-04-16 13:40:15 -04:00
aleos89
212ce7fd25 Added the ability to reload the tax database
* Attempts to refresh VAT for Vendors and Buyingstores.
* Miscellaneous cleanups.
2018-04-16 13:32:39 -04:00
aleos89
024a5de8dc Small cleanups
* Follow up to 0f88530.
* Removed underscores from YAML labels to follow standard.
2018-04-10 13:31:17 -04:00
aleos89
0f88530860 Corrected a message configuration conflict
* Resolved a conflict with the Refine UI messages.
Thanks to @Haikenz!
2018-04-10 13:21:04 -04:00
Aleos
1031189146
Merge branch 'master' into feature/store_taxes 2018-04-10 13:10:22 -04:00
aleos89
12768c49bc Merge branch 'feature/store_taxes' of https://github.com/rathena/rathena into feature/store_taxes 2018-04-02 14:05:28 -04:00
aleos89
a5897a1c3c Cleaned up and added documentation for Taxes 2018-04-02 14:04:58 -04:00
Aleos
68c8f6e30a
Merge branch 'master' into feature/store_taxes 2018-04-02 13:53:22 -04:00
Lemongrass3110
de46196963 Merge branch 'master' into feature/store_taxes
# Conflicts:
#	src/map/battle.cpp
#	src/map/battle.hpp
2018-02-02 17:36:23 +01:00
Cydh Ramdh
dc0512a3b8 Follow up 77fec5825e76c2380405d9d49ab1581148dd34d5
* Fixed missing reference for `sort` & `find_if`
2018-01-01 16:45:40 +07:00
Cydh Ramdh
77fec5825e 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
2018-01-01 07:35:49 +07:00
23 changed files with 644 additions and 60 deletions

View File

@ -16,16 +16,6 @@ vending_max_value: 1000000000
// If set to yes, the rest of the zeny above the char's capacity will disappear. // If set to yes, the rest of the zeny above the char's capacity will disappear.
vending_over_max: yes vending_over_max: yes
// Tax to apply to all vending transactions (eg: 10000 = 100%, 50 = 0.50%)
// When a tax is applied, the item's full price is charged to the buyer, but
// the vender will not get the whole price paid (they get 100% - this tax).
vending_tax: 500
// Minimum total of purchase until taxes are applied.
// Officially there is no tax for anything less than 100 million zeny.
// 0 will apply taxes to all transactions.
vending_tax_min: 100000000
// Show the buyer's name when successfully vended an item // Show the buyer's name when successfully vended an item
buyer_name: yes buyer_name: yes

View File

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

40
conf/import-tmpl/tax.yml Normal file
View File

@ -0,0 +1,40 @@
# This file is a part of rAthena.
# Copyright(C) 2019 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Tax Database
###########################################################################
#
# Tax Settings
#
###########################################################################
# Type Tax type. Valid types are TAX_SELLING for vending and TAX_BUYING for buyingstore.
# InTotal Tax applied for total transaction. Supports unlimited entries.
# - MinimalValue Minimum Zeny value before Tax is applied. (Default: 0)
# Tax Tax percentage applied to MinimalValue. (Default: 0)
# EachEntry Tax by selling entry. Supports unlimited entries.
# - MinimalValue Minimum Zeny value before Tax is applied. (Default: 0)
# Tax Tax percentage applied to MinimalValue. (Default: 0)
###########################################################################
Header:
Type: TAX_DB
Version: 1
#Body:
# - Type: TAX_SELLING
# - Type: TAX_BUYING

View File

@ -840,7 +840,14 @@
773: This command requires packet version 2016-10-12 or newer. 773: This command requires packet version 2016-10-12 or newer.
774: This command is disabled via configuration. 774: This command is disabled via configuration.
775: You have already opened the refine UI. 775: You have already opened the refine UI.
//776-781 reserved for tax system
// 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.
782: Star Emperor 782: Star Emperor
783: Soul Reaper 783: Soul Reaper

88
conf/tax.yml Normal file
View File

@ -0,0 +1,88 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Tax Database
###########################################################################
#
# Tax Settings
#
###########################################################################
# Type Tax type. Valid types are TAX_SELLING for vending and TAX_BUYING for buyingstore.
# InTotal Tax applied for total transaction. Supports unlimited entries.
# - MinimalValue Minimum Zeny value before Tax is applied. (Default: 0)
# Tax Tax percentage applied to MinimalValue. (Default: 0)
# EachEntry Tax by selling entry. Supports unlimited entries.
# - MinimalValue Minimum Zeny value before Tax is applied. (Default: 0)
# Tax Tax percentage applied to MinimalValue. (Default: 0)
###########################################################################
Header:
Type: TAX_DB
Version: 1
Body:
# Zeny received for the seller will be reduced after taxes from the total selling price but the buyer pays just the total selling price
- Type: TAX_SELLING
InTotal:
- MinimalValue: 0
Tax: 0
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
# Zeny received for the seller will be the total selling price but the buyer must pay taxes for the total selling price
- Type: TAX_BUYING
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
Footer:
Imports:
- Path: conf/import/tax.yml

View File

@ -52,6 +52,7 @@
#include "quest.hpp" #include "quest.hpp"
#include "script.hpp" #include "script.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "tax.hpp"
#include "trade.hpp" #include "trade.hpp"
#include "vending.hpp" #include "vending.hpp"
@ -4318,6 +4319,9 @@ ACMD_FUNC(reload) {
} else if (strstr(command, "attendancedb") || strncmp(message, "attendancedb", 4) == 0) { } else if (strstr(command, "attendancedb") || strncmp(message, "attendancedb", 4) == 0) {
attendance_db.reload(); attendance_db.reload();
clif_displaymessage(fd, msg_txt(sd, 795)); // Attendance database has been reloaded. clif_displaymessage(fd, msg_txt(sd, 795)); // Attendance 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; return 0;
@ -10954,6 +10958,7 @@ void atcommand_basecommands(void) {
ACMD_DEF2("reloadinstancedb", reload), ACMD_DEF2("reloadinstancedb", reload),
ACMD_DEF2("reloadachievementdb",reload), ACMD_DEF2("reloadachievementdb",reload),
ACMD_DEF2("reloadattendancedb",reload), ACMD_DEF2("reloadattendancedb",reload),
ACMD_DEF2("reloadtaxdb",reload),
ACMD_DEF(partysharelvl), ACMD_DEF(partysharelvl),
ACMD_DEF(mapinfo), ACMD_DEF(mapinfo),
ACMD_DEF(dye), ACMD_DEF(dye),

View File

@ -10030,8 +10030,6 @@ static const struct _battle_data {
{ "hom_rename", &battle_config.hom_rename, 0, 0, 1, }, { "hom_rename", &battle_config.hom_rename, 0, 0, 1, },
{ "homunculus_show_growth", &battle_config.homunculus_show_growth, 0, 0, 1, }, { "homunculus_show_growth", &battle_config.homunculus_show_growth, 0, 0, 1, },
{ "homunculus_friendly_rate", &battle_config.homunculus_friendly_rate, 100, 0, INT_MAX, }, { "homunculus_friendly_rate", &battle_config.homunculus_friendly_rate, 100, 0, INT_MAX, },
{ "vending_tax", &battle_config.vending_tax, 0, 0, 10000, },
{ "vending_tax_min", &battle_config.vending_tax_min, 0, 0, MAX_ZENY, },
{ "day_duration", &battle_config.day_duration, 0, 0, INT_MAX, }, { "day_duration", &battle_config.day_duration, 0, 0, INT_MAX, },
{ "night_duration", &battle_config.night_duration, 0, 0, INT_MAX, }, { "night_duration", &battle_config.night_duration, 0, 0, INT_MAX, },
{ "mob_remove_delay", &battle_config.mob_remove_delay, 60000, 1000, INT_MAX, }, { "mob_remove_delay", &battle_config.mob_remove_delay, 60000, 1000, INT_MAX, },
@ -10250,6 +10248,7 @@ static const struct _battle_data {
{ "idletime_mer_option", &battle_config.idletime_mer_option, 0x1F, 0x1, 0xFFF, }, { "idletime_mer_option", &battle_config.idletime_mer_option, 0x1F, 0x1, 0xFFF, },
{ "feature.refineui", &battle_config.feature_refineui, 1, 0, 1, }, { "feature.refineui", &battle_config.feature_refineui, 1, 0, 1, },
{ "rndopt_drop_pillar", &battle_config.rndopt_drop_pillar, 1, 0, 1, }, { "rndopt_drop_pillar", &battle_config.rndopt_drop_pillar, 1, 0, 1, },
{ "display_tax_info", &battle_config.display_tax_info, 0, 0, 1, },
{ "pet_legacy_formula", &battle_config.pet_legacy_formula, 0, 0, 1, }, { "pet_legacy_formula", &battle_config.pet_legacy_formula, 0, 0, 1, },
{ "pet_distance_check", &battle_config.pet_distance_check, 5, 0, 50, }, { "pet_distance_check", &battle_config.pet_distance_check, 5, 0, 50, },
{ "pet_hide_check", &battle_config.pet_hide_check, 1, 0, 1, }, { "pet_hide_check", &battle_config.pet_hide_check, 1, 0, 1, },

View File

@ -320,8 +320,6 @@ struct Battle_Config
int dead_branch_active; int dead_branch_active;
int vending_max_value; int vending_max_value;
int vending_over_max; int vending_over_max;
int vending_tax;
int vending_tax_min;
int show_steal_in_same_party; int show_steal_in_same_party;
int party_share_type; int party_share_type;
int party_hp_mode; int party_hp_mode;
@ -691,6 +689,7 @@ struct Battle_Config
int idletime_mer_option; int idletime_mer_option;
int feature_refineui; int feature_refineui;
int rndopt_drop_pillar; int rndopt_drop_pillar;
int display_tax_info;
int pet_legacy_formula; int pet_legacy_formula;
int pet_distance_check; int pet_distance_check;
int pet_hide_check; int pet_hide_check;

View File

@ -20,6 +20,7 @@
#include "log.hpp" // log_pick_pc, log_zeny #include "log.hpp" // log_pick_pc, log_zeny
#include "npc.hpp" #include "npc.hpp"
#include "pc.hpp" // struct map_session_data #include "pc.hpp" // struct map_session_data
#include "tax.hpp"
//Autotrader //Autotrader
static DBMap *buyingstore_autotrader_db; /// Holds autotrader info: char_id -> struct s_autotrader static DBMap *buyingstore_autotrader_db; /// Holds autotrader info: char_id -> struct s_autotrader
@ -229,6 +230,8 @@ int8 buyingstore_create( struct map_session_data* sd, int zenylimit, unsigned ch
sd->buyingstore.slots = i; // store actual amount of items sd->buyingstore.slots = i; // store actual amount of items
safestrncpy(sd->message, storename, sizeof(sd->message)); safestrncpy(sd->message, storename, sizeof(sd->message));
tax_db.setBuyingstoreTax(sd);
Sql_EscapeString( mmysql_handle, message_sql, sd->message ); Sql_EscapeString( mmysql_handle, message_sql, sd->message );
if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`id`, `account_id`, `char_id`, `sex`, `map`, `x`, `y`, `title`, `limit`, `autotrade`, `body_direction`, `head_direction`, `sit`) " if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`id`, `account_id`, `char_id`, `sex`, `map`, `x`, `y`, `title`, `limit`, `autotrade`, `body_direction`, `head_direction`, `sit`) "
@ -316,6 +319,39 @@ void buyingstore_open(struct map_session_data* sd, uint32 account_id)
clif_buyingstore_itemlist(sd, pl_sd); 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 struct PACKET_CZ_REQ_TRADE_BUYING_STORE_sub* itemlist, int count) {
std::shared_ptr<s_tax> tax = tax_db.find(TAX_BUYING);
if (tax == nullptr || tax->total.empty())
return 0;
double total = 0;
for (int i = 0; i < count; i++) {
const struct PACKET_CZ_REQ_TRADE_BUYING_STORE_sub* item = &itemlist[i];
if (item->amount <= 0)
continue;
int listidx;
ARR_FIND(0, sd->buyingstore.slots, listidx, sd->buyingstore.items[listidx].nameid == item->itemId);
if (listidx == sd->buyingstore.slots || sd->buyingstore.items[listidx].amount == 0)
continue;
total += ((double)sd->buyingstore.items[listidx].price * item->amount);
}
return tax->taxPercentage(tax->total, total);
}
/** /**
* Start transaction * Start transaction
* @param sd Player/Seller * @param sd Player/Seller
@ -324,7 +360,6 @@ void buyingstore_open(struct map_session_data* sd, uint32 account_id)
* @param count Number of item on the itemlist * @param count Number of item on the itemlist
*/ */
void buyingstore_trade( struct map_session_data* sd, uint32 account_id, unsigned int buyer_id, const struct PACKET_CZ_REQ_TRADE_BUYING_STORE_sub* itemlist, unsigned int count ){ void buyingstore_trade( struct map_session_data* sd, uint32 account_id, unsigned int buyer_id, const struct PACKET_CZ_REQ_TRADE_BUYING_STORE_sub* itemlist, unsigned int count ){
int zeny = 0;
unsigned int weight; unsigned int weight;
struct map_session_data* pl_sd; struct map_session_data* pl_sd;
@ -367,6 +402,8 @@ void buyingstore_trade( struct map_session_data* sd, uint32 account_id, unsigned
pl_sd->buyingstore.zenylimit = pl_sd->status.zeny; pl_sd->buyingstore.zenylimit = pl_sd->status.zeny;
} }
weight = pl_sd->weight; weight = pl_sd->weight;
int tax_total = buyinstore_tax_intotal(pl_sd, itemlist, count);
double zeny = 0, zeny_paid = 0;
// check item list // check item list
for( int i = 0; i < count; i++ ){ for( int i = 0; i < count; i++ ){
@ -427,22 +464,33 @@ void buyingstore_trade( struct map_session_data* sd, uint32 account_id, unsigned
weight += item->amount * sd->inventory_data[index]->weight; weight += item->amount * sd->inventory_data[index]->weight;
// buyer does not have enough zeny zeny += ((double)item->amount * (double)pl_sd->buyingstore.items[listidx].price);
if( item->amount * pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit - zeny ){ zeny_paid += ((double)item->amount * (double)pl_sd->buyingstore.items[listidx].price_vat);
zeny_paid += (zeny_paid / 10000. * tax_total);
if (zeny_paid > (double)pl_sd->buyingstore.zenylimit) {// buyer does not have enough zeny
clif_buyingstore_trade_failed_seller( sd, BUYINGSTORE_TRADE_SELLER_ZENY, item->itemId ); clif_buyingstore_trade_failed_seller( sd, BUYINGSTORE_TRADE_SELLER_ZENY, item->itemId );
return; return;
} }
if ((double)sd->status.zeny + zeny > (double)MAX_ZENY) { // Seller zeny overflow
zeny += item->amount * pl_sd->buyingstore.items[listidx].price; clif_buyingstore_trade_failed_seller( sd, BUYINGSTORE_TRADE_BUYER_ZENY, item->itemId );
return;
}
} }
// pay up
pc_payzeny(pl_sd, (int)zeny_paid, LOG_TYPE_BUYING_STORE, sd);
pc_getzeny(sd, (int)zeny, LOG_TYPE_BUYING_STORE, pl_sd);
pl_sd->buyingstore.zenylimit -= (int)zeny_paid;
// process item list // process item list
for( int i = 0; i < count; i++ ){ for( int i = 0; i < count; i++ ){
const struct PACKET_CZ_REQ_TRADE_BUYING_STORE_sub* item = &itemlist[i]; const struct PACKET_CZ_REQ_TRADE_BUYING_STORE_sub* item = &itemlist[i];
int listidx; int listidx;
ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == item->itemId ); ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == item->itemId );
zeny = item->amount * pl_sd->buyingstore.items[listidx].price; zeny = ((double)item->amount * (double)pl_sd->buyingstore.items[listidx].price);
zeny_paid = ((double)item->amount * (double)pl_sd->buyingstore.items[listidx].price_vat);
zeny_paid = zeny_paid + (zeny_paid / 10000. * tax_total);
int index = item->index - 2; // TODO: clif::server_index int index = item->index - 2; // TODO: clif::server_index
@ -461,14 +509,16 @@ void buyingstore_trade( struct map_session_data* sd, uint32 account_id, unsigned
} }
} }
// pay up if (battle_config.display_tax_info) {
pc_payzeny(pl_sd, zeny, LOG_TYPE_BUYING_STORE, sd); char msg[CHAT_SIZE_MAX];
pc_getzeny(sd, zeny, LOG_TYPE_BUYING_STORE, pl_sd);
pl_sd->buyingstore.zenylimit-= zeny; sprintf(msg, msg_txt(sd, 780), itemdb_ename(item->itemId), (double)zeny, (double)zeny_paid); // %s : %.0f => %.0f
clif_displaymessage(pl_sd->fd, msg);
}
// notify clients // notify clients
clif_buyingstore_delete_item(sd, index, item->amount, pl_sd->buyingstore.items[listidx].price); clif_buyingstore_delete_item(sd, index, item->amount, pl_sd->buyingstore.items[listidx].price);
clif_buyingstore_update_item(pl_sd, item->itemId, item->amount, sd->status.char_id, zeny); clif_buyingstore_update_item(pl_sd, item->itemId, item->amount, sd->status.char_id, (int)zeny_paid);
} }
if( save_settings&CHARSAVE_VENDING ) { if( save_settings&CHARSAVE_VENDING ) {

View File

@ -15,9 +15,10 @@ struct map_session_data;
struct s_buyingstore_item struct s_buyingstore_item
{ {
int price; int price; ///< Value
unsigned short amount; unsigned short amount; ///< Amount of items in Buyingstore
t_itemid nameid; t_itemid nameid; ///< Item ID
unsigned int price_vat; ///< Value after tax
}; };
struct s_buyingstore struct s_buyingstore

View File

@ -7720,7 +7720,7 @@ void clif_vendinglist( struct map_session_data* sd, struct map_session_data* vsd
/// 5 = "cannot use an npc shop while in a trade" /// 5 = "cannot use an npc shop while in a trade"
/// 6 = Because the store information was incorrect the item was not purchased. /// 6 = Because the store information was incorrect the item was not purchased.
/// 7 = No sales information. /// 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; int fd;
@ -7731,7 +7731,7 @@ void clif_buyvending(struct map_session_data* sd, int index, int amount, int fai
WFIFOW(fd,0) = 0x135; WFIFOW(fd,0) = 0x135;
WFIFOW(fd,2) = index+2; WFIFOW(fd,2) = index+2;
WFIFOW(fd,4) = amount; WFIFOW(fd,4) = amount;
WFIFOB(fd,6) = fail; WFIFOB(fd,6) = ack;
WFIFOSET(fd,packet_len(0x135)); WFIFOSET(fd,packet_len(0x135));
} }

View File

@ -209,6 +209,16 @@ enum class e_purchase_result : uint8{
PURCHASE_FAIL_ADD = 0xff, PURCHASE_FAIL_ADD = 0xff,
}; };
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 #define packet_len(cmd) packet_db[cmd].len
extern struct s_packet_db packet_db[MAX_PACKET_DB+1]; extern struct s_packet_db packet_db[MAX_PACKET_DB+1];
extern int packet_db_ack[MAX_ACK_FUNC + 1]; extern int packet_db_ack[MAX_ACK_FUNC + 1];
@ -807,7 +817,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_showvendingboard(struct block_list* bl, const char* message, int fd);
void clif_closevendingboard(struct block_list* bl, int fd); void clif_closevendingboard(struct block_list* bl, int fd);
void clif_vendinglist( struct map_session_data* sd, struct map_session_data* vsd ); void clif_vendinglist( struct map_session_data* sd, struct map_session_data* vsd );
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_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); void clif_vendingreport(struct map_session_data* sd, int index, int amount, uint32 char_id, int zeny);

View File

@ -225,6 +225,7 @@
<ClInclude Include="skill.hpp" /> <ClInclude Include="skill.hpp" />
<ClInclude Include="status.hpp" /> <ClInclude Include="status.hpp" />
<ClInclude Include="storage.hpp" /> <ClInclude Include="storage.hpp" />
<ClInclude Include="tax.hpp" />
<ClInclude Include="trade.hpp" /> <ClInclude Include="trade.hpp" />
<ClInclude Include="unit.hpp" /> <ClInclude Include="unit.hpp" />
<ClInclude Include="vending.hpp" /> <ClInclude Include="vending.hpp" />
@ -271,6 +272,7 @@
<ClCompile Include="skill.cpp" /> <ClCompile Include="skill.cpp" />
<ClCompile Include="status.cpp" /> <ClCompile Include="status.cpp" />
<ClCompile Include="storage.cpp" /> <ClCompile Include="storage.cpp" />
<ClCompile Include="tax.cpp" />
<ClCompile Include="trade.cpp" /> <ClCompile Include="trade.cpp" />
<ClCompile Include="unit.cpp" /> <ClCompile Include="unit.cpp" />
<ClCompile Include="vending.cpp" /> <ClCompile Include="vending.cpp" />
@ -304,6 +306,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\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\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\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_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_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')" /> <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')" />

View File

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

View File

@ -51,6 +51,7 @@
#include "pet.hpp" #include "pet.hpp"
#include "quest.hpp" #include "quest.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "tax.hpp"
#include "trade.hpp" #include "trade.hpp"
using namespace rathena; using namespace rathena;
@ -4900,6 +4901,7 @@ void do_final(void){
do_final_vending(); do_final_vending();
do_final_buyingstore(); do_final_buyingstore();
do_final_path(); do_final_path();
do_final_tax();
map_db->destroy(map_db, map_db_final); map_db->destroy(map_db, map_db_final);
@ -5229,6 +5231,7 @@ int do_init(int argc, char *argv[])
do_init_quest(); do_init_quest();
do_init_achievement(); do_init_achievement();
do_init_battleground(); do_init_battleground();
do_init_tax();
do_init_npc(); do_init_npc();
do_init_unit(); do_init_unit();
do_init_duel(); do_init_duel();

View File

@ -61,6 +61,7 @@
#include "pet.hpp" #include "pet.hpp"
#include "quest.hpp" #include "quest.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "tax.hpp" // e_tax_type
using namespace rathena; using namespace rathena;

View File

@ -8496,6 +8496,10 @@
/* timer related */ /* timer related */
export_constant(INFINITE_TICK); export_constant(INFINITE_TICK);
/* tax related */
export_constant(TAX_SELLING);
export_constant(TAX_BUYING);
/* block action */ /* block action */
export_constant(PCBLOCK_MOVE); export_constant(PCBLOCK_MOVE);
export_constant(PCBLOCK_ATTACK); export_constant(PCBLOCK_ATTACK);

272
src/map/tax.cpp Normal file
View File

@ -0,0 +1,272 @@
// 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.hpp"
#include "../common/nullpo.hpp"
#include "../common/showmsg.hpp"
#include "battle.hpp"
#include "buyingstore.hpp"
#include "clif.hpp"
#include "pc.hpp"
#include "vending.hpp"
/*
* Tax Database
*/
TaxDatabase tax_db;
const std::string TaxDatabase::getDefaultLocation() {
return "conf/tax.yml";
}
/**
* Reads and parses an entry from the tax_db.
* @param node: YAML node containing the entry.
* @return count of successfully parsed rows
*/
uint64 TaxDatabase::parseBodyNode(const YAML::Node &node) {
std::string type_str;
if (!this->nodeExists(node, "Type")) {
return 0;
}
if (!this->asString(node, "Type", type_str))
return 0;
int64 type;
if (!script_get_constant(type_str.c_str(), &type)) {
this->invalidWarning(node["Type"], "Invalid tax type '%s'.\n", type_str.c_str());
return 0;
}
std::shared_ptr<s_tax> taxdata = this->find(type);
bool exists = taxdata != nullptr;
if (!exists) {
taxdata = std::make_shared<s_tax>();
}
if (this->nodeExists(node, "InTotal")) {
taxdata->total.clear();
for (const auto &taxNode : node["InTotal"]) {
if (!this->nodesExist(taxNode, { "MinimalValue", "Tax" }))
continue;
s_tax_entry entry = {};
if (!this->asUInt64(taxNode, "MinimalValue", entry.minimal)) {
this->invalidWarning(taxNode["MinimalValue"], "Invalid value, defaulting to 0.\n");
entry.minimal = 0;
}
if (!this->asUInt16(taxNode, "Tax", entry.tax)) {
this->invalidWarning(taxNode["Tax"], "Invalid value, defaulting to 0.\n");
entry.tax = 0;
}
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; });
}
}
if (this->nodeExists(node, "EachEntry")) {
taxdata->each.clear();
for (const auto &taxNode : node["EachEntry"]) {
if (!this->nodesExist(taxNode, { "MinimalValue", "Tax" }))
continue;
s_tax_entry entry = { 0 };
if (!this->asUInt64(taxNode, "MinimalValue", entry.minimal)) {
this->invalidWarning(taxNode["MinimalValue"], "Invalid value, defaulting to 0.\n");
entry.minimal = 0;
}
if (!this->asUInt16(taxNode, "Tax", entry.tax)) {
this->invalidWarning(taxNode["Tax"], "Invalid value, defaulting to 0.\n");
entry.tax = 0;
}
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; });
}
}
if (!exists) {
this->put(type, taxdata);
}
return 1;
}
/*
* Set vending tax to a player
* @param sd Player
*/
void TaxDatabase::setVendingTax(map_session_data *sd)
{
std::shared_ptr<s_tax> taxdata = this->find(TAX_SELLING);
if (taxdata != nullptr) {
taxdata->vendingVAT(sd); // Calculate value after taxes
taxdata->inTotalInfo(sd);
}
}
/*
* Set buyingstore tax to a player
* @param sd Player
*/
void TaxDatabase::setBuyingstoreTax(map_session_data *sd)
{
std::shared_ptr<s_tax> taxdata = this->find(TAX_BUYING);
if (taxdata != nullptr) {
taxdata->buyingstoreVAT(sd); // Calculate value after taxes
taxdata->inTotalInfo(sd);
}
}
/**
* Returns the tax rate of a given amount.
* @param entry: Tax data.
* @param price: Value of item.
* @return Tax rate
*/
uint16 s_tax::taxPercentage(const std::vector <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;
}
/**
* Calculates the value after tax for Vendors.
* @param sd: Player data
*/
void s_tax::vendingVAT(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 < sd->vend_num; i++) {
if (sd->vending[i].amount == 0)
continue;
uint16 tax = this->taxPercentage(this->each, sd->vending[i].value);
sd->vending[i].value_vat = tax ? (unsigned int)(sd->vending[i].value - sd->vending[i].value / 10000. * tax) : sd->vending[i].value;
if (battle_config.display_tax_info) {
char msg[CHAT_SIZE_MAX];
sprintf(msg, msg_txt(sd, 777), itemdb_ename(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 s_tax::buyingstoreVAT(map_session_data *sd) {
nullpo_retv(sd);
if (battle_config.display_tax_info)
clif_displaymessage(sd->fd, msg_txt(sd, 776)); // [ Tax Information ]
for (uint8 i = 0; i < sd->buyingstore.slots; i++) {
if (sd->buyingstore.items[i].nameid == 0)
continue;
uint16 tax = this->taxPercentage(this->each, sd->buyingstore.items[i].price);
sd->buyingstore.items[i].price_vat = tax ? (unsigned int)(sd->buyingstore.items[i].price + sd->buyingstore.items[i].price / 10000. * tax) : sd->buyingstore.items[i].price;
if (battle_config.display_tax_info) {
char msg[CHAT_SIZE_MAX];
sprintf(msg, msg_txt(sd, 777), itemdb_ename(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);
}
}
}
/*
* Show selling/buying tax for total purchase value
* @param sd Vendor or buyer data
*/
void s_tax::inTotalInfo(map_session_data *sd)
{
if (!battle_config.display_tax_info || this->total.empty())
return;
clif_displaymessage(sd->fd, msg_txt(sd, 778)); // [ Total Transaction Tax ]
for (const auto &tax : this->total) {
char msg[CHAT_SIZE_MAX];
sprintf(msg, msg_txt(sd, 779), tax.tax / 100., tax.minimal); // Tax: %.2f%% Minimal Transaction: %u
clif_displaymessage(sd->fd, msg);
}
}
/**
* Reload value after taxes for players with a Vending/Buyingstore shop.
*/
void tax_reload_vat(void) {
map_session_data *sd;
s_mapiterator *iter = mapit_getallusers();
std::shared_ptr<s_tax> vending_tax = tax_db.find(TAX_SELLING);
std::shared_ptr<s_tax> buyingstore_tax = tax_db.find(TAX_BUYING);
for (sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter)) {
if (sd->state.vending && vending_tax != nullptr) {
vending_tax->vendingVAT(sd);
vending_tax->inTotalInfo(sd);
} else if (sd->state.buyingstore && buyingstore_tax != nullptr) {
buyingstore_tax->buyingstoreVAT(sd);
buyingstore_tax->inTotalInfo(sd);
}
}
mapit_free(iter);
}
/**
* 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_db.load();
}
/**
* Finalizes the tax database
*/
void do_final_tax(void) {
tax_db.clear();
}

56
src/map/tax.hpp Normal file
View File

@ -0,0 +1,56 @@
// 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.hpp"
#include "../common/database.hpp"
#include "pc.hpp"
enum e_tax_type : uint8 {
TAX_SELLING = 0,
TAX_BUYING,
TAX_MAX,
};
struct s_tax_entry {
uint64 minimal;
uint16 tax;
};
struct s_tax {
std::vector<s_tax_entry> each;
std::vector<s_tax_entry> total;
uint16 taxPercentage(const std::vector <s_tax_entry> entry, double price);
void vendingVAT(map_session_data * sd);
void buyingstoreVAT(map_session_data * sd);
void inTotalInfo(map_session_data *sd);
};
class TaxDatabase : public TypesafeYamlDatabase<uint64, s_tax> {
public:
TaxDatabase() : TypesafeYamlDatabase("TAX_DB", 1) {
}
const std::string getDefaultLocation();
uint64 parseBodyNode(const YAML::Node& node);
void setVendingTax(map_session_data *sd);
void setBuyingstoreTax(map_session_data *sd);
};
extern TaxDatabase tax_db;
void tax_reload_vat(void);
void tax_db_reload(void);
void do_init_tax(void);
void do_final_tax(void);
#endif /* _TAX_HPP_ */

94
src/map/vending.cpp Executable file → Normal file
View File

@ -24,6 +24,7 @@
#include "path.hpp" #include "path.hpp"
#include "pc.hpp" #include "pc.hpp"
#include "pc_groups.hpp" #include "pc_groups.hpp"
#include "tax.hpp"
static uint32 vending_nextid = 0; ///Vending_id counter static uint32 vending_nextid = 0; ///Vending_id counter
static DBMap *vending_db; ///DB holder the vender : charid -> map_session_data static DBMap *vending_db; ///DB holder the vender : charid -> map_session_data
@ -98,17 +99,42 @@ void vending_vendinglistreq(struct map_session_data* sd, int id)
} }
/** /**
* Calculates taxes for vending * Return the total amount after taxes
* @param sd: Vender * @param vsd: Vendor player
* @param zeny: Total amount to tax * @param data: Item data
* @return Total amount after taxes * @param count: Number of different items
* @return Total price after taxes
*/ */
static double vending_calc_tax(struct map_session_data *sd, double zeny) static unsigned short vending_tax_intotal(struct map_session_data* vsd, const uint8* data, int count) {
{ std::shared_ptr<s_tax> tax = tax_db.find(TAX_SELLING);
if (battle_config.vending_tax && zeny >= battle_config.vending_tax_min) double total = 0;
zeny -= zeny * (battle_config.vending_tax / 10000.); int i, j, vend_list[MAX_VENDING]; // against duplicate packets
return zeny; if (tax == nullptr || tax->total.empty())
return 0;
for (i = 0; i < count; i++) {
short amount = *(uint16*)(data + 4 * i + 0);
short idx = *(uint16*)(data + 4 * i + 2);
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 tax->taxPercentage(tax->total, total);
} }
/** /**
@ -123,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) 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]; 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 s_vending vending[MAX_VENDING]; // against duplicate packets
struct map_session_data* vsd = map_id2sd(aid); struct map_session_data* vsd = map_id2sd(aid);
@ -132,7 +158,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
return; // invalid shop return; // invalid shop
if( vsd->vender_id != uid ) { // shop has changed 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; return;
} }
@ -152,6 +178,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
// some checks // some checks
z = 0.; // zeny counter z = 0.; // zeny counter
w = 0; // weight counter w = 0; // weight counter
int tax_total = vending_tax_intotal(vsd, data, count);
for( i = 0; i < count; i++ ) { for( i = 0; i < count; i++ ) {
short amount = *(uint16*)(data + 4*i + 0); short amount = *(uint16*)(data + 4*i + 0);
short idx = *(uint16*)(data + 4*i + 2); short idx = *(uint16*)(data + 4*i + 2);
@ -171,18 +198,20 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
vend_list[i] = j; vend_list[i] = j;
z += ((double)vsd->vending[j].value * (double)amount); 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 ) { 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; return;
} }
if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) { if(z_gained + (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 clif_buyvending(sd, idx, vsd->vending[j].amount, VENDING_ACK_NOSTOCK); // too much zeny = overflow
return; return;
} }
w += itemdb_weight(vsd->cart.u.items_cart[idx].nameid) * amount; w += itemdb_weight(vsd->cart.u.items_cart[idx].nameid) * amount;
if( w + sd->weight > sd->max_weight ) { 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; return;
} }
@ -194,7 +223,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
// here, we check cumulative amounts // here, we check cumulative amounts
if( vending[j].amount < amount ) { if( vending[j].amount < amount ) {
// send more quantity is not a hack (an other player can have buy items just before) // 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; return;
} }
@ -215,19 +244,31 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd); pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd);
achievement_update_objective(sd, AG_SPEND_ZENY, 1, (int)z); achievement_update_objective(sd, AG_SPEND_ZENY, 1, (int)z);
z = vending_calc_tax(sd, z); pc_getzeny(vsd, (int)z_gained, LOG_TYPE_VENDING, sd);
pc_getzeny(vsd, (int)z, 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, (unsigned int)z_gained,
sd->status.account_id, sd->status.char_id, (unsigned int)z);
}
for( i = 0; i < count; i++ ) { for( i = 0; i < count; i++ ) {
short amount = *(uint16*)(data + 4*i + 0); short amount = *(uint16*)(data + 4*i + 0);
short idx = *(uint16*)(data + 4*i + 2); short idx = *(uint16*)(data + 4*i + 2);
idx -= 2; idx -= 2;
z = 0.; // zeny counter
// vending item // vending item
pc_additem(sd, &vsd->cart.u.items_cart[idx], amount, LOG_TYPE_VENDING); pc_additem(sd, &vsd->cart.u.items_cart[idx], amount, LOG_TYPE_VENDING);
vsd->vending[vend_list[i]].amount -= amount; vsd->vending[vend_list[i]].amount -= amount;
z += ((double)vsd->vending[vend_list[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_ename(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( 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 ) { 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 ) {
@ -240,8 +281,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); 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_gained);
clif_vendingreport(vsd, idx, amount, sd->status.char_id, (int)z);
//print buyer's name //print buyer's name
if( battle_config.buyer_name ) { if( battle_config.buyer_name ) {
@ -260,6 +300,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
vsd->vending[cursor].index = vsd->vending[i].index; vsd->vending[cursor].index = vsd->vending[i].index;
vsd->vending[cursor].amount = vsd->vending[i].amount; vsd->vending[cursor].amount = vsd->vending[i].amount;
vsd->vending[cursor].value = vsd->vending[i].value; vsd->vending[cursor].value = vsd->vending[i].value;
vsd->vending[cursor].value_vat = vsd->vending[i].value_vat;
} }
cursor++; cursor++;
@ -301,7 +342,7 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
int vending_skill_lvl; int vending_skill_lvl;
char message_sql[MESSAGE_SIZE*2]; char message_sql[MESSAGE_SIZE*2];
StringBuf buf; StringBuf buf;
nullpo_retr(false,sd); nullpo_retr(false,sd);
if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd)) { if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd)) {
@ -347,6 +388,7 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
sd->vending[i].index = index; sd->vending[i].index = index;
sd->vending[i].amount = amount; sd->vending[i].amount = amount;
sd->vending[i].value = min(value, (unsigned int)battle_config.vending_max_value); sd->vending[i].value = min(value, (unsigned int)battle_config.vending_max_value);
i++; // item successfully added i++; // item successfully added
} }
@ -367,6 +409,8 @@ int8 vending_openvending(struct map_session_data* sd, const char* message, const
sd->vender_id = vending_getuid(); sd->vender_id = vending_getuid();
sd->vend_num = i; sd->vend_num = i;
safestrncpy(sd->message, message, MESSAGE_SIZE); safestrncpy(sd->message, message, MESSAGE_SIZE);
tax_db.setVendingTax(sd);
Sql_EscapeString( mmysql_handle, message_sql, sd->message ); Sql_EscapeString( mmysql_handle, message_sql, sd->message );
@ -725,7 +769,7 @@ void vending_update(map_session_data &sd)
} }
/** /**
* Initialise the vending module * Destroy the vending module
* called in map::do_init * called in map::do_init
*/ */
void do_final_vending(void) void do_final_vending(void)
@ -735,7 +779,7 @@ void do_final_vending(void)
} }
/** /**
* Destory the vending module * Initialise the vending module
* called in map::do_final * called in map::do_final
*/ */
void do_init_vending(void) void do_init_vending(void)

View File

@ -13,9 +13,10 @@ struct s_search_store_search;
struct s_autotrader; struct s_autotrader;
struct s_vending { struct s_vending {
short index; /// cart index (return item data) short index; ///< Cart index (return item data)
short amount; ///amout of the item for vending short amount; ///< Amount of the item for vending
unsigned int value; ///at wich price unsigned int value; ///< Price
unsigned int value_vat; ///< Value after tax
}; };
DBMap * vending_getdb(); DBMap * vending_getdb();

View File

@ -54,6 +54,7 @@
#include "../map/script.hpp" #include "../map/script.hpp"
#include "../map/skill.hpp" #include "../map/skill.hpp"
#include "../map/storage.hpp" #include "../map/storage.hpp"
#include "../map/tax.hpp"
using namespace rathena; using namespace rathena;

View File

@ -51,6 +51,7 @@
#include "../map/script.hpp" #include "../map/script.hpp"
#include "../map/skill.hpp" #include "../map/skill.hpp"
#include "../map/storage.hpp" #include "../map/storage.hpp"
#include "../map/tax.hpp"
using namespace rathena; using namespace rathena;