add an extra fee to create items with prestige and/or role bonuses

to prevent abuse
This commit is contained in:
Crypto City 2023-01-29 20:45:44 +00:00
parent c429ab1b55
commit 12ade5e935
25 changed files with 364 additions and 70 deletions

View File

@ -22,6 +22,10 @@ TBLayout: axis: y, distribution-position: "left top", distribution: "gravity"
TBTextField: text: "Add"
TBInlineSelect: id: "amount", min: 1
TBTextField: text: "more"
TBLayout: axis: x
TBTextField: text: "Extra per item fee for prestige and/or role bonus:"
TBTextField: id: "prestige-role-bonus-fee", text: "0"
TBTextField: text: "gold"
TBLayout: axis: x
TBButton: text: "OK", id: "ok"

View File

@ -32,13 +32,23 @@ TBLayout: axis: y, distribution-position: "left top", distribution: "gravity"
TBClickLabel: text: "Allow creating more later (ie, no fixed supply)"
TBCheckBox: id: "can-create-more", value: 0
TBToggleContainer: id: "prestige-bonus-container", toggle: "expanded"
TBLayout: axis: x
TBTextField: text: "Prestige bonus (additive):"
TBInlineSelect: id: "prestige-bonus", value: 0, min: 0, max: 1000, step: 1
TBLayout: axis: y
TBLayout: axis: x
TBTextField: text: "Prestige bonus (additive):"
TBInlineSelect: id: "prestige-bonus", value: 0, min: 0, max: 1000, step: 1
TBLayout: axis: x
TBTextField: text: "Extra per item fee for prestige bonus:"
TBTextField: id: "prestige-bonus-fee", text: "0"
TBTextField: text: "gold"
TBToggleContainer: id: "role-bonus-container", toggle: "expanded"
TBLayout: axis: x
TBTextField: text: "Role bonus (percentage):"
TBLayout: axis: y, id: "role-bonus-widgets"
TBLayout: axis: y
TBLayout: axis: x
TBTextField: text: "Role bonus (percentage):"
TBLayout: axis: y, id: "role-bonus-widgets"
TBLayout: axis: x
TBTextField: text: "Extra per item fee for role bonus:"
TBTextField: id: "role-bonus-fee", text: "0"
TBTextField: text: "gold"
TBToggleContainer: id: "coin-design-container", toggle: expanded, gravity: "left right"
TBLayout: axis: y
TBImageWidget: id: "icon-base"

View File

@ -4082,6 +4082,16 @@ bool is_item_assignable_for_role(uint32_t item, const cc::cc_custom_item_t *cid,
return false;
}
uint64_t get_item_creation_fee(uint32_t prestige_bonus, const std::vector<uint32_t> &role_bonus)
{
CHECK_AND_ASSERT_THROW_MES(role_bonus.empty() || role_bonus.size() == NUM_ROLES, "Unexpected role_bonus size");
uint64_t prestige_bonus_fee = (prestige_bonus * sqrtint(prestige_bonus * (uint64_t)1048576)) * (uint64_t)PRESTIGE_BONUS_FEE / 1024;
uint64_t role_bonus_fee = 0;
for (uint32_t rb: role_bonus)
role_bonus_fee += (rb * sqrtint(rb * (uint64_t)1048576)) * (uint64_t)ROLE_BONUS_FEE / 1024;
return prestige_bonus_fee + role_bonus_fee;
}
void cc_derive_keys(const crypto::secret_key &base, crypto::secret_key &skey, crypto::public_key &pkey)
{
char data[33];

View File

@ -195,6 +195,7 @@ uint32_t get_max_user_textures(const std::vector<std::tuple<uint32_t, uint32_t,
bool get_compressed_string_pair(const std::string &s0, const std::string &s1, std::string &s, uint32_t &cutoff);
bool get_decompressed_string_pair(const std::string &s, size_t cutoff, std::string &s0, std::string &s1);
bool is_item_assignable_for_role(uint32_t item, const cc::cc_custom_item_t *cid, uint8_t role);
uint64_t get_item_creation_fee(uint32_t prestige_bonus, const std::vector<uint32_t> &role_bonus);
bool encode_blocks(const uint8_t *src_types, uint32_t n_src_types, uint8_t *encoded_types, uint32_t *encoded_types_len);
bool decode_blocks(const uint8_t *encoded_types, uint32_t encoded_types_len, uint8_t *dst_types, uint32_t *n_dst_types);

View File

@ -76,6 +76,11 @@ bool cc_command_handler_create_items::check(const cryptonote::BlockchainDB &db,
CHECK_COMMAND_SET(db.get_cc_account_data(create_items.cc_account, ad), tvc.m_cc_invalid_account, "Failed to get account");
CHECK_COMMAND_SET(ad.item_balances[create_items.item] <= std::numeric_limits<uint32_t>::max() - create_items.amount, tvc.m_cc_overflow, "Too many items");
const uint64_t per_item_bonus_fee = cc::get_item_creation_fee(cid.prestige_bonus, cid.role_bonus);
CHECK_COMMAND_SET(create_items.amount == 0 || per_item_bonus_fee <= std::numeric_limits<uint64_t>::max() / create_items.amount, tvc.m_cc_overflow, "Fee overflow");
const uint64_t bonus_fee = per_item_bonus_fee * create_items.amount;
uint64_t gold_paid = bonus_fee;
if (cid.gold > 0)
{
const uint64_t gold_fee_per_item = cid.gold * CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND / 1000;
@ -85,9 +90,12 @@ bool cc_command_handler_create_items::check(const cryptonote::BlockchainDB &db,
CHECK_COMMAND_SET(create_items.amount <= std::numeric_limits<uint64_t>::max() / gold_needed_per_item, tvc.m_cc_overflow, "Too much gold needed");
const uint64_t gold_needed = gold_needed_per_item * create_items.amount;
CHECK_COMMAND_SET(ad.balance >= gold_needed, tvc.m_cc_bad_item, "Not enough gold to create items");
CHECK_COMMAND_SET(gold_needed <= std::numeric_limits<uint64_t>::max() - gold_paid, tvc.m_cc_overflow, "Too much gold needed");
gold_paid += gold_needed;
}
CHECK_COMMAND_SET(ad.balance >= gold_paid, tvc.m_cc_bad_item, "Not enough gold to create items");
return true;
}
@ -109,15 +117,22 @@ bool cc_command_handler_create_items::execute(cryptonote::BlockchainDB &db, cons
const uint64_t gold_fee_per_item = cid.gold * CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND / 1000;
const uint64_t gold_needed_per_item = cid.gold + gold_fee_per_item;
gold_needed = gold_needed_per_item * create_items.amount;
ad.balance -= gold_needed;
std::map<uint32_t, int64_t> balance_changes;
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, gold_fee_per_item * create_items.amount, false, &balance_changes), false, "Failed to distribute to treasuries");
for (const auto &e: balance_changes)
events.add_full(cmd, e.first, create_items.cc_account, 0, create_items.item, create_items.amount, e.second, 0) << "Received item gilding fee";
}
db.set_cc_account_data(create_items.cc_account, ad);
const uint64_t bonus_fee = cc::get_item_creation_fee(cid.prestige_bonus, cid.role_bonus) * create_items.amount;
gold_needed += bonus_fee;
if (gold_needed > 0)
{
std::map<uint32_t, int64_t> balance_changes;
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, gold_needed - cid.gold * create_items.amount, false, &balance_changes), false, "Failed to distribute to treasuries");
for (const auto &e: balance_changes)
events.add_full(cmd, e.first, create_items.cc_account, 0, create_items.item, create_items.amount, e.second, 0) << "Received item creation fee";
ad.balance -= gold_needed;
db.set_cc_account_data(create_items.cc_account, ad);
}
CHECK_AND_ASSERT_MES(cc::increase_item_count(db, create_items.item, create_items.amount), false, "Item count anomaly");
events.add_full(cmd, create_items.cc_account, 0, 0, create_items.item, create_items.amount, -(int64_t)gold_needed, tx_fee) << "Created new items";
@ -137,16 +152,25 @@ bool cc_command_handler_create_items::revert(cryptonote::BlockchainDB &db, const
CHECK_AND_ASSERT_MES(ad.item_balances[create_items.item] <= std::numeric_limits<uint32_t>::max() - create_items.amount, false, "Too many items");
ad.item_balances[create_items.item] -= create_items.amount;
uint64_t gold_paid = 0;
if (cid.gold > 0)
{
const uint64_t gold_fee_per_item = cid.gold * CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND / 1000;
const uint64_t gold_needed_per_item = cid.gold + gold_fee_per_item;
const uint64_t gold_needed = gold_needed_per_item * create_items.amount;
ad.balance += gold_needed;
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, gold_fee_per_item * create_items.amount, true), false, "Failed to distribute from treasuries");
gold_paid += gold_needed;
}
const uint64_t bonus_fee = cc::get_item_creation_fee(cid.prestige_bonus, cid.role_bonus) * create_items.amount;
gold_paid += bonus_fee;
if (gold_paid > 0)
{
ad.balance += gold_paid;
db.set_cc_account_data(create_items.cc_account, ad);
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, gold_paid - cid.gold * create_items.amount, true), false, "Failed to distribute from treasuries");
}
db.set_cc_account_data(create_items.cc_account, ad);
CHECK_AND_ASSERT_MES(cc::decrease_item_count(db, create_items.item, create_items.amount), false, "Item count anomaly");
return true;

View File

@ -80,6 +80,7 @@ bool cc_command_handler_new_item::check(const cryptonote::BlockchainDB &db, cons
CHECK_COMMAND_SET(cc::is_role_enabled(e.first, hf_version), tvc.m_cc_bad_role, "Invalid role");
CHECK_COMMAND_SET(e.second > 0, tvc.m_cc_out_of_range, "Role bonus is 0");
CHECK_COMMAND_SET(e.second <= 100, tvc.m_cc_out_of_range, "Role bonus should be at most 100");
prev_role = e.first;
}
CHECK_COMMAND_SET(new_item.can_create_more || !new_item.is_group, tvc.m_cc_bad_item, "Groups must have can_create_more set");
CHECK_COMMAND_SET(!new_item.is_public || new_item.is_group, tvc.m_cc_bad_item, "Public should be false for non groups");
@ -105,7 +106,14 @@ bool cc_command_handler_new_item::check(const cryptonote::BlockchainDB &db, cons
CHECK_COMMAND_SET(gold_needed_per_item <= std::numeric_limits<uint64_t>::max() / new_item.amount, tvc.m_cc_overflow, "Too much gold in item");
}
CHECK_COMMAND_SET(gold_needed_per_item * new_item.amount <= std::numeric_limits<uint64_t>::max() - NEW_ITEM_FEE, tvc.m_cc_overflow, "Too much gold in item");
CHECK_COMMAND_SET(balance >= gold_needed_per_item * new_item.amount + NEW_ITEM_FEE, tvc.m_cc_balance, "Balance too low for the new item creation fee and/or gold content");
std::vector<uint32_t> role_bonus(NUM_ROLES, 0);
for (const auto &e: new_item.role_bonus)
role_bonus[e.first] = e.second;
const uint64_t per_item_bonus_fee = cc::get_item_creation_fee(new_item.prestige_bonus, role_bonus);
CHECK_COMMAND_SET(new_item.amount || per_item_bonus_fee <= std::numeric_limits<uint64_t>::max() / new_item.amount, tvc.m_cc_overflow, "Fee overflow");
const uint64_t bonus_fee = per_item_bonus_fee * new_item.amount;
CHECK_COMMAND_SET(bonus_fee <= std::numeric_limits<uint64_t>::max() - (gold_needed_per_item * new_item.amount + NEW_ITEM_FEE), tvc.m_cc_overflow, "Cost overflow");
CHECK_COMMAND_SET(balance >= gold_needed_per_item * new_item.amount + NEW_ITEM_FEE + bonus_fee, tvc.m_cc_balance, "Balance too low for the new item creation fee and/or gold content");
if (new_item.group)
{
cc::cc_custom_item_t cid;
@ -123,16 +131,17 @@ bool cc_command_handler_new_item::execute(cryptonote::BlockchainDB &db, const cr
uint64_t balance;
cryptonote::cc_account_data_t ad;
const uint64_t gold_fee = new_item.amount * new_item.gold * CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND / 1000;
CHECK_AND_ASSERT_MES(db.get_cc_account_balance(new_item.cc_account, balance), false, "Failed to get account balance");
CHECK_AND_ASSERT_MES(balance >= NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee, false, "Balance too low for the new item creation fee");
const uint64_t gold_paid = NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee;
CHECK_AND_ASSERT_MES(db.get_cc_account_data(new_item.cc_account, ad), false, "Failed to get account data");
ad.balance -= gold_paid;
std::vector<uint32_t> role_bonus(NUM_ROLES, 0);
for (const auto &e: new_item.role_bonus)
role_bonus[e.first] = e.second;
const uint64_t bonus_fee = cc::get_item_creation_fee(new_item.prestige_bonus, role_bonus) * new_item.amount;
const uint64_t gold_fee = new_item.amount * new_item.gold * CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND / 1000;
CHECK_AND_ASSERT_MES(db.get_cc_account_balance(new_item.cc_account, balance), false, "Failed to get account balance");
CHECK_AND_ASSERT_MES(balance >= NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee, false, "Balance too low for the new item creation fee");
const uint64_t gold_paid = NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee + bonus_fee;
CHECK_AND_ASSERT_MES(db.get_cc_account_data(new_item.cc_account, ad), false, "Failed to get account data");
ad.balance -= gold_paid;
std::vector<uint64_t> user_data(NUM_CUSTOM_ITEM_USER_DATA, 0);
user_data[0] = new_item.group == COINS_ITEM_GROUP ? MAKE_COLLECTIBLE_COIN_EVENT_USER_DATA_0() : 0;
@ -151,7 +160,7 @@ bool cc_command_handler_new_item::execute(cryptonote::BlockchainDB &db, const cr
db.add_cc_name(new_item.name, NAME_TYPE_CUSTOM_ITEM, id);
std::map<uint32_t, int64_t> balance_changes;
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, gold_fee + NEW_ITEM_FEE, false, &balance_changes), false, "Failed to distribute to treasuries");
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, gold_fee + NEW_ITEM_FEE + bonus_fee, false, &balance_changes), false, "Failed to distribute to treasuries");
for (const auto &e: balance_changes)
events.add_full(cmd, e.first, new_item.cc_account, 0, id, new_item.amount, e.second, 0) << "Received item creation fee";
@ -184,14 +193,19 @@ bool cc_command_handler_new_item::revert(cryptonote::BlockchainDB &db, const cry
db.delete_cc_custom_item(id);
db.remove_cc_name(new_item.name);
std::vector<uint32_t> role_bonus(NUM_ROLES, 0);
for (const auto &e: new_item.role_bonus)
role_bonus[e.first] = e.second;
const uint64_t bonus_fee = cc::get_item_creation_fee(new_item.prestige_bonus, role_bonus) * new_item.amount;
const uint64_t gold_fee = new_item.amount * new_item.gold * CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND / 1000;
CHECK_AND_ASSERT_MES(db.get_cc_account_balance(new_item.cc_account, balance), false, "Failed to get account balance");
CHECK_AND_ASSERT_MES(balance <= std::numeric_limits<uint64_t>::max() - (NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee), false, "Balance would overflow");
ad.balance += NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee;
ad.balance += NEW_ITEM_FEE + new_item.gold * new_item.amount + gold_fee + bonus_fee;
db.set_cc_account_data(new_item.cc_account, ad);
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, NEW_ITEM_FEE + gold_fee, true), false, "Failed to distribute from treasuries");
CHECK_AND_ASSERT_MES(cc::distribute_to_treasuries(db, NEW_ITEM_FEE + gold_fee + bonus_fee, true), false, "Failed to distribute from treasuries");
return true;
}

View File

@ -88,21 +88,27 @@ bool cc_command_handler_script_choice::check(const cryptonote::BlockchainDB &db,
CHECK_COMMAND_SET(db.get_cc_account_data(ad_player.script_owner, ad_owner), tvc.m_cc_invalid_account, "Account does not exist");
uint64_t reserved_owner_balance, reserved_player_balance;
uint64_t reserved_owner_fees, reserved_player_fees;
std::map<uint32_t, uint32_t> reserved_owner_items, reserved_player_items;
cc::script::get_choice_reserves(db, script, script_choice.state, script_choice.choice, {}, {}, reserved_owner_balance, reserved_owner_items, reserved_player_balance, reserved_player_items, script_choice.cc_account, script_choice.owner, script_choice.city);
CHECK_COMMAND_SET(reserved_owner_balance <= ad_owner.balance, tvc.m_cc_balance, "Owner does not have enough gold for reserve");
cc::script::get_choice_reserves(db, script, script_choice.state, script_choice.choice, {}, {}, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, reserved_player_balance, reserved_player_fees, reserved_player_items, script_choice.cc_account, script_choice.owner, script_choice.city);
CHECK_COMMAND_SET(reserved_owner_fees <= std::numeric_limits<uint64_t>::max() - reserved_owner_balance, tvc.m_cc_overflow, "Gold overflow");
CHECK_COMMAND_SET(reserved_owner_balance + reserved_owner_fees <= ad_owner.balance, tvc.m_cc_balance, "Owner does not have enough gold for reserve");
for (const auto &e: reserved_owner_items)
{
const auto i = ad_owner.item_balances.find(e.first);
CHECK_COMMAND_SET(i != ad_owner.item_balances.end() && i->second >= e.second, tvc.m_cc_balance, "Owner does not have enough item for reserve");
}
CHECK_COMMAND_SET(reserved_player_balance <= ad_player.balance, tvc.m_cc_balance, "Player does not have enough gold for reserve");
CHECK_COMMAND_SET(reserved_player_fees <= std::numeric_limits<uint64_t>::max() - reserved_player_balance, tvc.m_cc_overflow, "Gold overflow");
CHECK_COMMAND_SET(reserved_player_balance + reserved_player_fees <= ad_player.balance, tvc.m_cc_balance, "Player does not have enough gold for reserve");
for (const auto &e: reserved_player_items)
{
const auto i = ad_player.item_balances.find(e.first);
CHECK_COMMAND_SET(i != ad_player.item_balances.end() && i->second >= e.second, tvc.m_cc_balance, "Player does not have enough item for reserve");
}
CHECK_COMMAND_SET(reserved_owner_balance <= std::numeric_limits<uint64_t>::max() - reserved_player_balance, tvc.m_cc_overflow, "Overflow in cumulative gold reserves");
CHECK_COMMAND_SET(reserved_owner_balance + reserved_owner_fees <= std::numeric_limits<uint64_t>::max() - (reserved_player_balance + reserved_player_fees), tvc.m_cc_overflow, "Overflow in cumulative gold reserves");
uint32_t treasury = 0;
CHECK_AND_ASSERT_MES(db.get_cc_city_treasury(script_choice.city, treasury), false, "City treasury not found");
cc::script::script_effects_t effects;
try
@ -110,9 +116,11 @@ bool cc_command_handler_script_choice::check(const cryptonote::BlockchainDB &db,
BlockchainStateProxy proxy(db);
cryptonote::LockedTXN locked_txn((cryptonote::BlockchainDB&)db);
proxy.increase_reserve_balance(script_choice.owner, script_choice.cc_account, reserved_owner_balance);
proxy.increase_reserve_balance(script_choice.owner, treasury, reserved_owner_fees);
for (const auto &e: reserved_owner_items)
proxy.increase_reserve_item_balance(script_choice.owner, script_choice.cc_account, e.first, e.second);
proxy.increase_reserve_balance(script_choice.cc_account, script_choice.owner, reserved_player_balance);
proxy.increase_reserve_balance(script_choice.cc_account, treasury, reserved_player_fees);
for (const auto &e: reserved_player_items)
proxy.increase_reserve_item_balance(script_choice.cc_account, script_choice.owner, e.first, e.second);
cc::script::execute_script_choice_command(proxy, script, script_choice, new_events, new_state);
@ -174,7 +182,8 @@ bool cc_command_handler_script_choice::check(const cryptonote::BlockchainDB &db,
}
}
CHECK_COMMAND_SET(sum == reserved_player_balance + reserved_owner_balance, tvc.m_cc_bad_amount, "Balance changes do not balance");
CHECK_COMMAND_SET(sum == reserved_player_balance + reserved_player_fees + reserved_owner_balance + reserved_owner_fees,
tvc.m_cc_bad_amount, "Balance changes do not balance");
for (auto i = changes0.begin(); i != changes0.end();)
if (i->second == 0) i = changes0.erase(i); else ++i;
@ -204,8 +213,12 @@ bool cc_command_handler_script_choice::execute(cryptonote::BlockchainDB &db, con
CHECK_AND_ASSERT_MES(db.get_cc_account_data(ad_player.script_owner, ad_owner), false, "Failed to get account data");
uint64_t reserved_owner_balance, reserved_player_balance;
uint64_t reserved_owner_fees, reserved_player_fees;
std::map<uint32_t, uint32_t> reserved_owner_items, reserved_player_items;
cc::script::get_choice_reserves(db, script, script_choice.state, script_choice.choice, {}, {}, reserved_owner_balance, reserved_owner_items, reserved_player_balance, reserved_player_items, script_choice.cc_account, script_choice.owner, script_choice.city);
cc::script::get_choice_reserves(db, script, script_choice.state, script_choice.choice, {}, {}, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, reserved_player_balance, reserved_player_fees, reserved_player_items, script_choice.cc_account, script_choice.owner, script_choice.city);
uint32_t treasury = 0;
CHECK_AND_ASSERT_MES(db.get_cc_city_treasury(script_choice.city, treasury), false, "City treasury not found");
cc::script::script_effects_t effects;
BlockchainStateProxy proxy(db);
@ -217,9 +230,11 @@ bool cc_command_handler_script_choice::execute(cryptonote::BlockchainDB &db, con
cryptonote::LockedTXN locked_txn((cryptonote::BlockchainDB&)db);
proxy.increase_reserve_balance(script_choice.owner, script_choice.cc_account, reserved_owner_balance);
proxy.increase_reserve_balance(script_choice.owner, treasury, reserved_owner_fees);
for (const auto &e: reserved_owner_items)
proxy.increase_reserve_item_balance(script_choice.owner, script_choice.cc_account, e.first, e.second);
proxy.increase_reserve_balance(script_choice.cc_account, script_choice.owner, reserved_player_balance);
proxy.increase_reserve_balance(script_choice.cc_account, treasury, reserved_player_fees);
for (const auto &e: reserved_player_items)
proxy.increase_reserve_item_balance(script_choice.cc_account, script_choice.owner, e.first, e.second);
@ -231,20 +246,20 @@ bool cc_command_handler_script_choice::execute(cryptonote::BlockchainDB &db, con
// decrease actual balances after script execution since the script cannot actually touch real
// balances, just reserves, and changing balances first can impact enabled conditions
if (reserved_owner_balance || !reserved_owner_items.empty())
if (reserved_owner_balance || reserved_owner_fees || !reserved_owner_items.empty())
{
cryptonote::cc_account_data_t ad;
CHECK_AND_ASSERT_MES(db.get_cc_account_data(script_choice.owner, ad), false, "Failed to get account data");
ad.balance -= reserved_owner_balance;
ad.balance -= reserved_owner_balance + reserved_owner_fees;
for (const auto &e: reserved_owner_items)
ad.item_balances[e.first] -= e.second;
db.set_cc_account_data(script_choice.owner, ad);
}
if (reserved_player_balance || !reserved_player_items.empty())
if (reserved_player_balance || reserved_player_fees || !reserved_player_items.empty())
{
cryptonote::cc_account_data_t ad;
CHECK_AND_ASSERT_MES(db.get_cc_account_data(script_choice.cc_account, ad), false, "Failed to get account data");
ad.balance -= reserved_player_balance;
ad.balance -= reserved_player_balance + reserved_player_fees;
for (const auto &e: reserved_player_items)
ad.item_balances[e.first] -= e.second;
db.set_cc_account_data(script_choice.cc_account, ad);
@ -316,6 +331,12 @@ bool cc_command_handler_script_choice::execute(cryptonote::BlockchainDB &db, con
db.unreserve_cc_account(script_choice.cc_account, owner, player_account_data.first, player_account_data.second);
const auto &player_account_data_fees = ad_player.reserve[treasury];
if (player_account_data_fees.first != 0)
effects.balance_reserve.push_back({script_choice.cc_account, treasury, make_signed_checked(player_account_data_fees.first)});
db.unreserve_cc_account(script_choice.cc_account, treasury, player_account_data_fees.first, {});
CHECK_AND_ASSERT_MES(db.get_cc_account_data(ad_player.script_owner, ad_owner), false, "Failed to get account data");
const auto &owner_account_data = ad_owner.reserve[script_choice.cc_account];
@ -327,6 +348,12 @@ bool cc_command_handler_script_choice::execute(cryptonote::BlockchainDB &db, con
db.unreserve_cc_account(owner, script_choice.cc_account, owner_account_data.first, owner_account_data.second);
const auto &owner_account_data_fees = ad_owner.reserve[treasury];
if (owner_account_data_fees.first != 0)
effects.balance_reserve.push_back({owner, treasury, make_signed_checked(owner_account_data_fees.first)});
db.unreserve_cc_account(owner, treasury, owner_account_data_fees.first, {});
for (const auto &e: ad_player.script_string_overrides)
effects.string_override_removals.push_back({e.first, e.second});
}
@ -522,22 +549,23 @@ bool cc_command_handler_script_choice::revert(cryptonote::BlockchainDB &db, cons
// the reserves increase from execute will have been recorded in effects,
// therefore already undone, we just have the actual balances to update
uint64_t reserved_owner_balance, reserved_player_balance;
uint64_t reserved_owner_fees, reserved_player_fees;
std::map<uint32_t, uint32_t> reserved_owner_items, reserved_player_items;
cc::script::get_choice_reserves(db, script, script_choice.state, script_choice.choice, {}, {}, reserved_owner_balance, reserved_owner_items, reserved_player_balance, reserved_player_items, script_choice.cc_account, script_choice.owner, script_choice.city);
if (reserved_owner_balance || !reserved_owner_items.empty())
cc::script::get_choice_reserves(db, script, script_choice.state, script_choice.choice, {}, {}, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, reserved_player_balance, reserved_player_fees, reserved_player_items, script_choice.cc_account, script_choice.owner, script_choice.city);
if (reserved_owner_balance || reserved_owner_fees || !reserved_owner_items.empty())
{
cryptonote::cc_account_data_t ad;
CHECK_AND_ASSERT_MES(db.get_cc_account_data(script_choice.owner, ad), false, "Failed to get account data");
ad.balance += reserved_owner_balance;
ad.balance += reserved_owner_balance + reserved_owner_fees;
for (const auto &e: reserved_owner_items)
ad.item_balances[e.first] += e.second;
db.set_cc_account_data(script_choice.owner, ad);
}
if (reserved_player_balance || !reserved_player_items.empty())
if (reserved_player_balance || reserved_player_fees || !reserved_player_items.empty())
{
cryptonote::cc_account_data_t ad;
CHECK_AND_ASSERT_MES(db.get_cc_account_data(script_choice.cc_account, ad), false, "Failed to get account data");
ad.balance += reserved_player_balance;
ad.balance += reserved_player_balance + reserved_player_fees;
for (const auto &e: reserved_player_items)
ad.item_balances[e.first] += e.second;
db.set_cc_account_data(script_choice.cc_account, ad);

View File

@ -127,6 +127,7 @@ bool cc_command_handler_start_script::execute(cryptonote::BlockchainDB &db, cons
cryptonote::cc_script_data_t sd;
cc::script::ScriptHandle script;
uint64_t reserved_owner_balance, reserved_player_balance;
uint64_t reserved_owner_fees, reserved_player_fees;
std::map<uint32_t, uint32_t> reserved_owner_items, reserved_player_items;
game_events_t new_events(db.height());
uint32_t owner;
@ -174,9 +175,14 @@ bool cc_command_handler_start_script::execute(cryptonote::BlockchainDB &db, cons
if (std::get<0>(e) == cc::script::override_local)
db.set_cc_account_script_variable(start_script.cc_account, std::get<2>(e), std::get<1>(e), true);
cc::script::get_reserves(db, script, overrides, runestone.overrides, reserved_owner_balance, reserved_owner_items, reserved_player_balance, reserved_player_items, start_script.cc_account, owner, start_script.city);
uint32_t treasury = 0;
CHECK_AND_ASSERT_MES(db.get_cc_city_treasury(start_script.city, treasury), false, "City treasury not found");
cc::script::get_reserves(db, script, overrides, runestone.overrides, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, reserved_player_balance, reserved_player_fees, reserved_player_items, start_script.cc_account, owner, start_script.city);
db.reserve_cc_account(owner, start_script.cc_account, reserved_owner_balance, reserved_owner_items);
db.reserve_cc_account(owner, treasury, reserved_owner_fees, {});
db.reserve_cc_account(start_script.cc_account, owner, reserved_player_balance, reserved_player_items);
db.reserve_cc_account(start_script.cc_account, treasury, reserved_player_fees, {});
db.set_cc_account_script_state(start_script.cc_account, start_script.script, owner, 0, start_script.city, string_overrides);
@ -336,6 +342,7 @@ bool cc_command_handler_start_script::revert(cryptonote::BlockchainDB &db, const
cryptonote::cc_script_data_t sd;
cc::script::ScriptHandle script;
uint64_t reserved_owner_balance, reserved_player_balance;
uint64_t reserved_owner_fees, reserved_player_fees;
std::map<uint32_t, uint32_t> reserved_owner_items, reserved_player_items;
uint32_t owner;
@ -476,9 +483,14 @@ bool cc_command_handler_start_script::revert(cryptonote::BlockchainDB &db, const
CHECK_AND_ASSERT_MES(db.get_cc_runestone(start_script.flag, start_script.x, start_script.y, start_script.h, runestone), false, "No runestone found");
}
cc::script::get_reserves(db, script, overrides, runestone.overrides, reserved_owner_balance, reserved_owner_items, reserved_player_balance, reserved_player_items, start_script.cc_account, owner, start_script.city);
uint32_t treasury = 0;
CHECK_AND_ASSERT_MES(db.get_cc_city_treasury(start_script.city, treasury), false, "City treasury not found");
cc::script::get_reserves(db, script, overrides, runestone.overrides, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, reserved_player_balance, reserved_player_fees, reserved_player_items, start_script.cc_account, owner, start_script.city);
db.unreserve_cc_account(owner, start_script.cc_account, reserved_owner_balance, reserved_owner_items);
db.unreserve_cc_account(owner, treasury, reserved_owner_fees, {});
db.unreserve_cc_account(start_script.cc_account, owner, reserved_player_balance, reserved_player_items);
db.unreserve_cc_account(start_script.cc_account, treasury, reserved_player_fees, {});
db.set_cc_account_script_state(start_script.cc_account, 0, 0, 0, 0, {});
return true;

View File

@ -470,3 +470,6 @@ enum Item
#define DIVIDEND_FEE_PER_RECIPIENT (COIN / 1000)
#define MAX_CC_DIVIDEND_NOTE_LENGTH 64
#define PRESTIGE_BONUS_FEE 25000000
#define ROLE_BONUS_FEE 100000000

View File

@ -350,12 +350,14 @@ bool save_script_native(const ScriptHandle &s, std::string &source, uint32_t fla
source += indent + "reserves {\n";
for (const auto &e: script->player_reserves)
{
if (e.is_gold) source += indent + indent + "player gold " + get_operand_string(e.amount, op_gold) + "\n";
if (e.is_fees) source += indent + indent + "player fees " + get_operand_string(e.amount, op_gold) + "\n";
else if (e.is_gold) source += indent + indent + "player gold " + get_operand_string(e.amount, op_gold) + "\n";
else source += indent + indent + "player item " + get_operand_string(e.type, op_unsigned) + " " + get_operand_string(e.amount, op_unsigned) + "\n";
}
for (const auto &e: script->owner_reserves)
{
if (e.is_gold) source += indent + indent + "owner gold " + get_operand_string(e.amount, op_gold) + "\n";
if (e.is_fees) source += indent + indent + "owner fees " + get_operand_string(e.amount, op_gold) + "\n";
else if (e.is_gold) source += indent + indent + "owner gold " + get_operand_string(e.amount, op_gold) + "\n";
else source += indent + indent + "owner item " + get_operand_string(e.type, op_unsigned) + " " + get_operand_string(e.amount, op_unsigned) + "\n";
}
source += indent + "}\n";
@ -387,12 +389,14 @@ bool save_script_native(const ScriptHandle &s, std::string &source, uint32_t fla
source += indent + indent + indent + "reserves {\n";
if (!choice.player_reserves.empty()) for (const auto &e: choice.player_reserves)
{
if (e.is_gold) source + indent + indent + indent + indent + "player gold " + get_operand_string(e.amount, op_gold) + "\n";
if (e.is_fees) source + indent + indent + indent + indent + "player fees " + get_operand_string(e.amount, op_gold) + "\n";
else if (e.is_gold) source + indent + indent + indent + indent + "player gold " + get_operand_string(e.amount, op_gold) + "\n";
else source += indent + indent + indent + indent + "player item " + get_operand_string(e.type, op_unsigned) + " " + get_operand_string(e.amount, op_unsigned) + "\n";
}
if (!choice.owner_reserves.empty()) for (const auto &e: choice.owner_reserves)
{
if (e.is_gold) source + indent + indent + indent + indent + "owner gold " + get_operand_string(e.amount, op_gold) + "\n";
if (e.is_fees) source + indent + indent + indent + indent + "owner fees " + get_operand_string(e.amount, op_gold) + "\n";
else if (e.is_gold) source + indent + indent + indent + indent + "owner gold " + get_operand_string(e.amount, op_gold) + "\n";
else source += indent + indent + indent + indent + "owner item " + get_operand_string(e.type, op_unsigned) + " " + get_operand_string(e.amount, op_unsigned) + "\n";
}
source += indent + indent + indent + "}\n";
@ -1241,14 +1245,24 @@ static std::string get_expression_string(const Expression &c)
return s;
}
static bool accumulate_reserves(BlockchainStateProxy &proxy, const std::vector<Reserve> &reserves, uint64_t &gold, std::map<uint32_t, uint32_t> &items, uint32_t account, uint32_t owner, uint32_t city, const crypto::hash &seed, uint64_t &salt)
static bool accumulate_reserves(BlockchainStateProxy &proxy, const std::vector<Reserve> &reserves, uint64_t &gold, uint64_t &fees, std::map<uint32_t, uint32_t> &items, uint32_t account, uint32_t owner, uint32_t city, const crypto::hash &seed, uint64_t &salt)
{
const uint8_t hf_version = proxy.hf_version();
gold = 0;
fees = 0;
items.clear();
for (const auto &r: reserves)
{
if (r.is_gold)
if (r.is_fees)
{
cryptonote::cc_city_data_t cd;
CHECK_AND_ASSERT_MES(proxy.get_cc_city_data(city, cd), false, "Failed to get city data");
const std::pair<uint64_t, bool> amount_value = get_attribute(proxy, r.amount, account, cd.treasury, city, seed, salt);
CHECK_AND_ASSERT_MES(!amount_value.second, false, "Gold value is signed");
CHECK_AND_ASSERT_MES(gold <= std::numeric_limits<uint64_t>::max() - amount_value.first, false, "Overflow in reserves");
fees += amount_value.first;
}
else if (r.is_gold)
{
const std::pair<uint64_t, bool> amount_value = get_attribute(proxy, r.amount, account, owner, city, seed, salt);
CHECK_AND_ASSERT_MES(!amount_value.second, false, "Gold value is signed");
@ -1299,9 +1313,10 @@ bool is_available(const cryptonote::BlockchainDB &db, const ScriptHandle &s, con
}
uint64_t player_balance_reserve = 0, owner_balance_reserve = 0;
uint64_t player_fees_reserve = 0, owner_fees_reserve = 0;
std::map<uint32_t, uint32_t> player_item_reserve, owner_item_reserve;
CHECK_AND_ASSERT_MES(accumulate_reserves(proxy, script->player_reserves, player_balance_reserve, player_item_reserve, account, owner, city, seed, salt), false, "Failed to accumulate player reserves");
CHECK_AND_ASSERT_MES(accumulate_reserves(proxy, script->owner_reserves, owner_balance_reserve, owner_item_reserve, account, owner, city, seed, salt), false, "Failed to accumulate owner reserves");
CHECK_AND_ASSERT_MES(accumulate_reserves(proxy, script->player_reserves, player_balance_reserve, player_fees_reserve, player_item_reserve, account, owner, city, seed, salt), false, "Failed to accumulate player reserves");
CHECK_AND_ASSERT_MES(accumulate_reserves(proxy, script->owner_reserves, owner_balance_reserve, owner_fees_reserve, owner_item_reserve, account, owner, city, seed, salt), false, "Failed to accumulate owner reserves");
bool met = false;
cryptonote::cc_account_data_t ad{};
@ -1354,17 +1369,17 @@ bool is_available(const cryptonote::BlockchainDB &db, const ScriptHandle &s, con
return available;
}
void get_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city)
void get_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, uint64_t &reserved_owner_fees, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, uint64_t &reserved_player_fees, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city)
{
BlockchainStateProxyWithOverrides proxy(db, account, default_overrides, overrides);
const crypto::hash seed = proxy.get_cc_random_seed();
uint64_t salt = (((uint64_t)account) << 32) | 0x30000000;
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, s.script->player_reserves, reserved_player_balance, reserved_player_items, account, owner, city, seed, salt), "Failed to accumulate player reserves");
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, s.script->owner_reserves, reserved_owner_balance, reserved_owner_items, account, owner, city, seed, salt), "Failed to accumulate owner reserves");
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, s.script->player_reserves, reserved_player_balance, reserved_player_fees, reserved_player_items, account, owner, city, seed, salt), "Failed to accumulate player reserves");
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, s.script->owner_reserves, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, account, owner, city, seed, salt), "Failed to accumulate owner reserves");
}
void get_choice_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, uint32_t current_state, uint32_t choice, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city)
void get_choice_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, uint32_t current_state, uint32_t choice, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, uint64_t &reserved_owner_fees, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, uint64_t &reserved_player_fees, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city)
{
CHECK_AND_ASSERT_THROW_MES(current_state < s.script->states.size(), "State is out of bounds");
const auto &state = s.script->states[current_state];
@ -1381,8 +1396,8 @@ void get_choice_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle
const crypto::hash seed = proxy.get_cc_random_seed();
uint64_t salt = (((uint64_t)account) << 32) | 0x40000000;
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, c.player_reserves, reserved_player_balance, reserved_player_items, account, owner, city, seed, salt), "Failed to accumulate player reserves");
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, c.owner_reserves, reserved_owner_balance, reserved_owner_items, account, owner, city, seed, salt), "Failed to accumulate owner reserves");
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, c.player_reserves, reserved_player_balance, reserved_player_fees, reserved_player_items, account, owner, city, seed, salt), "Failed to accumulate player reserves");
CHECK_AND_ASSERT_THROW_MES(accumulate_reserves(proxy, c.owner_reserves, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, account, owner, city, seed, salt), "Failed to accumulate owner reserves");
}
bool get_state(BlockchainStateProxy &proxy, const ScriptHandle &s, uint32_t account, uint32_t owner, uint32_t current_state, uint32_t city, std::string &ui, std::string &text, std::string &image, std::vector<std::tuple<uint32_t, std::string, std::string>> &choices, std::string &choices_ui)
@ -1718,9 +1733,21 @@ static bool execute(cryptonote::cc_command_t cmd, BlockchainStateProxy &proxy, g
gold_needed = cid.gold * value_extra;
CHECK_AND_ASSERT_MES(proxy.decrease_reserve_balance(owner, account, gold_needed), false, "Not enough balance to pay for action");
}
const uint64_t per_item_fee_bonus = cc::get_item_creation_fee(cid.prestige_bonus, cid.role_bonus);
CHECK_AND_ASSERT_MES(per_item_fee_bonus <= std::numeric_limits<uint64_t>::max() / value_extra, false, "Bonus fee overflow");
const uint64_t fee_bonus = per_item_fee_bonus * value_extra;
if (fee_bonus > 0)
{
cryptonote::cc_city_data_t cd;
CHECK_AND_ASSERT_MES(proxy.get_cc_city_data(city, cd), false, "Failed to get city data");
CHECK_AND_ASSERT_MES(proxy.decrease_reserve_balance(owner, cd.treasury, fee_bonus), false, "Not enough balance to pay for action");
CHECK_AND_ASSERT_MES(proxy.increase_reserve_balance(cd.treasury, owner, fee_bonus), false, "Balance overflow");
std::vector<std::pair<uint32_t, uint32_t>> items(1, std::make_pair(value, value_extra));
events.add_full(cmd, cd.treasury, owner, 0, std::move(items), fee_bonus, 0) << "Item creation fee";
}
CHECK_AND_ASSERT_MES(proxy.increase_reserve_item_balance(owner, account, value, value_extra), false, "Item balance overflow");
proxy.increase_item_count(value, value_extra);
events.add_full(cmd, owner, account, 0, value, value_extra, -(int64_t)gold_needed, 0) << "Created " << value_extra << " " << proxy.get_item_name(value) << " in '" << get_script_name(script) << "'";
events.add_full(cmd, owner, account, 0, value, value_extra, -(int64_t)(gold_needed + fee_bonus), 0) << "Created " << value_extra << " " << proxy.get_item_name(value) << " in '" << get_script_name(script) << "'";
}
break;
}

View File

@ -169,8 +169,8 @@ std::vector<std::tuple<uint8_t, uint64_t, std::string>> get_script_overrides(con
bool is_script_public(const ScriptHandle &script);
bool is_script_storyline(const ScriptHandle &script);
bool is_available(const cryptonote::BlockchainDB &db, const ScriptHandle &script, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint32_t account, uint32_t owner, uint32_t city, std::vector<std::pair<std::string, bool>> *requirements = NULL);
void get_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city);
void get_choice_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, uint32_t current_state, uint32_t choice, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city);
void get_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, uint64_t &reserved_owner_fees, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, uint64_t &reserved_player_fees, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city);
void get_choice_reserves(const cryptonote::BlockchainDB &db, const ScriptHandle &s, uint32_t current_state, uint32_t choice, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &default_overrides, const std::vector<std::tuple<uint8_t, uint64_t, std::string>> &overrides, uint64_t &reserved_owner_balance, uint64_t &reserved_owner_fees, std::map<uint32_t, uint32_t> &reserved_owner_items, uint64_t &reserved_player_balance, uint64_t &reserved_player_fees, std::map<uint32_t, uint32_t> &reserved_player_items, uint32_t account, uint32_t owner, uint32_t city);
bool get_next_state(const ScriptHandle &s, uint32_t current_state, uint32_t choice, int32_t &new_state);
bool get_state(BlockchainStateProxy &proxy, const ScriptHandle &s, uint32_t account, uint32_t owner, uint32_t current_state, uint32_t city, std::string &ui, std::string &text, std::string &image, std::vector<std::tuple<uint32_t, std::string, std::string>> &choices, std::string &choices_ui);

View File

@ -286,12 +286,20 @@ struct WeightedAction
struct Reserve
{
uint8_t is_gold;
union
{
struct
{
uint8_t is_gold: 1;
uint8_t is_fees: 1;
};
uint8_t data;
};
Operand type;
Operand amount;
BEGIN_SERIALIZE_OBJECT()
FIELD(is_gold)
FIELD(data)
FIELD(type)
FIELD(amount)
END_SERIALIZE()
@ -435,6 +443,7 @@ void set_partial_state_standalone_predicate(script_partial_state_t *state, Funct
void set_partial_state_ternary(script_partial_state_t *state);
void set_partial_state_precondition(script_partial_state_t *state, const char *text);
void set_partial_state_gold_reserve(script_partial_state_t *state, bool player);
void set_partial_state_fees_reserve(script_partial_state_t *state, bool player);
void set_partial_state_item_reserve(script_partial_state_t *state, bool player);
void set_partial_state_name(script_partial_state_t *state, const char *s);
void set_partial_state_owner(script_partial_state_t *state, uint32_t owner);

View File

@ -337,6 +337,26 @@ void set_partial_state_gold_reserve(script_partial_state_t *state, bool player)
}
Reserve r;
r.is_gold = 1;
r.is_fees = 0;
r.amount = std::move(state->operands[state->operands.size() - 1]);
state->operands.pop_back();
if (state->use_choice_reserves)
(player ? state->choice.player_reserves : state->choice.owner_reserves).push_back(std::move(r));
else
(player ? state->script->player_reserves : state->script->owner_reserves).push_back(std::move(r));
}
void set_partial_state_fees_reserve(script_partial_state_t *state, bool player)
{
MDEBUG((player ? "player" : "owner") << " fees reserve");
if (state->operands.size() < 1)
{
tfscript_error("Fees reserve needs at least one operand");
return;
}
Reserve r;
r.is_gold = 0;
r.is_fees = 1;
r.amount = std::move(state->operands[state->operands.size() - 1]);
state->operands.pop_back();
if (state->use_choice_reserves)
@ -355,6 +375,7 @@ void set_partial_state_item_reserve(script_partial_state_t *state, bool player)
}
Reserve r;
r.is_gold = 0;
r.is_fees = 0;
r.type = std::move(state->operands[state->operands.size() - 2]);
r.amount = std::move(state->operands[state->operands.size() - 1]);
state->operands.pop_back();

View File

@ -123,6 +123,7 @@ role { return ROLE; }
flag { return FLAG; }
create { return CREATE; }
during { return DURING; }
fees { return FEES; }
\+ { return ADD; }
- { return SUB; }

View File

@ -37,7 +37,7 @@ static script_partial_state_t *state = NULL;
%token <number> PERCENT PAY CONSUME TEMPERATURE_NUMBER TEMPERATURE RANDOM
%token <number> IMAGE UI CHOICES CHOICE NEXT SELECTED ENABLED ACTIONS PUBLIC IS BLOCKCHAIN BY OVERRIDES ID
%token <number> GEMSTONE BLOCK LABOUR COIN FOOD CUSTOM IF ELSE STORYLINE RESTRICTED START BACKGROUND LOG
%token <number> PROCEDURE CALL WITH ROLE FLAG CREATE DURING
%token <number> PROCEDURE CALL WITH ROLE FLAG CREATE DURING FEES
%type <number> script script_contents script_content choice_contents choice_content
%type <number> name owner description icon precondition reserves state public storyline
@ -223,8 +223,10 @@ reserves_contents: reserves_contents reserves_content
reserves_content: OWNER GOLD operand { set_partial_state_gold_reserve(state, false); }
| OWNER ITEM operand operand { set_partial_state_item_reserve(state, false); }
| OWNER FEES operand { set_partial_state_fees_reserve(state, false); }
| PLAYER GOLD operand { set_partial_state_gold_reserve(state, true); }
| PLAYER ITEM operand operand { set_partial_state_item_reserve(state, true); }
| PLAYER FEES operand { set_partial_state_fees_reserve(state, true); }
;
state: STATE STRING '{' state_contents '}' { set_partial_state_state(state, $2); }

View File

@ -12,6 +12,7 @@
#include "cc/cc_custom_item.h"
#include "game-wallet.h"
#include "game-state.h"
#include "game-util.h"
#include "caching-source-builder.h"
#include "ui-tb-message-box.h"
#include "ui-create-items.h"
@ -31,6 +32,7 @@ UICreateItemsDialog::UICreateItemsDialog(Context *ctx, const GameState *game):
amountWidget = GetWidgetByIDAndType<TBInlineSelect>(TBIDC("amount"));
noItemsContainer = GetWidgetByIDAndType<TBToggleContainer>(TBIDC("no-items"));
okButton = GetWidgetByIDAndType<TBButton>(TBIDC("ok"));
prestigeRoleBonusFeeWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("prestige-role-bonus-fee"));
amountWidget->SetState(WIDGET_STATE_DISABLED, true);
okButton->SetState(WIDGET_STATE_DISABLED, true);
@ -120,6 +122,9 @@ void UICreateItemsDialog::FillItems(const GameState *game, const std::shared_ptr
const uint32_t max_amount = 0xffffffff - amount;
amountWidget->SetLimits(1, max_amount, true);
amountWidget->SetState(WIDGET_STATE_DISABLED, false);
const uint64_t bonus_fee = cc::get_item_creation_fee(prestige_bonus, role_bonus);
prestigeRoleBonusFeeWidget->SetText(game_util::print_money(bonus_fee));
okButton->SetState(WIDGET_STATE_DISABLED, false);
}

View File

@ -56,6 +56,7 @@ private:
tb::TBInlineSelect *amountWidget;
tb::TBToggleContainer *noItemsContainer;
tb::TBButton *okButton;
tb::TBTextField *prestigeRoleBonusFeeWidget;
uint32_t item;

View File

@ -71,6 +71,8 @@ UINewItemDialog::UINewItemDialog(Context *ctx, const GameState *game):
pdescErrorContainer = GetWidgetByIDAndType<TBToggleContainer>(TBIDC("pdesc-error-container"));
sdescErrorWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("sdesc-error"));
sdescErrorContainer = GetWidgetByIDAndType<TBToggleContainer>(TBIDC("sdesc-error-container"));
prestigeBonusFeeWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("prestige-bonus-fee"));
roleBonusFeeWidget = GetWidgetByIDAndType<TBTextField>(TBIDC("role-bonus-fee"));
prestigeBonusContainer->SetValue(game->playerState.id == GAME_ACCOUNT);
roleBonusContainer->SetValue(game->playerState.id == GAME_ACCOUNT);
@ -89,6 +91,7 @@ UINewItemDialog::UINewItemDialog(Context *ctx, const GameState *game):
row->AddChild(tf);
roleBonusWidget[i] = new TBInlineSelect();
roleBonusWidget[i]->SetLimits(0, 100, true);
roleBonusWidget[i]->SetID(TBIDC("role-bonus"));
row->AddChild(roleBonusWidget[i]);
roleBonusColumn->AddChild(row);
}
@ -476,6 +479,23 @@ void UINewItemDialog::HandleSecondaryDescriptionChanged(StringHash eventType, Va
sdescErrorContainer->SetValue(!valid);
}
void UINewItemDialog::HandlePrestigeBonusChanged(StringHash eventType, VariantMap& eventData)
{
const uint32_t prestige_bonus = prestigeBonusWidget->GetValue();
const uint64_t fee = cc::get_item_creation_fee(prestige_bonus, std::vector<uint32_t>(NUM_ROLES, 0));
prestigeBonusFeeWidget->SetText(game_util::print_money(fee));
}
void UINewItemDialog::HandleRoleBonusChanged(StringHash eventType, VariantMap& eventData)
{
std::vector<uint32_t> v(NUM_ROLES, 0);
for (int i = 0; i < NUM_ROLES; ++i)
if (roleBonusWidget[i])
v[i] = roleBonusWidget[i]->GetValue();
const uint64_t fee = cc::get_item_creation_fee(0, v);
roleBonusFeeWidget->SetText(game_util::print_money(fee));
}
void UINewItemDialog::HandleTBMessage(StringHash eventType, VariantMap& eventData)
{
#define CONNECT(name, function) do { if (ev->target->GetID() == TBIDC(name)) function(eventType, eventData); } while(0)
@ -497,6 +517,8 @@ void UINewItemDialog::HandleTBMessage(StringHash eventType, VariantMap& eventDat
CONNECT("name", HandleNameChanged);
CONNECT("primary-description", HandlePrimaryDescriptionChanged);
CONNECT("secondary-description", HandleSecondaryDescriptionChanged);
CONNECT("prestige-bonus", HandlePrestigeBonusChanged);
CONNECT("role-bonus", HandleRoleBonusChanged);
}
#undef CONNECT

View File

@ -47,6 +47,8 @@ private:
void HandleNameChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandlePrimaryDescriptionChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleSecondaryDescriptionChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandlePrestigeBonusChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleRoleBonusChanged(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleOK(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleCancel(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleTBMessage(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
@ -101,6 +103,8 @@ private:
tb::TBTextField *pdescErrorWidget;
tb::TBToggleContainer *sdescErrorContainer;
tb::TBTextField *sdescErrorWidget;
tb::TBTextField *prestigeBonusFeeWidget;
tb::TBTextField *roleBonusFeeWidget;
std::string last_refresh_stop_hash;
bool refresh;

View File

@ -6907,6 +6907,25 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_cc_get_item_creation_fee(const COMMAND_RPC_CC_GET_ITEM_CREATION_FEE::request& req, COMMAND_RPC_CC_GET_ITEM_CREATION_FEE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
PERF_TIMER(on_cc_get_item_creation_fee);
try
{
res.per_item_fee = cc::get_item_creation_fee(req.prestige_bonus, req.role_bonus);
}
catch (const std::exception &e)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Error getting item creation fee";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {
"rpc-bind-port"
, "Port for RPC server"

View File

@ -255,6 +255,7 @@ namespace cryptonote
MAP_JON_RPC_WE("cc_get_merchant_ship_available_items",on_cc_get_merchant_ship_available_items,COMMAND_RPC_CC_GET_MERCHANT_SHIP_AVAILABLE_ITEMS)
MAP_JON_RPC_WE("cc_get_merchant_ships", on_cc_get_merchant_ships, COMMAND_RPC_CC_GET_MERCHANT_SHIPS)
MAP_JON_RPC_WE("cc_get_building_cost", on_cc_get_building_cost, COMMAND_RPC_CC_GET_BUILDING_COST)
MAP_JON_RPC_WE("cc_get_item_creation_fee", on_cc_get_item_creation_fee, COMMAND_RPC_CC_GET_ITEM_CREATION_FEE)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -399,6 +400,7 @@ namespace cryptonote
bool on_cc_get_merchant_ship_available_items(const COMMAND_RPC_CC_GET_MERCHANT_SHIP_AVAILABLE_ITEMS::request& req, COMMAND_RPC_CC_GET_MERCHANT_SHIP_AVAILABLE_ITEMS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_cc_get_merchant_ships(const COMMAND_RPC_CC_GET_MERCHANT_SHIPS::request& req, COMMAND_RPC_CC_GET_MERCHANT_SHIPS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_cc_get_building_cost(const COMMAND_RPC_CC_GET_BUILDING_COST::request& req, COMMAND_RPC_CC_GET_BUILDING_COST::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_cc_get_item_creation_fee(const COMMAND_RPC_CC_GET_ITEM_CREATION_FEE::request& req, COMMAND_RPC_CC_GET_ITEM_CREATION_FEE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
//-----------------------
private:

View File

@ -6167,4 +6167,33 @@ namespace cryptonote
typedef epee::misc_utils::struct_init<response_t> response;
};
struct COMMAND_RPC_CC_GET_ITEM_CREATION_FEE
{
struct request_t
{
uint32_t prestige_bonus;
std::vector<uint32_t> role_bonus;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_OPT(prestige_bonus, (uint32_t)0)
KV_SERIALIZE(role_bonus)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
struct response_t
{
uint64_t per_item_fee;
std::string status;
bool untrusted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(per_item_fee)
KV_SERIALIZE(status)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
};
}

View File

@ -241,14 +241,15 @@ static void explore_script(const cc::script::ScriptHandle &script, const std::ve
delta_map_t script_reserve;
uint64_t reserved_owner_balance, reserved_player_balance;
uint64_t reserved_owner_fees, reserved_player_fees;
std::map<uint32_t, uint32_t> reserved_owner_items, reserved_player_items;
DummyDB db;
const auto overrides = cc::script::get_script_overrides(script);
cc::script::get_reserves(db, script, overrides, {}, reserved_owner_balance, reserved_owner_items, reserved_player_balance, reserved_player_items, PLAYER_ACCOUNT, owner, 0);
script_reserve[0].balance = - reserved_player_balance;
cc::script::get_reserves(db, script, overrides, {}, reserved_owner_balance, reserved_owner_fees, reserved_owner_items, reserved_player_balance, reserved_player_fees, reserved_player_items, PLAYER_ACCOUNT, owner, 0);
script_reserve[0].balance = - (reserved_player_balance + reserved_player_fees);
for (const auto &e: reserved_player_items)
script_reserve[0].items[e.first] = (int32_t)-e.second;
script_reserve[owner].balance = - reserved_owner_balance;
script_reserve[owner].balance = - (reserved_owner_balance + reserved_owner_fees);
for (const auto &e: reserved_owner_items)
script_reserve[owner].items[e.first] = (int32_t)-e.second;

View File

@ -88,6 +88,7 @@ ROLE_STONECUTTER = 10
ROLE_WORKFORCE = 14
ROLE_RESEARCH = 16
ROLE_FISHERY = 17
NUM_ROLES = 18
MAX_DISTANCE_PERCENTAGE = 1600
MAX_CC_NAME_LENGTH = 64
account_creation_fee = 100000000
@ -4501,6 +4502,32 @@ class CCTest():
res = daemon.cc_get_custom_items()
item = res.items_[-1]
# invalid bonuses
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, prestige_bonus = -1))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, prestige_bonus = 1001))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, role_bonus = [{'role': ROLE_AGRICULTURAL, 'bonus': 0}]))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, role_bonus = [{'role': 0, 'bonus': 1}]))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, role_bonus = [{'role': NUM_ROLES, 'bonus': 1}]))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, role_bonus = [{'role': ROLE_AGRICULTURAL, 'bonus': 101}]))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, role_bonus = [{'role': ROLE_AGRICULTURAL, 'bonus': -1}]))
self.assert_exception(lambda: self.wallet[3].cc_new_item(0, "name", False, False, 0, "primary desc", "secondary desc", can_create_more = True, role_bonus = [{'role': ROLE_AGRICULTURAL, 'bonus': 1}, {'role': ROLE_AGRICULTURAL, 'bonus': 1}]))
# bonus fee
role_bonus = [0] * NUM_ROLES
role_bonus[ROLE_AGRICULTURAL] = 5
role_bonus[ROLE_RESIDENTIAL1] = 4
res = daemon.cc_get_item_creation_fee(3, role_bonus)
per_item_fee = res.per_item_fee
res = self.wallet[3].cc_new_item(2, "bonus fee test", False, False, 0, "primary", "secondary", 5000, prestige_bonus = 3, role_bonus = [{'role': ROLE_AGRICULTURAL, 'bonus': 5}, {'role': ROLE_RESIDENTIAL1, 'bonus': 4}], can_create_more = True)
expected_balances[3] -= res.fee + NEW_ITEM_FEE + 2 * per_item_fee + 2 * (5000 * (1000 + CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND) // 1000)
expected_item_balances[3] = self.add_item(expected_item_balances[3], item.id + 1, 2)
self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = self.wallet[3].cc_create_items(item.id + 1, 6)
expected_balances[3] -= res.fee + 6 * per_item_fee + 6 * (5000 * (1000 + CUSTOM_ITEM_GOLD_GILDING_FEE_PER_THOUSAND) // 1000)
expected_item_balances[3] = self.add_item(expected_item_balances[3], item.id + 1, 6)
self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
# check balances
for i in range(4):
res = daemon.cc_get_account(account_id[i])
@ -4511,7 +4538,7 @@ class CCTest():
script = """
script {
name "AGR bonus test"
reserves { owner gold 0.03 }
reserves { owner gold 0.03 owner fees 3.0 }
state "0" {
init { create item ITEM 3 award item ITEM 3 }
}
@ -4529,6 +4556,12 @@ script {
expected_balances[2] -= res.fee
expected_item_balances[2] = self.add_item(expected_item_balances[2], item.id, 3)
expected_balances[3] -= 3 * 1000000
role_bonus = [0] * NUM_ROLES
role_bonus[ROLE_AGRICULTURAL] = 1
res = daemon.cc_get_item_creation_fee(0, role_bonus)
expected_balances[3] -= 3 * res.per_item_fee
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
# we don't use that item yet, it'll be used for assign/retrieve tests later
@ -4538,7 +4571,7 @@ script {
assert res.balance == expected_balances[i]
assert (res.item_balances if 'item_balances' in res else []) == expected_item_balances[i]
existing_items += 1
existing_items += 2
res = self.wallet[2].cc_new_item(100, "Test item 1", False, False, 0, "primary desc", "secondary desc")
expected_balances[2] -= res.fee

View File

@ -1458,3 +1458,15 @@ class Daemon(object):
'id': '0'
}
return self.rpc.send_json_rpc_request(cc_get_building_cost)
def cc_get_item_creation_fee(self, prestige_bonus, role_bonus):
cc_get_item_creation_fee = {
'method': 'cc_get_item_creation_fee',
'params': {
'prestige_bonus': prestige_bonus,
'role_bonus': role_bonus,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(cc_get_item_creation_fee)