diff --git a/docs/.vitepress/locales/en/components/sidebar.ts b/docs/.vitepress/locales/en/components/sidebar.ts index bd87695..1914b77 100644 --- a/docs/.vitepress/locales/en/components/sidebar.ts +++ b/docs/.vitepress/locales/en/components/sidebar.ts @@ -179,7 +179,6 @@ export default [ {text: 'GUI for quests and leaderboards', link:'/build/tutorials/howto-create_farming_game/Part18'} ] }, - { text: 'Tic-Tac-Toe Game', link: '/build/tutorials/tic-tac-toe-game/', diff --git a/docs/.vitepress/locales/es/components/sidebar.ts b/docs/.vitepress/locales/es/components/sidebar.ts index e9a5696..9f16959 100644 --- a/docs/.vitepress/locales/es/components/sidebar.ts +++ b/docs/.vitepress/locales/es/components/sidebar.ts @@ -145,6 +145,31 @@ export default [ link: '/es/build/tutorials/', collapsed: true, items: [ + { + "text": "Cómo crear un juego de cultivo", + "link": "/es/build/tutorials/howto-create_farming_game/", + "collapsed": true, + "items": [ + {"text": "Cómo crear un juego en WAX. Conceptos generales", "link": "/es/build/tutorials/howto-create_farming_game/Part1"}, + {"text": "Creación de un objeto o ítem cultivable en el estándar AtomicAssets", "link": "/es/build/tutorials/howto-create_farming_game/Part2"}, + {"text": "Creación de NFT de cultivo en Atomic Hub", "link": "/es/build/tutorials/howto-create_farming_game/Part3"}, + {"text": "Qué son los recursos y tokens en nuestro proceso de creación de juegos", "link": "/es/build/tutorials/howto-create_farming_game/Part4"}, + {"text": "Staking de NFTs", "link": "/es/build/tutorials/howto-create_farming_game/Part5"}, + {"text": "Tipos de cultivo y proceso de cultivo", "link": "/es/build/tutorials/howto-create_farming_game/Part6"}, + {"text": "Creación de GUI para juegos en WAX, staking y cultivo", "link": "/es/build/tutorials/howto-create_farming_game/Part7"}, + {"text": "Mejoras de ítems de juego en juegos de WAX", "link": "/es/build/tutorials/howto-create_farming_game/Part8"}, + {"text": "Mezclas de NFTs para juegos de WAX", "link": "/es/build/tutorials/howto-create_farming_game/Part9"}, + {"text": "Implementación de avatares en juegos de WAX", "link": "/es/build/tutorials/howto-create_farming_game/Part10"}, + {"text": "UI para mezclas, mejoras y avatares", "link": "/es/build/tutorials/howto-create_farming_game/Part11"}, + {"text": "Intercambios de tokens y recursos", "link": "/es/build/tutorials/howto-create_farming_game/Part12"}, + {"text": "Staking de tokens y votación en juegos", "link": "/es/build/tutorials/howto-create_farming_game/Part13"}, + {"text": "Gobernanza en juegos", "link": "/es/build/tutorials/howto-create_farming_game/Part14"}, + {"text": "GUI para intercambios, staking y gobernanza", "link": "/es/build/tutorials/howto-create_farming_game/Part15"}, + {"text": "Tablas de clasificación en juegos", "link": "/es/build/tutorials/howto-create_farming_game/Part16"}, + {"text": "Sistemas de misiones en el juego", "link": "/es/build/tutorials/howto-create_farming_game/Part17"}, + {"text": "GUI para misiones y tablas de clasificación", "link": "/es/build/tutorials/howto-create_farming_game/Part18"} + ] + }, { text: 'Tic-Tac-Toe Game', link: '/es/build/tutorials/tic-tac-toe-game/', diff --git a/docs/build/tutorials/howto-create_farming_game/Part10.md b/docs/build/tutorials/howto-create_farming_game/Part10.md index 36ff6c8..91a506a 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part10.md +++ b/docs/build/tutorials/howto-create_farming_game/Part10.md @@ -3,7 +3,8 @@ title: Part 10. Implementing avatars in WAX games order: 50 --- -This article will explore how to create avatars and their equipment, focusing on the customization and personalization aspects that enhance player experience. By detailing the process of designing avatars and selecting their gear, we aim to provide insights into building more engaging and interactive game elements, allowing players to deeply immerse themselves in the game world with characters that reflect their style and preferences. +This article will explore how to create avatars and their equipment, focusing on the customization and personalization aspects that enhance player experience. By detailing the process of designing avatars and selecting their gear, we aim to provide insights into building more engaging and interactive game elements, allowing players to deeply immerse themselves in the game world with characters that reflect their style and preferences. + ### 1. Creating categories Creating an avatar category involves defining a character with specific characteristics, which can later be enhanced by equipping items. This foundational step allows for the customization of avatars, providing players with the ability to tailor characters to their play style and preferences, thus enriching the gaming experience by adding depth to character development and interaction within the game world. @@ -21,9 +22,7 @@ Creating an avatar category involves defining a character with specific characte | bravery | uint32 | Will affect quests | | diplomacy | uint32 | Will affect interactions with other players | -Table. 1 – attributes of “avatar” - - +Table 1: Attributes of "avatar" Creating a category for equipment items mirrors the process of avatar creation, with each piece of equipment also possessing distinct characteristics. @@ -41,7 +40,8 @@ Creating a category for equipment items mirrors the process of avatar creation, | bravery | uint32 | Will affect quests | | diplomacy | uint32 | Will affect interactions with other players | -Table. 2 – attributes of “equip” +Table 2: Attributes of "equip" + ### 2. Creating templates Here is an example of creating an avatar and an item of equipment. @@ -51,9 +51,10 @@ Here is an example of creating an avatar and an item of equipment. ![](/public/assets/images/tutorials/howto-create_farming_game/part10/image4.png) The minting of avatars and equipment follows the processes outlined in previous articles, involving the creation and registration of these elements on the blockchain. + ### 3. Adding new tables to the contract code -Adding a table that links each player with their active avatar and equipped items is a strategic development step. This table not only tracks which avatars and items are currently in use but also facilitates interactions within the game, such as battles or resource collection, based on the equipped items’ attributes. +Adding a table that links each player with their active avatar and equipped items is a strategic development step. This table not only tracks which avatars and items are currently in use but also facilitates interactions within the game, such as battles or resource collection, based on the equipped items’ attributes. ```C struct [[eosio::table]] avatars_j @@ -61,15 +62,13 @@ struct [[eosio::table]] avatars_j name owner; std::vector equipment; - uint64_t primary_key() const { return owner.value; } }; typedef multi_index< "avatarsc"_n, avatars_j> avatars_t; ``` -owner – the account that puts on the avatar and equipment - -equipment – a vector of uint64_t – identifiers that indicate the active avatar and equipment +- **owner**: The account that puts on the avatar and equipment. +- **equipment**: A vector of `uint64_t` identifiers that indicate the active avatar and equipment. Creating a table for player stats involves aggregating the attributes of the avatar and any equipped items to reflect the player’s current capabilities within the game. @@ -79,19 +78,17 @@ struct [[eosio::table]] stats_j name owner; std::map stats; - uint64_t primary_key() const {return owner.value;} }; typedef multi_index<"stats"_n, stats_j> stats_t; ``` -owner – an account whose characteristics are specified in the table - -stats – a map containing characteristics in the format `{“economic” : 10, “bravery” : 7, etc}` +- **owner**: An account whose characteristics are specified in the table. +- **stats**: A map containing characteristics in the format `{"economic": 10, "bravery": 7, etc}`. -### 4. The logic for setting avatars and equipment. +### 4. The logic for setting avatars and equipment -The logic for setting avatars and equipment. in the game involves players selecting their character and outfitting them with various items to enhance their stats.  +The logic for setting avatars and equipment in the game involves players selecting their character and outfitting them with various items to enhance their stats. ```C else if (memo == "set avatar") @@ -106,7 +103,7 @@ The logic for setting avatars and equipment. in the game involves players select } ``` -Incorporating two memo options into the `receive_asset_transfer()` function allows players to either set their avatar by transferring a single avatar NFT with the memo "set avatar" or equip up to four different items by specifying "**set equipment**". The function then assigns the transferred avatar asset with the specified `asset_id` to the user's owner record, effectively updating the player's character or equipment setup in the game. +Incorporating two memo options into the `receive_asset_transfer()` function allows players to either set their avatar by transferring a single avatar NFT with the memo "set avatar" or equip up to four different items by specifying **"set equipment"**. The function then assigns the transferred avatar asset with the specified `asset_id` to the user's owner record, effectively updating the player's character or equipment setup in the game. ```C void game::set_avatar(const name &owner, const uint64_t &asset_id) @@ -155,68 +152,76 @@ void game::set_avatar(const name &owner, const uint64_t &asset_id) } ``` -1. The function's purpose is to validate the asset transferred by the player, ensuring it belongs to the correct collection and category for avatars or equipment.  - -```Cpp - auto assets = atomicassets::get_assets(get_self()); - auto asset_itr = assets.find(asset_id); - - check(asset_itr->collection_name == "collname"_n, "Wrong collection"); - check(asset_itr->schema_name == "avatar"_n, "Not an avatar asset"); -``` - -2\. To update a player's avatar in the game, the function retrieves the player's information from the avatars table using their username. - -```Cpp -avatars_t avatars_table(get_self(), get_self().value); - auto owner_avatar_itr = avatars_table.find(owner.value); -``` - -3\. If the user does not already exist in the avatars table, the function adds them by setting up a vector with five elements initialized to zeros. The avatar's ID is then placed in the first position of this vector, effectively registering the new avatar under the player's username.  - -```Cpp -if (owner_avatar_itr == std::end(avatars_table)) - { - avatars_table.emplace(get_self(), [&](auto &new_row) - { - new_row.owner = owner; - new_row.equipment.resize(5); - new_row.equipment[0] = asset_id; }); - } -``` - -4\. If the player already exists in the avatars table, the function updates their avatar with the new one provided in the argument. The old avatar is then returned to the player via an atomicassets::transfer, ensuring the player retains ownership of their previous avatar. +1. **Validation of the Transferred Asset**: + The function's purpose is to validate the asset transferred by the player, ensuring it belongs to the correct collection and category for avatars or equipment. + + ```C + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + + check(asset_itr->collection_name == "collname"_n, "Wrong collection"); + check(asset_itr->schema_name == "avatar"_n, "Not an avatar asset"); + ``` + +2. **Retrieving Player Information from Avatars Table**: + To update a player's avatar in the game, the function retrieves the player's information from the avatars table using their username. + + ```C + avatars_t avatars_table(get_self(), get_self().value); + auto owner_avatar_itr = avatars_table.find(owner.value); + ``` + +3. **Adding a New Player to the Avatars Table**: + If the user does not already exist in the avatars table, the function adds them by setting up a vector with five elements initialized to zeros. The avatar's ID is then placed in the first position of this vector, effectively registering the new avatar under the player's username. + + ```C + if (owner_avatar_itr == std::end(avatars_table)) + { + avatars_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.equipment.resize(5); + new_row.equipment[0] = asset_id; }); + } + ``` + +4. **Updating an Existing Player's Avatar**: + If the player already exists in the avatars table, the function updates their avatar with the new one provided in the argument. The old avatar is then returned to the player via an `atomicassets::transfer`, ensuring the player retains ownership of their previous avatar. + + ```C + else + { + // should return avatar asset back to player + const uint64_t old_avatar_id = owner_avatar_itr->equipment[0]; + + const std::vector assets_to_transfer = {old_avatar_id}; + const std::string memo = "return avatar"; + + action( + permission_level{get_self(), "active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "transfer"_n, + std::make_tuple( + get_self(), + owner, + assets_to_transfer, + memo)) + .send(); + + avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) + { row.equipment[0] = asset_id; }); + } + ``` + +5. **Recalculate Stats**: ```Cpp -else - { - // should return avatar asset back to player - const uint64_t old_avatar_id = owner_avatar_itr->equipment[0]; - - const std::vector assets_to_transfer = {old_avatar_id}; - const std::string memo = "return avatar"; - - action( - permission_level{get_self(), "active"_n}, - atomicassets::ATOMICASSETS_ACCOUNT, - "transfer"_n, - std::make_tuple( - get_self(), - owner, - assets_to_transfer, - memo)) - .send(); - - avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) - { row.equipment[0] = asset_id; }); - } + recalculate_stats(owner); ``` -5\. Now we need to recalculate new characteristics +This step recalculates the player's statistics after updating their avatar or equipment, ensuring that all bonuses or changes are accurately reflected in the player's profile. -```Cpp - recalculate_stats(owner); -``` +### Equipping Items Function Overview The function for equipping items involves listing the asset IDs of equipment to be worn by the player's avatar. This process checks each item for compatibility with the avatar and updates the player's equipment list in the game's database. @@ -254,49 +259,54 @@ const std::vector &asset_ids) } ``` -Function description: +### Function Description: -1\. **Prepare for Changes:** Create a vector to hold asset IDs of equipment to be returned and a map to ensure each equipment type is equipped no more than once. +1. **Prepare for Changes**: + Create a vector to hold asset IDs of equipment to be returned and a map to ensure each equipment type is equipped no more than once. -```Cpp - std::vector assets_to_return; + ```Cpp + std::vector assets_to_return; - std::map equiped_types; - equiped_types.insert(std::pair("flag", 0)); - equiped_types.insert(std::pair("jewelry", 0)); - equiped_types.insert(std::pair("crown", 0)); - equiped_types.insert(std::pair("cloak", 0)); -``` + std::map equiped_types; + equiped_types.insert(std::pair("flag", 0)); + equiped_types.insert(std::pair("jewelry", 0)); + equiped_types.insert(std::pair("crown", 0)); + equiped_types.insert(std::pair("cloak", 0)); + ``` -**2\. Equip New Items:** Iterate through the provided asset IDs, equipping each item while adhering to the rule that each equipment type can only be worn once. +2. **Equip New Items**: + Iterate through the provided asset IDs, equipping each item while adhering to the rule that each equipment type can only be worn once. -```Cpp - for (uint64_t asset_id : asset_ids) - { - set_equipment_item(owner, asset_id, assets_to_return, equiped_types); - } -``` + ```Cpp + for (uint64_t asset_id : asset_ids) + { + set_equipment_item(owner, asset_id, assets_to_return, equiped_types); + } + ``` -**3\. Update and Return:** Return any old assets back to the player's inventory and recalculate the player's characteristics based on the new equipment setup to reflect the changes in the player's abilities or stats accurately. +3. **Update and Return**: + Return any old assets back to the player's inventory and recalculate the player's characteristics based on the new equipment setup to reflect the changes in the player's abilities or stats accurately. -```Cpp -const std::string memo = "return equipment"; + ```Cpp + const std::string memo = "return equipment"; - action( - permission_level{get_self(), "active"_n}, - atomicassets::ATOMICASSETS_ACCOUNT, - "transfer"_n, - std::make_tuple( - get_self(), - owner, - assets_to_return, - memo)) - .send(); + action( + permission_level{get_self(), "active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "transfer"_n, + std::make_tuple( + get_self(), + owner, + assets_to_return, + memo)) + .send(); - recalculate_stats(owner); -``` + recalculate_stats(owner); + ``` -Let's consider the functions of putting on a single item **asset_id** on the player owner. **assets_to_return** stores the assets to be returned, **equiped_types** stores the number of worn items of each type of equipment. +### Function to Equip a Single Item + +The `set_equipment_item` function handles equipping a single item (`asset_id`) on the player (`owner`). The `assets_to_return` vector stores assets to be returned, and `equiped_types` tracks the number of worn items of each equipment type. ```Cpp void game::set_equipment_item(const name &owner, const uint64_t asset_id, @@ -318,7 +328,7 @@ std::vector &assets_to_return, std::map &equipe const std::string type = std::get(equipment_template_idata["type"]); equiped_types[type]++; - check(equiped_types[type] <= 1, "You can wear only 4 different euipment types at once"); + check(equiped_types[type] <= 1, "You can wear only 4 different equipment types at once"); if (type == "flag") { @@ -353,311 +363,295 @@ std::vector &assets_to_return, std::map &equipe } ``` -Function description: - -1\. Verify the player's presence in the equipment and avatar table; terminate if absent. Ensure the asset, from the correct collection and type, points correctly in the asset table. Load immutable template data into `equipment_template_idata`. - -```Cpp - avatars_t avatars_table(get_self(), get_self().value); - - auto owner_avatar_itr = avatars_table.find(owner.value); - check(owner_avatar_itr != std::end(avatars_table), "You can put equipment only when you have an avatar"); - - auto assets = atomicassets::get_assets(get_self()); - auto asset_itr = assets.find(asset_id); - auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); - - check(asset_itr->collection_name == "collname"_n, "Wrong collection"); - check(asset_itr->schema_name == "equip"_n, "Not an equipment item"); -``` - -2\. Identify the equipment type, incrementing its count in the map. If it's a duplicate, issue an error. Determine the `position` variable based on the equipment type for the new ID's placement. - -```Cpp -int32_t position = 0; - const std::string type = std::get(equipment_template_idata["type"]); - - equiped_types[type]++; - check(equiped_types[type] <= 1, "You can wear only 4 different euipment types at once"); - - if (type == "flag") - { - position = 1; - } - else if (type == "jewelry") - { - position = 2; - } - else if (type == "crown") - { - position = 3; - } - else if (type == "cloak") - { - position = 4; - } - else - { - check(false, "Wrong type of equipment"); - } -``` - -3\. If an existing item is found at the position, add its ID to the return vector. Update the table with the new asset. - -```Cpp - const uint64_t old_equip_id = owner_avatar_itr->equipment[position]; - - if (old_equip_id != 0) - { - assets_to_return.push_back(old_equip_id); - } - - avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) - { row.equipment[position] = asset_id; }); -``` - -4\. Recalculate player characteristics based on the new equipment setup. - -```Cpp -void game::recalculate_stats(const name &owner) -{ - stats_t stats_table(get_self(), get_self().value); - auto stats_itr = stats_table.find(owner.value); - - std::map stats; - - // init stats - stats.insert(std::pair("economic", 0)); - stats.insert(std::pair("productivity", 0)); - stats.insert(std::pair("vitality", 0)); - stats.insert(std::pair("bravery", 0)); - stats.insert(std::pair("diplomacy", 0)); - - // read stats - avatars_t avatars_table(get_self(), get_self().value); - auto avatar_itr = avatars_table.require_find(owner.value, "Your avatar was deleted"); - - auto assets = atomicassets::get_assets(get_self()); - - for (uint64_t asset_id : avatar_itr->equipment) - { - if (asset_id == 0) - { - continue; - } - - auto asset_itr = assets.find(asset_id); - auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); - - for (auto &key_value_pair : stats) - { - if (equipment_template_idata.find(key_value_pair.first) != std::end(equipment_template_idata)) - { - key_value_pair.second += std::get(equipment_template_idata[key_value_pair.first]); - } - } - } - - if (stats_itr == std::end(stats_table)) - { - stats_table.emplace(get_self(), [&](auto &new_row) - { - new_row.owner = owner; - new_row.stats = stats; }); - } - else - { - stats_table.modify(stats_itr, get_self(), [&](auto &row) - { row.stats = stats; }); - } -} -``` +This function ensures that each item equipped is compatible with the player's avatar and that equipment slots are managed appropriately, with old equipment being returned to the player's inventory if replaced. + +### Function Description: + +1. **Verify Player Presence**: + Verify the player's presence in the equipment and avatar table; terminate if absent. Ensure the asset, from the correct collection and type, points correctly in the asset table. Load immutable template data into `equipment_template_idata`. + + ```Cpp + avatars_t avatars_table(get_self(), get_self().value); + + auto owner_avatar_itr = avatars_table.find(owner.value); + check(owner_avatar_itr != std::end(avatars_table), "You can put equipment only when you have an avatar"); + + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + + check(asset_itr->collection_name == "collname"_n, "Wrong collection"); + check(asset_itr->schema_name == "equip"_n, "Not an equipment item"); + ``` + +2. **Determine Equipment Type and Position**: + Identify the equipment type, incrementing its count in the map. If it's a duplicate, issue an error. Determine the `position` variable based on the equipment type for the new ID's placement. + + ```Cpp + int32_t position = 0; + const std::string type = std::get(equipment_template_idata["type"]); + + equiped_types[type]++; + check(equiped_types[type] <= 1, "You can wear only 4 different equipment types at once"); + + if (type == "flag") + { + position = 1; + } + else if (type == "jewelry") + { + position = 2; + } + else if (type == "crown") + { + position = 3; + } + else if (type == "cloak") + { + position = 4; + } + else + { + check(false, "Wrong type of equipment"); + } + ``` + +3. **Update Equipment and Return Old Items**: + If an existing item is found at the position, add its ID to the return vector. Update the table with the new asset. + + ```Cpp + const uint64_t old_equip_id = owner_avatar_itr->equipment[position]; + + if (old_equip_id != 0) + { + assets_to_return.push_back(old_equip_id); + } + + avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) + { row.equipment[position] = asset_id; }); + ``` + +4. **Recalculate Stats**: + Recalculate player characteristics based on the new equipment setup. + + ```Cpp + void game::recalculate_stats(const name &owner) + { + stats_t stats_table(get_self(), get_self().value); + auto stats_itr = stats_table.find(owner.value); + + std::map stats; + + // init stats + stats.insert(std::pair("economic", 0)); + stats.insert(std::pair("productivity", 0)); + stats.insert(std::pair("vitality", 0)); + stats.insert(std::pair("bravery", 0)); + stats.insert(std::pair("diplomacy", 0)); + + // read stats + avatars_t avatars_table(get_self(), get_self().value); + auto avatar_itr = avatars_table.require_find(owner.value, "Your avatar was deleted"); + + auto assets = atomicassets::get_assets(get_self()); + + for (uint64_t asset_id : avatar_itr->equipment) + { + if (asset_id == 0) + { + continue; + } + + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + + for (auto &key_value_pair : stats) + { + if (equipment_template_idata.find(key_value_pair.first) != std::end(equipment_template_idata)) + { + key_value_pair.second += std::get(equipment_template_idata[key_value_pair.first]); + } + } + } + + if (stats_itr == std::end(stats_table)) + { + stats_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.stats = stats; }); + } + else + { + stats_table.modify(stats_itr, get_self(), [&](auto &row) + { row.stats = stats; }); + } + } + ``` **The function for calculating player characteristics involves several key steps:** -1\. Retrieve the player's stats and avatars from their respective tables, initializing characteristics to zero. +1. **Retrieve the player's stats and avatars from their respective tables, initializing characteristics to zero.** -```Cpp - stats_t stats_table(get_self(), get_self().value); - auto stats_itr = stats_table.find(owner.value); + ```Cpp + stats_t stats_table(get_self(), get_self().value); + auto stats_itr = stats_table.find(owner.value); - std::map stats; + std::map stats; - // init stats - stats.insert(std::pair("economic", 0)); - stats.insert(std::pair("productivity", 0)); - stats.insert(std::pair("vitality", 0)); - stats.insert(std::pair("bravery", 0)); - stats.insert(std::pair("diplomacy", 0)); + // init stats + stats.insert(std::pair("economic", 0)); + stats.insert(std::pair("productivity", 0)); + stats.insert(std::pair("vitality", 0)); + stats.insert(std::pair("bravery", 0)); + stats.insert(std::pair("diplomacy", 0)); - // read stats - avatars_t avatars_table(get_self(), get_self().value); - auto avatar_itr = avatars_table.require_find(owner.value, "Your avatar was deleted"); -``` + // read stats + avatars_t avatars_table(get_self(), get_self().value); + auto avatar_itr = avatars_table.require_find(owner.value, "Your avatar was deleted"); + ``` -2. Process active assets, skipping any with an ID of 0, and read template data for each asset. +2. **Process active assets, skipping any with an ID of 0, and read template data for each asset.** -```Cpp - auto assets = atomicassets::get_assets(get_self()); + ```Cpp + auto assets = atomicassets::get_assets(get_self()); - for (uint64_t asset_id : avatar_itr->equipment) - { - if (asset_id == 0) - { - continue; - } + for (uint64_t asset_id : avatar_itr->equipment) + { + if (asset_id == 0) + { + continue; + } - auto asset_itr = assets.find(asset_id); - auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + ``` -``` +3. **Calculate Characteristics**: + Iterate through all the characteristics to be calculated, as listed in the map. If a characteristic is present in a given item, add it to the total value. -3\. We go through all the characteristics that we want to calculate, and they are listed in the map. If a given characteristic is present in a given item, we add it to the total value + ```Cpp + for (auto &key_value_pair : stats) + { + if (equipment_template_idata.find(key_value_pair.first) != std::end(equipment_template_idata)) + { + key_value_pair.second += std::get(equipment_template_idata[key_value_pair.first]); + } + } + ``` -```Cpp -for (auto &key_value_pair : stats) - { - if (equipment_template_idata.find(key_value_pair.first) != std::end(equipment_template_idata)) - { - key_value_pair.second += std::get(equipment_template_idata[key_value_pair.first]); - } - } -``` + `key_value_pair` are pairs of the type {"economic", 0}, {"bravery", 0}, etc. If an element has the "economic" characteristic with a value of 3, then after this code, the "economic" field in the stats map will be updated to 3. -key_value_pair are pairs of the type {"economic", 0}, {"bravery", 0}, etc. If this element has the "economic" characteristic with a value of 3, then after this code, the "economic" field in the stats map will be 0 + 3 = 3. +4. **Sum Up Values and Update the Stats Table**: + Sum up the values for each characteristic listed in the map, adding to the total if present in the item. -4\. Sum up the values for each characteristic listed in the map, adding to the total if present in the item. + ```Cpp + if (stats_itr == std::end(stats_table)) + { + stats_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.stats = stats; }); + } + else + { + stats_table.modify(stats_itr, get_self(), [&](auto &row) + { row.stats = stats; }); + } + ``` -```Cpp - if (stats_itr == std::end(stats_table)) - { - stats_table.emplace(get_self(), [&](auto &new_row) - { - new_row.owner = owner; - new_row.stats = stats; }); - } - else - { - stats_table.modify(stats_itr, get_self(), [&](auto &row) - { row.stats = stats; }); - } -``` +5. **Claim Function Modification**: + Update the claim function to reflect player characteristics. -5\. Update the player's characteristics in the table if they exist; otherwise, add the player with the new stats. + ```Cpp + std::map stats = get_stats(owner); + ``` -Let's describe the lines that have been added or changed in the functions. + This retrieves the current characteristics of the player. -**Claim:** + ```Cpp + const uint8_t upgrade_percentage = 2 + stats["vitality"] / 10.0f; -```Cpp -std::map stats = get_stats(owner); -``` + const std::pair item_reward = claim_item(assets_itr, upgrade_percentage, time_now, stats); + ``` -This is where we get the actual characteristics of the player. + Now `upgrade_percentage` is not a constant, but depends on the "vitality" characteristic. The `claim_item` function also accepts `stats` to avoid unnecessary recalculations inside. -```Cpp -const uint8_t upgrade_percentage = 2 + stats["vitality"] / 10.0f; +6. **Mining Rate Calculation Modification**: - const std::pair item_reward = claim_item(assets_itr, upgrade_percentage, time_now, stats); -``` + ```Cpp + float miningRate_according2lvl = miningRate + stats.at("productivity") / 10.0f; + ``` -Now upgrade_percentage is not a constant, but depends on the "vitality" characteristic. The claim_item function now accepts stats to avoid doing unnecessary calculations inside. + The mining rate now also depends on the "productivity" characteristic. -**Claim_item:** +**Upgradeitem Function Changes:** -```Cpp -float miningRate_according2lvl = miningRate + stats.at("productivity") / 10.0f; -``` +1. **Update Characteristics and Call Updated Claim Item**: -and miningRate now also depends on the "productivity" characteristic + ```Cpp + std::map stats = get_stats(owner); + const uint8_t upgrade_percentage = 2 + stats["vitality"] / 10.0f; + const std::pair item_reward = claim_item(asset_itr, upgrade_percentage, time_now, stats); + ``` -**Upgradeitem:** + To read the player characteristics and calculate `upgrade_percentage`, the function calls the updated `claim_item`. The `upgrade_percentage` now depends on the player's "vitality" characteristic. -```Cpp -std::map stats = get_stats(owner); - const uint8_t upgrade_percentage = 2 + stats["vitality"] / 10.0f; - const std::pair item_reward = claim_item(asset_itr, upgrade_percentage, time_now, stats); -``` + ```Cpp + upgrade_item(asset_itr, upgrade_percentage, owner, next_level, time_now, stats); + ``` -In order to read characteristics, calculate upgrade_percentage, call the updated claim_item + The `upgrade_item` function now accepts `stats` to avoid unnecessary recalculations. -```Cpp -upgrade_item(asset_itr, upgrade_percentage, owner, next_level, time_now, stats); -``` +2. **Mining Rate and Resource Price Calculation in upgrade_item**: -and upgrade_item now accepts stats to avoid unnecessary calculations. + ```Cpp + float miningRate_according2lvl = mining_rate + stats.at("productivity") / 10.0f; + ``` -**upgrade_item:** + Here, the mining rate (`miningRate_according2lvl`) is updated to depend on the player's "productivity" characteristic. -```Cpp -float miningRate_according2lvl = mining_rate + stats.at("productivity") / 10.0f; -``` + ```Cpp + const float &resource_price = upgrade_time * miningRate_according2lvl * (1.0f - stats.at("economic") / 100.0f); + ``` -Here we get updated miningRate + The `resource_price` now decreases as the "economic" characteristic grows, making upgrades cheaper for players with higher "economic" stats. -```Cpp -const float &resource_price = upgrade_time * miningRate_according2lvl * (1.0f - stats.at("economic") / 100.0f); -``` - -and resource_price now decreases with the growth of the "economic" characteristic. - -Consider the get_stats function, which returns a map with player characteristics. - -```Cpp -std::map game::get_stats(const name &owner) -{ - std::map stats; - stats_t stats_table(get_self(), get_self().value); - auto stats_itr = stats_table.find(owner.value); - - if (stats_itr == std::end(stats_table)) - { - stats.insert(std::pair("economic", 0)); - stats.insert(std::pair("productivity", 0)); - stats.insert(std::pair("vitality", 0)); - stats.insert(std::pair("bravery", 0)); - stats.insert(std::pair("diplomacy", 0)); - } - else - { - stats = stats_itr->stats; - } +3. **get_stats Function**: - return stats; -} -``` + ```Cpp + std::map game::get_stats(const name &owner) + { + std::map stats; + stats_t stats_table(get_self(), get_self().value); + auto stats_itr = stats_table.find(owner.value); -To calculate player characteristics: + if (stats_itr == std::end(stats_table)) + { + stats.insert(std::pair("economic", 0)); + stats.insert(std::pair("productivity", 0)); + stats.insert(std::pair("vitality", 0)); + stats.insert(std::pair("bravery", 0)); + stats.insert(std::pair("diplomacy", 0)); + } + else + { + stats = stats_itr->stats; + } -1\. A map is created to hold the function's results. A pointer is then set to the player's entry in the stats table using their name. + return stats; + } + ``` -```Cpp - std::map stats; - stats_t stats_table(get_self(), get_self().value); - auto stats_itr = stats_table.find(owner.value); -``` + **Explanation**: -2\. If the player isn't found in the table, the function returns a map with all characteristics set to zero. If the player is found, it retrieves and returns their stats from the table, effectively summarizing their current game attributes. + 1. **Initialization**: A map is created to hold the function's results, and a pointer is set to the player's entry in the stats table using their name. -```Cpp - if (stats_itr == std::end(stats_table)) - { - stats.insert(std::pair("economic", 0)); - stats.insert(std::pair("productivity", 0)); - stats.insert(std::pair("vitality", 0)); - stats.insert(std::pair("bravery", 0)); - stats.insert(std::pair("diplomacy", 0)); - } - else - { - stats = stats_itr->stats; - } + 2. **Check for Player Stats**: If the player isn't found in the table, the function returns a map with all characteristics set to zero. If the player is found, it retrieves and returns their stats from the table, effectively summarizing their current game attributes. - return stats; -``` - -This article delves into creating and managing avatars and their equipment in a game, outlining the process from initial avatar category creation to the dynamic assignment of equipment. It covers the integration of avatars with in-game mechanics, such as staking and claiming rewards, and emphasizes the importance of customization in enhancing player experience.  +This article delves into creating and managing avatars and their equipment in a game, outlining the process from initial avatar category creation to the dynamic assignment of equipment. It covers the integration of avatars with in-game mechanics, such as staking and claiming rewards, and emphasizes the importance of customization in enhancing player experience. The article also discusses the technical aspects of setting up and updating player stats based on equipped items, ensuring a rich and interactive gaming environment. -**PS.** The [Following link](https://github.com/dapplicaio/GameAvatars) leads us to a repository that corresponds everything described, so you can simply build that code and use in a way you want. \ No newline at end of file +**PS.** The [following link](https://github.com/dapplicaio/GameAvatars) leads to a repository that corresponds with everything described, so you can simply build that code and use it as you wish. + diff --git a/docs/build/tutorials/howto-create_farming_game/Part11.md b/docs/build/tutorials/howto-create_farming_game/Part11.md index 693dcc0..e5b0234 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part11.md +++ b/docs/build/tutorials/howto-create_farming_game/Part11.md @@ -8,7 +8,7 @@ Building on our ReactJS and WAX smart contract interaction guide, this article a **Blending assets** ------------------- -Blends in smart contracts allow collection owners to create new, improved assets by combining existing ones. Imagine upgrading your tools by blending two of the same type, resulting in a tool that extracts significantly more resources.  +Blends in smart contracts allow collection owners to create new, improved assets by combining existing ones. Imagine upgrading your tools by blending two of the same type, resulting in a tool that extracts significantly more resources. This process enriches the user experience by adding depth to resource management and gameplay strategy, seamlessly integrated into the UI for easy user interaction. @@ -79,7 +79,7 @@ export const blend = async ({ activeUser, componentIds, assets, blendId }) => from: activeUser.accountName, to: 'dappgamemine', assets_ids: assets, - memo: `blend:${blendId}` + memo: `blend:${blendId}` } }); }; @@ -106,7 +106,7 @@ Our tool starts at level 1, but we can upgrade it. When this tool is staking on First, we need to decide which tool or workplace we want to improve. In order to perform this action, our tool needs to be staked. Then we pass it to the configuration of our action. -The UI should look at the maximum allowed level of the tool we want to upgrade before calling the action. It is written in the data of this tool.  +The UI should look at the maximum allowed level of the tool we want to upgrade before calling the action. It is written in the data of this tool. The next level should be higher than the current one. For our example, let's assume that our instrument has an initial first level. @@ -143,7 +143,7 @@ Here we use an action game called "upgradeitem". - **owner** -- this is our nickname of the user who connected his wallet, we take it from activeUser.accountName; - **item_to-upgrade** -- ID of the tool we want to upgrade; -- **stake_at_farmingitem** --  the ID of the workplace to which the tool is staking; +- **stake_at_farmingitem** -- the ID of the workplace to which the tool is staking; After a successful upgrade, our tool becomes level 2: diff --git a/docs/build/tutorials/howto-create_farming_game/Part12.md b/docs/build/tutorials/howto-create_farming_game/Part12.md index 8f22f99..5e6b25c 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part12.md +++ b/docs/build/tutorials/howto-create_farming_game/Part12.md @@ -1,20 +1,20 @@ --- -title: Part 12. Token and resource swaps +title: Part 12. Token and Resource Swaps order: 60 --- -In this article, we're building on previous discussions about upgrading items by introducing a method to exchange resources for tokens. We'll add a new table to track resources, where each entry includes a `key_id` (numerical ID for the resource), `resource_name`, and a `ratio` defining how many resources convert to one token. For instance, a ratio of 25 means 100 units of wood would exchange for 4 tokens. We'll also integrate the standard `eosio.token` contract, previously covered, to handle these transactions. +In this article, we're building on previous discussions about upgrading items by introducing a method to exchange resources for tokens. We'll add a new table to track resources, where each entry includes a `key_id` (numerical ID for the resource), `resource_name`, and a `ratio` defining how many resources convert to one token. For instance, a ratio of 25 means 100 units of wood would exchange for 4 tokens. We'll also integrate the standard `eosio.token` contract, previously covered, to handle these transactions. ```cpp struct [[eosio::table]] resourcecost { uint64_t key_id; std::string resource_name; - float ratio; // if user swap 100 wood and ration is 25 it means that user will receive 4 tokens + float ratio; // if user swaps 100 wood and the ratio is 25, it means that the user will receive 4 tokens - uint64_t primary_key() const { return blend_id; } + uint64_t primary_key() const { return key_id; } }; - typedef multi_index< "resourcecost"_n, resourcecost_j > resourcecost_t; + typedef multi_index< "resourcecost"_n, resourcecost > resourcecost_t; ``` @@ -63,7 +63,7 @@ void game::swap(const name& owner, const std::string& resource, const float& amo auto resourcecost_table_itr = resourcecost_table.require_find(stringToUint64(resource), "Could not find resource cost config"); const float token_amount = amount2swap / resourcecost_table_itr->ratio; - const asset tokens2receive = asset(token_amount * 10000, symbol("GAME", 4)); // change to token you have deployed + const asset tokens2receive = asset(token_amount * 10000, symbol("GAME", 4)); // change to the token you have deployed reduce_owner_resources_balance(owner, std::map({{resource, amount2swap}})); tokens_transfer(owner, tokens2receive); @@ -82,13 +82,13 @@ where To execute a resource-to-token swap in the game: 1. **Record Retrieval**: The function first retrieves the relevant record from the resource table based on the specified resource name. If the resource isn't found, an error is thrown. -2. **Token Calculation**: It then calculates the number of tokens the player should receive based on the amount of resource they want to swap and the predefined ratio in the table: *const* *float* token_amount = amount2swap / resourcecost_table_itr->ratio; -3. **Token Asset Creation**: An asset variable, representing the tokens, is created:\ - This step formats the token amount to consider the token's decimal places, ensuring the proper amount is processed for the swap:  *const* **asset** tokens2receive = **asset**(token_amount * 10000, **symbol**("GAME", 4)); +2. **Token Calculation**: It then calculates the number of tokens the player should receive based on the amount of resource they want to swap and the predefined ratio in the table: *const float token_amount = amount2swap / resourcecost_table_itr->ratio;* +3. **Token Asset Creation**: An asset variable, representing the tokens, is created: + This step formats the token amount to consider the token's decimal places, ensuring the proper amount is processed for the swap:  *const asset tokens2receive = asset(token_amount * 10000, symbol("GAME", 4));* The multiplication by (10^4) is necessary because the GAME token is defined with 4 decimal places. To accurately reflect the decimal in transactions, the `token_amount` calculated must be scaled up by 10,000. Additionally, the token symbol configuration requires two parameters: the token's name ("GAME") and its number of decimal places (4). This setup ensures that the token amount and its representation are correctly handled in the system for precise and valid transactions. -Note. While making your own game, make sure to replace GAME with your token name. +Note: While making your own game, make sure to replace GAME with your token name. ```cpp reduce_owner_resources_balance(owner, std::map({{resource, amount2swap}})); @@ -120,10 +120,6 @@ void game::tokens_transfer(const name& to, const asset& quantity) To complete the resource-to-token exchange process, the function initiates a token transfer using the contract's token transfer functionality. You will need to replace the referenced token contract name with the name of your specific token contract to ensure the transfer aligns with your game's tokenomics and smart contract settings. This step finalizes the swap by moving the calculated token amount from the game contract to the player's account. -"tokencontr"_n - This article detailed the process of exchanging in-game resources for tokens within a smart contract framework. It covered setting up a table to define resource-to-token conversion rates, calculating the number of tokens based on resources submitted by players, and handling token transactions effectively by ensuring all data aligns with the defined token characteristics. The focus was on the seamless integration of these functionalities into the game's ecosystem, facilitating a dynamic exchange mechanism that enhances player interaction and game economics. -**PS.** The [Following link](https://github.com/dapplicaio/TokenSwaps) leads us to a repository that corresponds everything described, so you can simply build that code and use in a way you want. - -### \ No newline at end of file +**PS.** The [following link](https://github.com/dapplicaio/TokenSwaps) leads us to a repository that corresponds to everything described, so you can simply build that code and use it in a way you want. diff --git a/docs/build/tutorials/howto-create_farming_game/Part13.md b/docs/build/tutorials/howto-create_farming_game/Part13.md index ae710fe..136bb00 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part13.md +++ b/docs/build/tutorials/howto-create_farming_game/Part13.md @@ -50,7 +50,6 @@ void game::receive_token_transfer else check(0, "Invalid memo"); } - ``` and @@ -77,13 +76,11 @@ void game::increase_tokens_balance(const name& owner, const asset& quantity) }); } } - ``` The function for managing token staking operates by accessing the balance table to locate a specific player's entry. If the player already has a recorded balance, the function increments the number of staked tokens accordingly. If no existing balance is found, it creates a new record for the player, documenting the amount of tokens they have staked. -Voting ------- +## Voting Now that token staking is in place, we'll focus on implementing a voting system to change the rate in resource swaps. To facilitate this, we'll introduce a new table specifically designed to manage voting records. This table will track each vote related to rate adjustments, allowing staked token holders to influence the resource-to-token conversion rates based on their preferences and stake in the game. This mechanism integrates democratic decision-making into the game's economic model. @@ -98,7 +95,6 @@ Now that token staking is in place, we'll focus on implementing a voting system uint64_t primary_key() const { return voting_id; } }; typedef multi_index< "changeration"_n, changeration_j > changeration_t; - ``` To support voting on changes in resource swap rates, we will establish a new table structured as follows: @@ -133,7 +129,6 @@ void game::createvoting( new_row.new_ratio = new_ratio; }); } - ``` To begin with, we check whether such a resource exists in the config table: @@ -206,7 +201,6 @@ void game::vote( }); } } - ``` Let's describe the code above in parts: @@ -217,7 +211,7 @@ Let's describe the code above in parts: require_auth(player); ``` -2\. For effective management and processing of player votes regarding resource-to-token ratio changes, the system incorporates critical data structures. These include the Token Balance Table to check players' staked tokens for voting power, the Resource Price Config to reference current and proposed ratio changes, and the Voting Table to accurately manage and tally votes using its iterators. These components are essential for ensuring transparency and integrity in the game's democratic decision-making process. +2. For effective management and processing of player votes regarding resource-to-token ratio changes, the system incorporates critical data structures. These include the Token Balance Table to check players' staked tokens for voting power, the Resource Price Config to reference current and proposed ratio changes, and the Voting Table to accurately manage and tally votes using its iterators. These components are essential for ensuring transparency and integrity in the game's democratic decision-making process. ```cpp balance_t balance_table(get_self(), get_self().value); @@ -229,23 +223,21 @@ auto changeration_table_itr = changeration_table.require_find(voting_id, "Could auto resourcecost_table_itr = resourcecost_table.require_find(stringToUint64(changeration_table_itr->resource_name)); ``` -3\. For the voting process, a specific variable is initialized to track progress towards the approval threshold predefined in the vote setup. If this threshold is met, the vote is considered successful, and changes can be applied to the resource ratio settings. +3. For the voting process, a specific variable is initialized to track progress towards the approval threshold predefined in the vote setup. If this threshold is met, the vote is considered successful, and changes can be applied to the resource ratio settings. ```cpp const asset goal_votes = asset(100 * 10000, symbol("GAME", 4)); // 100.0000 GAME tokens to apply changes asset total_votes = asset(0, symbol("GAME", 4)); - ``` -4\. So total votes count needs to be done +4. So total votes count needs to be done ```cpp for (const auto& map_itr : changeration_table_itr->voted) total_votes += map_itr.second; - ``` -5\. If the total votes reach the threshold set for the proposal, indicating approval by the players, then the resource price configuration they voted on is updated accordingly. Following this update, the specific vote is concluded and removed from the voting table, finalizing the decision and reflecting the players' collective choice in the game's settings. +5. If the total votes reach the threshold set for the proposal, indicating approval by the players, then the resource price configuration they voted on is updated accordingly. Following this update, the specific vote is concluded and removed from the voting table, finalizing the decision and reflecting the players' collective choice in the game's settings. ```cpp if(total_votes + balance_table_itr->quantity >= goal_votes) @@ -257,10 +249,9 @@ if(total_votes + balance_table_itr->quantity >= goal_votes) changeration_table.erase(changeration_table_itr); } - ``` -6\. otherwise, simply add the votes currently cast by the player +6. otherwise, simply add the votes currently cast by the player ```cpp else @@ -270,11 +261,8 @@ else new_row.voted[player] = balance_table_itr->quantity; }); } - ``` This article focused on implementing a token staking and voting system within a game environment. It detailed setting up a voting structure for players to influence changes in resource-to-token exchange rates through a democratic process. Key components included creating tables for tracking votes and configuring tokens staked by players to determine their voting power. The article also described how votes are tallied and the conditions under which proposed changes are implemented, emphasizing the integration of these functionalities into the game's smart contract framework. -**PS.** The [Following link](https://github.com/dapplicaio/TokenStakingAndVoting) leads us to a repository that corresponds everything described. - -### \ No newline at end of file +**PS.** The [Following link](https://github.com/dapplicaio/TokenStakingAndVoting) leads us to a repository that corresponds everything described. \ No newline at end of file diff --git a/docs/build/tutorials/howto-create_farming_game/Part14.md b/docs/build/tutorials/howto-create_farming_game/Part14.md index e1eb45c..9add3f1 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part14.md +++ b/docs/build/tutorials/howto-create_farming_game/Part14.md @@ -106,6 +106,8 @@ struct [[eosio::table]] votings_info_j This table records voting information in a separate table. With its help, we can view the total number of staked tokens, see if the voting time has expired, and so on. +3. Creating votings + ```cpp struct [[eosio::table]] voting_j { @@ -136,7 +138,7 @@ This table (scope -- the name of the vote) stores the voting options and std::ma This table stores pairs (voting, voting result). -3\. Creating votings +4. Creating votings ```cpp void game::crgenvt( @@ -217,6 +219,8 @@ void game::cravote( This function creates a vote that affects some variable from the config table. First, for each option, we check if its type matches the variable name. If so, we add information about the vote to the **vtsinfo** and **genvtngs** tables. +5. Adding voting options + ```cpp void game::add_voting_with_options(const name &voting_name, const std::vector &options) { @@ -232,12 +236,11 @@ void game::add_voting_with_options(const name &voting_name, const std::vector -Game **governance by users/players** +**Game governance by users/players** ------------------------------------ Let's outline the structure of the voting system: @@ -216,5 +216,3 @@ a table with all created rate change votes: **PS.** The [Following link](https://github.com/dapplicaio/GUIStakingGovernanceSwaps) leads us to a repository that corresponds everything described. - -### \ No newline at end of file diff --git a/docs/build/tutorials/howto-create_farming_game/Part16.md b/docs/build/tutorials/howto-create_farming_game/Part16.md index 825be36..ad1617b 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part16.md +++ b/docs/build/tutorials/howto-create_farming_game/Part16.md @@ -89,14 +89,13 @@ In the **increase_owner_resource_balance** and **decrease_owner_resource_balance ```cpp eosio::name resource_name(static_cast(resources_table_itr->resource_name)); set_lb_points(resource_name, owner, resources_table_itr->amount); - ``` The first of them creates **eosio::name** from a string denoting the name of the resource. That is, it is a leaderboard of wood or stone, etc. Then we set the already calculated value of the resource in the table. **4\. Using leaderboards for mining rate** -At the very end of the stake_items and **upgradeitem** functions, add a line with the **upgrade_mining_power_lb** function, which recalculates the new total mining rate and enters it into the leaderboard. +At the very end of the **stake_items** and **upgradeitem** functions, add a line with the **update_mining_power_lb** function, which recalculates the new total mining rate and enters it into the leaderboard. ```cpp void game::update_mining_power_lb(const name &account) @@ -124,12 +123,11 @@ void game::update_mining_power_lb(const name &account) set_lb_points("miningpwr"_n, account, mining_power); } - ``` Function description: -1. Take the pointer to the table where all the stacked items are located. We get a map with the player's characteristics. Take the pointer to the table of assets. +1. Take the pointer to the table where all the staked items are located. We get a map with the player's characteristics. Take the pointer to the table of assets. ```cpp staked_t staked_table(get_self(), account.value); @@ -139,27 +137,26 @@ const std::map stats = get_stats(account); auto assets = atomicassets::get_assets(get_self()); ``` -2\. Go through all the staked items. For each one, we take a pointer to its asset and find the value of miningBoost. Then, for each asset inside the staked one, we calculate the mining rate and add it to the total amount +2. Go through all the staked items. For each one, we take a pointer to its asset and find the value of miningBoost. Then, for each asset inside the staked one, we calculate the mining rate and add it to the total amount ```cpp for (const auto &staked : staked_table) - { - auto farmingitem_itr = assets.find(staked.asset_id); - auto farmingitem_mdata = get_mdata(farmingitem_itr); +{ + auto farmingitem_itr = assets.find(staked.asset_id); + auto farmingitem_mdata = get_mdata(farmingitem_itr); - float miningBoost = 1; - if (farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) - miningBoost = std::get(farmingitem_mdata["miningBoost"]); + float miningBoost = 1; + if (farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) + miningBoost = std::get(farmingitem_mdata["miningBoost"]); - for (const uint64_t asset_id : staked.staked_items) - { - mining_power += get_mining_power(asset_id, stats); - } + for (const uint64_t asset_id : staked.staked_items) + { + mining_power += get_mining_power(asset_id, stats); } - +} ``` -3\. Update the mining rate for this player in the leaderboard +3. Update the mining rate for this player in the leaderboard ```cpp set_lb_points("miningpwr"_n, account, mining_power); @@ -195,8 +192,8 @@ float game::get_mining_power(const uint64_t asset_id, const std::map +In the next article, we will cover the NFT staking process as preparation for resource or token farming. -In the next article we will cover NFT staking process as preparation to resource or token farming. \ No newline at end of file diff --git a/docs/build/tutorials/howto-create_farming_game/Part5.md b/docs/build/tutorials/howto-create_farming_game/Part5.md index b8d05f7..4bac71f 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part5.md +++ b/docs/build/tutorials/howto-create_farming_game/Part5.md @@ -9,18 +9,18 @@ Staking NFTs simplifies ownership tracking, crucial for rewarding owners periodi Staking NFTs in our game is a straightforward process: -1. The player picks an NFT to stake. -2. They send this NFT to our contract. -3. Our contract acknowledges and processes the transfer. -4. Finally, the contract logs the player's staked NFT in a table, ready for future interactions. +1. The player picks an NFT to stake. +2. They send this NFT to our contract. +3. Our contract acknowledges and processes the transfer. +4. Finally, the contract logs the player's staked NFT in a table, ready for future interactions. This process ensures a seamless and efficient staking experience, integral to the game's dynamics. -Now lets dig into whole staking source code and example  +### Staking Source Code and Example -Main file game.hpp  +Main file: `game.hpp` -```C +```cpp #include #include #include @@ -29,9 +29,9 @@ Main file game.hpp  using namespace eosio; ``` -At the top of the file, we connect all the necessary libraries and namespaces +At the top of the file, we connect all the necessary libraries and namespaces. -```C +```cpp class [[eosio::contract]] game : public contract { public: @@ -40,129 +40,132 @@ class [[eosio::contract]] game : public contract }; ``` -This is what an empty class called a game looks like, in it we will implement all the functions needed for staking.  +This is what an empty class called `game` looks like. In it, we will implement all the functions needed for staking. -The first step is to add a function for listening to the transfer, make it public:  +The first step is to add a function for listening to the transfer. Make it public: -```C - // listening atomicassets transfer - [[eosio::on_notify("atomicassets::transfer")]] - void receive_asset_transfer - ( - const name& from, - const name& to, - std::vector & asset_ids, - const std::string& memo - ); +```cpp +// listening atomicassets transfer +[[eosio::on_notify("atomicassets::transfer")]] +void receive_asset_transfer +( + const name& from, + const name& to, + std::vector& asset_ids, + const std::string& memo +); ``` -Regarding eosio::on_notify you can check more about it here   +Regarding `eosio::on_notify`, you can check more about it [here](https://developers.eos.io/welcome/v2.0/smart-contract-guides/payable-actions/#the-on_notify-attribute). In this function, we configure it to listen to the Atomic Assets contract and its transfer function. Here's a brief rundown: -- `from`: This represents the player sending the NFT. -- `to`: This should be set to our contract. -- `asset_ids`: These are the gaming NFTs involved in the transaction. -- `memo`: A message included with the transfer. Future memos will be specified to guide our contract on how to process the data. +- `from`: This represents the player sending the NFT. +- `to`: This should be set to our contract. +- `asset_ids`: These are the gaming NFTs involved in the transaction. +- `memo`: A message included with the transfer. Future memos will be specified to guide our contract on how to process the data. This setup is crucial for correctly handling NFT transfers in our game environment. -```C -//scope: owner - struct [[eosio::table]] staked_j - { - uint64_t asset_id; // item - std::vector staked_items; // farming items +```cpp +// scope: owner +struct [[eosio::table]] staked_j +{ + uint64_t asset_id; // item + std::vector staked_items; // farming items - uint64_t primary_key() const { return asset_id; } - }; - typedef multi_index< "staked"_n, staked_j > staked_t; + uint64_t primary_key() const { return asset_id; } +}; +typedef multi_index<"staked"_n, staked_j> staked_t; ``` In this part, we've set up a table to keep track of staked NFTs: -- Scope: Defined by the player's nickname. -- asset_id: Identifies the specific NFT (item). -- staked_items: An array containing the staked NFTs (farming items). -- primary_key: A necessary function in all tables, determining the search key for records. +- **Scope**: Defined by the player's nickname. +- **asset_id**: Identifies the specific NFT (item). +- **staked_items**: An array containing the staked NFTs (farming items). +- **primary_key**: A necessary function in all tables, determining the search key for records. -Additionally, we've crafted helper functions to enhance the code's readability within the contract. +Additionally, we've crafted helper functions to enhance the code's readability within the contract: -```C - void stake_farmingitem(const name& owner, const uint64_t& asset_id); - void stake_items(const name& owner, const uint64_t& farmingitem, const std::vector& items_to_stake); +```cpp +void stake_farmingitem(const name& owner, const uint64_t& asset_id); +void stake_items(const name& owner, const uint64_t& farmingitem, const std::vector& items_to_stake); - // get mutable data from NFT - atomicassets::ATTRIBUTE_MAP get_mdata(atomicassets::assets_t::const_iterator& assets_itr); - // get immutable data from template of NFT - atomicassets::ATTRIBUTE_MAP get_template_idata(const int32_t& template_id, const name& collection_name); - // update mutable data of NFT - void update_mdata(atomicassets::assets_t::const_iterator& assets_itr, const atomicassets::ATTRIBUTE_MAP& new_mdata, const name& owner); +// get mutable data from NFT +atomicassets::ATTRIBUTE_MAP get_mdata(atomicassets::assets_t::const_iterator& assets_itr); +// get immutable data from template of NFT +atomicassets::ATTRIBUTE_MAP get_template_idata(const int32_t& template_id, const name& collection_name); +// update mutable data of NFT +void update_mdata(atomicassets::assets_t::const_iterator& assets_itr, const atomicassets::ATTRIBUTE_MAP& new_mdata, const name& owner); + } + ] +} ``` -Now, we're diving deeper into the `game.cpp` file to detail the implementation of the function that monitors atomic transfers. This is where the magic happens in handling NFT transactions within our game's framework. +Now, we're diving deeper into the `game.cpp` file to detail the implementation of the function that monitors atomic transfers. This is where the magic happens in handling NFT transactions within our game's framework. -```C +```cpp void game::receive_asset_transfer ( const name& from, const name& to, - std::vector & asset_ids, + std::vector& asset_ids, const std::string& memo ) { - if(to != get_self()) + if (to != get_self()) return; - if(memo == "stake farming item") + if (memo == "stake farming item") { check(asset_ids.size() == 1, "You must transfer only one farming item to stake"); stake_farmingitem(from, asset_ids[0]); } - else if(memo.find("stake items:") != std::string::npos) + else if (memo.find("stake items:") != std::string::npos) { const uint64_t farmingitem_id = std::stoll(memo.substr(12)); stake_items(from, farmingitem_id, asset_ids); } else check(0, "Invalid memo"); - } ``` -First, we verify if the NFT was sent to our contract using `get_self()`. Depending on the memo, we distinguish between staking a farming item and other items. - -For a farming item, we confirm that only one NFT is sent, adhering to our game rule of staking one item at a time. Then, we invoke `stake_farmingitem`. +First, we verify if the NFT was sent to our contract using `get_self()`. Depending on the memo, we distinguish between staking a farming item and other items. -For staking other items, the memo must include the ID of the farming item where the NFTs are to be staked, formatted as "stake items:id", with the actual ID of the farming item. +- **Farming Item**: We confirm that only one NFT is sent, adhering to our game rule of staking one item at a time. Then, we invoke `stake_farmingitem`. +- **Other Items**: For staking other items, the memo must include the ID of the farming item where the NFTs are to be staked, formatted as "stake items:id", with the actual ID of the farming item. -```C +```cpp std::stoll(memo.substr(12)); ``` -Here we parse the id from the string (memo) and then we call the internal function for item staking: +Here, we parse the ID from the string (`memo`) and then call the internal function for item staking. -```C +```cpp else - check(0, "Invalid memo"); + check(0, "Invalid memo"); ``` If the transfer to the contract doesn't match the specified memos for staking, the contract will flag an error. This ensures only valid transactions are processed. Next, we'll explore additional functions used in this process, further detailing how the contract operates. -```C +### Function: `stake_farmingitem` + +```cpp void game::stake_farmingitem(const name& owner, const uint64_t& asset_id) { - auto assets = atomicassets::get_assets(get_self()); - auto asset_itr = assets.find(asset_id); + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); auto farmingitem_mdata = get_mdata(asset_itr); - if(farmingitem_mdata.find("slots") == std::end(farmingitem_mdata)) + if (farmingitem_mdata.find("slots") == std::end(farmingitem_mdata)) { auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); check(farmingitem_template_idata.find("maxSlots") != std::end(farmingitem_template_idata), - "Farming item slots was not initialized. Contact ot dev team"); + "Farming item slots were not initialized. Contact the dev team"); check(farmingitem_template_idata.find("stakeableResources") != std::end(farmingitem_template_idata), - "stakeableResources items at current farming item was not initialized. Contact to dev team"); + "stakeableResources items at the current farming item were not initialized. Contact the dev team"); farmingitem_mdata["slots"] = (uint8_t)1; farmingitem_mdata["level"] = (uint8_t)1; @@ -178,22 +181,22 @@ void game::stake_farmingitem(const name& owner, const uint64_t& asset_id) } ``` -Following is explanation of this function: +Following is an explanation of this function: -```C -auto assets = atomicassets::get_assets(get_self()); - auto asset_itr = assets.find(asset_id); +```cpp +auto assets = atomicassets::get_assets(get_self()); +auto asset_itr = assets.find(asset_id); ``` -This part covers how we retrieve a record of our contract's balance from the atomicassets table and locate the specific NFT the user wishes to stake. We'll be using functions from the atomicassets namespace. These are detailed in the header files included with the article, providing a straightforward tutorial on working with the atomic assets standard. No deep diving into the code is required; it's designed to be user-friendly for those implementing the atomic assets standard in their projects. +This part covers how we retrieve a record of our contract's balance from the `atomicassets` table and locate the specific NFT the user wishes to stake. We'll be using functions from the `atomicassets` namespace. These are detailed in the header files included with the article, providing a straightforward tutorial on working with the atomic assets standard. -```C +```cpp auto farmingitem_mdata = get_mdata(asset_itr); ``` -Here we extract the metadata of the NFT for further work with the data located in the NFT: +Here, we extract the metadata of the NFT for further work with the data located in the NFT. -```C +```cpp atomicassets::ATTRIBUTE_MAP game::get_mdata(atomicassets::assets_t::const_iterator& assets_itr) { auto schemas = atomicassets::get_schemas(assets_itr->collection_name); @@ -209,84 +212,82 @@ atomicassets::ATTRIBUTE_MAP game::get_mdata(atomicassets::assets_t::const_iterat } ``` -this is our data extraction function, this is where the schema(category) is taken: +This is our data extraction function, where the schema (category) is retrieved: -```C - auto schemas = atomicassets::get_schemas(assets_itr->collection_name); - auto schema_itr = schemas.find(assets_itr->schema_name.value); +```cpp +auto schemas = atomicassets::get_schemas(assets_itr->collection_name); +auto schema_itr = schemas.find(assets_itr->schema_name.value); ``` -The process involves passing data to the atomic data deserialization function in atomicdata. We'll include these files with the code for easy reference. Regarding staking, when we receive the metadata of the NFT, we follow specific steps to ensure accurate processing and recording within the contract. +The process involves passing data to the atomic data deserialization function in `atomicdata`. We'll include these files with the code for easy reference. Regarding staking, when we receive the metadata of the NFT, we follow specific steps to ensure accurate processing and recording within the contract. -```C +```cpp if(farmingitem_mdata.find("slots") == std::end(farmingitem_mdata)) - { - auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); - check(farmingitem_template_idata.find("maxSlots") != std::end(farmingitem_template_idata), - "Farming item slots was not initialized. Contact ot dev team"); - check(farmingitem_template_idata.find("stakeableResources") != std::end(farmingitem_template_idata), - "stakeableResources items at current farming item was not initialized. Contact to dev team"); - - farmingitem_mdata["slots"] = (uint8_t)1; - farmingitem_mdata["level"] = (uint8_t)1; +{ + auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + check(farmingitem_template_idata.find("maxSlots") != std::end(farmingitem_template_idata), + "Farming item slots were not initialized. Contact the dev team"); + check(farmingitem_template_idata.find("stakeableResources") != std::end(farmingitem_template_idata), + "stakeableResources items at the current farming item were not initialized. Contact the dev team"); - update_mdata(asset_itr, farmingitem_mdata, get_self()); - } + farmingitem_mdata["slots"] = (uint8_t)1; + farmingitem_mdata["level"] = (uint8_t)1; + update_mdata(asset_itr, farmingitem_mdata, get_self()); +} ``` When staking an NFT for the first time, we check for a 'slots' field. If it's absent, we follow the game's requirements to initialize fields, setting up slots and the farming item's level. This initialization is crucial only for the first-time staking of an NFT. -```C - staked_t staked_table(get_self(), owner.value); - staked_table.emplace(get_self(), [&](auto &new_row) - { - new_row.asset_id = asset_id; - }); - +```cpp +staked_t staked_table(get_self(), owner.value); +staked_table.emplace(get_self(), [&](auto &new_row) +{ + new_row.asset_id = asset_id; +}); ``` -Next, we record the staked NFT in our table, using the `owner.value` as the scope. This ensures that the entry is user-specific. The `emplace` function then takes over, where the first parameter is the account authorized to pay for the RAM, and the second parameter is a lambda function for adding a new record to the table. +Next, we record the staked NFT in our table, using the `owner.value` as the scope. This ensures that the entry is user-specific. The `emplace` function then takes over, where the first parameter is the account authorized to pay for the RAM, and the second parameter is a lambda function for adding a new record to the table. This sets the stage for detailing the item staking function. -```C +```cpp void game::stake_items(const name& owner, const uint64_t& farmingitem, const std::vector& items_to_stake) { - auto assets = atomicassets::get_assets(get_self()); + auto assets = atomicassets::get_assets(get_self()); staked_t staked_table(get_self(), owner.value); auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find farming staked item"); auto asset_itr = assets.find(farmingitem); - auto farmingitem_mdata = get_mdata(asset_itr); + auto farmingitem_mdata = get_mdata(asset_itr); auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); check(std::get(farmingitem_mdata["slots"]) >= staked_table_itr->staked_items.size() + items_to_stake.size(), - "You don't have empty slots on current farming item to stake this amount of items"); + "You don't have empty slots on the current farming item to stake this amount of items"); atomicdata::string_VEC stakeableResources = std::get(farmingitem_template_idata["stakeableResources"]); - for(const uint64_t& item_to_stake : items_to_stake) + for (const uint64_t& item_to_stake : items_to_stake) { asset_itr = assets.find(item_to_stake); auto item_mdata = get_mdata(asset_itr); item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); auto template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); - if(item_mdata.find("level") == std::end(item_mdata)) + if (item_mdata.find("level") == std::end(item_mdata)) { check(template_idata.find("farmResource") != std::end(template_idata), - "farmResource at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); + "farmResource at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); check(template_idata.find("miningRate") != std::end(template_idata), - "miningRate at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); + "miningRate at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); check(template_idata.find("maxLevel") != std::end(template_idata), - "maxLevel at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); + "maxLevel at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); item_mdata["level"] = (uint8_t)1; } check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), - "Item [" + std::to_string(item_to_stake) + "] can not be staked at current farming item"); + "Item [" + std::to_string(item_to_stake) + "] cannot be staked at the current farming item"); update_mdata(asset_itr, item_mdata, get_self()); } @@ -297,35 +298,37 @@ void game::stake_items(const name& owner, const uint64_t& farmingitem, const std } ``` -Now step by step +In this function, we detail the staking of multiple items, including checks for available slots and ensuring that each item meets the necessary criteria before being staked. + +### Step-by-Step Breakdown -```C - auto assets = atomicassets::get_assets(get_self()); +```cpp +auto assets = atomicassets::get_assets(get_self()); ``` -here we are receiving NFTs from contract +Here, we are retrieving the NFTs from the contract. This line fetches the collection of assets owned by the contract. -```C - staked_t staked_table(get_self(), owner.value); - auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find farming staked item"); +```cpp +staked_t staked_table(get_self(), owner.value); +auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find farming staked item"); ``` -The process involves extracting the player's table and searching for the specific farming item ID mentioned in the memo. If the specified ID isn't found, the system triggers an error message. +This step involves extracting the player's table and searching for the specific farming item ID mentioned in the memo. If the specified ID isn't found, the system triggers an error message. -```C - auto asset_itr = assets.find(farmingitem); +```cpp +auto asset_itr = assets.find(farmingitem); ``` -Next, the process involves locating the NFT in the atomic table to extract its data. +Next, we locate the NFT in the atomic table to extract its data. -```C - auto farmingitem_mdata = get_mdata(asset_itr); - auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); +```cpp +auto farmingitem_mdata = get_mdata(asset_itr); +auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); ``` -In this step, we extract the NFT metadata and immutable template data. The function `get_template_idata` is used for this purpose, functioning similarly to `get_mdata`. This extraction is vital for accurately understanding and utilizing the NFT's characteristics within the game. +In this step, we extract the NFT metadata and immutable template data. The function `get_template_idata` is used for this purpose, functioning similarly to `get_mdata`. This extraction is vital for accurately understanding and utilizing the NFT's characteristics within the game. -```C +```cpp atomicassets::ATTRIBUTE_MAP game::get_template_idata(const int32_t& template_id, const name& collection_name) { auto templates = atomicassets::get_templates(collection_name); @@ -344,86 +347,89 @@ atomicassets::ATTRIBUTE_MAP game::get_template_idata(const int32_t& template_id, In this part, we're extracting information about the NFT template. From this template data, we then pull out the specific details we need. -```C +```cpp check(std::get(farmingitem_mdata["slots"]) >= staked_table_itr->staked_items.size() + items_to_stake.size(), - "You don't have empty slots on current farming item to stake this amount of items"); - + "You don't have empty slots on the current farming item to stake this amount of items"); ``` The next step involves verifying if there's sufficient space in the farming item to store new items. This check is essential to ensure that the item's capacity aligns with the game's rules and mechanics. -```C +```cpp atomicdata::string_VEC stakeableResources = std::get(farmingitem_template_idata["stakeableResources"]); ``` In this phase, we utilize a vector or array of types. This is where we'll record all the resources that the player's chosen items are set to farm. -* * * * * - -```C -for(const uint64_t& item_to_stake : items_to_stake) - { - asset_itr = assets.find(item_to_stake); - auto item_mdata = get_mdata(asset_itr); - - item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); - auto template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); - if(item_mdata.find("level") == std::end(item_mdata)) - { - check(template_idata.find("farmResource") != std::end(template_idata), - "farmResource at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); - check(template_idata.find("miningRate") != std::end(template_idata), - "miningRate at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); - check(template_idata.find("maxLevel") != std::end(template_idata), - "maxLevel at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); +--- - item_mdata["level"] = (uint8_t)1; - } +```cpp +for (const uint64_t& item_to_stake : items_to_stake) +{ + asset_itr = assets.find(item_to_stake); + auto item_mdata = get_mdata(asset_itr); - check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), - "Item [" + std::to_string(item_to_stake) + "] can not be staked at current farming item"); - update_mdata(asset_itr, item_mdata, get_self()); + item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); + auto template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + if (item_mdata.find("level") == std::end(item_mdata)) + { + check(template_idata.find("farmResource") != std::end(template_idata), + "farmResource at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + check(template_idata.find("miningRate") != std::end(template_idata), + "miningRate at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + check(template_idata.find("maxLevel") != std::end(template_idata), + "maxLevel at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + + item_mdata["level"] = (uint8_t)1; } + + check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), + "Item [" + std::to_string(item_to_stake) + "] cannot be staked at the current farming item"); + update_mdata(asset_itr, item_mdata, get_self()); +} ``` Next, we iterate through the items the player wants to stake, extracting NFT data for each, similar to earlier steps. -```C +```cpp item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); ``` -We then record the 'last time stamped' field for each item, crucial for future resource farming calculations. This timestamp defaults to the moment the item is processed. +We then record the 'last timestamped' field for each item, which is crucial for future resource farming calculations. This timestamp defaults to the moment the item is processed. -```C -if(item_mdata.find("level") == std::end(item_mdata)) - { - check(template_idata.find("farmResource") != std::end(template_idata), - "farmResource at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); - check(template_idata.find("miningRate") != std::end(template_idata), - "miningRate at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); - check(template_idata.find("maxLevel") != std::end(template_idata), - "maxLevel at item[" + std::to_string(item_to_stake) + "] was not initialized. Contact to dev team"); +### Verifying and Updating Staked Items - item_mdata["level"] = (uint8_t)1; - } +```cpp +if(item_mdata.find("level") == std::end(item_mdata)) +{ + check(template_idata.find("farmResource") != std::end(template_idata), + "farmResource at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + check(template_idata.find("miningRate") != std::end(template_idata), + "miningRate at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + check(template_idata.find("maxLevel") != std::end(template_idata), + "maxLevel at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + + item_mdata["level"] = (uint8_t)1; +} ``` At this point, we verify if the item is being staked for the first time. If it's the first staking instance and the 'level' field is missing, it indicates that we need to add this field to the NFT. Additionally, we check other mandatory fields in the template to ensure they're properly initialized. -```C - check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), - "Item [" + std::to_string(item_to_stake) + "] can not be staked at current farming item"); +```cpp +check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), + "Item [" + std::to_string(item_to_stake) + "] cannot be staked at the current farming item"); ``` In this step, we assess if the farming item can accommodate the staking of an item that mines a specific resource. This involves checking the array of resources that the farming item can mine and ensuring that the items the player wants to stake align with the capabilities of the corresponding farming item. -```C +```cpp update_mdata(asset_itr, item_mdata, get_self()); ``` Once we confirm that everything is in order, we proceed to update the NFT metadata as described in the previous steps. This ensures that the NFT is correctly modified to reflect its new status and capabilities within the game's ecosystem. -```C +### Updating Staking Table + +```cpp void game::update_mdata(atomicassets::assets_t::const_iterator& assets_itr, const atomicassets::ATTRIBUTE_MAP& new_mdata, const name& owner) { action @@ -444,13 +450,16 @@ void game::update_mdata(atomicassets::assets_t::const_iterator& assets_itr, cons Next, we call the atomic function, inputting all the relevant data. After updating the NFT metadata, we also make corresponding changes to the staking table. -```C - staked_table.modify(staked_table_itr, get_self(), [&](auto &new_row) - { - new_row.staked_items.insert(std::end(new_row.staked_items), std::begin(items_to_stake), std::end(items_to_stake)); - }); +```cpp +staked_table.modify(staked_table_itr, get_self(), [&](auto &new_row) +{ + new_row.staked_items.insert(std::end(new_row.staked_items), std::begin(items_to_stake), std::end(items_to_stake)); +}); ``` -Here we use modify, since such an entry already exists in the table and we just need to change it. The first parameter is an iterator that needs to be changed (an entry in the table), the second is who pays for the RAM, the third is a lambda for editing an entry in the table. +Here, we use `modify`, since such an entry already exists in the table and we just need to update it. The first parameter is an iterator that points to the entry to be changed, the second is who pays for the RAM, and the third is a lambda function for editing the entry in the table. + +### Additional Notes + +PS. The [following link](https://github.com/dapplicaio/StakingNFTS) leads to a repository that corresponds to everything described here, so you can simply build that code and use it as needed. Future articles will also include previous code examples, allowing our framework to evolve over time while incorporating all past articles. -PS. The [Following link](https://github.com/dapplicaio/StakingNFTS) leads us to a repository that corresponds everything described, so you can simply build that code and use in a way you want. Next articles will contain also past code examples, so our framework will evolve over time containing all past articles. \ No newline at end of file diff --git a/docs/build/tutorials/howto-create_farming_game/Part6.md b/docs/build/tutorials/howto-create_farming_game/Part6.md index 37c516f..14f8f8f 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part6.md +++ b/docs/build/tutorials/howto-create_farming_game/Part6.md @@ -26,10 +26,9 @@ In the resource farming table, we have the following fields: - amount: The quantity of the specific resource. - resource_name: The name of the resource, such as "stone", "wood", etc. -Additionally, we'll use a helper function to convert the resource name into a numerical `key_id`. This function simplifies the process of managing and referencing resources in our table. +Additionally, we'll use a helper function to convert the resource name into a numerical `key_id`. This function simplifies the process of managing and referencing resources in our table. ```C - const uint64_t pixelfarm::stringToUint64(const std::string& str) { uint64_t hash = 0; @@ -47,27 +46,18 @@ const uint64_t pixelfarm::stringToUint64(const std::string& str) } ``` -Function, which takes the string `str` as an input parameter and returns a 64-bit unsigned integer of type `uint64_t`. The function uses a simple hashing algorithm to convert a string into a unique integer value. - -Initialize a 64-bit unsigned `hash` variable to 0. - -Check the length of the input string `str`. If it's empty (length 0), return the `hash` value. - -Iterate through each character in `str`. - -Calculate and store the ASCII code of each character in `char_s`. - -Perform the hashing operation: - -Shift `hash` value 4 bits left: `hash << 4`. - -Subtract `hash` from the shifted value: `(hash << 4) - hash`. - -Add the ASCII code of the character: `+ char_s`. - -Apply `hash = hash & hash` to limit the `hash` value to 64 bits and prevent overflow. +The function takes the string `str` as an input parameter and returns a 64-bit unsigned integer of type `uint64_t`. The function uses a simple hashing algorithm to convert a string into a unique integer value. -Return the final `hash` value after the loop ends. +- Initialize a 64-bit unsigned `hash` variable to 0. +- Check the length of the input string `str`. If it's empty (length 0), return the `hash` value. +- Iterate through each character in `str`. + - Calculate and store the ASCII code of each character in `char_s`. + - Perform the hashing operation: + - Shift `hash` value 4 bits left: `hash << 4`. + - Subtract `hash` from the shifted value: `(hash << 4) - hash`. + - Add the ASCII code of the character: `+ char_s`. + - Apply `hash = hash & hash` to limit the `hash` value to 64 bits and prevent overflow. +- Return the final `hash` value after the loop ends. Next, we will incorporate the stamp function into our code for further processing. @@ -75,50 +65,47 @@ Next, we will incorporate the stamp function into our code for further processin void claim(const name& owner, const uint64_t& farmingitem); ``` -and support functions for claiming +And support functions for claiming. +### Step-by-Step Explanation -Explanation of above, step by step. +1. `auto item_mdata = get_mdata(assets_itr);` Getting metadata for NFT using the `assets_itr` iterator. -1\. ```auto item_mdata = get_mdata(assets_itr);``` Getting metadata (metadata) for NFT using the `assets_itr` iterator. +2. `const uint32_t& lastClaim = std::get(item_mdata["lastClaim"]);` Getting the value of "lastClaim" from the NFT metadata. This represents the time of the last output of the resource. -2\. const uint32_t& lastClaim = std::get(item_mdata["lastClaim"]); Getting the value of "lastClaim" from the NFT metadata. This represents the time of the last output of the resource. +3. `std::pair mined_resource;` Creating an object of type `std::pair`, which will be used to store the mined resource and its quantity. -3\. std::pair mined_resource; Creation of an object of type `std::pair`, which will be used to store the mined resource and its quantity. +4. `if(time_now > lastClaim) { ... }` Checking whether time has passed since the last claim of the resource. If so, additional steps are taken. -4. if(time_now > lastClaim) { ... }: Checking whether time has passed since the last claim of the resource. If so, additional steps are taken. +5. `auto item_template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name);` Obtaining template data for NFT using its identifier and collection name. -5\. auto item_template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); Obtaining template data (template) for NFT using its identifier and collection name. +6. Obtaining the necessary data from the template: + - `const float& miningRate = std::get(item_template_idata["miningRate"]);` Getting the resource mining rate. + - `const std::string& farmResource = std::get(item_template_idata["farmResource"]);` Get the name of the resource to mine. -6\. Obtaining the necessary data from the template: +7. `const uint8_t& current_lvl = std::get(item_mdata["level"]);` Obtaining the current level of NFT from metadata. -    -- const float& miningRate = std::get>(item_template_idata["miningRate"]); Getting the resource mining rate. +8. Calculating the rate of resource extraction according to the level: + ```C + float miningRate_according2lvl = miningRate; + for(uint8_t i = 1; i < current_lvl; ++i) + miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); + ``` -    -- const std::string& farmResource = std::get(item_template_idata["farmResource"]); Get the name of the resource to mine. +9. Calculating the amount of extracted resource: + ```C + const float& reward = (time_now - lastClaim) * miningRate_according2lvl; + ``` -7\. const uint8_t& current_lvl = std::get(item_mdata["level"]); Obtaining the current level (level) of NFT from metadata. +10. `item_mdata["lastClaim"] = time_now;` Update the value of "lastClaim" in the NFT metadata to the current time. -8\. Calculation of the rate of resource extraction according to the level: +11. `update_mdata(assets_itr, item_mdata, get_self());` Update NFT metadata with the new value "lastClaim". -    float miningRate_according2lvl = miningRate; +12. Filling the `mined_resource` object with data on the mined resource and its quantity. -    for(uint8_t i = 1; i < current_lvl; ++i) +13. `return mined_resource;` Return of the `mined_resource` object as a result of the function. -        miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); - -9\. Calculation of the amount of extracted resource: - -    const float& reward = (time_now -- lastClaim) * miningRate_according2lvl; - -10\. item_mdata["lastClaim"] = time_now; Update the value of "lastClaim" in the NFT metadata to the current time. - -11\. update_mdata(assets_itr, item_mdata, get_self()); Update NFT metadata with new value "lastClaim". - -12\. Filling the mined_resource object with data on the mined resource and its quantity. - -13\. return mined_resource; Return of the mined_resource object as a result of the function. - -and an additional function to increase the balance of resources: +### Function to Increase Resource Balance ```C void game::increase_owner_resources_balance(const name& owner, const std::map& resources) @@ -149,27 +136,25 @@ void game::increase_owner_resources_balance(const name& owner, const std::mapstaked_items) { - auto assets_itr = assets.find(item_to_collect); - const std::pair item_reward = claim_item(assets_itr, 2, time_now); // 2 is the percentage of increase in mainrate for each level + auto assets_itr = assets.find(item_to_collect); + const std::pair item_reward = claim_item(assets_itr, 2, time_now); // 2 is the percentage of increase in mining rate for each level if(item_reward != std::pair()) if(item_reward.second > 0) @@ -205,34 +190,35 @@ void pixelfarm::claim(const name& owner, const uint64_t& farmingitem) } ``` -1\. require_auth(owner); Checking whether the user who called the function has sufficient authorization rights for the owner (`owner`). +1. `require_auth(owner);` Checking whether the user who called the function has sufficient authorization rights for the owner (`owner`). -2\. staked_t staked_table(get_self(), owner.value); Declaration and initialization of the `staked_t` table to track nested elements of the game owner (`owner`). +2. `staked_t staked_table(get_self(), owner.value);` Declaration and initialization of the `staked_t` table to track nested elements of the game owner (`owner`). -3\. auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find staked farming item"); Search for an entry in the table of nested items using the unique identifier `farmingitem`. If the record is not found, an error is generated. +3. `auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find staked farming item");` Search for an entry in the table of nested items using the unique identifier `farmingitem`. If the record is not found, an error is generated. -4\. auto assets = atomicassets::get_assets(get_self()); Get all assets using `atomicassets::get_assets` function. +4. `auto assets = atomicassets::get_assets(get_self());` Get all assets using `atomicassets::get_assets` function. -5\. `auto assets_itr = assets.find(farmingitem);`: Search for an asset with the unique identifier `farmingitem` in the asset collection. +5. `auto assets_itr = assets.find(farmingitem);` Search for an asset with the unique identifier `farmingitem` in the asset collection. -6\. `auto farmingitem_mdata = get_mdata(assets_itr);`: Get metadata for the specified asset. +6. `auto farmingitem_mdata = get_mdata(assets_itr);` Get metadata for the specified asset. -7\. `float miningBoost = 1;`: Initialize the `miningBoost` variable with the value 1. +7. `float miningBoost = 1;` Initialize the `miningBoost` variable with the value 1. -8\. `if(farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) miningBoost = std::get(farmingitem_mdata["miningBoost"]);`: Checking the presence of the "miningBoost" key in asset metadata and update `miningBoost` if present. +8. `if(farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) miningBoost = std::get(farmingitem_mdata["miningBoost"]);` Checking the presence of the "miningBoost" key in asset metadata and update `miningBoost` if present. -9\. std::map mined_resources; Creating a dictionary to store mined resources, where the key is the name of the resource, and the value is its quantity. +9. `std::map mined_resources;` Creating a dictionary to store mined resources, where the key is the name of the resource, and the value is its quantity. -10\. const uint32_t& time_now = current_time_point().sec_since_epoch(); Get the current time in seconds since the epoch. +10. `const uint32_t& time_now = current_time_point().sec_since_epoch();` Get the current time in seconds since the epoch. -11\. Cycle for each bid item: +11. Loop through each staked item: + - `const std::pair item_reward = claim_item(assets_itr, 2, time_now);` Call the `claim_item` function to get a reward for mining from the specified asset. + - `if(item_reward != std::pair()) if(item_reward.second > 0) mined_resources[item_reward.first] += item_reward.second;` Adding a mined resource to the dictionary `mined_resources` if the reward has been received and is positive. -     -- const std::pair item_reward = claim_item(assets_itr, 2, time_now); Call the `claim_item` function to get a reward for mining from the specified asset. +12. `check(mined_resources.size() > 0, "Nothing to claim");` Checking if there is anything to claim using the `check` function. -     -- if(item_reward != std::pair()) if(item_reward.second > 0) mined_resources[item_reward.first] += item_reward.second; Adding a mined resource to the dictionary ` mined_resources` if the reward has been received and is positive. +13. `increase_owner_resources_balance(owner, mined_resources);` Call the `increase_owner_resources_balance` function to increase the resource balance of the game owner. -12\. check(mined_resources.size() > 0, "Nothing to claim"); Checking if there is anything to claim using the `check` function. +### Additional Resources -13\. increase_owner_resources_balance(owner, mined_resources); Call the `increase_owner_resources_balance` function to increase the resource balance of the game owner. +For a complete reference and additional examples, you can find more information in the [staking and farming repository](https://github.com/dapplicaio/FarmingResources). -PS. Here you can find [link](https://github.com/dapplicaio/FarmingResources) to repository with staking and farming code. \ No newline at end of file diff --git a/docs/build/tutorials/howto-create_farming_game/Part8.md b/docs/build/tutorials/howto-create_farming_game/Part8.md index 69f4b17..2f40594 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part8.md +++ b/docs/build/tutorials/howto-create_farming_game/Part8.md @@ -40,57 +40,67 @@ void game::upgradeitem( } ``` -1\. Authentication confirmation: +1. **Authentication confirmation**: + + `require_auth(owner);` + + Checking that the transaction is authorized by the owner (`owner`). -require_auth(owner);  +2. **Getting the current time**: -Checking that the transaction is authorized by the owner (`owner`). + `const int32_t& time_now = current_time_point().sec_since_epoch();` + + Gets the current time in seconds from the epoch. -2\. Getting the current time: +3. **Obtaining assets and checking the availability of an asset for an upgrade**: -const int32_t& time_now = current_time_point().sec_since_epoch(); + ```C + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.require_find(item_to_upgrade, ("Could not find staked item[" + std::to_string(item_to_upgrade) +"]").c_str()); + ``` -Gets the current time in seconds from the epoch. + Obtaining assets and verifying the existence of an asset with an identifier `item_to_upgrade`. -3\. Obtaining assets and checking the availability of an asset for an upgrade: +4. **Obtaining the staking table and checking the availability of the upgrade stake**: -   auto assets     = atomicassets::get_assets(get_self()); + ```C + staked_t staked_table(get_self(), owner.value); + auto staked_table_itr = staked_table.require_find(staked_at_farmingitem, "Could not find staked farming item"); + ``` -   auto asset_itr  = assets.require_find(item_to_upgrade, ("Could not find staked item[" + std::to_string(item_to_upgrade) +"]").c_str()); + Get the staking table for the owner and check for the existence of a stake that matches the upgrade. -Obtaining assets and verifying the existence of an asset with an identifier 'item_to_upgrade'. +5. **Checking the presence of an asset in the stake**: -4\. Obtaining the staking table and checking the availability of the upgrade stake: + ```C + check(std::find(std::begin(staked_table_itr->staked_items), std::end(staked_table_itr->staked_items), item_to_upgrade) != std::end(staked_table_itr->staked_items), + "Item [" + std::to_string(item_to_upgrade) + "] is not staked at farming item"); + ``` -   staked_t staked_table(get_self(), owner.value); + Checking that the asset to be upgraded is actually in the stake. -   auto staked_table_itr = staked_table.require_find(staked_at_farmingitem, "Could not find staked farming item"); +6. **Collecting mined resources before upgrading**: -Get the staking table for the owner and check for the existence of a stake that matches the upgrade. + ```C + const std::pair item_reward = claim_item(asset_itr, 2, time_now); + if(item_reward != std::pair()) + { + if(item_reward.second > 0) + { + increase_owner_resources_balance(owner, std::map({item_reward})); + } + } + ``` -5\. Checking the presence of an asset in the stake: + Call the `claim_item` function to collect mined resources before upgrading. If a reward is received, it is added to the owner's balance. -   check(std::find(std::begin(staked_table_itr->staked_items), std::end(staked_table_itr->staked_items), item_to_upgrade) != std::end(staked_table_itr->staked_items), "Item [" + std::to_string(item_to_upgrade) + "] is not staked at farming item"); +7. **Item upgrade**: -Checking that the asset to be upgraded is actually in the stack. + ```C + upgrade_item(asset_itr, 2, owner, next_level, time_now); + ``` -6\. Collecting of mined resources before upgrading: - -   const std::pair item_reward = claim_item(asset_itr, 2, time_now); - -   if(item_reward != std::pair()) { - -       if(item_reward.second > 0) - -       { increase_owner_resources_balance(owner, std::map({item_reward})); } } - -  Call the `claim_item` function to collect mined resources before upgrading. If a reward is received, it is added to the owner's balance. - -7\. Item upgrade: - -   upgrade_item(asset_itr, 2, owner, next_level, time_now); - -Call the `upgrade_item` function to upgrade the asset. In this case, the upgrade is carried out with an increase in mining speed by 2% for each level. + Call the `upgrade_item` function to upgrade the asset. In this case, the upgrade is carried out with an increase in mining speed by 2% for each level. Alongside detailing the item upgrade process, we'll also outline the auxiliary functions involved. These functions are crucial for implementing upgrades effectively, facilitating the enhancement of items' resource mining capabilities. @@ -106,11 +116,11 @@ void game::upgrade_item( auto mdata = get_mdata(assets_itr); auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); - const float& mining_rate = std::get>(template_idata["miningRate"]); + const float& mining_rate = std::get(template_idata["miningRate"]); const uint8_t& current_lvl = std::get(mdata["level"]); const std::string& resource_name = std::get(template_idata["farmResource"]); - check(current_lvl < new_level, "New level must be higher then current level"); - check(new_level <= std::get(template_idata["maxLevel"]), "New level can not be higher then max level"); + check(current_lvl < new_level, "New level must be higher than current level"); + check(new_level <= std::get(template_idata["maxLevel"]), "New level cannot be higher than max level"); check(std::get(mdata["lastClaim"]) < time_now, "Item is upgrading"); float miningRate_according2lvl = mining_rate; @@ -128,66 +138,60 @@ void game::upgrade_item( update_mdata(assets_itr, mdata, get_self()); } ``` +### Detailed Explanation of Upgrade Functions -1\. Obtaining metadata and template data: - -   auto mdata  = get_mdata(assets_itr); - -   auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); - -Obtaining metadata and template data for asset processing. - -2\. Obtaining the required values: - - ```C -   const float& mining_rate   = std::get<(template_idata["miningRate"]); - -   const uint8_t& current_lvl = std::get(mdata["level"]); - -   const std::string& resource_name = std::get(template_idata["farmResource"]); -``` - -Obtaining the mining rate, current level and resource name for a given asset. - -3\. Checking conditions: - -   check(current_lvl < new_level, "New level must be higher than the current level"); - -   check(new_level <= std::get(template_idata["maxLevel"]), "New level cannot be higher than the max level"); - -   check(std::get(mdata["lastClaim"]) < time_now, "Item is upgrading"); - -Checking a number of conditions that must be met before upgrading an asset. - -4\. Calculation of new mining speed values: - -   float miningRate_according2lvl = mining_rate; - -   for(uint8_t i = 1; i < new_level; ++i) - -     miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); +1. **Obtaining Metadata and Template Data**: -  Calculation of the new mining speed, taking into account the percentage of improvement for each level. + ```C + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + ``` + Obtaining metadata and template data for asset processing. -5\. Calculation of the cost of the upgrade: +2. **Obtaining the Required Values**: -   const int32_t& upgrade_time  = get_upgrading_time(new_level) -- get_upgrading_time(current_lvl); + ```C + const float& mining_rate = std::get(template_idata["miningRate"]); + const uint8_t& current_lvl = std::get(mdata["level"]); + const std::string& resource_name = std::get(template_idata["farmResource"]); + ``` + Obtaining the mining rate, current level, and resource name for a given asset. -   const float& resource_price = upgrade_time * miningRate_according2lvl; +3. **Checking Conditions**: -   Calculation of upgrade time and upgrade cost based on the difference between the upgrade times of the new and current levels. + ```C + check(current_lvl < new_level, "New level must be higher than the current level"); + check(new_level <= std::get(template_idata["maxLevel"]), "New level cannot be higher than the max level"); + check(std::get(mdata["lastClaim"]) < time_now, "Item is upgrading"); + ``` + Checking a number of conditions that must be met before upgrading an asset. -6\. Updating data and reducing the balance of the owner: +4. **Calculation of New Mining Speed Values**: -   std::get(mdata["level"]) = new_level; + ```C + float miningRate_according2lvl = mining_rate; + for(uint8_t i = 1; i < new_level; ++i) + miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); + ``` + Calculation of the new mining speed, taking into account the percentage of improvement for each level. -   std::get(mdata["lastClaim"]) = time_now + upgrade_time; +5. **Calculation of the Cost of the Upgrade**: -   reduce_owner_resources_balance(owner, std::map({{resource_name, resource_price}})); + ```C + const int32_t& upgrade_time = get_upgrading_time(new_level) - get_upgrading_time(current_lvl); + const float& resource_price = upgrade_time * miningRate_according2lvl; + ``` + Calculation of upgrade time and upgrade cost based on the difference between the upgrade times of the new and current levels. -   update_mdata(assets_itr, mdata, get_self()); +6. **Updating Data and Reducing the Balance of the Owner**: -   Update asset data, set new level and time of last upgrade. Reduction of the owner's balance by the cost of the upgrade. Updating the asset data table. + ```C + std::get(mdata["level"]) = new_level; + std::get(mdata["lastClaim"]) = time_now + upgrade_time; + reduce_owner_resources_balance(owner, std::map({{resource_name, resource_price}})); + update_mdata(assets_itr, mdata, get_self()); + ``` + Update asset data, set new level and time of last upgrade. Reduction of the owner's balance by the cost of the upgrade. Updating the asset data table. In addition to item upgrades, the code below includes a mechanism to deduct resources from the player's balance as payment for the upgrade. @@ -215,58 +219,59 @@ void game::reduce_owner_resources_balance(const name& owner, const std::mapamount >= map_itr.second, ("Overdrawn balance: " + map_itr.first).c_str()); + ``` + Checking whether there are enough funds on the balance to carry out the withdrawal. -  The upgrade process involves a loop that goes through each resource listed in a map (resources). In this map, each resource's name is the key, and the amount to be deducted from the player's balance is the value. This step is crucial for accurately adjusting the player's resource balance according to the costs of the upgrades they wish to apply to their items. +5. **Updating the Resource Table**: -3\. Obtaining the key and searching in the resource table: + ```C + if(resources_table_itr->amount == map_itr.second) + resources_table.erase(resources_table_itr); + else + { + resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) + { + new_row.amount -= map_itr.second; + }); + } + ``` + - If the amount of the resource on the balance is equal to the amount to be subtracted (`map_itr.second`), then the corresponding record is deleted from the table. + - Otherwise, the amount of the resource is updated, reducing it by the appropriate amount. The `modify` function updates an entry in the table. -   const uint64_t& key_id = stringToUint64(map_itr.first); +### Upgrading the Farming Item -   auto resources_table_itr = resources_table.require_find(key_id, ("Could not find balance of " + map_itr.first).c_str()); - -   Obtaining a unique identifier (`key_id`) from a string and using it to find the corresponding entry in the `resources_table` table. - -4\. Balance sufficiency check: - -   check(resources_table_itr->amount >= map_itr.second, ("Overdrawn balance: " + map_itr.first).c_str()); - -   Checking whether there are enough funds on the balance to carry out the withdrawal. - -5\. Updating the resource table: - -   if(resources_table_itr->amount == map_itr.second) - -     resources_table.erase(resources_table_itr); - -   else { - -     resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) { - -       new_row.amount -= map_itr.second; }); - -   } - -   -- If the amount of the resource on the balance is equal to the amount to be subtracted (`map_itr.second`), then the corresponding record is deleted from the table. - -   -- Otherwise, the amount of the resource is updated, reducing it by the appropriate amount. The `modify' function updates an entry in the table. - -Now let's describe how the farming item is upgraded, Let's add the following fragments to our code: +The following code outlines how to upgrade a farming item: ```C void game::upgfarmitem(const name& owner, const uint64_t& farmingitem_to_upgrade, const bool& staked) @@ -289,72 +294,82 @@ void game::upgfarmitem(const name& owner, const uint64_t& farmingitem_to_upgrade auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("You do not own farmingitem[" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); upgrade_farmingitem(asset_itr, owner); } +} ``` -Let's look at the main steps of the function: - -1\. Authentication confirmation: - -   require_auth(owner); - -  Checking that the transaction is authorized by the owner (`owner`). - -2\. Checking whether the oil is stuck: +### Main Steps of the Upgrade Function -   if(staked) { +1. **Authentication Confirmation**: -     auto assets  = atomicassets::get_assets(get_self()); + ```C + require_auth(owner); + ``` + Checking that the transaction is authorized by the owner (`owner`). -     auto asset_itr  = assets.require_find(farmingitem_to_upgrade, ("Could not find staked item[" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); +2. **Checking Whether the Item is Staked**: -     staked_t staked_table(get_self(), owner.value); + ```C + if(staked) + { + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("Could not find staked item[" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); -     staked_table.require_find(farmingitem_to_upgrade, "Could not find staked farming item");    } + staked_t staked_table(get_self(), owner.value); + staked_table.require_find(farmingitem_to_upgrade, "Could not find staked farming item"); + } + else + { + auto assets = atomicassets::get_assets(owner); + auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("You do not own farmingitem[" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); + } + ``` + Depending on whether the farming item is staked, different steps are performed to verify the ownership or staking status of the item. -   else  { +3. **Upgrading the Farming Item**: -     auto assets = atomicassets::get_assets(owner); + ```C + upgrade_farmingitem(asset_itr, get_self()); + ``` + Call the `upgrade_farmingitem` function to upgrade the farming item. The function takes an iterator to the NFT and the name of the owner. -     auto asset_itr  = assets.require_find(farmingitem_to_upgrade, ("You do not own farmingitem[" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); - -   } - -3\. Upgrade item farming item: - -   upgrade_farmingitem(asset_itr, get_self()); - -   Call the `upgrade_farmingitem` function to upgrade a farming item. The function takes an iterator to NFT and the name of the owner. +### Upgrading Farming Item Logic ```C void pixelfarm::upgrade_farmingitem(atomicassets::assets_t::const_iterator& assets_itr, const name& owner) { - auto mdata = get_mdata(assets_itr); - auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); - check(std::get(mdata["slots"])++ < std::get(template_idata["maxSlots"]), "Farmingitem has max slots"); + check(std::get(mdata["slots"])++ < std::get(template_idata["maxSlots"]), "Farmingitem has max slots"); - update_mdata(assets_itr, mdata, owner); + update_mdata(assets_itr, mdata, owner); } ``` -1\. Getting metadata and data templates: - -   auto mdata  = get_mdata(assets_itr); - -   auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); +1. **Getting Metadata and Template Data**: -2\. Checking the number of slots: + ```C + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + ``` + Obtaining metadata and template data for asset processing. -   check(std::get(mdata["slots"])++ < std::get(template_idata["maxSlots"]), "Farmingitem has max slots"); +2. **Checking the Number of Slots**: -   Checking whether the number of farm item slots after the increment does not exceed the maximum number of slots defined in the nft template. Note that a postfix increment is used here (`++` before `std::get(mdata["slots"])`), so the slots value will be incremented before the comparison. + ```C + check(std::get(mdata["slots"])++ < std::get(template_idata["maxSlots"]), "Farmingitem has max slots"); + ``` + Checking whether the number of farm item slots after the increment does not exceed the maximum number of slots defined in the NFT template. Note that a postfix increment is used here (`++` before `std::get(mdata["slots"])`), so the slots value will be incremented before the comparison. -3\. Farming item data update: +3. **Farming Item Data Update**: -   update_mdata(assets_itr, mdata, owner); + ```C + update_mdata(assets_itr, mdata, owner); + ``` + Updating the farm item metadata in the table using the `update_mdata` function. This feature is used to save new data after an upgrade. -   Updating the farm item metadata in the table using the `update_mdata` function. This feature seems to be used to save new data after an upgrade. +### Next Steps -In the next article we will cover a similar function, which is creating new items, so called blends.  +In the next article, we will cover a similar function, which is creating new items, also known as blends. -PS. The [Following link](https://github.com/dapplicaio/GamItemUpgrades) leads us to a repository that corresponds everything described, so you can simply build that code and use in a way you want. \ No newline at end of file +PS. [The Following link](https://github.com/dapplicaio/GamItemUpgrades) leads us to a repository that corresponds to everything described, so you can simply build that code and use it as you wish. \ No newline at end of file diff --git a/docs/build/tutorials/howto-create_farming_game/Part9.md b/docs/build/tutorials/howto-create_farming_game/Part9.md index 6575b26..8868ad5 100644 --- a/docs/build/tutorials/howto-create_farming_game/Part9.md +++ b/docs/build/tutorials/howto-create_farming_game/Part9.md @@ -3,7 +3,7 @@ title: Part 9. Blends of NFTs for WAX games order: 45 --- -In this article, following our previous one  on upgrading farming items, we're diving into creating blends. Blending involves combining specific items to create new or enhanced ones within the game. This feature adds depth and strategy to the game, offering players the opportunity to craft unique items with potentially higher value or utility.  +In this article, following our previous one on upgrading farming items, we're diving into creating blends. Blending involves combining specific items to create new or enhanced ones within the game. This feature adds depth and strategy to the game, offering players the opportunity to craft unique items with potentially higher value or utility. We'll outline the necessary code additions to implement this functionality, enriching the game's interactive elements and player engagement. @@ -22,11 +22,11 @@ We'll outline the necessary code additions to implement this functionality, enri Introducing a new table for saving blend recipes enriches the game by allowing the creation of unique items through blending. Admins can define recipes, which players then use to blend items, specifying: -**-- **blend_id**:** A unique identifier for each recipe. +- **blend_id**: A unique identifier for each recipe. -**-- **blend_components**:** The list of NFT templates required for the blend. +- **blend_components**: The list of NFT templates required for the blend. -**-- **resulting_item**:** The ID of the new NFT template created from the blend. +- **resulting_item**: The ID of the new NFT template created from the blend. This feature adds a strategic layer, encouraging players to collect specific NFTs to create more valuable or powerful items. @@ -63,7 +63,7 @@ Implementing this function ensures that only the contract administrator has the const uint64_t new_blend_id = blends_table.available_primary_key(); ``` -The `available_primary_key` function is designed to provide a unique, incremental key for new entries in a table. If existing keys in the table are 1, 5, and 8, the function will return 9, ensuring that each new entry receives a distinct identifier.  +The `available_primary_key` function is designed to provide a unique, incremental key for new entries in a table. If existing keys in the table are 1, 5, and 8, the function will return 9, ensuring that each new entry receives a distinct identifier. ```C blends_table.emplace(get_self(), [&](auto new_row) @@ -74,9 +74,9 @@ The `available_primary_key` function is designed to provide a unique, incrementa }); ``` -Creating a new record in the table involves specifying the necessary fields as previously described.  +Creating a new record in the table involves specifying the necessary fields as previously described. -To integrate blending into the game, we'll expand the `receive_asset_transfer` function, previously utilized for staking items. This enhancement involves adding conditions to the if statement to recognize and process blend transactions.  +To integrate blending into the game, we'll expand the `receive_asset_transfer` function, previously utilized for staking items. This enhancement involves adding conditions to the if statement to recognize and process blend transactions. ```C else if(memo.find("blend:") != std::string::npos) @@ -98,7 +98,7 @@ Extracting the blend ID from the memo is a crucial step in the blending process, blend(from, asset_ids, blend_id); ``` -After extracting the blend ID from the memo, the next step involves calling an auxiliary function.  +After extracting the blend ID from the memo, the next step involves calling an auxiliary function. ```C void game::blend(const name& owner, const std::vector asset_ids, const uint64_t& blend_id) @@ -155,100 +155,101 @@ void game::blend(const name& owner, const std::vector asset_ids, const } ``` -Here's description of what code above does: - -1. The process involves extracting detailed information about NFTs currently held on the contract. This step is essential to later identify the NFTs sent by the user, allowing for the extraction of all relevant template data necessary for the blending process.  - -```C -auto assets = atomicassets::get_assets(get_self()); - auto templates = atomicassets::get_templates(get_self()); - -``` - -2\. First, the process involves checking the blends table to verify if the player's specified blend exists; if not, an error is thrown. Then, it's essential to confirm that the player has submitted the correct quantity of NFTs required for the blend, ensuring compliance with the blend recipe's specifications.  - -```C -blends_t blends_table(get_self(), get_self().value); - auto blends_table_itr = blends_table.require_find(blend_id, "Could not find blend id"); - check(blends_table_itr->blend_components.size() == asset_ids.size(), "Blend components count mismatch"); -``` - -3\. A temporary variable is created to ensure the player has submitted the correct components for the blend. This step is followed by iterating over all the NFTs the player has provided, checking each against the blend's requirements. - -```C -std::vector temp = blends_table_itr->blend_components; - for(const uint64_t& asset_id : asset_ids) - { - .......... - } -``` - -4\. A code that is inside the loop: - -```C - auto assets_itr = assets.find(asset_id); - check(assets_itr->collection_name == name("collname"), // replace collection with your collection name to check for fake nfts - ("Collection of asset [" + std::to_string(asset_id) + "] mismatch").c_str()); -``` - -During the blend verification process, information about each submitted NFT is extracted to ensure it belongs to the game's collection. This involves checking if the NFT's collection name matches the game's specified collection name, which requires replacing "collnamed" with the actual name of your collection in the code.  - -5\. The blending process involves a meticulous check against the blend recipe, using a temporary variable that contains the ID templates of the blend. For each submitted NFT, the system verifies its template against the temporary variable. If a match is found, that template is removed from the variable to prevent duplicates and identify any incorrect templates submitted by the player.  - -Following this verification, the burning function is called. - -```C -auto found = std::find(std::begin(temp), std::end(temp), assets_itr->template_id); - if(found != std::end(temp)) - temp.erase(found); - - action - ( - permission_level{get_self(),"active"_n}, - atomicassets::ATOMICASSETS_ACCOUNT, - "burnasset"_n, - std::make_tuple - ( - get_self(), - asset_id - ) - ).send(); -``` - -6\. Code after the loop: - -```C -check(temp.size() == 0, "Invalid blend components"); -``` - -Next step in the blending process is to ensure that the temporary vector, which contains the components of the blend recipe, is empty. This checks that the player has correctly reset all the required components for the blend.  - -If the vector is empty, it confirms that all components were correctly submitted and processed, allowing the blend to be completed successfully. - -Then there is a search for the resulting template in the table of atoms and creation (mint) of NFT with the required template. - -```C -auto templates_itr = templates.find(blends_table_itr->resulting_item); - -action -( - permission_level{get_self(),"active"_n}, - atomicassets::ATOMICASSETS_ACCOUNT, - "mintasset"_n, - std::make_tuple - ( - get_self(), - get_self(), - templates_itr->schema_name, - blends_table_itr->resulting_item, - owner, - (atomicassets::ATTRIBUTE_MAP) {}, //immutable_data - (atomicassets::ATTRIBUTE_MAP) {}, //mutable data - (std::vector) {} // token back - ) -).send(); -``` +### Detailed Explanation of the Blending Function + +1. **Extracting Asset and Template Information**: + + ```C + auto assets = atomicassets::get_assets(get_self()); + auto templates = atomicassets::get_templates(get_self()); + ``` + The process involves extracting detailed information about NFTs currently held by the contract. This step is essential to later identify the NFTs sent by the user, allowing for the extraction of all relevant template data necessary for the blending process. + +2. **Verifying Blend Existence and Component Count**: + + ```C + blends_t blends_table(get_self(), get_self().value); + auto blends_table_itr = blends_table.require_find(blend_id, "Could not find blend id"); + check(blends_table_itr->blend_components.size() == asset_ids.size(), "Blend components count mismatch"); + ``` + First, the process involves checking the blends table to verify if the player's specified blend exists; if not, an error is thrown. Then, it's essential to confirm that the player has submitted the correct quantity of NFTs required for the blend, ensuring compliance with the blend recipe's specifications. + +3. **Verifying Blend Components**: + + ```C + std::vector temp = blends_table_itr->blend_components; + for(const uint64_t& asset_id : asset_ids) + { + // ... + } + ``` + A temporary variable is created to ensure the player has submitted the correct components for the blend. This step is followed by iterating over all the NFTs the player has provided, checking each against the blend's requirements. + +4. **Checking NFT Collection and Burning Assets**: + + ```C + auto assets_itr = assets.find(asset_id); + check(assets_itr->collection_name == name("collname"), // replace collection with your collection name to check for fake nfts + ("Collection of asset [" + std::to_string(asset_id) + "] mismatch").c_str()); + ``` + During the blend verification process, information about each submitted NFT is extracted to ensure it belongs to the game's collection. This involves checking if the NFT's collection name matches the game's specified collection name, which requires replacing "collname" with the actual name of your collection in the code. + +5. **Removing Matched Templates and Burning the NFTs**: + + ```C + auto found = std::find(std::begin(temp), std::end(temp), assets_itr->template_id); + if(found != std::end(temp)) + temp.erase(found); + + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "burnasset"_n, + std::make_tuple + ( + get_self(), + asset_id + ) + ).send(); + ``` + The blending process involves a meticulous check against the blend recipe, using a temporary variable that contains the ID templates of the blend. For each submitted NFT, the system verifies its template against the temporary variable. If a match is found, that template is removed from the variable to prevent duplicates and identify any incorrect templates submitted by the player. Following this verification, the burning function is called. + +6. **Final Blend Verification**: + + ```C + check(temp.size() == 0, "Invalid blend components"); + ``` + The next step in the blending process is to ensure that the temporary vector, which contains the components of the blend recipe, is empty. This checks that the player has correctly submitted all the required components for the blend. If the vector is empty, it confirms that all components were correctly submitted and processed, allowing the blend to be completed successfully. + +7. **Minting the Resulting NFT**: + + ```C + auto templates_itr = templates.find(blends_table_itr->resulting_item); + + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "mintasset"_n, + std::make_tuple + ( + get_self(), + get_self(), + templates_itr->schema_name, + blends_table_itr->resulting_item, + owner, + (atomicassets::ATTRIBUTE_MAP) {}, //immutable_data + (atomicassets::ATTRIBUTE_MAP) {}, //mutable data + (std::vector) {} // token back + ) + ).send(); + ``` + Once all the verification is complete, the system mints a new NFT with the specified template, representing the result of the blend. + +### Conclusion This article has guided you through the process of creating blends in a game, from setting up blend recipes to verifying player submissions and minting new NFTs. It covers checking NFT components against blend requirements, ensuring all components are correctly submitted, and concluding with the minting of a new item that results from the blend. This blending mechanism enriches the gameplay by allowing players to combine NFTs into new, more valuable assets, fostering a deeper engagement with the game's ecosystem. -**PS.** The [Following link](https://github.com/dapplicaio/GamingItemBlend) leads us to a repository that corresponds everything described, so you can simply build that code and use in a way you want. \ No newline at end of file +**PS.** The [Following link](https://github.com/dapplicaio/GamingItemBlend) leads us to a repository that corresponds to everything described, so you can simply build that code and use it as you wish. + diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part1.md b/docs/es/build/tutorials/howto-create_farming_game/Part1.md new file mode 100644 index 0000000..a9f6e41 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part1.md @@ -0,0 +1,81 @@ +Cómo crear un juego de agricultura +=== + +## Bienvenido a la Guía Definitiva para Crear un Juego en WAX + +¿Estás listo para desatar tu creatividad y construir un juego único de agricultura de recursos en la blockchain de WAX? ¡No busques más! Esta guía completa te llevará a través de cada paso del proceso, desde la configuración de tu kit de herramientas Atomic Assets hasta la implementación de gobernanza DAO y tablas de líderes. + +### Los Componentes Fundamentales de tu Juego en WAX + +En esta serie, desglosaremos los componentes esenciales de un exitoso juego de agricultura de recursos en WAX. Desde la creación de objetos del juego como elementos agrícolas y avatares, hasta la gestión de recursos y tokens, staking de NFT, tipos de agricultura y más. + +### Prepárate para un Profundo Viaje al Desarrollo de Juegos en WAX + +A lo largo de los próximos artículos, profundizaremos en cada uno de estos temas en detalle. Ya seas un desarrollador experimentado o estés empezando, nuestra guía te proporcionará el conocimiento y las herramientas necesarias para construir un destacado juego de agricultura de recursos en WAX. ¡Así que empecemos! + +## Parte 1. Cómo crear un juego de agricultura en WAX. Conceptos generales + +¡Hola, creadores de juegos en WAX! ¿Listos para una guía paso a paso para construir su propio juego en WAX? Vamos a sumergirnos: + +* **Configuración de Atomic Assets**: Tu kit de herramientas para crear todo lo genial en el juego. +* **Entidades Principales del Juego**: Desde ítems agrícolas y avatares hasta perfiles de usuario, lo cubrimos todo. +* **Uso de Funciones Aleatorias en WAX**: Añade un toque de imprevisibilidad a tu juego. +* **Recursos y Tokens**: La columna vertebral de la economía de tu juego. +* **Staking de NFT y Tokens**: Permite a los jugadores invertir y ganar en tu juego. +* **Agricultura de Recursos**: El corazón de la gestión de recursos. +* **Combinaciones y Mejoras**: Mantén a los jugadores comprometidos con una jugabilidad en constante evolución. +* **DAO y Gobernanza**: Da voz a tu comunidad. +* **Tablas de Líderes**: Alimenta el espíritu competitivo. +* **Sistemas de Misiones**: Mantén la aventura en marcha. + +Estos elementos son tu boleto para construir un destacado juego de agricultura de recursos en WAX, completo con la emoción de play2earn. ¡Convirtamos esas ideas creativas en una realidad jugable! + +¿Listo para profundizar en los bloques de construcción de tu juego en WAX? Vamos a desglosarlo: + +* **Artículos 1 y 2**: Abordaremos los objetos del juego, como ítems agrícolas y objetos cultivables. Imagina un pico, una herramienta clásica, encajando en una cantera. Exploraremos cómo interactúan estos ítems y sus propiedades únicas en la agricultura de recursos y tokens. + +* **Artículo 3**: Se trata de recursos y tokens. Redefinimos los recursos como elementos del juego exclusivos para la economía del mismo. Como desarrollador, aprenderás a gestionar qué ítems utilizar y cómo. + +* **Artículo 4**: Hablemos del staking de NFT. Es una opción, pero una jugada inteligente para 'congelar' activos en un contrato inteligente, manteniéndolos fuera de los mercados abiertos para aumentar su valor. Cubriremos cómo el staking mantiene los datos de tus NFTs en casa, eliminando la necesidad de seguimiento por terceros. + +* **Artículo 5** te lleva al mundo de los tipos y procesos de agricultura; es algo complejo, pero lo tenemos desglosado línea por línea. Piensa en la agricultura como la recolección de tokens o recursos de algo como una casa, un planeta o una parcela de tierra. + +Teniendo en cuenta todo lo anterior, nuestro ejemplo de juego podría verse así: + +![](/public/assets/images/tutorials/howto-create_farming_game/part1/Farming-screen-980x551.jpg) + +Esto concluye nuestra serie introductoria. A continuación, profundizaremos en la progresión del juego, mejoras y configuraciones, culminando con la parte de la interfaz de usuario. Se trata de llevar tu juego de algo básico a algo brillante. Prepárate para una comprensión más profunda de cómo hacer que tu juego sea realmente atractivo. + +**Artículo 1** se sumerge en las combinaciones, una forma genial de fusionar dos ítems en uno nuevo mediante reglas específicas. Proporcionaremos un fragmento de código versátil, perfecto para combinaciones de NFTs en juegos o para gamificar colecciones de NFTs. + +En **Artículo 2**, exploramos las mejoras, una variación de las combinaciones donde se mejoran los ítems cambiando atributos. La magia radica en establecer reglas a través de NFTs o lógica del juego. Las mejoras en nuestro juego aumentan las tasas de minería. + +**Artículo 3** se centra en los avatares, que no son solo apariencia: cambian el juego al afectar las tasas de minería y más. Hablaremos de cómo los avatares, equipados con ítems únicos, enriquecen la jugabilidad y el mercado de NFTs. + +**El 4º artículo** cubre los perfiles de juego, consideraciones esenciales sobre el almacenamiento de datos en la blockchain, incluso algo tan básico como un nombre de usuario. + +Finalmente, **Artículo 5** aborda la interfaz de usuario con ReactJS, enseñándote a interactuar con contratos inteligentes y crear una interfaz básica para el juego. + +La próxima sección trata sobre DAO y gobernanza en tu juego. + +* **Artículo 1** aborda el intercambio de recursos, una variación del proceso de agricultura. Se trata de convertir recursos en tokens, con tus propias reglas para los fondos, tasas de intercambio dinámicas e incluso un sistema de impuestos. + +* **Artículo 2** se sumerge en el staking de tokens y la mecánica básica de DAO. Aprende cómo el staking de tokens puede otorgar poder de voto e influir en la configuración del juego. + +* **El tercer artículo** trata sobre el sistema de propuestas, enfocándose en la votación dentro del juego que afecta la dinámica y la tokenómica del juego. + +* **El artículo final** se centrará en las interfaces de usuario para las características discutidas, particularmente en torno a DAO y gobernanza. + +El próximo bloque trata sobre la progresión del juego y las tablas de líderes. + +* **Tablas de Líderes**: Son simples pero variadas. Te mostraremos cómo configurar diferentes tipos, como los mejores mineros de recursos específicos dentro de un período de tiempo establecido. + +* **Misiones**: Esto es más complejo, con posibilidades diversas y dinámicas. Espera sistemas generales y ejemplos como inicios de sesión diarios, staking de una cierta cantidad de tokens o intercambios de recursos. Las misiones, al igual que las tablas de líderes, ofrecen recompensas por completarlas. + +* **El artículo final** se centra en el diseño de la interfaz de usuario para misiones y tablas de líderes, con ejemplos prácticos de código. + +El último artículo de esta sección profundizará en los aspectos de la interfaz de usuario de misiones y tablas de líderes, con ejemplos prácticos de código para guiarte. + +Para concluir la serie, reuniremos todo el juego, cubriendo los procesos de construcción y prueba. Recuerda, aunque nos enfoquemos en un juego completamente en cadena, es posible la integración con componentes centralizados para elementos como batallas rápidas PVP. Esto permite un enfoque híbrido, combinando las fortalezas de los aspectos de juegos descentralizados y centralizados. + +P.D. Ten en cuenta que este artículo y el marco de trabajo que lo acompaña pueden evolucionar a medida que avance el desarrollo. diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part10.md b/docs/es/build/tutorials/howto-create_farming_game/Part10.md new file mode 100644 index 0000000..3919638 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part10.md @@ -0,0 +1,656 @@ +--- +title: Parte 10. Implementación de avatares en juegos WAX +order: 50 +--- + +Este artículo explorará cómo crear avatares y su equipo, enfocándose en los aspectos de personalización que mejoran la experiencia del jugador. Al detallar el proceso de diseño de avatares y la selección de sus accesorios, nuestro objetivo es brindar ideas para construir elementos de juego más atractivos e interactivos, permitiendo a los jugadores sumergirse profundamente en el mundo del juego con personajes que reflejen su estilo y preferencias. + +### 1. Creación de categorías + +Crear una categoría de avatares implica definir un personaje con características específicas, que luego pueden ser mejoradas al equipar ítems. Este paso fundamental permite la personalización de los avatares, proporcionando a los jugadores la capacidad de adaptar los personajes a su estilo de juego y preferencias, enriqueciendo así la experiencia de juego al agregar profundidad al desarrollo e interacción del personaje dentro del mundo del juego. + +![](/public/assets/images/tutorials/howto-create_farming_game/part10/image1.png) + +| Nombre del Atributo | Tipo de Atributo | Descripción | +|---------------------|------------------|----------------------------------------------------| +| name | string | Nombre del avatar | +| description | string | Descripción de la historia y propiedades del avatar | +| img | image | Imagen del avatar | +| economic | uint32 | Reduce el costo de mejora | +| productivity | uint32 | Aumenta la tasa de extracción | +| vitality | uint32 | Incrementa el porcentaje de mejora | +| bravery | uint32 | Afecta a las misiones | +| diplomacy | uint32 | Afecta las interacciones con otros jugadores | + +Tabla 1: Atributos del "avatar" + +Crear una categoría para los ítems de equipo refleja el proceso de creación de avatares, ya que cada pieza de equipo también posee características distintas. + +![](/public/assets/images/tutorials/howto-create_farming_game/part10/image2.png) + +| Nombre del Atributo | Tipo de Atributo | Descripción | +|---------------------|------------------|---------------------------------------------------------| +| name | string | Nombre del ítem | +| description | string | Descripción de la historia y propiedades del ítem | +| img | image | Imagen del ítem | +| type | string | Representa el tipo de ítem (joyería, bandera, corona, etc.)| +| economic | uint32 | Reduce el costo de mejora | +| productivity | uint32 | Aumenta la tasa de extracción | +| vitality | uint32 | Incrementa el porcentaje de mejora | +| bravery | uint32 | Afecta a las misiones | +| diplomacy | uint32 | Afecta las interacciones con otros jugadores | + +Tabla 2: Atributos del "equipo" + +### 2. Creación de plantillas + +A continuación, se presenta un ejemplo de cómo crear un avatar y un ítem de equipo. + +![](/public/assets/images/tutorials/howto-create_farming_game/part10/image3.png) + +![](/public/assets/images/tutorials/howto-create_farming_game/part10/image4.png) + +La acuñación de avatares y equipo sigue los procesos descritos en artículos anteriores, que implican la creación y registro de estos elementos en la blockchain. + +### 3. Agregar nuevas tablas al código del contrato + +Agregar una tabla que vincule a cada jugador con su avatar activo y los ítems equipados es un paso estratégico de desarrollo. Esta tabla no solo hace seguimiento de los avatares y ítems que están en uso actualmente, sino que también facilita las interacciones dentro del juego, como batallas o recolección de recursos, en función de los atributos de los ítems equipados. + +```C +struct [[eosio::table]] avatars_j + { + name owner; + std::vector equipment; + + uint64_t primary_key() const { return owner.value; } + }; + typedef multi_index< "avatarsc"_n, avatars_j> avatars_t; +``` + +- **owner**: La cuenta que usa el avatar y el equipo. +- **equipment**: Un vector de identificadores `uint64_t` que indica el avatar y equipo activos. + +Crear una tabla para las estadísticas del jugador implica agregar los atributos del avatar y los ítems equipados para reflejar las capacidades actuales del jugador dentro del juego. + +```C +struct [[eosio::table]] stats_j + { + name owner; + std::map stats; + + uint64_t primary_key() const {return owner.value;} + }; + typedef multi_index<"stats"_n, stats_j> stats_t; +``` + +- **owner**: Una cuenta cuyas características se especifican en la tabla. +- **stats**: Un mapa que contiene las características en el formato `{"economic": 10, "bravery": 7, etc}`. + +### 4. Lógica para establecer avatares y equipo + +La lógica para establecer avatares y equipo en el juego implica que los jugadores seleccionen su personaje y lo equipen con varios ítems para mejorar sus estadísticas. + +```C + else if (memo == "set avatar") + { + check(asset_ids.size() == 1, "You must transfer only one avatar"); + set_avatar(from, asset_ids[0]); + } + else if (memo == "set equipment") + { + check(asset_ids.size() <= 4, "You can wear only 4 different equipment types at once"); + set_equipment_list(from, asset_ids); + } +``` + +Incorporar dos opciones de memo en la función `receive_asset_transfer()` permite a los jugadores establecer su avatar transfiriendo un NFT de avatar con el memo "set avatar" o equipar hasta cuatro ítems diferentes especificando **"set equipment"**. La función luego asigna el asset del avatar transferido con el `asset_id` especificado al registro del propietario del usuario, actualizando efectivamente el personaje o la configuración de equipo del jugador en el juego. + +```C +void game::set_avatar(const name &owner, const uint64_t &asset_id) +{ + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + + check(asset_itr->collection_name == "collname"_n, "Wrong collection"); + check(asset_itr->schema_name == "avatar"_n, "Not an avatar asset"); + + avatars_t avatars_table(get_self(), get_self().value); + auto owner_avatar_itr = avatars_table.find(owner.value); + + if (owner_avatar_itr == std::end(avatars_table)) + { + avatars_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.equipment.resize(5); + new_row.equipment[0] = asset_id; }); + } + else + { + // should return avatar asset back to player + const uint64_t old_avatar_id = owner_avatar_itr->equipment[0]; + + const std::vector assets_to_transfer = {old_avatar_id}; + const std::string memo = "return avatar"; + + action( + permission_level{get_self(), "active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "transfer"_n, + std::make_tuple( + get_self(), + owner, + assets_to_transfer, + memo)) + .send(); + + avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) + { row.equipment[0] = asset_id; }); + } + + recalculate_stats(owner); +} +``` + +1. **Validación del asset transferido**: + La función tiene como propósito validar el asset transferido por el jugador, asegurándose de que pertenece a la colección correcta y categoría para avatares o equipo. + + ```C + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + + check(asset_itr->collection_name == "collname"_n, "Wrong collection"); + check(asset_itr->schema_name == "avatar"_n, "Not an avatar asset"); + ``` + +2. **Recuperar información del jugador de la tabla de avatares**: + Para actualizar el avatar de un jugador en el juego, la función recupera la información del jugador desde la tabla de avatares usando su nombre de usuario. + + ```C + avatars_t avatars_table(get_self(), get_self().value); + auto owner_avatar_itr = avatars_table.find(owner.value); + ``` + +3. **Agregar un nuevo jugador a la tabla de avatares**: + Si el usuario no existe aún en la tabla de avatares, la función lo añade configurando un vector con cinco elementos inicializados en ceros. Luego, el ID del avatar se coloca en la primera posición de este vector, registrando efectivamente el nuevo avatar bajo el nombre de usuario del jugador. + + ```C + if (owner_avatar_itr == std::end(avatars_table)) + { + avatars_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.equipment.resize(5); + new_row.equipment[0] = asset_id; }); + } + ``` + +4. **Actualizar el avatar de un jugador existente**: + Si el jugador ya existe en la tabla de avatares, la función actualiza su avatar con el nuevo proporcionado en el argumento. Luego, el avatar anterior es devuelto al jugador a través de un `atomicassets::transfer`, asegurando que el jugador retenga la propiedad de su avatar anterior. + + ```C + else + { + // should return avatar asset back to player + const uint64_t old_avatar_id = owner_avatar_itr->equipment[0]; + + const std::vector assets_to_transfer = {old_avatar_id}; + const std::string memo = "return avatar"; + + action( + permission_level{get_self(), "active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "transfer"_n, + std::make_tuple( + get_self(), + owner, + assets_to_transfer, + memo)) + .send(); + + avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) + { row.equipment[0] = asset_id; }); + } + ``` + +5. **Recalcular Estadísticas**: + +```Cpp + recalculate_stats(owner); +``` + +Este paso recalcula las estadísticas del jugador después de actualizar su avatar o equipo, asegurándose de que todos los bonos o cambios se reflejen con precisión en el perfil del jugador. + +### Resumen de la función para equipar ítems + +La función para equipar ítems implica listar los IDs de los assets del equipo que serán usados por el avatar del jugador. Este proceso verifica la compatibilidad de cada ítem con el avatar y actualiza la lista de equipo del jugador en la base de datos del juego. + +```Cpp +void game::set_equipment_list(const name &owner, +const std::vector &asset_ids) +{ + std::vector assets_to_return; + + std::map equiped_types; + equiped_types.insert(std::pair("flag", 0)); + equiped_types.insert(std::pair("jewelry", 0)); + equiped_types.insert(std::pair("crown", 0)); + equiped_types.insert(std::pair("cloak", 0)); + + for (uint64_t asset_id : asset_ids) + { + set_equipment_item(owner, asset_id, assets_to_return, equiped_types); + } + + const std::string memo = "return equipment"; + + action( + permission_level{get_self(), "active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "transfer"_n, + std::make_tuple( + get_self(), + owner, + assets_to_return, + memo)) + .send(); + + recalculate_stats(owner); +} +``` + +### Descripción de la función: + +1. **Preparar para los cambios**: + Crear un vector para almacenar los IDs de los assets del equipo que serán devueltos y un mapa para asegurarse de que cada tipo de equipo sea equipado no más de una vez. + + ```Cpp + std::vector assets_to_return; + + std::map equiped_types; + equiped_types.insert(std::pair("flag", 0)); + equiped_types.insert(std::pair("jewelry", 0)); + equiped_types.insert(std::pair("crown", 0)); + equiped_types.insert(std::pair("cloak", 0)); + ``` + +2. **Equipar nuevos ítems**: + Iterar a través de los IDs de assets proporcionados, equipando cada ítem mientras se respeta la regla de que cada tipo de equipo solo puede ser usado una vez. + + ```Cpp + for (uint64_t asset_id : asset_ids) + { + set_equipment_item(owner, asset_id, assets_to_return, equiped_types); + } + ``` + +3. **Actualizar y devolver**: + Devolver cualquier ítem antiguo al inventario del jugador y recalcular las características del jugador en base a la nueva configuración de equipo para reflejar los cambios en las habilidades o estadísticas del jugador de manera precisa. + + ```Cpp + const std::string memo = "return equipment"; + + action( + permission_level{get_self(), "active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "transfer"_n, + std::make_tuple( + get_self(), + owner, + assets_to_return, + memo)) + .send(); + + recalculate_stats(owner); + ``` + +### Función para Equipar un Ítem Individual + +La función `set_equipment_item` maneja el equipamiento de un solo ítem (`asset_id`) en el jugador (`owner`). El vector `assets_to_return` almacena los ítems que deben ser devueltos, y `equiped_types` lleva un registro de la cantidad de ítems usados de cada tipo de equipo. + +```Cpp +void game::set_equipment_item(const name &owner, const uint64_t asset_id, +std::vector &assets_to_return, std::map &equiped_types) +{ + avatars_t avatars_table(get_self(), get_self().value); + + auto owner_avatar_itr = avatars_table.find(owner.value); + check(owner_avatar_itr != std::end(avatars_table), "You can put equipment only when you have an avatar"); + + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + + check(asset_itr->collection_name == "collname"_n, "Wrong collection"); + check(asset_itr->schema_name == "equip"_n, "Not an equipment item"); + + uint32_t position = 0; + const std::string type = std::get(equipment_template_idata["type"]); + + equiped_types[type]++; + check(equiped_types[type] <= 1, "You can wear only 4 different equipment types at once"); + + if (type == "flag") + { + position = 1; + } + else if (type == "jewelry") + { + position = 2; + } + else if (type == "crown") + { + position = 3; + } + else if (type == "cloak") + { + position = 4; + } + else + { + check(false, "Wrong type of equipment"); + } + + const uint64_t old_equip_id = owner_avatar_itr->equipment[position]; + + if (old_equip_id != 0) + { + assets_to_return.push_back(old_equip_id); + } + + avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) + { row.equipment[position] = asset_id; }); +} +``` + +Esta función asegura que cada ítem equipado sea compatible con el avatar del jugador y que las ranuras de equipo sean gestionadas adecuadamente, devolviendo el equipo antiguo al inventario del jugador si es reemplazado. + +### Descripción de la Función: + +1. **Verificar la Presencia del Jugador**: + Verificar la presencia del jugador en la tabla de equipo y avatares; terminar si no está presente. Asegurarse de que el asset, de la colección y tipo correctos, apunta correctamente en la tabla de assets. Cargar los datos de plantilla inmutables en `equipment_template_idata`. + + ```Cpp + avatars_t avatars_table(get_self(), get_self().value); + + auto owner_avatar_itr = avatars_table.find(owner.value); + check(owner_avatar_itr != std::end(avatars_table), "You can put equipment only when you have an avatar"); + + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + + check(asset_itr->collection_name == "collname"_n, "Wrong collection"); + check(asset_itr->schema_name == "equip"_n, "Not an equipment item"); + ``` + +2. **Determinar el Tipo y Posición del Equipo**: + Identificar el tipo de equipo, incrementando su conteo en el mapa. Si es un duplicado, emitir un error. Determinar la variable `position` basada en el tipo de equipo para la colocación del nuevo ID. + + ```Cpp + int32_t position = 0; + const std::string type = std::get(equipment_template_idata["type"]); + + equiped_types[type]++; + check(equiped_types[type] <= 1, "You can wear only 4 different equipment types at once"); + + if (type == "flag") + { + position = 1; + } + else if (type == "jewelry") + { + position = 2; + } + else if (type == "crown") + { + position = 3; + } + else if (type == "cloak") + { + position = 4; + } + else + { + check(false, "Wrong type of equipment"); + } + ``` + +3. **Actualizar el Equipo y Devolver Ítems Antiguos**: + Si se encuentra un ítem existente en la posición, agregar su ID al vector de retorno. Actualizar la tabla con el nuevo asset. + + ```Cpp + const uint64_t old_equip_id = owner_avatar_itr->equipment[position]; + + if (old_equip_id != 0) + { + assets_to_return.push_back(old_equip_id); + } + + avatars_table.modify(owner_avatar_itr, get_self(), [&](auto &row) + { row.equipment[position] = asset_id; }); + ``` + +4. **Recalcular Estadísticas**: + Recalcular las características del jugador en base a la nueva configuración de equipo. + + ```Cpp + void game::recalculate_stats(const name &owner) + { + stats_t stats_table(get_self(), get_self().value); + auto stats_itr = stats_table.find(owner.value); + + std::map stats; + + // inicializar estadísticas + stats.insert(std::pair("economic", 0)); + stats.insert(std::pair("productivity", 0)); + stats.insert(std::pair("vitality", 0)); + stats.insert(std::pair("bravery", 0)); + stats.insert(std::pair("diplomacy", 0)); + + // leer estadísticas + avatars_t avatars_table(get_self(), get_self().value); + auto avatar_itr = avatars_table.require_find(owner.value, "Your avatar was deleted"); + + auto assets = atomicassets::get_assets(get_self()); + + for (uint64_t asset_id : avatar_itr->equipment) + { + if (asset_id == 0) + { + continue; + } + + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + + for (auto &key_value_pair : stats) + { + if (equipment_template_idata.find(key_value_pair.first) != std::end(equipment_template_idata)) + { + key_value_pair.second += std::get(equipment_template_idata[key_value_pair.first]); + } + } + } + + if (stats_itr == std::end(stats_table)) + { + stats_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.stats = stats; }); + } + else + { + stats_table.modify(stats_itr, get_self(), [&](auto &row) + { row.stats = stats; }); + } + } + ``` + +**La función para calcular las características del jugador implica varios pasos clave:** + +1. **Recuperar las estadísticas del jugador y los avatares de sus respectivas tablas, inicializando las características a cero.** + + ```Cpp + stats_t stats_table(get_self(), get_self().value); + auto stats_itr = stats_table.find(owner.value); + + std::map stats; + + // inicializar estadísticas + stats.insert(std::pair("economic", 0)); + stats.insert(std::pair("productivity", 0)); + stats.insert(std::pair("vitality", 0)); + stats.insert(std::pair("bravery", 0)); + stats.insert(std::pair("diplomacy", 0)); + + // leer estadísticas + avatars_t avatars_table(get_self(), get_self().value); + auto avatar_itr = avatars_table.require_find(owner.value, "Your avatar was deleted"); + ``` + +2. **Procesar activos activos, omitiendo cualquier ID que sea 0, y leer los datos de la plantilla para cada activo.** + + ```Cpp + auto assets = atomicassets::get_assets(get_self()); + + for (uint64_t asset_id : avatar_itr->equipment) + { + if (asset_id == 0) + { + continue; + } + + auto asset_itr = assets.find(asset_id); + auto equipment_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + ``` + +3. **Calcular Características**: + Iterar a través de todas las características a calcular, como se indica en el mapa. Si una característica está presente en un ítem, agregarla al valor total. + + ```Cpp + for (auto &key_value_pair : stats) + { + if (equipment_template_idata.find(key_value_pair.first) != std::end(equipment_template_idata)) + { + key_value_pair.second += std::get(equipment_template_idata[key_value_pair.first]); + } + } + ``` + + `key_value_pair` son pares del tipo {"economic", 0}, {"bravery", 0}, etc. Si un elemento tiene la característica "economic" con un valor de 3, entonces después de este código, el campo "economic" en el mapa de stats se actualizará a 3. + +4. **Sumar Valores y Actualizar la Tabla de Estadísticas**: + Sumar los valores de cada característica listada en el mapa, añadiéndolos al total si están presentes en el ítem. + + ```Cpp + if (stats_itr == std::end(stats_table)) + { + stats_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.stats = stats; }); + } + else + { + stats_table.modify(stats_itr, get_self(), [&](auto &row) + { row.stats = stats; }); + } + ``` + +5. **Modificación de la Función Claim**: + Actualizar la función claim para reflejar las características del jugador. + + ```Cpp + std::map stats = get_stats(owner); + ``` + + Esto recupera las características actuales del jugador. + + ```Cpp + const uint8_t upgrade_percentage = 2 + stats["vitality"] / 10.0f; + + const std::pair item_reward = claim_item(assets_itr, upgrade_percentage, time_now, stats); + ``` + + Ahora `upgrade_percentage` no es una constante, sino que depende de la característica "vitality". La función `claim_item` también acepta `stats` para evitar recalculaciones innecesarias. + +6. **Modificación del Cálculo de la Tasa de Minado**: + + ```Cpp + float miningRate_according2lvl = miningRate + stats.at("productivity") / 10.0f; + ``` + + La tasa de minado ahora también depende de la característica "productivity". + +**Cambios en la Función Upgradeitem**: + +1. **Actualizar Características y Llamar a Claim Item Actualizado**: + + ```Cpp + std::map stats = get_stats(owner); + const uint8_t upgrade_percentage = 2 + stats["vitality"] / 10.0f; + const std::pair item_reward = claim_item(asset_itr, upgrade_percentage, time_now, stats); + ``` + + Para leer las características del jugador y calcular `upgrade_percentage`, la función llama al `claim_item` actualizado. El `upgrade_percentage` ahora depende de la característica "vitality" del jugador. + + ```Cpp + upgrade_item(asset_itr, upgrade_percentage, owner, next_level, time_now, stats); + ``` + + La función `upgrade_item` ahora acepta `stats` para evitar recalculaciones innecesarias. + +2. **Cálculo de la Tasa de Minado y Precio del Recurso en upgrade_item**: + + ```Cpp + float miningRate_according2lvl = mining_rate + stats.at("productivity") / 10.0f; + ``` + + Aquí, la tasa de minado (`miningRate_according2lvl`) se actualiza para depender de la característica "productivity" del jugador. + + ```Cpp + const float &resource_price = upgrade_time * miningRate_according2lvl * (1.0f - stats.at("economic") / 100.0f); + ``` + + El `resource_price` ahora disminuye a medida que crece la característica "economic", haciendo que las mejoras sean más baratas para los jugadores con estadísticas "economic" más altas. + +3. **Función get_stats**: + + ```Cpp + std::map game::get_stats(const name &owner) + { + std::map stats; + stats_t stats_table(get_self(), get_self().value); + auto stats_itr = stats_table.find(owner.value); + + if (stats_itr == std::end(stats_table)) + { + stats.insert(std::pair("economic", 0)); + stats.insert(std::pair("productivity", 0)); + stats.insert(std::pair("vitality", 0)); + stats.insert(std::pair("bravery", 0)); + stats.insert(std::pair("diplomacy", 0)); + } + else + { + stats = stats_itr->stats; + } + + return stats; + } + ``` + + **Explicación**: + + 1. **Inicialización**: Se crea un mapa para contener los resultados de la función, y se establece un puntero a la entrada del jugador en la tabla de stats utilizando su nombre. + + 2. **Comprobar las Estadísticas del Jugador**: Si el jugador no se encuentra en la tabla, la función devuelve un mapa con todas las características en cero. Si el jugador se encuentra, recupera y devuelve sus stats de la tabla, resumiendo efectivamente sus atributos actuales en el juego. + +Este artículo profundiza en la creación y gestión de avatares y su equipo en un juego, describiendo el proceso desde la creación inicial de la categoría de avatares hasta la asignación dinámica de equipo. Cubre la integración de avatares con la mecánica del juego, como el staking y la reclamación de recompensas, y enfatiza la importancia de la personalización para mejorar la experiencia del jugador. + +El artículo también aborda los aspectos técnicos de configurar y actualizar las estadísticas del jugador en función de los elementos equipados, asegurando un entorno de juego rico e interactivo. + +**PD.** El [siguiente enlace](https://github.com/dapplicaio/GameAvatars) lleva a un repositorio que corresponde a todo lo descrito, por lo que puedes simplemente construir ese código y usarlo como desees. \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part11.md b/docs/es/build/tutorials/howto-create_farming_game/Part11.md new file mode 100644 index 0000000..db58667 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part11.md @@ -0,0 +1,282 @@ +--- +title: Parte 11. Interfaz para Mezclas, Mejoras y Avatares +order: 55 +--- + +Basándonos en nuestra guía de interacción con contratos inteligentes de ReactJS y WAX, este artículo avanza en el desarrollo de nuestra aplicación. Nos adentraremos en las complejidades de las mezclas y mejoras del espacio de trabajo, junto con las herramientas necesarias. Además, se proporcionará una guía sobre acciones de la interfaz sincronizadas con nuestro contrato inteligente, mejorando la funcionalidad y la experiencia del usuario en la aplicación. + +**Mezclando activos** +------------------- + +Las mezclas en contratos inteligentes permiten a los propietarios de colecciones crear nuevos activos mejorados al combinar los existentes. Imagina mejorar tus herramientas mezclando dos del mismo tipo, resultando en una herramienta que extrae significativamente más recursos. + +Este proceso enriquece la experiencia del usuario al agregar profundidad a la gestión de recursos y la estrategia de juego, todo integrado sin problemas en la interfaz de usuario para una interacción fácil. + +Así es como se ve. + +![](/public/assets/images/tutorials/howto-create_farming_game/part11/image1.png) + +El contrato inteligente cuenta con recetas de mezcla predefinidas que los usuarios pueden utilizar a través de la función `fetchBlends`. Esta funcionalidad permite la extracción de varias recetas de mezcla desde el contrato, permitiendo a los usuarios crear herramientas y activos mejorados siguiendo instrucciones específicas de mezcla directamente desde la interfaz de la aplicación. + +```Js +export const fetchBlends = async () => { + const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: 'dappgamemine', + table: "blendrecipes" + }); + + const templateOneRequests = rows.map(row => axios.get(`https://test.wax.api.atomicassets.io/atomicassets/v1/templates?collection_name=minersgamers&template_id=${row.blend_components}`)); + + const responsesOne = await Promise.all(templateOneRequests); + + const templateTwoRequests = rows.map(row => axios.get(`https://test.wax.api.atomicassets.io/atomicassets/v1/templates?collection_name=minersgamers&template_id=${row.resulting_item}`)); + + const responsesTwo = await Promise.all(templateTwoRequests); + + const updatedRows = rows.map((row, index) => { + const { data:dataOne } = responsesOne[index]; + const { data:dataTwo } = responsesTwo[index]; + return { ...row, blend_components: dataOne.data, resulting_item: dataTwo.data[0] }; + }) + + return updatedRows; + } +``` + +- Dentro de la función, primero se obtienen filas de datos de una tabla especificada (blendrecipes) utilizando la función fetchRows. +- Una vez que se han obtenido las filas, la función procede a crear un array de solicitudes para obtener datos desde una API usando Axios. Itera sobre cada fila y construye una URL para obtener datos relacionados con los componentes de mezcla y elementos resultantes. +- Estas solicitudes luego se realizan de forma simultánea usando Promise.all, lo que espera a que todas las solicitudes se resuelvan. +- Las respuestas de estas consultas se almacenan en dos arrays separados (responsesOne y responsesTwo). +- Habiendo obtenido las respuestas para los componentes de la mezcla y los elementos resultantes, la función vuelve a mapear las filas originales. +- Dentro de la función map, extrae los datos relevantes (dataOne y dataTwo) de las respuestas correspondientes. +- Finalmente, crea filas actualizadas fusionando los datos originales con los componentes de mezcla y el elemento resultante obtenidos. + +También hemos implementado una función que verifica si el usuario puede hacer una mezcla con el inventario disponible según una de las recetas. Comprobamos esto usando la siguiente función: + +```js +const blendComponentsMatch = userToolAsset.some(asset => asset.template.template_id === item.blend_components[0].template_id || asset.template.template_id === item.blend_components[1].template_id); + +const isAvailable = blendComponentsMatch && +userToolAsset.some(asset => Number(asset.template.template_id) !== Number(item.blend_components[0].template_id) && Number(asset.template.template_id) !== Number(item.blend_components[1].template_id)); +``` + +- **userToolAsset** -- array de herramientas del usuario; +- **item** -- cada elemento del array que contiene las recetas; + +Si la función isAvailable es true, el usuario puede hacer una mezcla; si es false, el usuario no tiene suficientes herramientas para una receta en particular. + +Ahora veamos cómo la interfaz de usuario interactúa con el contrato inteligente para crear una mezcla. + +```js + +export const blend = async ({ activeUser, componentIds, assets, blendId }) => { + return await signTransaction({ + activeUser, + account: 'atomicassets', + action: 'transfer', + data: { + from: activeUser.accountName, + to: 'dappgamemine', + assets_ids: assets, + memo: `blend:${blendId}` + } + }); +}; +``` + +Para una mezcla exitosa de nuestras herramientas, veamos las configuraciones que debemos pasar a nuestra función: + +- **owner** -- este es el apodo del usuario que conectó su billetera, lo tomamos de activeUser.accountName; +- **component_ids** -- este es un array con nuestros activos de herramientas para mezclar; +- **blend_id** -- este es el id de la mezcla. + +Aquí hay un enlace a la acción en el contrato inteligente para mezclar herramientas: + + + +**Mejorando activos** +-------------------- + +Lo siguiente que vamos a ver es cómo mejorar nuestras herramientas y espacios de trabajo. + +Nuestra herramienta comienza en el nivel 1, pero podemos mejorarla. Cuando esta herramienta está en staking en el espacio de trabajo, calcula la cantidad de recursos necesarios para mejorarla y, si el usuario tiene suficientes, aumenta la tasa de extracción (mining_rate) del ítem. Como sabemos del artículo anterior, esto afecta cuán rápido se extraen nuestros recursos. + +![](/public/assets/images/tutorials/howto-create_farming_game/part11/image2.png) + +Primero, debemos decidir qué herramienta o espacio de trabajo queremos mejorar. Para realizar esta acción, nuestra herramienta debe estar en staking. Luego la pasamos a la configuración de nuestra acción. + +La interfaz de usuario debe verificar el nivel máximo permitido de la herramienta que queremos mejorar antes de llamar a la acción. Esto está escrito en los datos de la herramienta. + +El siguiente nivel debe ser superior al actual. En nuestro ejemplo, supongamos que nuestro instrumento tiene un primer nivel inicial. + +Acción para mejorar herramienta: + +Al inicio de la función, usamos una comprobación para ver si nuestra herramienta ha alcanzado el nivel máximo. + +```js +export const upgradeTool = async ({ activeUser, toolID, wpID }) => { + const maxLevel = toolID.data.maxLevel; + const currentLevel = toolID.data.level; + + const nextLvl = currentLevel + 1; + + if (nextLvl > maxLevel) { + throw new Error('Error: El siguiente nivel excede el nivel máximo del instrumento.'); + } + + return await signTransaction({ + activeUser, + account: 'dappgamemine', + action: 'upgradeitem', + data: { + owner: activeUser.accountName, + item_to_upgrade: toolID.asset_id, + next_level: nextLvl, + staked_at_farmingitem: wpID + } + }); +}; +``` + +Aquí utilizamos una acción del juego llamada "upgradeitem". + +- **owner** -- este es nuestro apodo del usuario que conectó su billetera, lo tomamos de activeUser.accountName; +- **item_to-upgrade** -- ID de la herramienta que queremos mejorar; +- **stake_at_farmingitem** -- el ID del espacio de trabajo donde la herramienta está en staking; + +Después de una mejora exitosa, nuestra herramienta se convierte en nivel 2: + +![](/public/assets/images/tutorials/howto-create_farming_game/part11/image3.png) + +Acción para mejorar el espacio de trabajo: + +```js +export const upgradeWorkplace = async ({ activeUser, wpID, stakedBool}) => { + return await signTransaction({ + activeUser, + account: 'dappgamemine', + action: 'upgfarmitem', + data: { + owner: activeUser.accountName, + farmingitem_to_upgrade: wpID, + staked: stakedBool, + } + }); +}; +``` + +Aquí utilizamos una acción del juego llamada "upgfarmitem". + +- **owner** -- este es nuestro apodo del usuario que conectó su billetera, lo tomamos de activeUser.accountName; +- **farmingitem_to_upgrade** -- ID del espacio de trabajo que queremos mejorar; +- **staked** (booleano) -- Si está en staking, entonces true; si no, entonces false. + +Aquí hay un enlace a la acción en el contrato inteligente para mejorar herramientas: + + + +y para mejorar espacios de trabajo: + + + +**Avatares** +----------- + +Veamos qué son los avatares y para qué sirven. + +Los avatares son personajes que tienen atributos adicionales. También se les pueden poner objetos que mejoren esos atributos. + +![](/public/assets/images/tutorials/howto-create_farming_game/part11/image4.png) + +Esta acción te permite establecer el avatar seleccionado como activo y devolver el antiguo a nuestro inventario. + +Función para establecer avatar: + +```js +export const setAvatar = async ({ activeUser, avatarId }) => { + return await signTransaction({ + activeUser, + account: 'atomicassets', + action: 'transfer', + data: { + from: activeUser.accountName, + to: 'dappgamemine', + asset_ids: avatarId, + memo: 'set avatar' + } + }); +}; +``` + +Para esta acción, utilizamos las siguientes configuraciones: + +- **from** -- este es nuestro apodo del usuario que conectó su billetera, lo tomamos de activeUser.accountName; +- **to** -- nombre del contrato del juego; +- **asset_ids** -- array de avatares; +- **memo** -- "set avatar" -- para establecer tu avatar. + +Función para establecer equipamiento: + +```js +export const setEquipment = async ({ activeUser, equipments }) => { + return await signTransaction({ + activeUser, + account: 'atomicassets', + action: 'transfer', + data: { + from: activeUser.accountName, + to: 'dappgamemine', + asset_ids: equipments, + memo: 'set equipment' + } + }); +}; +``` + +Las configuraciones son las mismas que en la acción anterior, solo cambia el memo. + +- **asset_ids** -- array de equipamientos; +- **memo** -- "set equipment" -- para establecer el equipamiento de tu avatar. + +Aquí hay un enlace a la acción en el contrato inteligente para establecer avatar y equipamiento: + + + +También tenemos una tabla de avatares, donde para cada usuario hay un array con los id que contiene su avatar y equipamiento. Para esto utilizaremos una lectura de la tabla. En el último artículo vimos la función fetchRows. + +```js +export const fetchAvatars = async ({ activeUser }) => { + const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: activeUser.accountName, + table: "avatars" + }); + + return rows; + } +``` + +La tabla de estadísticas, que contiene las características actuales del usuario (la suma de los atributos del avatar y el equipamiento). + +```js +export const fetchStats = async ({ activeUser }) => { + const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: activeUser.accountName, + table: "stats" + }); + + return rows; +} +``` + +Aquí hay un enlace a la acción en el contrato inteligente para ver avatares: + + + +y estadísticas: + + + +**PD.** El [siguiente enlace](https://github.com/dapplicaio/GameAvatars) nos lleva a un repositorio que corresponde a todo lo descrito, para que puedas simplemente construir ese código y usarlo como quieras. \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part12.md b/docs/es/build/tutorials/howto-create_farming_game/Part12.md new file mode 100644 index 0000000..0c4384d --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part12.md @@ -0,0 +1,125 @@ +--- +title: Parte 12. Intercambio de Tokens y Recursos +order: 60 +--- + +En este artículo, ampliamos las discusiones anteriores sobre la mejora de elementos introduciendo un método para intercambiar recursos por tokens. Añadiremos una nueva tabla para realizar un seguimiento de los recursos, donde cada entrada incluye un `key_id` (ID numérico del recurso), `resource_name` (nombre del recurso), y un `ratio` que define cuántos recursos se convierten en un token. Por ejemplo, un ratio de 25 significa que 100 unidades de madera se intercambiarían por 4 tokens. También integraremos el contrato estándar `eosio.token`, cubierto previamente, para manejar estas transacciones. + +```cpp + struct [[eosio::table]] resourcecost + { + uint64_t key_id; + std::string resource_name; + float ratio; // si el usuario intercambia 100 unidades de madera y el ratio es 25, significa que recibirá 4 tokens + + uint64_t primary_key() const { return key_id; } + }; + typedef multi_index< "resourcecost"_n, resourcecost > resourcecost_t; + +``` + +Continuando desde la configuración anterior, ahora nos enfocamos en optimizar el sistema de transacciones con tokens. Hemos preminteado toda la oferta de tokens y la hemos asignado al contrato del juego, permitiendo transferencias fluidas entre los jugadores. Sin embargo, una alternativa es acuñar tokens de manera dinámica según sea necesario, como cuando un jugador intercambia recursos por tokens, asegurando que se cree y distribuya la cantidad justa. + +A continuación, implementaremos una función para crear entradas en la tabla `resourcecost`. Esta función, restringida a ser llamada solo por el contrato, facilitará la configuración o actualización de las tasas de intercambio entre recursos y tokens. El token GAME, integral para nuestro sistema, tiene 4 decimales y ya está distribuido al contrato del juego para interacciones activas. + +```cpp +void game::setratio(const std::string& resource, const float& ratio) +{ + require_auth(get_self()); + + const uint64_t key_id = stringToUint64(resource); + resourcecost_t resourcecost_table(get_self(), get_self().value); + auto resourcecost_table_itr = resourcecost_table.find(key_id); + + if(resourcecost_table_itr == std::end(resourcecost_table)) + { + resourcecost_table.emplace(get_self(), [&](auto &new_row) + { + new_row.key_id = key_id; + new_row.resource_name = resource; + new_row.ratio = ratio; + }); + } + else + { + resourcecost_table.modify(resourcecost_table_itr, get_self(), [&](auto &new_row) + { + new_row.resource_name = resource; + new_row.ratio = ratio; + }); + } +} + +``` + +Dado que el código de inicialización para agregar entradas a la tabla `resourcecost` refleja procesos descritos en artículos anteriores, no profundizaremos nuevamente en esos detalles aquí. Ahora que hemos establecido la relación recurso-token, el siguiente paso implica escribir la función de intercambio. Esta función manejará la conversión de recursos en tokens GAME, facilitando las transacciones de los jugadores dentro del entorno del juego. + +```cpp +void game::swap(const name& owner, const std::string& resource, const float& amount2swap) +{ + require_auth(owner); + + resourcecost_t resourcecost_table(get_self(), get_self().value); + auto resourcecost_table_itr = resourcecost_table.require_find(stringToUint64(resource), "No se pudo encontrar la configuración del costo del recurso"); + + const float token_amount = amount2swap / resourcecost_table_itr->ratio; + const asset tokens2receive = asset(token_amount * 10000, symbol("GAME", 4)); // cambiar al token que hayas implementado + + reduce_owner_resources_balance(owner, std::map({{resource, amount2swap}})); + tokens_transfer(owner, tokens2receive); +} + +``` + +donde + +**owner** -- jugador que desea realizar un intercambio + +**resource** -- el nombre del recurso + +**amount2swap** -- cantidad de recurso que el jugador quiere intercambiar por tokens. + +Para ejecutar un intercambio de recursos por tokens en el juego: + +1. **Recuperación de Registro**: La función primero recupera el registro correspondiente de la tabla de recursos según el nombre del recurso especificado. Si el recurso no se encuentra, se lanza un error. +2. **Cálculo de Tokens**: Luego, calcula el número de tokens que el jugador debería recibir en función de la cantidad de recurso que quiere intercambiar y el ratio predefinido en la tabla: *const float token_amount = amount2swap / resourcecost_table_itr->ratio;* +3. **Creación del Activo Token**: Se crea una variable de tipo asset, que representa los tokens: + Este paso formatea la cantidad de tokens para considerar los lugares decimales del token, asegurando que la cantidad correcta se procese para el intercambio:  *const asset tokens2receive = asset(token_amount * 10000, symbol("GAME", 4));* + +La multiplicación por (10^4) es necesaria porque el token GAME se define con 4 lugares decimales. Para reflejar con precisión los decimales en las transacciones, la `token_amount` calculada debe ser escalada por 10,000. Además, la configuración del símbolo del token requiere dos parámetros: el nombre del token ("GAME") y su número de decimales (4). Esta configuración garantiza que la cantidad del token y su representación se manejen correctamente en el sistema para transacciones precisas y válidas. + +Nota: Al hacer tu propio juego, asegúrate de reemplazar GAME con el nombre de tu token. + +```cpp + reduce_owner_resources_balance(owner, std::map({{resource, amount2swap}})); + tokens_transfer(owner, tokens2receive); +``` + +Ya estamos familiarizados con esta funcionalidad de la parte anterior y también añadiremos una función de transferencia de tokens. + +```cpp +void game::tokens_transfer(const name& to, const asset& quantity) +{ + + action + ( + permission_level{get_self(),"active"_n}, + "tokencontr"_n, // cambiar al contrato del token implementado + "transfer"_n, + std::make_tuple + ( + get_self(), + to, + quantity, + std::string("") + ) + ).send(); +} + +``` + +Para completar el proceso de intercambio de recursos por tokens, la función inicia una transferencia de tokens utilizando la funcionalidad de transferencia del token del contrato. Necesitarás reemplazar el nombre del contrato de token referenciado con el nombre de tu contrato de token específico para garantizar que la transferencia se alinee con la economía de tokens de tu juego y la configuración del contrato inteligente. Este paso finaliza el intercambio moviendo la cantidad calculada de tokens desde el contrato del juego a la cuenta del jugador. + +Este artículo detalló el proceso de intercambio de recursos dentro del juego por tokens en un marco de contrato inteligente. Cubrió la configuración de una tabla para definir las tasas de conversión de recursos a tokens, el cálculo del número de tokens basado en los recursos enviados por los jugadores, y el manejo efectivo de transacciones de tokens asegurando que todos los datos se alineen con las características definidas del token. El enfoque estuvo en la integración sin problemas de estas funcionalidades en el ecosistema del juego, facilitando un mecanismo de intercambio dinámico que mejora la interacción de los jugadores y la economía del juego. + +**PD.** El [siguiente enlace](https://github.com/dapplicaio/TokenSwaps) nos lleva a un repositorio que corresponde a todo lo descrito, para que puedas simplemente construir ese código y usarlo como quieras. \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part13.md b/docs/es/build/tutorials/howto-create_farming_game/Part13.md new file mode 100644 index 0000000..5d9d2fb --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part13.md @@ -0,0 +1,268 @@ +--- +title: Parte 13. Stake de Tokens y Votación en juegos +order: 65 +--- + +Para profundizar en el stake de tokens y su papel en la gobernanza, comenzamos configurando una nueva tabla para gestionar el proceso de staking. Esta tabla hará un seguimiento de los tokens en stake y sus derechos de votación correspondientes, lo cual es crucial para permitir que los jugadores participen en procesos clave de toma de decisiones, como el cambio de ratios de recurso a token en intercambios. Esta funcionalidad no solo aumenta el compromiso de los jugadores, sino que también descentraliza la gobernanza del juego, permitiendo que los jugadores tengan voz en las estrategias económicas del juego. + +```cpp +struct [[eosio::table]] balance_j + { + name owner; + asset quantity; + + uint64_t primary_key() const { return owner.value; } + }; +typedef multi_index< "balance"_n, balance_j > balance_t; +``` + +Para la implementación del staking de tokens, estableceremos una nueva tabla con los campos 'owner' y 'número de tokens en stake'. Esta tabla hará un seguimiento de los tokens que cada jugador tiene en stake en el juego. Además, para facilitar el staking, se agregará una función que escuche las transferencias de tokens. Esta función actualizará automáticamente la tabla de staking cada vez que se transfieran tokens al contrato, asegurando que los tokens en stake de los jugadores se registren y gestionen con precisión. + +```cpp +[[eosio::on_notify("tokencont::transfer")]] // tokencont cambiar por el contrato de tu token +void receive_token_transfer +( + const name& from, + const name& to, + const asset& quantity, + const std::string& memo +); +``` + +Al configurar la funcionalidad de staking de tokens, asegúrate de personalizar el código reemplazando `tokencont` con el nombre real de tu contrato de token. Esto es esencial para garantizar que la función de staking interactúe correctamente con el contrato de token específico implementado para tu juego, permitiendo un seguimiento y una gestión precisos de los tokens en stake. + +```cpp +void game::receive_token_transfer +( + const name& from, + const name& to, + const asset& quantity, + const std::string& memo +) +{ + if(to != get_self()) + return; + + if(memo == "stake") + { + increase_tokens_balance(from, quantity); + } + else + check(0, "Invalid memo"); +} +``` + +y + +```cpp +void game::increase_tokens_balance(const name& owner, const asset& quantity) +{ + balance_t balance_table(get_self(), get_self().value); + auto balance_table_itr = balance_table.find(owner.value); + + if(balance_table_itr == std::end(balance_table)) + { + balance_table.emplace(get_self(), [&](auto &new_row) + { + new_row.owner = owner; + new_row.quantity = quantity; + }); + } + else + { + balance_table.modify(balance_table_itr, get_self(), [&](auto &new_row) + { + new_row.quantity += quantity; + }); + } +} +``` + +La función para gestionar el staking de tokens opera accediendo a la tabla de balances para localizar la entrada de un jugador específico. Si el jugador ya tiene un balance registrado, la función incrementa la cantidad de tokens en stake en consecuencia. Si no se encuentra un balance existente, crea un nuevo registro para el jugador, documentando la cantidad de tokens que ha puesto en stake. + +## Votación + +Ahora que el staking de tokens está en su lugar, nos centraremos en implementar un sistema de votación para cambiar el ratio en los intercambios de recursos. Para facilitar esto, introduciremos una nueva tabla diseñada específicamente para gestionar los registros de votación. Esta tabla hará un seguimiento de cada voto relacionado con los ajustes de ratios, permitiendo que los titulares de tokens en stake influyan en las tasas de conversión de recursos a tokens según sus preferencias y la cantidad de tokens en stake que tengan en el juego. Este mecanismo integra la toma de decisiones democrática en el modelo económico del juego. + +```cpp + struct [[eosio::table]] changeration_j + { + uint64_t voting_id; + std::string resource_name; + float new_ratio; + std::map voted; // el primero es el nombre del jugador, el segundo es el poder de voto (en tokens) + + uint64_t primary_key() const { return voting_id; } + }; + typedef multi_index< "changeration"_n, changeration_j > changeration_t; +``` + +Para admitir la votación sobre cambios en los ratios de intercambio de recursos, estableceremos una nueva tabla estructurada de la siguiente manera: + +- **voting_id**: Un identificador único para cada evento de votación. +- **resource_name**: El nombre del recurso sujeto al cambio de ratio. +- **new_ratio**: El nuevo ratio propuesto de intercambio del recurso por el token. +- **voted**: Una lista que detalla qué jugadores han votado y la cantidad de votos que cada jugador ha emitido, reflejando la cantidad de tokens en stake. + +Esta configuración permite a los titulares de tokens participar directamente en las decisiones que afectan la dinámica económica del juego. + +```cpp +void game::createvoting( + const name& player, + const std::string& resource_name, + const float& new_ratio +) +{ + require_auth(player); + + const uint64_t key_id = stringToUint64(resource_name); + resourcecost_t resourcecost_table(get_self(), get_self().value); + auto resourcecost_table_itr = resourcecost_table.require_find(key_id, "Could not find selected resource name"); + + changeration_t changeration_table(get_self(), get_self().value); + const uint64_t new_voting_id = changeration_table.available_primary_key(); + + changeration_table.emplace(player, [&](auto &new_row) + { + new_row.voting_id = new_voting_id; + new_row.resource_name = resource_name; + new_row.new_ratio = new_ratio; + }); +} +``` + +Para empezar, verificamos si tal recurso existe en la tabla de configuración: + +```cpp + const uint64_t key_id = stringToUint64(resource_name); + resourcecost_t resourcecost_table(get_self(), get_self().value); + auto resourcecost_table_itr = resourcecost_table.require_find(key_id, "Could not find selected resource name"); + +``` + +Después de eso, extraemos un nuevo id para la votación + +```cpp + changeration_t changeration_table(get_self(), get_self().value); + const uint64_t new_voting_id = changeration_table.available_primary_key(); + +``` + +y hacemos un nuevo registro, teniendo en cuenta que ahora el jugador paga por los marcos para evitar abusos + +```cpp + changeration_table.emplace(player, [&](auto &new_row) + { + new_row.voting_id = new_voting_id; + new_row.resource_name = resource_name; + new_row.new_ratio = new_ratio; + }); + +``` + +Ahora vamos a crear una función para la votación. Imaginemos que, condicionalmente, necesitamos 100 votos (100 tokens para que la votación se complete y se aprueben los cambios). + +```cpp +void game::vote( + const name& player, + const uint64_t& voting_id +) +{ + require_auth(player); + + balance_t balance_table(get_self(), get_self().value); + resourcecost_t resourcecost_table(get_self(), get_self().value); + changeration_t changeration_table(get_self(), get_self().value); + + auto balance_table_itr = balance_table.require_find(player.value, "You don't have staked tokens to vote"); + auto changeration_table_itr = changeration_table.require_find(voting_id, "Could not find selected voting id"); + auto resourcecost_table_itr = resourcecost_table.require_find(stringToUint64(changeration_table_itr->resource_name)); + + const asset goal_votes = asset(100 * 10000, symbol("GAME", 4)); // 100.0000 GAME tokens para aplicar los cambios + asset total_votes = asset(0, symbol("GAME", 4)); + + for (const auto& map_itr : changeration_table_itr->voted) + total_votes += map_itr.second; + + if(total_votes + balance_table_itr->quantity >= goal_votes) + { + resourcecost_table.modify(resourcecost_table_itr, get_self(), [&](auto &new_row) + { + new_row.ratio = changeration_table_itr->new_ratio; + }); + + changeration_table.erase(changeration_table_itr); + } + else + { + changeration_table.modify(changeration_table_itr, get_self(), [&](auto &new_row) + { + new_row.voted[player] = balance_table_itr->quantity; + }); + } +} +``` + +Describamos el código anterior por partes: + +1. **Autorización del jugador** + +```cpp + require_auth(player); +``` + +2. Para la gestión y el procesamiento efectivos de los votos de los jugadores respecto a los cambios en el ratio de recurso a token, el sistema incorpora estructuras de datos críticas. Estas incluyen la Tabla de Balance de Tokens para verificar los tokens en stake de los jugadores como poder de voto, la Configuración del Precio del Recurso para hacer referencia a los cambios actuales y propuestos de los ratios, y la Tabla de Votación para gestionar y contabilizar los votos con sus iteradores. Estos componentes son esenciales para garantizar la transparencia e integridad en el proceso de toma de decisiones democrática del juego. + +```cpp +balance_t balance_table(get_self(), get_self().value); +resourcecost_t resourcecost_table(get_self(), get_self().value); +changeration_t changeration_table(get_self(), get_self().value); + +auto balance_table_itr = balance_table.require_find(player.value, "You don't have staked tokens to vote"); +auto changeration_table_itr = changeration_table.require_find(voting_id, "Could not find selected voting id"); +auto resourcecost_table_itr = resourcecost_table.require_find(stringToUint64(changeration_table_itr->resource_name)); +``` + +3. Para el proceso de votación, se inicializa una variable específica para realizar un seguimiento del progreso hacia el umbral de aprobación predefinido en la configuración del voto. Si se cumple este umbral, la votación se considera exitosa y se pueden aplicar los cambios a la configuración del ratio del recurso. + +```cpp +const asset goal_votes = asset(100 * 10000, symbol("GAME", 4)); // 100.0000 GAME tokens para aplicar los cambios + asset total_votes = asset(0, symbol("GAME", 4)); +``` + +4. Contabilización total de los votos + +```cpp +for (const auto& map_itr : changeration_table_itr->voted) + total_votes += map_itr.second; +``` + +5. Si los votos totales alcanzan el umbral establecido para la propuesta, indicando la aprobación de los jugadores, entonces se actualiza la configuración del precio del recurso sobre el que se votó en consecuencia. Tras esta actualización, la votación específica se concluye y se elimina de la tabla de votaciones, finalizando la decisión y reflejando la elección colectiva de los jugadores en la configuración del juego. + +```cpp +if(total_votes + balance_table_itr->quantity >= goal_votes) +{ + resourcecost_table.modify(resourcecost_table_itr, get_self(), [&](auto &new_row) + { + new_row.ratio = changeration_table_itr->new_ratio; + }); + + changeration_table.erase(changeration_table_itr); +} +``` + +6. De lo contrario, simplemente se añaden los votos emitidos por el jugador + +```cpp +else +{ + changeration_table.modify(changeration_table_itr, get_self(), [&](auto &new_row) + { + new_row.voted[player] = balance_table_itr->quantity; + }); +} +``` + +Este artículo se centró en implementar un sistema de staking y votación de tokens dentro de un entorno de juego. Detalló la configuración de una estructura de votación para que los jugadores influyan en los cambios en los ratios de intercambio de recursos a tokens a través de un proceso democrático. Los componentes clave incluyeron la creación de tablas para realizar un seguimiento de los votos y la configuración de los tokens en stake de los jugadores para determinar su poder de voto. El artículo también describió cómo se contabilizan los votos y las condiciones bajo las cuales se implementan los cambios propuestos, enfatizando la integración de estas funcionalidades en el marco del contrato inteligente del juego. + +**PD.** El [siguiente enlace](https://github.com/dapplicaio/TokenStakingAndVoting) nos lleva a un repositorio que corresponde a todo lo descrito. \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part14.md b/docs/es/build/tutorials/howto-create_farming_game/Part14.md new file mode 100644 index 0000000..983c4b9 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part14.md @@ -0,0 +1,544 @@ +--- +title: Parte 14. Gobernanza en juegos +order: 70 +--- + +En este artículo, desarrollaremos un sistema de votación que permitirá a los usuarios proporcionar sugerencias a los desarrolladores o cambiar el valor de algunos aspectos del juego. + +1. Creando la tabla de configuraciones + +```cpp +struct [[eosio::table]] mconfig_j + { + std::string variable_name; + std::string variable_type; + std::string variable_value; + + uint64_t primary_key() const { return stringToUint64(variable_name); } + }; + typedef multi_index<"config"_n, mconfig_j> mconfigs_t; + +``` + +La tabla se organiza en filas (nombre de la variable, tipo de variable, valor de la variable). + +```cpp +typedef std::variant CONFIG_TYPE; + +void game::setcnfg( + const std::string &variable_name, + const CONFIG_TYPE &variable) +{ + require_auth(get_self()); + mconfigs_t configs_table(get_self(), get_self().value); + + auto var_iter = configs_table.find(stringToUint64(variable_name)); + + auto variant_data = get_config_variant_data(variable); + + if (var_iter == configs_table.end()) + { + configs_table.emplace(get_self(), [&](auto &new_row) + { + new_row.variable_name = variable_name; + new_row.variable_type = variant_data.first; + new_row.variable_value = variant_data.second; }); + } + else + { + check(var_iter->variable_type == variant_data.first, "Types mismatch"); + configs_table.modify(var_iter, get_self(), [&](auto &row) + { row.variable_value = variant_data.second; }); + } +} + +``` + +El tipo **CONFIG_TYPE** indica los tipos válidos para las variables de configuración. La acción **setcnfg** acepta el nombre de la variable y su valor (junto con el tipo, la plataforma **eosio** verifica por sí misma el tipo correcto). Si esta variable está presente en la tabla de configuración, verificamos si el tipo de la variable coincide con el pasado por el usuario y cambiamos el campo de la tabla. Si la variable no está en la tabla, la agregamos. + +```cpp +std::pair game::get_config_variant_data(const CONFIG_TYPE &var) +{ + if (std::holds_alternative(var)) + { + return {"string", std::get(var)}; + } + else if (std::holds_alternative(var)) + { + return {"float", std::to_string(std::get(var))}; + } + else if (std::holds_alternative(var)) + { + return {"uint32", std::to_string(std::get(var))}; + } + else if (std::holds_alternative(var)) + { + return {"int32", std::to_string(std::get(var))}; + } + else + { + return {"error", ""}; + } +} + +``` + +Esta función auxiliar traduce los datos de **CONFIG_TYPE** en un par (tipo de variable, valor de la variable). + +2. Tablas principales + +```cpp +struct [[eosio::table]] votings_info_j + { + name voting_name; + name creator; + uint64_t min_staked_tokens; + uint64_t max_staked_tokens; + uint64_t total_staked; + uint32_t creation_time; + uint32_t time_to_vote; + std::string variable_to_change; + + uint64_t primary_key() const { return voting_name.value; }; + }; + typedef multi_index<"vtsinfo"_n, votings_info_j> vinfo_t; +``` + +Esta tabla registra la información de las votaciones en una tabla separada. Con su ayuda, podemos ver el número total de tokens apostados, verificar si el tiempo de votación ha expirado, entre otros. + +3. Creando votaciones + +```cpp + struct [[eosio::table]] voting_j + { + uint64_t id; + std::string voting_option; + std::map voted; + uint64_t total_voted_option; + + uint64_t primary_key() const { return id; } + }; + typedef multi_index<"genvtngs"_n, voting_j> votings_t; + +``` + +Esta tabla (scope: el nombre de la votación) almacena las opciones de votación y el std::map con pares (jugador, número de tokens apostados), así como la cantidad total de tokens apostados para esta opción. + +```cpp + struct [[eosio::table]] closed_votings_j + { + uint64_t voting_id; + name voting_name; + std::string winner_option; + + uint64_t primary_key() const { return voting_id; }; + }; + typedef multi_index<"closedvts"_n, closed_votings_j> closed_votings_t; +``` + +Esta tabla almacena pares (votación, resultado de la votación). + +4. Creando votaciones + +```cpp +void game::crgenvt( + const name &player, + const name &voting_name, + const std::vector &options, + uint64_t min_staked_tokens, + uint32_t time_to_vote, + uint64_t max_staked_tokens) + +{ + require_auth(player); + vinfo_t vinfo(get_self(), get_self().value); + check(vinfo.find(voting_name.value) == std::end(vinfo), "Voting with this name is active"); + + // agregar a la información + + vinfo.emplace(get_self(), [&](auto &new_row) + { + new_row.voting_name = voting_name; + new_row.creator = player; + new_row.min_staked_tokens = min_staked_tokens; + new_row.max_staked_tokens = max_staked_tokens; + new_row.total_staked = 0; + new_row.creation_time = current_time_point().sec_since_epoch(); + new_row.time_to_vote = time_to_vote; }); + + add_voting_with_options(voting_name, options); +} +``` + +Esta función escribe los datos de la votación en la tabla **vtsinfo** y añade campos a la tabla **genvtngs** (nombre de la opción, mapa con los votantes, cantidad total de tokens apostados para la opción). + +```cpp +void game::cravote( + const name &player, + const name &voting_name, + const std::string &variable_name, + const std::vector &options_typed, + int64_t min_staked_tokens, + int32_t time_to_vote, + uint64_t max_staked_tokens) +{ + require_auth(player); + vinfo_t vinfo(get_self(), get_self().value); + check(vinfo.find(voting_name.value) == std::end(vinfo), "Voting with this name is active"); + + // verificar la corrección del tipo + + std::vector options; + mconfigs_t configs_table(get_self(), get_self().value); + auto config_iter = configs_table.require_find(stringToUint64(variable_name), "No such variable in configs table"); + std::string variable_type = config_iter->variable_type; + + for (const auto &option : options_typed) + { + auto variant_data = get_config_variant_data(option); + check(variant_data.first == variable_type, "Types mismatch in options"); + options.push_back(variant_data.second); + } + + // agregar a la información + + vinfo.emplace(get_self(), [&](auto &new_row) + { + new_row.voting_name = voting_name; + new_row.creator = player; + new_row.min_staked_tokens = min_staked_tokens; + new_row.max_staked_tokens = max_staked_tokens; + new_row.total_staked = 0; + new_row.creation_time = current_time_point().sec_since_epoch(); + new_row.time_to_vote = time_to_vote; + new_row.variable_to_change = variable_name; }); + + add_voting_with_options(voting_name, options); +} +``` + +Esta función crea una votación que afecta alguna variable de la tabla de configuración. Primero, para cada opción, verificamos si su tipo coincide con el nombre de la variable. Si es así, añadimos la información de la votación a las tablas **vtsinfo** y **genvtngs**. + +5. Añadiendo opciones de votación + +```cpp +void game::add_voting_with_options(const name &voting_name, const std::vector &options) +{ + votings_t votings(get_self(), voting_name.value); + + for (uint64_t index = 0; index < options.size(); index++) + { + votings.emplace(get_self(), [&](auto &new_row) + { + new_row.id = index; + new_row.voting_option = options[index]; + new_row.voted = {}; + new_row.total_voted_option = 0; }); + } +} +``` + +Esta es una función auxiliar que añade filas a la tabla **genvtngs** (id, nombre de la opción, mapa con votantes, número total de tokens). + +**6. Proceso de votación** + +```cpp +void game::gvote( + const name &player, + const name &voting_name, + const std::string &voting_option, + uint64_t voting_power) +{ + require_auth(player); + balance_t balance_table(get_self(), get_self().value); + auto player_balance_iter = balance_table.require_find(player.value, "No tokens staked"); + check(player_balance_iter->quantity.amount >= voting_power, "Not enough tokens to vote with that voting power"); + + vinfo_t vinfo(get_self(), get_self().value); + auto voting_info_iter = vinfo.require_find(voting_name.value, "No such voting"); + + // verificar el tiempo + + if (voting_info_iter->time_to_vote != 0) + { + check((current_time_point().sec_since_epoch() - voting_info_iter->creation_time) <= voting_info_iter->time_to_vote, + "Time for voting is over"); + } + + // verificar límite máximo + + if (voting_info_iter->max_staked_tokens != 0) + { + check(voting_info_iter->total_staked < voting_info_iter->max_staked_tokens, "Max limit for staking is reached"); + } + + votings_t votings(get_self(), voting_name.value); + auto option_iter = votings.end(); + auto current_iter = votings.end(); + uint64_t id = 0; + + while (true) + { + current_iter = votings.find(id); + + if (current_iter == votings.end()) + { + break; + } + + if (current_iter->voting_option == voting_option) + { + option_iter = current_iter; + } + + check(current_iter->voted.find(player) == current_iter->voted.end(), "Already voted"); + + id++; + } + + check(option_iter != votings.end(), "No such option"); + + votings.modify(option_iter, get_self(), [&](auto &row) + { + row.voted[player] = voting_power; + row.total_voted_option += voting_power; }); + + vinfo.modify(voting_info_iter, get_self(), [&](auto &row) + { row.total_staked += voting_power; }); + + balance_table.modify(player_balance_iter, get_self(), [&](auto& row) + { + row.quantity.amount -= voting_power; + }); + + // verificar límite máximo y cerrar si se ha alcanzado + if (voting_info_iter->max_staked_tokens != 0) + { + if (voting_info_iter->total_staked >= voting_info_iter->max_staked_tokens) + { + if (voting_info_iter->variable_to_change.empty()) + { + close_general_voting(voting_name); + } + else + { + close_automatic_voting(voting_name); + } + } + } +} +``` + +Esta función gestiona el proceso de votación. Primero, verifica si el usuario tiene tokens, si son suficientes y si la votación a la que hace referencia existe. Luego, comprueba si el tiempo de votación ha expirado y si se ha alcanzado el límite superior de votos. Si es así, ya no es posible votar. + +De lo contrario, revisa las opciones de votación y verifica si la opción a la que el jugador se refiere está entre ellas. También se verifica si ya ha votado antes. Si es así, se interrumpe la función. + +Si se encuentra la opción, se modifica el campo de la tabla **genvtngs**: se añade al jugador al mapa std::map y se incrementa la cantidad total de tokens apostados para la opción. En la tabla **vtsinfo**, se incrementa el campo **total_tokens_staked**. También se descuentan del jugador la cantidad especificada de tokens. + +Al final, se verifica si se ha alcanzado el número máximo de votos. Si es así, se cierra la votación. + +**7. Cancelando un voto** + +```cpp +void game::cancelvote(const name& player, const name& voting_name) +{ + require_auth(player); + + vinfo_t vinfo(get_self(), get_self().value); + auto voting_info_iter = vinfo.require_find(voting_name.value, "No such voting"); + + balance_t balance_table(get_self(), get_self().value); + auto player_balance_iter = balance_table.require_find(player.value, "No staked tokens for this player"); + + votings_t votings(get_self(), voting_name.value); + auto option_iter = votings.end(); + auto current_iter = votings.end(); + + uint64_t voting_power = 0; + uint32_t id = 0; + + while (true) + { + current_iter = votings.find(id); + + if (current_iter == votings.end()) + { + break; + } + + auto player_voting_iter = current_iter->voted.find(player); + if (player_voting_iter != current_iter->voted.end()) + { + voting_power = player_voting_iter->second; + + votings.modify(current_iter, get_self(), [&](auto& row) + { + row.voted.erase(player_voting_iter); + row.total_voted_option -= voting_power; + }); + + break; + } + + id++; + } + + if (voting_power != 0) + { + vinfo.modify(voting_info_iter, get_self(), [&](auto& row) + { + row.total_staked -= voting_power; + }); + + balance_table.modify(player_balance_iter, get_self(), [&](auto& row) + { + row.quantity.amount += voting_power; + }); + } +} +``` + +Esta funcionalidad permite a un jugador cancelar su voto en una encuesta. Se recorren las opciones de votación y, si se encuentra la que el jugador votó, se elimina del mapa, se reduce el número de tokens apostados en ambas tablas y se devuelven los tokens al jugador. + +**8. Cerrando la votación** + +```cpp +void game::clsvt(const name &voting_name) +{ + vinfo_t vinfo(get_self(), get_self().value); + auto voting_info_iter = vinfo.require_find(voting_name.value, "No such voting"); + + require_auth(voting_info_iter->creator); + + if (voting_info_iter->variable_to_change.empty()) + { + close_general_voting(voting_name); + } + else + { + close_automatic_voting(voting_name); + } +} +``` + +Debemos comprobar si existe dicha votación. Luego, llamamos a funciones separadas para dos tipos diferentes de votación. + +```cpp +void game::close_general_voting(const name &voting_name) +{ + clear_voting_from_vinfo(voting_name); + std::string winner = get_voting_winner_clear(voting_name); + + // agregar a las votaciones cerradas + closed_votings_t closed_votings(get_self(), get_self().value); + uint64_t voting_id = closed_votings.available_primary_key(); + + closed_votings.emplace(get_self(), [&](auto &new_row) + { + new_row.voting_id = voting_id; + new_row.voting_name = voting_name; + new_row.winner_option = winner; }); +} +``` + +Primero, eliminamos los datos de la votación de la tabla **vtsinfo**. Luego obtenemos la opción con el mayor número de tokens apostados y eliminamos los campos de la tabla **genvtngs**. Como la votación es del tipo general, se añade una fila (id, nombre de la votación, opción ganadora) a la tabla con los resultados de la votación general. + +**Cerrando votación automática** + +```cpp +void game::close_automatic_voting(const name &voting_name) +{ + std::string variable_name = clear_voting_from_vinfo(voting_name); + std::string winner = get_voting_winner_clear(voting_name); + + // realizar la transacción + mconfigs_t config_table(get_self(), get_self().value); + auto config_iter = config_table.require_find(stringToUint64(variable_name), "Variable in question was deleted"); + config_table.modify(config_iter, get_self(), [&](auto &row) + { row.variable_value = winner; }); +} +``` + +Cerrar la votación automática difiere en que al final modificamos el valor de la variable en la tabla de configuración. + +```cpp +std::string game::clear_voting_from_vinfo(const name &voting_name) +{ + vinfo_t vinfo(get_self(), get_self().value); + auto voting_info_iter = vinfo.require_find(voting_name.value, "No such voting"); + + std::string variable_name = voting_info_iter->variable_to_change; + uint64_t min_staked = voting_info_iter->min_staked_tokens; + uint64_t total_staked = voting_info_iter->total_staked; + + check(total_staked >= min_staked, "Minimal rate is not reached yet"); + + if (voting_info_iter->time_to_vote != 0) + { + check((current_time_point().sec_since_epoch() - voting_info_iter->creation_time) >= voting_info_iter->time_to_vote, + "Time for voting is not over yet"); + } + + if (voting_info_iter->max_staked_tokens != 0) + { + check(total_staked >= voting_info_iter->max_staked_tokens, "Voting limit is not reached yet"); + } + + vinfo.erase(voting_info_iter); + + return variable_name; +} +``` + +Esta función verifica si se cumplen las condiciones para cerrar la votación y elimina los datos de la misma de la tabla **vtsinfo**. + +```cpp +std::string game::get_voting_winner_clear(const name &voting_name) +{ + std::string winner = ""; + uint64_t max_voted = 0; + uint64_t id = 0; + + votings_t votings(get_self(), voting_name.value); + + balance_t balance_table(get_self(), get_self().value); + + while (true) + { + auto option_iter = votings.find(id); + + if (option_iter == votings.end()) + { + break; + } + + if (option_iter->total_voted_option > max_voted) + { + max_voted = option_iter->total_voted_option; + winner = option_iter->voting_option; + } + + for (const auto& player_amount : option_iter->voted) + { + auto player_balance_iter = balance_table.require_find(player_amount.first.value, "Player was deleted"); + balance_table.modify(player_balance_iter, get_self(), [&](auto& row) + { + row.quantity.amount += player_amount.second; + }); + } + + votings.erase(option_iter); + + id++; + } + + return winner; +} +``` + +Esta función recorre las opciones de votación, encuentra la opción ganadora y elimina las filas de la tabla **genvtngs** una por una. También devuelve los tokens del jugador a su balance. + +En este artículo describimos paso a paso el proceso de crear una gobernanza mediante votaciones, para que los usuarios puedan influir en el diseño general del juego o en ciertas partes de la economía del juego. + +**PD.** El [enlace siguiente](https://github.com/dapplicaio/GamesGovernane) nos lleva a un repositorio que corresponde a todo lo descrito. + \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part15.md b/docs/es/build/tutorials/howto-create_farming_game/Part15.md new file mode 100644 index 0000000..7ed5920 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part15.md @@ -0,0 +1,218 @@ +--- +title: Parte 15. Interfaz Gráfica para intercambios, staking y gobernanza +order: 75 +--- + +En esta sección, ampliamos los artículos anteriores integrándolo en una interfaz de ReactJS y leyendo datos desde una tabla de contratos inteligentes de WAX. También profundizaremos en los intercambios. Exploraremos el uso de tokens WAX o NFTs en los juegos y cómo funciona la gobernanza por parte de los usuarios o jugadores. + +**Intercambio de Tokens** +------------------------- + +El intercambio está diseñado para intercambiar la cantidad de un recurso al tipo de cambio especificado en la tabla llamada "**resourcecost**". Después de completar la acción, el contrato envía tokens WAX (no están en staking, el jugador puede ponerlos en staking más tarde). + +En términos simples, vendes tu recurso y recibes tokens WAX a cambio. Para hacerlo, necesitas llamar a la acción del contrato "**swap**". + +```js +export const swap = async ({ activeUser, resource, amount2swap }) => { + return await signTransaction({ + activeUser, + account: 'dappgamemine', + action: 'swap', + data: { + owner: activeUser.accountName, + resource: resource, + amount2swap: amount2swap + } + }); +}; +``` + +Aquí usamos una acción del juego llamada "swap". + +- **owner** -- este es el apodo del usuario que conectó su cartera, lo tomamos de activeUser.accountName; +- **resource** -- nombre del recurso; +- **amount2swap** (número) -- cantidad a intercambiar. + +Ahora echemos un vistazo a la tabla "**resourcecost**". + +```js +export const resourceCost = async () => { +const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: 'dappgamemine', + table: "resourcecost" +}); + return rows; +} + +``` + +La tabla está estructurada de la siguiente manera: cada fila está identificada por un 'id', que es un identificador único. Junto a él se encuentra el 'nombre del recurso', que es el tipo o nombre del recurso. Además, la columna 'ratio' indica la proporción del recurso al token WAX. Este 'ratio' esencialmente cuantifica la relación entre el recurso y su token WAX asociado, proporcionando una medida de su intercambiabilidad o equivalencia. + +Así es como se ve en la interfaz: + +![](/public/assets/images/tutorials/howto-create_farming_game/part15/image1.png) + +El usuario selecciona el recurso en el molino aleatorio e ingresa la cantidad. Después de eso, la cantidad de WAX cambiará automáticamente en el campo inferior. + +Aquí está el enlace a la acción en el contrato inteligente para intercambio: + + + +y la tabla **resourcecost:** + + + +**Staking de Tokens en juegos** +------------------------------ + +¡Entendido! Vamos a profundizar en el proceso de staking de tokens WAX y su uso para votar. Exploraremos cómo llamar la acción necesaria para poner nuestros tokens WAX en staking y obtener el derecho a votar. + +```js +export const tokenStake = async ({ activeUser, quantity }) => { + return await signTransaction({ + activeUser, + account: 'eosio.token', + action: 'transfer', + data: { + from: activeUser.accountName, + to: 'dappgamemine', + quantity: quantity, + memo: 'staking' + } + }); +}; + +``` + +Aquí usamos una acción llamada "transfer" para poner en staking los tokens WAX. + +- **from** -- este es el apodo del usuario que conectó su cartera, lo tomamos de activeUser.accountName; +- **to** -- nombre del contrato del juego; +- **quantity** (cadena, como '10.00000000 WAX') -- cantidad para poner en staking. +- **memo** -- si queremos hacer staking con nuestros WAX en nuestro juego, utilizamos el MEMO 'staking'. + +Y así es como podemos obtener los datos de la tabla de balance mencionada anteriormente. + +```js +export const fetchBalance = async () => { + const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: 'dappgamemine', + table: "balance" + }); + + return rows; +} + +``` + +En la interfaz, se ve así: el usuario ingresa la cantidad de WAX que desea apostar y, tras hacer clic en el botón "stake", se llama a la acción descrita anteriormente. + +![](/public/assets/images/tutorials/howto-create_farming_game/part15/image2.png) + +Aquí está el enlace a la acción en el contrato inteligente para hacer staking de tokens WAX: + + + +y la tabla de balances: + + + +**Gobernanza del juego por parte de los usuarios/jugadores** +------------------------------------ + +Vamos a delinear la estructura del sistema de votación: + +- La votación puede originarse de los desarrolladores (votación del sistema) o de jugadores con suficientes tokens en staking (votación comunitaria). +- La votación puede estar relacionada con variables del contrato (automática) o puede ser una propuesta que el equipo se compromete a cumplir (general). +- Una votación se considera válida si una parte suficiente de la comunidad del juego ha participado en ella (límite inferior). + +Estas son las condiciones para cerrar una votación: + +- La cantidad de tokens bloqueados en la votación alcanza un límite predeterminado (límite de cierre). +- El tiempo especificado para la votación expira (fecha límite). + +Al crear una encuesta, eliges el tipo de finalización. Puede ser 1 o 2, o un híbrido (1 o 2). + +El poder de voto de un jugador depende directamente de la cantidad de tokens WAX en staking. + +Ahora veamos cómo integrar esto en React JS. Para crear una encuesta, tenemos una función llamada **createVoting()**. + +```js +export const createVoting = async ({ activeUser, resName, ratio }) => { + return await signTransaction({ + activeUser, + account: 'dappgamemine', + action: 'createvoting', + data: { + player: activeUser.accountName, + resource_name: resName, + new_ratio: ratio, + } + }); +}; + +``` + +Aquí utilizamos una acción llamada "**createvoting**" para crear una votación para cambiar el ratio. + +- **player** -- este es el apodo del usuario que conectó su cartera, lo tomamos de activeUser.accountName; +- **resource_name (string)** -- nombre del recurso; +- **new_ratio** (float32) -- nuevo ratio. + +En la imagen de la interfaz, vemos una votación para cambiar el ratio. + +![](/public/assets/images/tutorials/howto-create_farming_game/part15/image3.png) + +También se enumeran el propósito de la encuesta, la fecha límite, el autor, el número de participantes y el número total de tokens contribuidos. + +Ahora veamos cómo votar. Primero, necesitamos asegurar tokens WAX. Hemos descrito cómo hacerlo anteriormente. + +```js +export const vote = async ({ activeUser, id }) => { + return await signTransaction({ + activeUser, + account: 'dappgamemine', + action: 'vote', + data: { + player: activeUser.accountName, + voting_id: id, + } + }); +}; + +``` + +Aquí usamos una acción llamada "vote" para emitir tu voto. + +- **player** -- este es el apodo del usuario que conectó su cartera, lo tomamos de activeUser.accountName; +- **voting_id** -- ID de la votación de la tabla сhangeration; + +Una vez creada, tu voto se añade a la tabla '**changeration**'. Para extraerlo para nuestra interfaz, utilizamos la función **changeRation()**. + +```js +export const changeRation = async () => { + const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: 'dappgamemine', + table: "changeration" + }); + return rows; + } + +``` + +Aquí está el enlace a la acción en el contrato inteligente para crear una votación: + + + +acción para votar: + + + +una tabla con todas las votaciones creadas para cambios de tasa: + + + +**PS.** El [enlace siguiente](https://github.com/dapplicaio/GUIStakingGovernanceSwaps) nos lleva a un repositorio que corresponde a todo lo descrito. diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part16.md b/docs/es/build/tutorials/howto-create_farming_game/Part16.md new file mode 100644 index 0000000..06570ae --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part16.md @@ -0,0 +1,198 @@ +--- +title: Parte 16. Clasificaciones en juegos +order: 80 +--- + +En este artículo, analizaremos la creación de tablas de clasificación y consideraremos la implementación de una clasificación para los recursos de los usuarios y su tasa de minería general. + +1. **Nuevas tablas** + +```cpp + struct [[eosio::table]] lboard_j + { + name account; + uint64_t points; + + uint64_t primary_key() const { return account.value; }; + }; + typedef multi_index<"lboards"_n, lboard_j> lboards_t; + +``` + +points -- el valor de los puntos en la tabla de clasificación + +account -- el nombre de la cuenta para la cual registramos los puntos + +**2\. Actualización de puntos en la tabla de clasificación** + +Para modificar los valores de los puntos en la tabla de clasificación, introduciremos tres funciones: aumentar, disminuir y establecer un cierto número de puntos. Consideremos la última, las otras se realizan de la misma manera. + +```cpp +void incr_lb_points(const name &lbname, const name &account, uint64_t points); +void decr_lb_points(const name &lbname, const name &account, uint64_t points); +void set_lb_points(const name &lbname, const name &account, uint64_t points); +``` + +```cpp +void game::set_lb_points(const name &lbname, const name &account, uint64_t points) +{ + lboards_t lboard(get_self(), lbname.value); + + auto account_itr = lboard.find(account.value); + + if (account_itr == std::end(lboard)) + { + lboard.emplace(get_self(), [&](auto &new_row) + { + new_row.account = account; + new_row.points = points; }); + } + else + { + lboard.modify(account_itr, get_self(), [&](auto &row) + { row.points = points; }); + } +} +``` + +Descripción de la función: + +1) Obtenemos el puntero a la tabla de clasificación por el nombre de la tabla. Luego encontramos el registro con el jugador + +```cpp +lboards_t lboard(get_self(), lbname.value); +auto account_itr = lboard.find(account.value); +``` + +2) Si el jugador no está en la tabla, lo añadimos. Si ya está, actualizamos el valor de los puntos + +```cpp +if (account_itr == std::end(lboard)) +{ + lboard.emplace(get_self(), [&](auto &new_row) + { + new_row.account = account; + new_row.points = points; }); +} +else +{ + lboard.modify(account_itr, get_self(), [&](auto &row) + { row.points = points; }); +} +``` + +**3\. Uso de las tablas de clasificación para los registros de recursos** + +En las funciones **increase_owner_resource_balance** y **decrease_owner_resource_balance**, añada las siguientes líneas dentro del bucle que recorre el mapa con los recursos + +```cpp +eosio::name resource_name(static_cast(resources_table_itr->resource_name)); +set_lb_points(resource_name, owner, resources_table_itr->amount); +``` + +La primera de ellas crea **eosio::name** a partir de una cadena que denota el nombre del recurso. Es decir, es una tabla de clasificación de madera o piedra, etc. Luego establecemos el valor ya calculado del recurso en la tabla. + +**4\. Uso de las tablas de clasificación para la tasa de minería** + +Al final de las funciones **stake_items** y **upgradeitem**, agregue una línea con la función **update_mining_power_lb**, que recalcula la nueva tasa total de minería y la ingresa en la tabla de clasificación. + +```cpp +void game::update_mining_power_lb(const name &account) +{ + staked_t staked_table(get_self(), account.value); + float mining_power = 0.0f; + + const std::map stats = get_stats(account); + auto assets = atomicassets::get_assets(get_self()); + + for (const auto &staked : staked_table) + { + auto farmingitem_itr = assets.find(staked.asset_id); + auto farmingitem_mdata = get_mdata(farmingitem_itr); + + float miningBoost = 1; + if (farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) + miningBoost = std::get(farmingitem_mdata["miningBoost"]); + + for (const uint64_t asset_id : staked.staked_items) + { + mining_power += get_mining_power(asset_id, stats); + } + } + + set_lb_points("miningpwr"_n, account, mining_power); +} +``` + +Descripción de la función: + +1. Tome el puntero a la tabla donde se encuentran todos los ítems apostados. Obtenemos un mapa con las características del jugador. Tome el puntero a la tabla de activos. + +```cpp +staked_t staked_table(get_self(), account.value); +float mining_power = 0.0f; + +const std::map stats = get_stats(account); +auto assets = atomicassets::get_assets(get_self()); +``` + +2. Recorra todos los ítems apostados. Para cada uno, tomamos un puntero a su activo y encontramos el valor de **miningBoost**. Luego, para cada activo dentro del ítem apostado, calculamos la tasa de minería y la sumamos al total + +```cpp +for (const auto &staked : staked_table) +{ + auto farmingitem_itr = assets.find(staked.asset_id); + auto farmingitem_mdata = get_mdata(farmingitem_itr); + + float miningBoost = 1; + if (farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) + miningBoost = std::get(farmingitem_mdata["miningBoost"]); + + for (const uint64_t asset_id : staked.staked_items) + { + mining_power += get_mining_power(asset_id, stats); + } +} +``` + +3. Actualice la tasa de minería para este jugador en la tabla de clasificación + +```cpp +set_lb_points("miningpwr"_n, account, mining_power); +``` + +Considere la función utilizada para calcular la tasa de minería de un ítem + +```cpp +float game::get_mining_power(const uint64_t asset_id, const std::map &stats) +{ + auto assets = atomicassets::get_assets(get_self()); + auto assets_itr = assets.require_find(asset_id, "asset not found"); + + auto item_mdata = get_mdata(assets_itr); + auto item_template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + + const float &miningRate = std::get(item_template_idata["miningRate"]); + + uint8_t current_lvl = 1; + + if (item_mdata.find("level") != std::end(item_mdata)) + { + current_lvl = std::get(item_mdata["level"]); + } + + const uint8_t upgrade_percentage = 2 + stats.at("vitality") / 10.0f; + + float miningRate_according2lvl = miningRate + stats.at("productivity") / 10.0f; + for (uint8_t i = 1; i < current_lvl; ++i) + miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); + + return miningRate_according2lvl; +} +``` + +Repite completamente la lógica de cálculo del activo de la tasa de minería, que se describió en artículos anteriores y se utilizó en **claim** y **upgradeitem**. + +En este artículo, describimos cómo podemos crear varias tablas de clasificación en el juego, que es una de las funciones principales. + +**PS.** El [siguiente enlace](https://github.com/dapplicaio/GameLeaderboards) lleva a un repositorio que corresponde a todo lo descrito. \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part17.md b/docs/es/build/tutorials/howto-create_farming_game/Part17.md new file mode 100644 index 0000000..902aaf0 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part17.md @@ -0,0 +1,162 @@ +--- +title: Parte 17. Sistemas de misiones en el juego +order: 85 +--- + +En este artículo, agregaremos un sistema de misiones que nos permitirá interactuar más con los jugadores en nuestros juegos. + +1. **Agreguemos las tablas necesarias** + +```cpp +struct quest +{ + std::string type; + float required_amount; + float current_amount; +}; + +struct [[eosio::table]] quests_j +{ + name player; + std::vector quests; + + uint64_t primary_key() const {return player.value;}; +}; +typedef multi_index<"quests"_n, quests_j> quests_t; +``` + +La estructura **quest** denota un triple (tipo de misión, cantidad requerida de acciones/recursos/tokens, cantidad actual de acciones/recursos/tokens), donde el tipo de misión indica lo que el jugador necesita hacer. Ejemplos de tipos se encuentran más adelante. + +La tabla de misiones consta de las siguientes columnas (jugador, misiones[]). + +**2\. Agregar y eliminar misiones** + +```cpp +void game::addquest( + const name &player, + const std::string &type, + float required_amount) +{ + quests_t quests_table(get_self(), get_self().value); + auto player_iter = quests_table.find(player.value); + + quest temp_quest = {type, required_amount, 0.0f}; + + if (player_iter == quests_table.end()) + { + quests_table.emplace(get_self(), [&](auto& new_row) + { + new_row.player = player; + new_row.quests = {temp_quest}; + }); + } + else + { + quests_table.modify(player_iter, get_self(), [&](auto& row) + { + row.quests.push_back(temp_quest); + }); + } +} +``` + +Aquí tomamos el puntero a la tabla. Si hay un jugador, agregamos una nueva misión al vector ya creado. Si no hay un jugador, creamos una fila para él o ella y agregamos la misión al vector. + +```cpp +void game::cmpltquest( + const name &player, + uint32_t quest_index) +{ + quests_t quests_table(get_self(), get_self().value); + auto player_iter = quests_table.require_find(player.value, "No such quest"); + + check(player_iter->quests.size() > quest_index, "Index outside of scope"); + + quest temp = player_iter->quests[quest_index]; + check(temp.current_amount >= temp.required_amount, "Quest conditions are not met"); + + quests_table.modify(player_iter, get_self(), [&](auto& row){ + row.quests.erase(row.quests.begin() + quest_index); + }); +} +``` + +Tomamos el iterador a la tabla. Verificamos si hay un campo para este jugador. Luego verificamos si hay una misión para este índice en el vector. Comprobamos si se cumplen los requisitos de la misión. Si es así, eliminamos esta misión del array. + +**3\. Conectar el sistema de misiones con otros sistemas del juego** + +Presentemos varios tipos de misiones como ejemplo. + +**"Staking" -- apostar X herramientas** + +```cpp +update_quests(owner, "staking", items_to_stake.size()); +``` + +Se agregó una línea en la función **stake_items** que actualiza la información sobre la cantidad de herramientas apostadas. + +**"Swap" -- intercambiar X tokens** + +```cpp +update_quests(owner, "swap", tokens2receive.amount); +``` + +Se agregó una línea en la función **swap** que actualiza la información sobre la cantidad de tokens intercambiados por recursos. + +**"Upgrade" -- mejorar un ítem al nivel X** + +```cpp +update_quests(owner, "upgrade", new_level, true); +``` + +Se agregó una línea en la función **upgrade_item**, actualizando la información sobre el nivel al que el jugador ha mejorado una herramienta. + +**"Tokens" -- apostar X tokens** + +```cpp +update_quests(owner, "tokens", quantity.amount); +``` + +Se agregó una línea en la función **increase_tokens_balance**, actualizando la información sobre la cantidad de tokens apostados en el balance del jugador. + +**"Stone/Wood/etc." -- recolectar X recurso** + +```cpp +for (const auto& resource_amount_pair : mined_resources) +{ + update_quests(owner, resource_amount_pair.first, resource_amount_pair.second); +} +``` + +Se agregó una línea en la función **claim**. Recorremos los nombres de los recursos e ingresamos la información sobre cuántos de ellos el jugador ha obtenido. + +```cpp +void game::update_quests(const name &player, const std::string &type, float update_amount, bool set) +{ + quests_t quests_table(get_self(), get_self().value); + auto player_iter = quests_table.require_find(player.value, "No such quest"); + + quests_table.modify(player_iter, get_self(), [&](auto& row){ + for (auto& quest : row.quests) + { + if (quest.type == type) + { + if (!set) + { + quest.current_amount += update_amount; + } + else + { + quest.current_amount = std::max(quest.current_amount, update_amount); + } + } + } + }); +} +``` + +Esta función recorre las misiones del jugador. Si el tipo de misión coincide con el que queremos actualizar, actualizamos el **current_amount** de esta misión. Por ejemplo, un jugador ha obtenido 10 árboles y tiene dos misiones para recolectar árboles con valores actuales de 20, 100. Después de llamar a la función, se convertirán en 30 y 110. + +PS. Este artículo muestra un sistema de misiones simple y robusto, que se puede actualizar a medida que avanzamos, ya que es posible que no sepamos qué tipos de misiones necesitaremos en el futuro. + +**PS. PS.** El [siguiente enlace](https://github.com/dapplicaio/GamingQuests) lleva a un repositorio que corresponde a todo lo descrito. \ No newline at end of file diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part18.md b/docs/es/build/tutorials/howto-create_farming_game/Part18.md new file mode 100644 index 0000000..388acfd --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part18.md @@ -0,0 +1,98 @@ +--- +title: Parte 18. GUI para misiones y tablas de clasificación +dorder: 90 +--- + +En nuestro artículo anterior, analizamos los intercambios, la apuesta de tokens y los mecanismos de gobernanza. + +Basándonos en esa base, en este artículo también exploraremos la funcionalidad de las tablas de clasificación y profundizaremos en las misiones en el contexto del ecosistema del juego. + +**Tabla de Clasificación** +---------------- + +Una tabla de clasificación es esencial para rastrear y mostrar el rendimiento y el progreso de los participantes en un entorno de juego. Sirve como una representación visual de los rankings, motivando a los jugadores a esforzarse por posiciones más altas y fomentando una competencia saludable. + +La tabla de clasificación suele clasificarse en función de los puntos acumulados por cada participante, mostrando su éxito relativo. Dentro de este marco, incluye áreas como madera, piedra, alimentos y gemas recolectadas o ganadas por hora. + +Por ejemplo, un jugador usa la acción **claim** y recibe 100 piedras. Luego, en la tabla **lboards("Stone")** en el campo correspondiente, **points = points + 100**. + +En la interfaz de usuario, la tabla de clasificación se ve así: + +![](/public/assets/images/tutorials/howto-create_farming_game/part18/image1.png) + +Todos nuestros datos se almacenan en una tabla de contratos inteligentes, donde cada alcance tiene una tabla separada. Estos datos se pueden recuperar utilizando la función **leaderboadrTable()**. + +Es decir, los alcances posibles son: **stone, wood, food, gems, tokens, miningpwr** + +```js +export const leaderboadrTable = async () => { + const { rows } = await fetchRows({ + contract: "dappgamemine", + scope: "stone", + table: "lboards" + }); + + return rows; +} +``` + +Aquí hay un enlace a la tabla de clasificación en el contrato inteligente: + + + +**Sistema de Misiones** +----------------- + +Cada misión es una acción específica que el jugador debe realizar y la cantidad de tiempo que tarda en completarse. Por ejemplo, "Recolectar 10 piedras" o "Fabricar 3 herramientas". El estado de la misión se actualiza con la acción correspondiente. Cuando se cumplen las condiciones de la misión, el jugador puede completar la misión (eliminarla de la lista de misiones) y recoger la recompensa (si la hay). + +Las misiones permiten dirigir el juego del jugador al revelar gradualmente las mecánicas del juego. Por ejemplo, al principio del juego, las misiones serán del tipo "construir una granja", "fabricar una herramienta", "obtener 100 unidades de alimentos", etc. + +En la interfaz de usuario, la tabla de misiones se ve así: + +![](/public/assets/images/tutorials/howto-create_farming_game/part18/image2.png) + +Cuando la misión está completada, podemos llamar a la acción **Claim** para recoger la recompensa. Para hacer esto, utilizamos la función **collectQuest()**: + +```js +export const collectQuest = async ({ activeUser, index }) => { + return await signTransaction({ + activeUser, + account: 'dappgamemine', + action: 'compltquest', + data: { + player: activeUser.accountName, + quest_index: index, + } + }); +}; +``` + +Aquí utilizamos una acción llamada **"compltquest"** para reclamar la recompensa. + +- **player** -- este es nuestro apodo del usuario que conectó su billetera, lo tomamos de **activeUser.accountName**; +- **quest_index** -- ID de la misión de la tabla **"quests"**; + +Para recuperar todas las misiones creadas por el contrato, debes llamar a la función **fetchQuests()**: + +```js +export const fetchQuests = async () => { + const { rows } = await fetchRows({ + contract: 'dappgamemine', + scope: 'dappgamemine', + table: "quests" + }); + return rows; +} +``` + +Aquí hay un enlace para reclamar la misión completada: + + + +y la tabla de misiones: + + + +En este artículo hicimos una visión general de los elementos finales de nuestra serie de desarrollo de juegos en WAX. Así, cubrimos las tablas de clasificación y las misiones como características principales de la mayoría de los juegos modernos de web3. + +**PS. PS.** El [siguiente enlace](https://github.com/dapplicaio/GUIQuestsLeaderboards) lleva a un repositorio que corresponde a todo lo descrito. diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part2.md b/docs/es/build/tutorials/howto-create_farming_game/Part2.md new file mode 100644 index 0000000..e8dedaf --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part2.md @@ -0,0 +1,128 @@ +--- +title: Parte 2. Creación de un objeto o ítem cultivable en el estándar AtomicAssets. +order: 10 +--- + +Este es el siguiente artículo en la serie de creación de juegos. Puedes seguir primero el [artículo introductorio](/build/tutorials/howto-create_farming_game/Part1) si aún no lo has leído. + +El gaming Play-to-Earn (P2E) está revolucionando el mundo web3 y blockchain, redefiniendo cómo los jugadores interactúan con los juegos. ¿A la vanguardia? La blockchain de WAX. No se trata solo de activos virtuales; es un centro para la agricultura rentable y el comercio de NFTs únicos. WAX es donde el juego se encuentra con las ganancias, combinando la diversión con el potencial financiero. + +### Paso 1: Creación de tu Colección de NFTs en WAX + +¿Comenzando tu colección de NFTs? Empieza con fuerza eligiendo un tema impresionante. Ópera espacial, épica histórica o un mundo de fantasía: el cielo es el límite. Tu elección debe resonar con tu audiencia. Se trata de crear un universo NFT que cautive y conecte. + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/collection_creation-980x517.png) +*Pantalla de creación de colección en Atomic Hub* + +1. **Nombre de la Colección** + - Es hora de darle una marca a tu colección de NFTs. Elige un nombre único que capture el espíritu de tu proyecto. Mantenlo en un máximo de 12 caracteres, mezclando números (1-5) y letras. No se permiten espacios. Ejemplo: `juegogalactico`. + +2. **Nombre para Mostrar** + - Esto es lo que los jugadores verán. Elige algo atractivo que represente la vibra de tu juego. Ejemplo: `Juego Galáctico`. + +3. **URL del Sitio Web** + - Agrega el enlace de tu sitio web donde los jugadores e inversionistas puedan profundizar en tu mundo. Por ejemplo: [https://wax.io](https://wax.io). + +4. **Comisión del Mercado** + - Decide la comisión de intercambio para la plataforma de tu juego. Digamos, un 2% para el intercambio de tus NFTs. + +5. **Descripción de la Colección** + - Sé creativo con la descripción de tu colección. En 3000 caracteres o menos, haz que resalten los temas e ideas principales de tu proyecto. + +6. **Imágenes de la Colección** + - **Imagen de Fondo**: Establece el escenario con una imagen de fondo que refleje el ambiente y tema de tu juego. + - **Imagen del Logo**: Elige un logo icónico para representar tu colección, haciendo que sea reconocible al instante. + +7. **Redes Sociales** + - Conéctate con tu comunidad. Agrega todos tus enlaces sociales: Twitter, Medium, Facebook, GitHub, Discord, YouTube, Telegram. + +Estos pasos son tu plan para crear una colección de NFTs destacada en WAX. Recuerda, cada detalle, desde el nombre hasta las redes sociales, da forma al atractivo de tu juego. + +### Paso 2: Creación de Ítems NFT en la Testnet de WAX + +#### 2.1 Creación de una Categoría (Esquema) para NFT + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/creatrschema1-980x500.png) +![](/public/assets/images/tutorials/howto-create_farming_game/part2/createschema2-980x504.png) +*Pantalla de creación de esquemas en Atomic Hub* + +Tendremos los siguientes campos: + +- **name**: + - Tipo de atributo: *string* + - Descripción: Representa el nombre del jugador u objeto que se está determinando. + +- **img**: + - Tipo de atributo: *imagen* + - Descripción: Corresponde a una representación visual o imagen asociada con un jugador u objeto. + +- **video**: + - Tipo de atributo: *string* + - Descripción: Indica cualquier contenido de video asociado con un jugador u objeto que proporcione elementos multimedia adicionales. + +- **slots**: + - Tipo de atributo: *uint8* + - Descripción: Muestra el número actual de ranuras disponibles para recursos, ítems u otros elementos en el juego. + +- **maxSlots**: + - Tipo de atributo: *uint8* + - Descripción: Indica el número máximo de ranuras que se pueden obtener o mejorar en el juego. + +- **level**: + - Tipo de atributo: *uint8* + - Descripción: Indica el nivel o rango de un jugador u objeto, señalando el progreso en el juego. + +- **upgradable**: + - Tipo de atributo: *bool* + - Descripción: Un valor booleano que indica si un jugador u objeto puede ser mejorado o subir de nivel. + +- **rarity**: + - Tipo de atributo: *string* + - Descripción: Describe el nivel de rareza de un jugador o ítem, señalando su singularidad o rareza. + +- **faction**: + - Tipo de atributo: *string* + - Descripción: Representa la facción o afiliación grupal de un jugador u objeto en el juego. + +- **miningBoost**: + - Tipo de atributo: *float* + - Descripción: Indica cualquier aumento o bonificación asociada con el botín relacionado con un jugador u objeto. + +- **staking**: + - Tipo de atributo: *int32[]* + - Descripción: Llama al motor de staking, proporcionando información sobre las capacidades de staking de un jugador u objeto. + +- **stakeableResources**: + - Tipo de atributo: *string[]* + - Descripción: Enumera los tipos de recursos que un jugador u objeto puede colocar en el juego. + +#### 2.2 Creación de Plantillas para NFT + +Primero, necesitamos elegir una categoría (esquema): + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/selectschema-980x222.png) +*Seleccionar esquema para la Plantilla* + +Luego necesitamos ingresar todos los datos para nuestra Plantilla: + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/CreateTemplate1-980x494.png) +*Creación de Plantilla NFT mediante Atomic Hub* + +Después de una creación exitosa, podemos ver el resultado, como se muestra a continuación: + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/templateCreated-1024x258.png) +*Plantillas creadas para la colección en Atomic Hub* + +#### 2.3 Minteo de NFT + +Necesitamos elegir una categoría (esquema) y una plantilla: + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/createtemplate-result-980x476.png) +*Minteo de nuevo NFT en Atomic Hub* + +Luego ingresamos a quién mintear el NFT en el campo de Receptor de Activos y completamos los campos del NFT, si es necesario: + +![](/public/assets/images/tutorials/howto-create_farming_game/part2/createtemplate3-1-980x481.png) + +Este es un proceso breve de creación de uno de nuestros principales tipos de activos en esta serie de artículos. Ahora, procedamos con otro tipo de activo. + diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part3.md b/docs/es/build/tutorials/howto-create_farming_game/Part3.md new file mode 100644 index 0000000..30a5ebb --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part3.md @@ -0,0 +1,105 @@ +--- +title: Parte 3. Creación de NFT para agricultura en Atomic Hub +order: 15 +--- + +En nuestro último artículo, te guiamos en la creación de una colección y un ítem. Ahora, vamos a subir de nivel y centrarnos en la elaboración de un ítem para agricultura. + +### 3.1 Creación de una Categoría (Esquema) para NFTs + +Todo se trata de establecer la base para tus NFTs. Nos sumergiremos en la configuración de una categoría o esquema que definirá cómo funcionarán tus ítems de agricultura y cómo destacarán. + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/creatingSchema-1024x467.png) +![](/public/assets/images/tutorials/howto-create_farming_game/part3/creatingSchemaEnd-1024x427.png) +*Creando Esquema* + +- **name**: + - Tipo de Atributo: *string* + - Descripción: Representa el nombre del jugador, personaje u objeto dentro del juego. + +- **img (imagen)**: + - Tipo de Atributo: *imagen* + - Descripción: Corresponde a la representación visual o imagen asociada con el jugador, personaje u objeto. + +- **video**: + - Tipo de Atributo: *string* + - Descripción: Se refiere a cualquier contenido de video vinculado al jugador, personaje u objeto, proporcionando elementos multimedia adicionales. + +- **farmResource**: + - Tipo de Atributo: *string* + - Descripción: Especifica el tipo de recurso que puede ser cultivado o cosechado por el jugador u objeto. + +- **amount**: + - Tipo de Atributo: *float* + - Descripción: Representa la cantidad o monto del recurso cultivado. + +- **level**: + - Tipo de Atributo: *uint8* + - Descripción: Denota el nivel o rango del jugador, personaje u objeto, mostrando el progreso dentro del aspecto de agricultura del juego. + +- **upgradable**: + - Tipo de Atributo: *bool* + - Descripción: Un valor booleano que indica si el recurso de agricultura, jugador u objeto puede ser mejorado. + +- **rewardInterval**: + - Tipo de Atributo: *uint32* + - Descripción: Indica el intervalo de tiempo (en segundos) entre cada ciclo de recompensa o cosecha. + +- **rarity**: + - Tipo de Atributo: *uint32* + - Descripción: Especifica el nivel de rareza del jugador, personaje u objeto en el contexto de la agricultura, distinguiéndolo en términos de singularidad o escasez. + +- **faction**: + - Tipo de Atributo: *string* + - Descripción: Representa la facción o afiliación grupal del jugador u objeto dentro del aspecto de agricultura del juego. + +- **lastClaim**: + - Tipo de Atributo: *uint32* + - Descripción: Indica la marca de tiempo o información respecto a la última vez que se reclamó o cosechó el recurso. + +- **maxLevel**: + - Tipo de Atributo: *uint32* + - Descripción: Especifica el nivel máximo que el recurso de agricultura, jugador u objeto puede alcanzar. + +- **initialAmount**: + - Tipo de Atributo: *uint8* + - Descripción: Representa la cantidad inicial del recurso cultivado al comenzar el juego o al adquirir el objeto. + +- **miningRate**: + - Tipo de Atributo: *float* + - Descripción: Indica la tasa a la cual se genera o cosecha el recurso de agricultura a lo largo del tiempo. + +### 3.2 Creación de Plantillas de NFT + +Primero lo primero: selecciona la categoría correcta (o esquema) para tu plantilla NFT. Es como elegir el molde perfecto para tu ítem de agricultura, fundamental para definir su identidad y función. + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/create-new-template-1024x524.png) +*Creación de Nueva Plantilla mediante Atomic Hub* + +Luego, es momento de darle vida a tu NFT. Sube la imagen que representará a tu ítem de agricultura y completa los campos necesarios para darle carácter y contexto. + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/temp1-1024x536.png) +*Creación de Plantilla NFT* + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/temp2-1024x499.png) +*Así es como se ve la plantilla creada:* + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/tempres-1024x262.png) +*Creación de Plantilla NFT completada* + +### 3.3 Mintear NFT + +Primero, necesitamos elegir una categoría (esquema) y una plantilla: + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/mintNFT-1024x502.png) +*Minteo de NFT* + +Luego, decide si necesitas actualizar los datos del NFT y considera agregar detalles adicionales si es necesario. Ten en cuenta que los datos de tu NFT pueden ser estáticos o dinámicos, cambiando a medida que el juego progresa o basado en otras lógicas. ¡Es como darle a tu NFT una personalidad que evoluciona! + +![](/public/assets/images/tutorials/howto-create_farming_game/part3/mintNFTend-1024x542.png) +*Minteo de NFT y envío al receptor* + +Después de la creación, nuestro NFT se mostrará en el inventario del usuario y podrá utilizarse en el futuro en nuestro juego. + +En el próximo artículo, explicaremos los tokens y recursos que se necesitan para nuestro juego. + diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part4.md b/docs/es/build/tutorials/howto-create_farming_game/Part4.md new file mode 100644 index 0000000..f181f60 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part4.md @@ -0,0 +1,91 @@ +--- +title: Parte 4. Qué son los recursos y tokens en nuestro proceso de creación de juegos. +order: 20 +--- + +En este artículo, nos sumergimos en dos elementos fundamentales: recursos y tokens. + +### Recursos + +Primero, los recursos. Piensa en ellos como los componentes básicos de la economía de tu juego. Son los materiales e ítems que los jugadores extraen, producen o adquieren durante el juego. Cada recurso tiene su propia identidad: rareza, habilidades de fabricación y formas de interactuar dentro del juego. + +Los recursos no son solo objetos coleccionables; son intercambiables, utilizables y esenciales para el desarrollo de personajes y la fabricación de ítems. En nuestro juego, son la clave para intercambiar tokens y mejorar objetos. Además, están organizados en una tabla de balance de recursos específica para cada usuario. + +![](/public/assets/images/tutorials/howto-create_farming_game/part4/resourcesandtokens-1024x140.png) + +- **key_id**: Claves para buscar en la tabla, creadas convirtiendo el nombre del recurso en un número. +- **amount**: Cantidad del recurso adquirido. +- **resource_name**: Nombre del recurso. + +A continuación, veamos un ejemplo práctico: código para agregar un recurso específico a la tabla de un jugador. Este fragmento de código muestra cómo actualizar el balance de recursos de un jugador en el juego. + +```C +resources_t resources_table(get_self(), owner.value); +``` + +Ahora, abordemos la inicialización de la tabla de recursos para un jugador. Piense en ello como configurar una cuenta bancaria de recursos personal. Usamos `get_self()` para especificar nuestro contrato como la ubicación de la tabla, asegurando que los recursos estén vinculados al propietario correcto. + +```C +const float amount = 25.3; +const std::string resource_name = "wood"; +const uint64_t key_id = stringToUint64(resource_name); +``` + +Vamos a crear nuestras variables para inicializar el recurso: + +- **amount**: Cantidad de recurso extraído. +- **resource_name**: Nombre del recurso. +- **key_id**: Generado a partir de una cadena, es decir, convierte la cadena en un número para escribir en la tabla. + +```C +auto resources_table_itr = resources_table.find(key_id); +if (resources_table_itr == std::end(resources_table)) +{ + resources_table.emplace(get_self(), [&](auto &new_row) { + new_row.key_id = key_id; + new_row.resource_name = resource_name; + new_row.amount = amount; + }); +} +else +{ + resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) { + new_row.amount += amount; + }); +} +``` + +A continuación, necesitamos verificar si dicha clave ya existe en la tabla. Si existe, simplemente modificamos la tabla. + +```C +resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) { + new_row.amount += amount; +}); +``` + +En este segmento, profundizamos en el manejo del iterador de la tabla de recursos (`resources_table_itr`). Este iterador es crucial para actualizar las cantidades de recursos en nuestro contrato, representado por `get_self()`. Usamos una función lambda para editar el campo `amount`, sumando el nuevo valor al existente. + +Además, si no existe un registro para un recurso específico, es importante agregar una nueva entrada para realizar un seguimiento preciso de los recursos de cada jugador. + +```C +resources_table.emplace(get_self(), [&](auto &new_row) { + new_row.key_id = key_id; + new_row.resource_name = resource_name; + new_row.amount = amount; +}); +``` + +En esta parte, nos centramos en los parámetros para la gestión de recursos. El primer parámetro determina quién paga la transacción, lo cual es crucial para mantener la economía del juego. El segundo parámetro es una función lambda utilizada para agregar nuevos registros a la tabla de recursos. + +Es importante tener en cuenta que los recursos en este diseño del juego no son transferibles entre usuarios. En cambio, son convertibles en tokens o utilizables dentro del juego, proporcionando un enfoque único para la gestión de recursos y la interacción del jugador. + +### Ahora, hablemos de los tokens + +Los tokens, en el mundo blockchain, son activos digitales que representan valor o bienes. Son versátiles y se utilizan para activos digitales, contratos inteligentes y más. En el ámbito de los juegos, son la moneda virtual para comprar, vender o poseer ítems únicos dentro del juego. Los tokens empoderan a los jugadores con una economía descentralizada y propiedad, mejorando la experiencia de play-to-earn. + +En nuestro juego, usamos un contrato separado para los tokens basado en el estándar `eosio.token` común en WAX. Esta configuración garantiza una integración fluida de los tokens en el entorno del juego. + +Puedes seguir este enlace para consultar la implementación estándar del token [aqui](https://github.com/EOSIO/eosio.contracts/tree/master/contracts/eosio.token). + +En el próximo artículo, cubriremos el proceso de staking de NFTs como preparación para la agricultura de recursos o tokens. + diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part5.md b/docs/es/build/tutorials/howto-create_farming_game/Part5.md new file mode 100644 index 0000000..0391316 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part5.md @@ -0,0 +1,462 @@ +--- +title: Parte 5. Staking de NFTs +order: 25 +--- + +Hoy vamos a profundizar en el staking de NFTs dentro del contrato inteligente de nuestro juego. Esta estrategia ayuda a gestionar los precios del mercado al bloquear los NFTs en el contrato. Imagina establecer un período de desbloqueo de entre 3 y 30 días para tener mayor control. + +El staking de NFTs simplifica el seguimiento de la propiedad, lo cual es crucial para recompensar a los propietarios periódicamente, incluso por cada bloque de la cadena. Este método evita la necesidad de sistemas centralizados para rastrear la propiedad, un desafío común con alternativas que dependen del historial de transacciones o APIs externas. + +El staking de NFTs en nuestro juego es un proceso sencillo: + +1. El jugador elige un NFT para hacer staking. +2. Envía este NFT a nuestro contrato. +3. Nuestro contrato reconoce y procesa la transferencia. +4. Finalmente, el contrato registra el NFT en staking del jugador en una tabla, listo para futuras interacciones. + +Este proceso asegura una experiencia de staking eficiente y fluida, integral para la dinámica del juego. + +### Código fuente de Staking y Ejemplo + +Archivo principal: `game.hpp` + +```cpp +#include +#include +#include +#include "atomicassets.hpp" + +using namespace eosio; +``` + +Al comienzo del archivo, conectamos todas las bibliotecas y espacios de nombres necesarios. + +```cpp +class [[eosio::contract]] game : public contract +{ + public: + using contract::contract; + private: +}; +``` + +Así es como se ve una clase vacía llamada `game`. En ella implementaremos todas las funciones necesarias para el staking. + +El primer paso es añadir una función para escuchar la transferencia. Hazla pública: + +```cpp +// escuchando transferencias de atomicassets +[[eosio::on_notify("atomicassets::transfer")]] +void receive_asset_transfer +( + const name& from, + const name& to, + std::vector& asset_ids, + const std::string& memo +); +``` + +Respecto a `eosio::on_notify`, puedes encontrar más información [aquí](https://developers.eos.io/welcome/v2.0/smart-contract-guides/payable-actions/#the-on_notify-attribute). + +En esta función, la configuramos para escuchar el contrato de Atomic Assets y su función de transferencia. Aquí hay un resumen: + +- `from`: Representa al jugador que envía el NFT. +- `to`: Debe estar configurado como nuestro contrato. +- `asset_ids`: Son los NFTs de juego involucrados en la transacción. +- `memo`: Un mensaje incluido con la transferencia. Los memos futuros se especificarán para guiar a nuestro contrato sobre cómo procesar los datos. + +Esta configuración es crucial para manejar correctamente las transferencias de NFTs en el entorno de nuestro juego. + +```cpp +// scope: propietario +struct [[eosio::table]] staked_j +{ + uint64_t asset_id; // ítem + std::vector staked_items; // elementos para farming + + uint64_t primary_key() const { return asset_id; } +}; +typedef multi_index<"staked"_n, staked_j> staked_t; +``` + +En esta parte, hemos configurado una tabla para hacer un seguimiento de los NFTs en staking: + +- **Scope**: Definido por el apodo del jugador. +- **asset_id**: Identifica el NFT específico (ítem). +- **staked_items**: Un array que contiene los NFTs en staking (elementos de farming). +- **primary_key**: Una función necesaria en todas las tablas, que determina la clave de búsqueda de los registros. + +Además, hemos creado funciones auxiliares para mejorar la legibilidad del código dentro del contrato: + +```cpp +void stake_farmingitem(const name& owner, const uint64_t& asset_id); +void stake_items(const name& owner, const uint64_t& farmingitem, const std::vector& items_to_stake); + +// obtener datos mutables del NFT +atomicassets::ATTRIBUTE_MAP get_mdata(atomicassets::assets_t::const_iterator& assets_itr); +// obtener datos inmutables de la plantilla del NFT +atomicassets::ATTRIBUTE_MAP get_template_idata(const int32_t& template_id, const name& collection_name); +// actualizar datos mutables del NFT +void update_mdata(atomicassets::assets_t::const_iterator& assets_itr, const atomicassets::ATTRIBUTE_MAP& new_mdata, const name& owner); + } + ] +} +``` +Ahora, nos sumergimos más profundamente en el archivo `game.cpp` para detallar la implementación de la función que monitorea las transferencias atómicas. Aquí es donde ocurre la magia al manejar transacciones de NFTs dentro del marco de nuestro juego. + +```cpp +void game::receive_asset_transfer +( + const name& from, + const name& to, + std::vector& asset_ids, + const std::string& memo +) +{ + if (to != get_self()) + return; + + if (memo == "stake farming item") + { + check(asset_ids.size() == 1, "Debes transferir solo un ítem de farming para hacer staking"); + stake_farmingitem(from, asset_ids[0]); + } + else if (memo.find("stake items:") != std::string::npos) + { + const uint64_t farmingitem_id = std::stoll(memo.substr(12)); + stake_items(from, farmingitem_id, asset_ids); + } + else + check(0, "Memo inválido"); +} +``` + +Primero, verificamos si el NFT fue enviado a nuestro contrato usando `get_self()`. Dependiendo del memo, distinguimos entre hacer staking de un ítem de farming y otros ítems. + +- **Ítem de Farming**: Confirmamos que solo se envíe un NFT, siguiendo la regla del juego de hacer staking de un ítem a la vez. Luego, invocamos `stake_farmingitem`. +- **Otros ítems**: Para hacer staking de otros ítems, el memo debe incluir el ID del ítem de farming donde los NFTs serán puestos en staking, con el formato "stake items:id", con el ID real del ítem de farming. + +```cpp +std::stoll(memo.substr(12)); +``` + +Aquí, analizamos el ID desde el string (`memo`) y luego llamamos a la función interna para el staking de ítems. + +```cpp +else + check(0, "Memo inválido"); +``` + +Si la transferencia al contrato no coincide con los memos especificados para staking, el contrato marcará un error. Esto asegura que solo se procesen transacciones válidas. A continuación, exploraremos funciones adicionales utilizadas en este proceso, detallando cómo opera el contrato. + +### Función: `stake_farmingitem` + +```cpp +void game::stake_farmingitem(const name& owner, const uint64_t& asset_id) +{ + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.find(asset_id); + + auto farmingitem_mdata = get_mdata(asset_itr); + if (farmingitem_mdata.find("slots") == std::end(farmingitem_mdata)) + { + auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + check(farmingitem_template_idata.find("maxSlots") != std::end(farmingitem_template_idata), + "Las ranuras del ítem de farming no fueron inicializadas. Contacta al equipo de desarrollo"); + check(farmingitem_template_idata.find("stakeableResources") != std::end(farmingitem_template_idata), + "Los elementos stakeables en el ítem de farming actual no fueron inicializados. Contacta al equipo de desarrollo"); + + farmingitem_mdata["slots"] = (uint8_t)1; + farmingitem_mdata["level"] = (uint8_t)1; + + update_mdata(asset_itr, farmingitem_mdata, get_self()); + } + + staked_t staked_table(get_self(), owner.value); + staked_table.emplace(get_self(), [&](auto &new_row) + { + new_row.asset_id = asset_id; + }); +} +``` + +A continuación se explica esta función: + +```cpp +auto assets = atomicassets::get_assets(get_self()); +auto asset_itr = assets.find(asset_id); +``` + +Esta parte cubre cómo recuperamos un registro del saldo de nuestro contrato desde la tabla `atomicassets` y localizamos el NFT específico que el usuario desea poner en staking. Usaremos funciones del espacio de nombres `atomicassets`. Estas se detallan en los archivos de encabezado incluidos con el artículo, proporcionando un tutorial sencillo sobre cómo trabajar con el estándar de activos atómicos. + +```cpp +auto farmingitem_mdata = get_mdata(asset_itr); +``` + +Aquí, extraemos los metadatos del NFT para trabajar posteriormente con los datos localizados en el NFT. + +```cpp +atomicassets::ATTRIBUTE_MAP game::get_mdata(atomicassets::assets_t::const_iterator& assets_itr) +{ + auto schemas = atomicassets::get_schemas(assets_itr->collection_name); + auto schema_itr = schemas.find(assets_itr->schema_name.value); + + atomicassets::ATTRIBUTE_MAP deserialized_mdata = atomicdata::deserialize + ( + assets_itr->mutable_serialized_data, + schema_itr->format + ); + + return deserialized_mdata; +} +``` + +Esta es nuestra función de extracción de datos, donde se recupera el esquema (categoría): + +```cpp +auto schemas = atomicassets::get_schemas(assets_itr->collection_name); +auto schema_itr = schemas.find(assets_itr->schema_name.value); +``` + +El proceso implica pasar los datos a la función de deserialización de datos atómicos en `atomicdata`. Incluiremos estos archivos con el código para una referencia fácil. En cuanto al staking, cuando recibimos los metadatos del NFT, seguimos pasos específicos para asegurar un procesamiento y registro precisos dentro del contrato. + +```cpp +if(farmingitem_mdata.find("slots") == std::end(farmingitem_mdata)) +{ + auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + check(farmingitem_template_idata.find("maxSlots") != std::end(farmingitem_template_idata), + "Las ranuras del ítem de farming no fueron inicializadas. Contacta al equipo de desarrollo"); + check(farmingitem_template_idata.find("stakeableResources") != std::end(farmingitem_template_idata), + "Los elementos stakeables en el ítem de farming actual no fueron inicializados. Contacta al equipo de desarrollo"); + + farmingitem_mdata["slots"] = (uint8_t)1; + farmingitem_mdata["level"] = (uint8_t)1; + + update_mdata(asset_itr, farmingitem_mdata, get_self()); +} +``` + +Cuando se pone en staking un NFT por primera vez, verificamos si existe el campo 'slots'. Si no está presente, seguimos los requisitos del juego para inicializar los campos, configurando las ranuras y el nivel del ítem de farming. Esta inicialización es crucial solo para el staking inicial de un NFT. + +```cpp +staked_t staked_table(get_self(), owner.value); +staked_table.emplace(get_self(), [&](auto &new_row) +{ + new_row.asset_id = asset_id; +}); +``` + +A continuación, registramos el NFT en staking en nuestra tabla, usando `owner.value` como el ámbito. Esto asegura que la entrada sea específica para el usuario. Luego, la función `emplace` toma el control, donde el primer parámetro es la cuenta autorizada para pagar la RAM, y el segundo es una función lambda para agregar un nuevo registro a la tabla. + +Esto prepara el escenario para detallar la función de staking de ítems. + +```cpp +void game::stake_items(const name& owner, const uint64_t& farmingitem, const std::vector& items_to_stake) +{ + auto assets = atomicassets::get_assets(get_self()); + + staked_t staked_table(get_self(), owner.value); + auto staked_table_itr = staked_table.require_find(farmingitem, "No se pudo encontrar el ítem de farming en staking"); + auto asset_itr = assets.find(farmingitem); + + auto farmingitem_mdata = get_mdata(asset_itr); + auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + + check(std::get(farmingitem_mdata["slots"]) >= staked_table_itr->staked_items.size() + items_to_stake.size(), + "No tienes suficientes ranuras vacías en el ítem de farming actual para hacer staking de esta cantidad de ítems"); + + atomicdata::string_VEC stakeableResources = std::get(farmingitem_template_idata["stakeableResources"]); + for (const uint64_t& item_to_stake : items_to_stake) + { + asset_itr = assets.find(item_to_stake); + auto item_mdata = get_mdata(asset_itr); + + item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); + auto template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + if (item_mdata.find("level") == std::end(item_mdata)) + { + check(template_idata.find("farmResource") != std::end(template_idata), + "El recurso de cultivo en el ítem [" + std::to_string(item_to_stake) + "] no fue inicializado. Contacta al equipo de desarrollo"); + check(template_idata.find("miningRate") != std::end(template_idata), + "La tasa de minado en el ítem [" + std::to_string(item_to_stake) + "] no fue inicializada. Contacta al equipo de desarrollo"); + check(template_idata.find("maxLevel") != std::end(template_idata), + "El nivel máximo en el ítem [" + std::to_string(item_to_stake) + "] no fue inicializado. Contacta al equipo de desarrollo"); + + item_mdata["level"] = (uint8_t)1; + } + + check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), + "El ítem [" + std::to_string(item_to_stake) + "] no puede ser puesto en staking en el ítem de farming actual"); + update_mdata(asset_itr, item_mdata, get_self()); + } + + staked_table.modify(staked_table_itr, get_self(), [&](auto &new_row) + { + new_row.staked_items.insert(std::end(new_row.staked_items), std::begin(items_to_stake), std::end(items_to_stake)); + }); +} +``` +En esta función, detallamos el staking de múltiples ítems, incluyendo verificaciones para comprobar los espacios disponibles y asegurando que cada ítem cumpla con los criterios necesarios antes de ser puesto en staking. + +### Desglose Paso a Paso + +```cpp +auto assets = atomicassets::get_assets(get_self()); +``` + +Aquí, estamos recuperando los NFTs del contrato. Esta línea obtiene la colección de activos propiedad del contrato. + +```cpp +staked_t staked_table(get_self(), owner.value); +auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find farming staked item"); +``` + +Este paso implica extraer la tabla del jugador y buscar el ID del ítem de farming especificado en el memo. Si el ID especificado no se encuentra, el sistema desencadena un mensaje de error. + +```cpp +auto asset_itr = assets.find(farmingitem); +``` + +A continuación, localizamos el NFT en la tabla atómica para extraer sus datos. + +```cpp +auto farmingitem_mdata = get_mdata(asset_itr); +auto farmingitem_template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); +``` + +En este paso, extraemos los metadatos del NFT y los datos del template inmutable. La función `get_template_idata` se usa para este propósito, funcionando de manera similar a `get_mdata`. Esta extracción es vital para comprender y utilizar correctamente las características del NFT dentro del juego. + +```cpp +atomicassets::ATTRIBUTE_MAP game::get_template_idata(const int32_t& template_id, const name& collection_name) +{ + auto templates = atomicassets::get_templates(collection_name); + auto template_itr = templates.find(template_id); + + auto schemas = atomicassets::get_schemas(collection_name); + auto schema_itr = schemas.find(template_itr->schema_name.value); + + return atomicdata::deserialize + ( + template_itr->immutable_serialized_data, + schema_itr->format + ); +} +``` + +En esta parte, estamos extrayendo información sobre el template del NFT. De estos datos del template, luego extraemos los detalles específicos que necesitamos. + +```cpp +check(std::get(farmingitem_mdata["slots"]) >= staked_table_itr->staked_items.size() + items_to_stake.size(), + "You don't have empty slots on the current farming item to stake this amount of items"); +``` + +El siguiente paso implica verificar si hay suficiente espacio en el ítem de farming para almacenar nuevos ítems. Esta verificación es esencial para asegurar que la capacidad del ítem se alinee con las reglas y mecánicas del juego. + +```cpp +atomicdata::string_VEC stakeableResources = std::get(farmingitem_template_idata["stakeableResources"]); +``` + +En esta fase, utilizamos un vector o arreglo de tipos. Aquí es donde registraremos todos los recursos que los ítems elegidos por el jugador están destinados a farmear. + +--- + +```cpp +for (const uint64_t& item_to_stake : items_to_stake) +{ + asset_itr = assets.find(item_to_stake); + auto item_mdata = get_mdata(asset_itr); + + item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); + auto template_idata = get_template_idata(asset_itr->template_id, asset_itr->collection_name); + if (item_mdata.find("level") == std::end(item_mdata)) + { + check(template_idata.find("farmResource") != std::end(template_idata), + "farmResource at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + check(template_idata.find("miningRate") != std::end(template_idata), + "miningRate at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + check(template_idata.find("maxLevel") != std::end(template_idata), + "maxLevel at item [" + std::to_string(item_to_stake) + "] was not initialized. Contact the dev team"); + + item_mdata["level"] = (uint8_t)1; + } + + check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), + "Item [" + std::to_string(item_to_stake) + "] cannot be staked at the current farming item"); + update_mdata(asset_itr, item_mdata, get_self()); +} +``` + +A continuación, iteramos a través de los ítems que el jugador quiere poner en staking, extrayendo los datos del NFT para cada uno, similar a los pasos anteriores. + +```cpp +item_mdata["lastClaim"] = current_time_point().sec_since_epoch(); +``` + +Luego registramos el campo 'lastClaim' para cada ítem, que es crucial para futuros cálculos de farmear recursos. Este timestamp se establece por defecto en el momento en que el ítem es procesado. + +### Verificando y Actualizando Ítems en Staking + +```cpp +if(item_mdata.find("level") == std::end(item_mdata)) +{ + check(template_idata.find("farmResource") != std::end(template_idata), + "El campo farmResource del ítem [" + std::to_string(item_to_stake) + "] no fue inicializado. Contacta al equipo de desarrollo"); + check(template_idata.find("miningRate") != std::end(template_idata), + "El campo miningRate del ítem [" + std::to_string(item_to_stake) + "] no fue inicializado. Contacta al equipo de desarrollo"); + check(template_idata.find("maxLevel") != std::end(template_idata), + "El campo maxLevel del ítem [" + std::to_string(item_to_stake) + "] no fue inicializado. Contacta al equipo de desarrollo"); + + item_mdata["level"] = (uint8_t)1; +} +``` + +En este punto, verificamos si el ítem está siendo puesto en staking por primera vez. Si es la primera instancia de staking y falta el campo 'level', esto indica que debemos agregar este campo al NFT. Además, comprobamos otros campos obligatorios en la plantilla para asegurarnos de que estén correctamente inicializados. + +```cpp +check(std::find(std::begin(stakeableResources), std::end(stakeableResources), std::get(template_idata["farmResource"])) != std::end(stakeableResources), + "El ítem [" + std::to_string(item_to_stake) + "] no puede ser puesto en staking en el ítem de farming actual"); +``` + +En este paso, evaluamos si el ítem de farming puede acomodar el staking de un ítem que mina un recurso específico. Esto implica comprobar el arreglo de recursos que el ítem de farming puede minar y asegurar que los ítems que el jugador quiere poner en staking estén alineados con las capacidades del ítem de farming correspondiente. + +```cpp +update_mdata(asset_itr, item_mdata, get_self()); +``` + +Una vez que confirmamos que todo está en orden, procedemos a actualizar los metadatos del NFT como se describió en los pasos anteriores. Esto asegura que el NFT sea modificado correctamente para reflejar su nuevo estado y capacidades dentro del ecosistema del juego. + +### Actualizando la Tabla de Staking + +```cpp +void game::update_mdata(atomicassets::assets_t::const_iterator& assets_itr, const atomicassets::ATTRIBUTE_MAP& new_mdata, const name& owner) +{ + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "setassetdata"_n, + std::make_tuple + ( + get_self(), + owner, + assets_itr->asset_id, + new_mdata + ) + ).send(); +} +``` + +Luego, llamamos a la función atómica, ingresando todos los datos relevantes. Después de actualizar los metadatos del NFT, también hacemos los cambios correspondientes en la tabla de staking. + +```cpp +staked_table.modify(staked_table_itr, get_self(), [&](auto &new_row) +{ + new_row.staked_items.insert(std::end(new_row.staked_items), std::begin(items_to_stake), std::end(items_to_stake)); +}); +``` + +Aquí usamos `modify`, ya que tal entrada ya existe en la tabla y solo necesitamos actualizarla. El primer parámetro es un iterador que apunta a la entrada a modificar, el segundo es quién paga por la RAM, y el tercero es una función lambda para editar la entrada en la tabla. + +### Notas Adicionales + +PD. El [enlace siguiente](https://github.com/dapplicaio/StakingNFTS) lleva a un repositorio que corresponde a todo lo descrito aquí, por lo que simplemente puedes construir ese código y usarlo según sea necesario. Los futuros artículos también incluirán ejemplos de código anteriores, permitiendo que nuestro marco evolucione con el tiempo mientras incorpora todos los artículos previos. diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part6.md b/docs/es/build/tutorials/howto-create_farming_game/Part6.md new file mode 100644 index 0000000..fc87670 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part6.md @@ -0,0 +1,223 @@ +--- +title: Parte 6. Tipos de Cultivo y Proceso de Cultivo +order: 30 +--- + +En este artículo, desglosaremos el proceso de cultivo de recursos. Basándonos en el código de staking de nuestro artículo anterior, introduciremos tablas y funciones adicionales específicas para el cultivo de recursos. + +El primer paso es agregar una tabla a nuestro código para el almacenamiento de recursos. Esta tabla es clave para gestionar y rastrear los recursos que los jugadores recolectan y utilizan dentro del juego. + +```C +//scope: owner + struct [[eosio::table]] resources_j + { + uint64_t key_id; + float amount; + std::string resource_name; + + uint64_t primary_key() const { return key_id; } + }; + typedef multi_index< "resources"_n, resources_j > resources_t; +``` + +En la tabla de cultivo de recursos, tenemos los siguientes campos: + +- **key_id**: Es el identificador único para cada recurso, representado como un número para facilitar las búsquedas en la tabla. +- **amount**: La cantidad del recurso específico. +- **resource_name**: El nombre del recurso, como "piedra", "madera", etc. + +Además, utilizaremos una función auxiliar para convertir el nombre del recurso en un `key_id` numérico. Esta función simplifica el proceso de gestionar y hacer referencia a los recursos en nuestra tabla. + +```C +const uint64_t pixelfarm::stringToUint64(const std::string& str) +{ + uint64_t hash = 0; + + if (str.size() == 0) return hash; + + for (int i = 0; i < str.size(); ++i) + { + int char_s = str[i]; + hash = ((hash << 4) - hash) + char_s; + hash = hash & hash; + } + + return hash; +} +``` + +La función toma la cadena `str` como parámetro de entrada y devuelve un entero sin signo de 64 bits de tipo `uint64_t`. La función utiliza un algoritmo de hashing simple para convertir una cadena en un valor entero único. + +- Inicializa una variable `hash` sin signo de 64 bits en 0. +- Verifica la longitud de la cadena de entrada `str`. Si está vacía (longitud 0), devuelve el valor de `hash`. +- Itera a través de cada carácter en `str`. + - Calcula y almacena el código ASCII de cada carácter en `char_s`. + - Realiza la operación de hashing: + - Desplaza el valor de `hash` 4 bits a la izquierda: `hash << 4`. + - Resta `hash` del valor desplazado: `(hash << 4) - hash`. + - Agrega el código ASCII del carácter: `+ char_s`. + - Aplica `hash = hash & hash` para limitar el valor de `hash` a 64 bits y evitar desbordamientos. +- Devuelve el valor final de `hash` después de finalizar el bucle. + +Luego, incorporaremos la función de reclamación en nuestro código para continuar con el procesamiento. + +```C +void claim(const name& owner, const uint64_t& farmingitem); +``` + +Y funciones de soporte para realizar la reclamación. + +### Explicación Paso a Paso + +1. `auto item_mdata = get_mdata(assets_itr);` Obtiene los metadatos del NFT utilizando el iterador `assets_itr`. + +2. `const uint32_t& lastClaim = std::get(item_mdata["lastClaim"]);` Obtiene el valor de "lastClaim" de los metadatos del NFT. Esto representa la última vez que se generó el recurso. + +3. `std::pair mined_resource;` Crea un objeto de tipo `std::pair`, que se utilizará para almacenar el recurso extraído y su cantidad. + +4. `if(time_now > lastClaim) { ... }` Verifica si ha pasado suficiente tiempo desde la última reclamación del recurso. Si es así, se toman pasos adicionales. + +5. `auto item_template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name);` Obtiene los datos de la plantilla del NFT utilizando su identificador y el nombre de la colección. + +6. Obtener los datos necesarios de la plantilla: + - `const float& miningRate = std::get(item_template_idata["miningRate"]);` Obtiene la tasa de extracción del recurso. + - `const std::string& farmResource = std::get(item_template_idata["farmResource"]);` Obtiene el nombre del recurso a extraer. + +7. `const uint8_t& current_lvl = std::get(item_mdata["level"]);` Obtiene el nivel actual del NFT de los metadatos. + +8. Calculando la tasa de extracción de recursos según el nivel: + ```C + float miningRate_according2lvl = miningRate; + for(uint8_t i = 1; i < current_lvl; ++i) + miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); + ``` + +9. Calculando la cantidad de recursos extraídos: + ```C + const float& reward = (time_now - lastClaim) * miningRate_according2lvl; + ``` + +10. `item_mdata["lastClaim"] = time_now;` Actualiza el valor de "lastClaim" en los metadatos del NFT al tiempo actual. + +11. `update_mdata(assets_itr, item_mdata, get_self());` Actualiza los metadatos del NFT con el nuevo valor de "lastClaim". + +12. Llenar el objeto `mined_resource` con los datos del recurso extraído y su cantidad. + +13. `return mined_resource;` Devuelve el objeto `mined_resource` como resultado de la función. + +### Función para Incrementar el Balance de Recursos + +```C +void game::increase_owner_resources_balance(const name& owner, const std::map& resources) +{ + resources_t resources_table(get_self(), owner.value); + for(const auto& map_itr : resources) + { + const uint64_t& key_id = stringToUint64(map_itr.first); + + auto resources_table_itr = resources_table.find(key_id); + if(resources_table_itr == std::end(resources_table)) + { + resources_table.emplace(get_self(), [&](auto &new_row) + { + new_row.key_id = key_id; + new_row.resource_name = map_itr.first; + new_row.amount = map_itr.second; + }); + } + else + { + resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) + { + new_row.amount += map_itr.second; + }); + } + } +} +``` + +### Descripción de la Funcionalidad + +1. `resources_t resources_table(get_self(), owner.value);` Declaración e inicialización del objeto de tabla `resources_t`, que se utiliza para almacenar los recursos del propietario del juego (`owner`). Se crea un objeto de tabla para el contrato usando el ID del propietario. + +2. `for(const auto& map_itr : resources) { ... }` Un bucle que recorre todos los pares clave-valor en el diccionario de entrada `resources`. + +3. `const uint64_t& key_id = stringToUint64(map_itr.first);` Obtiene un identificador único (`key_id`) para un recurso basado en el nombre del recurso del diccionario de entrada. Se utiliza la función `stringToUint64` proporcionada anteriormente. + +4. `auto resources_table_itr = resources_table.find(key_id);` Busca una entrada en la tabla con el `key_id` recibido. + +5. `if(resources_table_itr == std::end(resources_table)) { ... }` Verifica si existe una entrada para el `key_id` especificado en la tabla. + +6. Si no se encuentra el registro (rama `if`): + - `resources_table.emplace(get_self(), [&](auto &new_row) { ... });` Agrega un nuevo registro a la tabla usando la función `emplace`. El registro contiene un `key_id` único, el nombre del recurso y su cantidad. + +7. Si el registro existe (rama `else`): + - `resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) { ... });` Modifica una entrada existente en la tabla, aumentando su cantidad por el valor del diccionario de entrada. + +### Función de Reclamación + +```C +void pixelfarm::claim(const name& owner, const uint64_t& farmingitem) +{ + require_auth(owner); + + staked_t staked_table(get_self(), owner.value); + auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find staked farming item"); + auto assets = atomicassets::get_assets(get_self()); + auto assets_itr = assets.find(farmingitem); + + // to get mining boost + auto farmingitem_mdata = get_mdata(assets_itr); + float miningBoost = 1; + if(farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) + miningBoost = std::get(farmingitem_mdata["miningBoost"]); + + // first - resource name, second - resource amount + std::map mined_resources; + const uint32_t& time_now = current_time_point().sec_since_epoch(); + for(const uint64_t& item_to_collect : staked_table_itr->staked_items) + { + auto assets_itr = assets.find(item_to_collect); + const std::pair item_reward = claim_item(assets_itr, 2, time_now); // 2 es el porcentaje de aumento en la tasa de minería por cada nivel + + if(item_reward != std::pair()) + if(item_reward.second > 0) + mined_resources[item_reward.first] += item_reward.second; + } + check(mined_resources.size() > 0, "Nothing to claim"); + + increase_owner_resources_balance(owner, mined_resources); +} +``` + +1. `require_auth(owner);` Verifica si el usuario que llamó a la función tiene suficientes derechos de autorización para el propietario (`owner`). + +2. `staked_t staked_table(get_self(), owner.value);` Declaración e inicialización de la tabla `staked_t` para rastrear los elementos anidados del propietario del juego (`owner`). + +3. `auto staked_table_itr = staked_table.require_find(farmingitem, "Could not find staked farming item");` Busca una entrada en la tabla de elementos anidados usando el identificador único `farmingitem`. Si no se encuentra el registro, se genera un error. + +4. `auto assets = atomicassets::get_assets(get_self());` Obtiene todos los activos usando la función `atomicassets::get_assets`. + +5. `auto assets_itr = assets.find(farmingitem);` Busca un activo con el identificador único `farmingitem` en la colección de activos. + +6. `auto farmingitem_mdata = get_mdata(assets_itr);` Obtiene los metadatos del activo especificado. + +7. `float miningBoost = 1;` Inicializa la variable `miningBoost` con el valor 1. + +8. `if(farmingitem_mdata.find("miningBoost") != std::end(farmingitem_mdata)) miningBoost = std::get(farmingitem_mdata["miningBoost"]);` Verifica la presencia de la clave "miningBoost" en los metadatos del activo y actualiza `miningBoost` si está presente. + +9. `std::map mined_resources;` Crea un diccionario para almacenar los recursos extraídos, donde la clave es el nombre del recurso y el valor es su cantidad. + +10. `const uint32_t& time_now = current_time_point().sec_since_epoch();` Obtiene el tiempo actual en segundos desde la época. + +11. Bucle a través de cada elemento en staking: + - `const std::pair item_reward = claim_item(assets_itr, 2, time_now);` Llama a la función `claim_item` para obtener una recompensa por la minería del activo especificado. + - `if(item_reward != std::pair()) if(item_reward.second > 0) mined_resources[item_reward.first] += item_reward.second;` Agrega un recurso extraído al diccionario `mined_resources` si se ha recibido la recompensa y es positiva. + +12. `check(mined_resources.size() > 0, "Nothing to claim");` Verifica si hay algo que reclamar usando la función `check`. + +13. `increase_owner_resources_balance(owner, mined_resources);` Llama a la función `increase_owner_resources_balance` para aumentar el balance de recursos del propietario del juego. + +### Recursos Adicionales + +Para una referencia completa y ejemplos adicionales, puedes encontrar más información en el [repositorio de staking y cultivo](https://github.com/dapplicaio/FarmingResources). diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part7.md b/docs/es/build/tutorials/howto-create_farming_game/Part7.md new file mode 100644 index 0000000..06eba27 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part7.md @@ -0,0 +1,308 @@ +--- +title: Parte 7. Creación de GUI para el juego en WAX, staking y cultivo +order: 35 +--- + +Este artículo te guiará en la conexión de una cartera a la blockchain de WAX, integrándola en una interfaz ReactJS y leyendo datos de una tabla de un contrato inteligente en WAX. También cubriremos el proceso de staking de NFT y cómo reclamar recompensas. + +Empecemos con el procedimiento sobre cómo conectar tu aplicación web a la blockchain de WAX. + +Comenzando con la conexión de la cartera WAX, utilizamos la Universal Authenticator Library (UAL) en nuestra aplicación ReactJS para una integración y autenticación fácil de la cartera. El componente `UALProvider` sirve como proveedor del contexto de autenticación, facilitando las interacciones fluidas entre la aplicación y varias carteras de blockchain, simplificando el proceso de integración para los desarrolladores. + +Incorpora el `UALProvider` en tu aplicación React para agilizar el proceso de autenticación, ofreciendo una interfaz unificada para varios proveedores de cartera. Esta configuración es fundamental para conectar carteras de manera eficiente. Para una guía paso a paso y configuraciones, el [repositorio de GitHub de UAL](https://github.com/EOSIO/ual-reactjs-renderer) es un recurso invaluable, proporcionando documentación detallada y ejemplos para la integración en React. + +Componente `UALProvider`: + +- `chains={[waxChain]}`: Especifica la configuración de la blockchain. +- `authenticators={waxAuthenticators}`: Indica un array de autenticadores disponibles para la selección del usuario (e.g., Anchor y Wax). +- `appName={'AppName'}`: Define el nombre de la aplicación utilizado para la identificación durante la autenticación. + +```js + + + +``` + +A continuación, necesitamos configurar el archivo de configuración para conectar nuestras carteras a WAX. + +- **El objeto `waxChain`**: + + *Contiene la configuración para conectarse a la red WAX, como la ID de la cadena y las direcciones del servidor RPC.* + +- **Creación de instancias de Anchor y la cartera en la nube de WAX**: + + *`const anchor = new Anchor([waxChain], { appName: name })`* + + *Se crea una instancia de Anchor con la configuración de la red WAX y el nombre de la aplicación.* + + *`const waxCloudWallet = new Wax([waxChain], { appName: name })`* + + *Crea una instancia de Wax con la misma configuración.* + +- **Array `waxAuthenticators`**: + + *Define un array de autenticadores que se utilizarán según una condición. Aquí podemos editar el estado de la red principal (mainnet) o de la red de prueba (testnet).* + +A continuación se muestra un ejemplo de código del archivo `wax.config.js`: + +```js +import { Anchor } from 'ual-anchor'; +import { Wax } from '@eosdacio/ual-wax'; + +import { + WAX_CHAIN_ID, + WAX_RPC_ENDPOINTS_HOST, + WAX_RPC_ENDPOINTS_PROTOCOL, +} from '../constants/wax.constants'; + +export const waxChain = { + chainId: WAX_CHAIN_ID, + rpcEndpoints: [ + { + protocol: WAX_RPC_ENDPOINTS_PROTOCOL, + host: WAX_RPC_ENDPOINTS_HOST, + port: '', + }, + ], +}; + +const anchor = new Anchor([waxChain], { appName: 'TestGame' }); +const waxCloudWallet = new Wax([waxChain], { appName: 'TestGame' }); + +export const waxAuthenticators = + process.env.REACT_APP_MAINNET === 'mainnet' + ? [anchor, waxCloudWallet] + : [anchor]; +``` + +Después de activar el método `showModal()` en la interfaz de usuario, aparecerá una ventana modal que mostrará las carteras disponibles para la conexión. Este paso es crucial para que los usuarios elijan su cartera preferida para interactuar con la blockchain de WAX dentro de tu aplicación. + +```js +import { UALContext } from 'ual-reactjs-renderer'; +const { activeUser, showModal } = useContext(UALContext); +``` + +![Imagen del tutorial](/public/assets/images/tutorials/howto-create_farming_game/part7/image1.png) + +Después de conectarse a la blockchain de WAX, el siguiente paso es leer datos de una tabla de un contrato inteligente en WAX. + +Esto implica usar una función para obtener filas de datos de la tabla. La función `FetchRows` está diseñada específicamente para este propósito, permitiendo a la aplicación acceder y mostrar los datos requeridos desde la blockchain. Este proceso es esencial para integrar los datos de la blockchain en la interfaz de usuario de tu aplicación, proporcionando a los usuarios información en tiempo real directamente desde la blockchain de WAX. + +```js +export const getTableData = async ({ contract, scope, table }) => { + const pageSize = 1000; + let lowerBound = 0; + let fetchMore = true; + + const assets = []; + + while (fetchMore) { + // eslint-disable-next-line no-await-in-loop + const { rows, more, next_key } = await fetchRows({ + contract, + scope, + table, + limit: pageSize, + lowerBound, + }); + + assets.push(...rows); + + if (more) lowerBound = next_key; + else fetchMore = false; + } + + return assets; +}; +``` + +### Función FetchRows para Leer Datos de la Blockchain WAX + +La función `FetchRows`, integral para leer datos de la blockchain WAX, utiliza una configuración que emplea `rpc` y el método `get_table_rows`. Esta configuración facilita la recuperación de datos directamente de la tabla especificada, permitiendo que la interfaz muestre datos de la blockchain de manera dinámica. + +```js +export const fetchRows = async ({ + contract, + scope, + table, + limit, + lowerBound = null, + upperBound = null, +}) => { + try { + const config = { + json: true, + code: contract, + scope, + table, + limit, + lower_bound: lowerBound, + upper_bound: upperBound, + }; + + if (!lowerBound) delete config['lower_bound']; + + if (!upperBound) delete config['upper_bound']; + + return await rpc.get_table_rows(config); + } catch (e) { + if (!e.message.includes('assertion failure')) { + const isNewNetworkExist = reinitializeRcp(); + + if (!isNewNetworkExist) throw new Error('NetworkError!'); + + return await fetchRows({ + contract, + scope, + table, + limit, + lowerBound, + upperBound, + }); + } else { + throw new Error(e.message); + } + } +}; +``` + +**Ejemplo**: Recuperar todos los recursos del usuario. + +Para hacer esto, se utiliza la función **getResources**. + +- **activeUser**: el usuario activo. +- **table**: la tabla del contrato de la cual se desea extraer los datos de recursos. + +```js +export const getResources = async ({ activeUser }) => { + const { rows } = await fetchRows({ + contract: GAME_CONTRACT, + scope: activeUser.accountName, + table: 'resources', + limit: 100, + }); + + return rows; +}; +``` + +### Staking de NFTs + +El análisis del proceso de staking implica comprender las herramientas y mecanismos específicos utilizados en el elemento de farming. + +Este proceso es crucial para optimizar el uso de los NFTs dentro del juego, asegurando que los jugadores puedan stakear sus activos de manera eficiente y efectiva para el farming de recursos u otros beneficios. + +![](/public/assets/images/tutorials/howto-create_farming_game/part7/image2.png) + +Para stakear un "farmingItem" con tu contrato, comienza invocando la acción específica diseñada para staking, acompañada de la función auxiliar `signTransaction()` para ejecutar la transacción. Este método asegura la transacción y garantiza que el "farmingItem" se stakee como se pretende dentro del marco operativo de tu contrato inteligente. + +```js +export const stakeFarmingTool = async ({ activeUser, selectItem }) => { + return await signTransaction({ + activeUser, + account: 'atomicassets', + action: 'transfer', + data: { + from: activeUser.accountName, + to: GAME_CONTRACT, + asset_ids: [selectItem], + memo: `stake farming item` + } + }); +}; +``` + +```js +export const signTransaction = async ({ + activeUser, + account, + action, + data, +}) => { + await activeUser.signTransaction( + { + actions: [ + { + account, + name: action, + authorization: [ + { + actor: activeUser.accountName, + permission: 'active', + }, + ], + data, + }, + ], + }, + { + blocksBehind: 3, + expireSeconds: 30, + } + ); +}; +``` + +Una vez que tu "farmingItem" esté stakeado con éxito, el siguiente paso es stakear tus herramientas. Para esta acción, debes referirte a la función específica proporcionada en el contrato inteligente. Esta acción permite el staking de herramientas, involucrándose más con la mecánica del juego y mejorando la utilidad de los activos dentro del mismo. + +Para instrucciones detalladas o ejecutar esta acción, normalmente puedes consultar el siguiente enlace al contrato inteligente: + + + +La acción para stakear herramientas implica especificar el `wpId`, que identifica el "farmingItem" en el cual se está haciendo el staking de la herramienta. Este parámetro es crucial para dirigir el proceso de staking al ítem correcto, asegurando que la herramienta mejore las capacidades o beneficios del "farmingItem" en el juego. + +```js +export const stakeTool = async ({ activeUser, selectItem, wpId }) => { + return await signTransaction({ + activeUser, + account: 'atomicassets', + action: 'transfer', + data: { + from: activeUser.accountName, + to: GAME_CONTRACT, + asset_ids: [selectItem], + memo: `stake items:${wpId}` + } + }); +}; +``` + +El paso final consiste en reclamar los recursos obtenidos a través de tu "farming item" y herramientas stakeados. + +Después de stakear tu "farming item" y utilizar herramientas de tu inventario, los recursos se acumulan con el tiempo, disponibles para reclamar cada hora desde cualquier granja equipada con herramientas. La interfaz de usuario muestra los recursos acumulados, indicando la cantidad que puedes reclamar, lo que facilita el seguimiento de tu progreso y recompensas dentro del juego. + +![](/public/assets/images/tutorials/howto-create_farming_game/part7/image3.png) + +La acción de reclamar tiene los siguientes campos: + +- **action**: el nombre de la acción en el contrato. +- **owner**, **farmingitem**: campos que se envían al contrato. + +```js +export const claimRes = async ({ activeUser }) => { + return await signTransaction({ + activeUser, + account: GAME_CONTRACT, + action: 'claim', + data: { + owner: activeUser.accountName, + farmingitem: 1, + }, + }); +}; +``` + +Aquí hay un enlace a la acción en el contrato inteligente para reclamar recompensas: + + + +Este artículo ha descrito de manera exhaustiva los principios y objetos definidos a nivel de contrato inteligente, centándose en cómo implementar efectivamente una interfaz de usuario para estos elementos en ReactJS. + +Al integrar componentes de interfaz de usuario, los desarrolladores pueden crear una experiencia interactiva y fluida que permita a los usuarios interactuar con las funcionalidades del contrato inteligente, como stakear elementos y reclamar recursos, directamente dentro de una aplicación ReactJS. + +PS: El [siguiente enlace](https://github.com/dapplicaio/UIForFarmingandStaking) lleva a un repositorio que corresponde a todo lo descrito, para que puedas simplemente construir ese código y utilizarlo como desees. + diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part8.md b/docs/es/build/tutorials/howto-create_farming_game/Part8.md new file mode 100644 index 0000000..6b981c8 --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part8.md @@ -0,0 +1,380 @@ +--- +title: Parte 8. Mejoras de los objetos del juego en WAX +order: 40 +--- + +En artículos anteriores, exploramos cómo recolectar recursos usando nuestros NFT. Ahora, profundizaremos en la mejora de objetos para hacer más eficiente la minería de recursos. Mejorar los objetos implica potenciar sus capacidades, haciéndolos más efectivos en la minería de recursos. Este proceso es clave para avanzar y alcanzar una mayor eficiencia en el juego. Comenzaremos agregando el código necesario para implementar las mejoras de objetos, enfocándonos en mejorar el rendimiento de nuestros NFT para la recolección de recursos. + +```C +void game::upgradeitem( + const name& owner, + const uint64_t& item_to_upgrade, + const uint8_t& next_level, + const uint64_t& staked_at_farmingitem +) +{ + require_auth(owner); + + const int32_t& time_now = current_time_point().sec_since_epoch(); + + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.require_find(item_to_upgrade, ("Could not find staked item[" + std::to_string(item_to_upgrade) +"]").c_str()); + + staked_t staked_table(get_self(), owner.value); + auto staked_table_itr = staked_table.require_find(staked_at_farmingitem, "Could not find staked farming item"); + + check(std::find(std::begin(staked_table_itr->staked_items), std::end(staked_table_itr->staked_items), item_to_upgrade) != std::end(staked_table_itr->staked_items), + "Item [" + std::to_string(item_to_upgrade) + "] is not staked at farming item"); + + // Reclamo de recursos minados antes de la mejora + const std::pair item_reward = claim_item(asset_itr, 2, time_now); // 2 es el porcentaje de aumento en la tasa de minería por cada nivel + if(item_reward != std::pair()) + { + if(item_reward.second > 0) + { + increase_owner_resources_balance(owner, std::map({item_reward})); + } + } + // Mejora del objeto + upgrade_item(asset_itr, 2, owner, next_level, time_now); // 2 es el porcentaje de aumento en la tasa de minería por cada nivel +} +``` + +1. **Confirmación de Autenticación**: + + ```C + require_auth(owner); + ``` + Verificar que la transacción está autorizada por el propietario (`owner`). + +2. **Obtener el Tiempo Actual**: + + ```C + const int32_t& time_now = current_time_point().sec_since_epoch(); + ``` + Obtener el tiempo actual en segundos desde la época. + +3. **Obtención de Activos y Verificación de Disponibilidad para Mejorar**: + + ```C + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.require_find(item_to_upgrade, ("Could not find staked item[" + std::to_string(item_to_upgrade) +"]").c_str()); + ``` + Obtener los activos y verificar la existencia del activo con el identificador `item_to_upgrade`. + +4. **Obtención de la Tabla de Staking y Verificación de Disponibilidad del Staking para Mejorar**: + + ```C + staked_t staked_table(get_self(), owner.value); + auto staked_table_itr = staked_table.require_find(staked_at_farmingitem, "Could not find staked farming item"); + ``` + Obtener la tabla de staking para el propietario y verificar la existencia del staking correspondiente para la mejora. + +5. **Verificar la Presencia del Activo en el Staking**: + + ```C + check(std::find(std::begin(staked_table_itr->staked_items), std::end(staked_table_itr->staked_items), item_to_upgrade) != std::end(staked_table_itr->staked_items), + "Item [" + std::to_string(item_to_upgrade) + "] is not staked at farming item"); + ``` + Comprobar que el activo a mejorar esté realmente en el staking. + +6. **Recolección de Recursos Minados Antes de la Mejora**: + + ```C + const std::pair item_reward = claim_item(asset_itr, 2, time_now); + + if(item_reward != std::pair()) { + if(item_reward.second > 0) { + increase_owner_resources_balance(owner, std::map({item_reward})); + } + } + ``` + Llamar a la función `claim_item` para recolectar los recursos minados antes de la mejora. Si se recibe una recompensa, se agrega al balance del propietario. + +7. **Mejora del Objeto**: + + ```C + upgrade_item(asset_itr, 2, owner, next_level, time_now); + ``` + Llamar a la función `upgrade_item` para mejorar el activo. En este caso, la mejora se realiza con un incremento en la velocidad de minería del 2% por cada nivel. + +Junto con la explicación del proceso de mejora del objeto, también detallaremos las funciones auxiliares involucradas. Estas funciones son fundamentales para implementar las mejoras de manera efectiva, facilitando el aumento de las capacidades de minería de recursos de los objetos. + +### Funciones Detalladas para la Mejora + +```C +void game::upgrade_item( + atomicassets::assets_t::const_iterator& assets_itr, + const uint8_t& upgrade_percentage, + const name& owner, + const uint8_t& new_level, + const uint32_t& time_now +) +{ + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + + const float& mining_rate = std::get(template_idata["miningRate"]); + const uint8_t& current_lvl = std::get(mdata["level"]); + const std::string& resource_name = std::get(template_idata["farmResource"]); + check(current_lvl < new_level, "El nuevo nivel debe ser mayor que el nivel actual"); + check(new_level <= std::get(template_idata["maxLevel"]), "El nuevo nivel no puede ser superior al nivel máximo"); + check(std::get(mdata["lastClaim"]) < time_now, "El objeto está siendo mejorado"); + + float miningRate_according2lvl = mining_rate; + for(uint8_t i = 1; i < new_level; ++i) + miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); + + const int32_t& upgrade_time = get_upgrading_time(new_level) - get_upgrading_time(current_lvl); + const float& resource_price = upgrade_time * miningRate_according2lvl; + + std::get(mdata["level"]) = new_level; + std::get(mdata["lastClaim"]) = time_now + upgrade_time; + + reduce_owner_resources_balance(owner, std::map({{resource_name, resource_price}})); + + update_mdata(assets_itr, mdata, get_self()); +} +``` + +### Explicación Detallada de las Funciones de Mejora + +1. **Obtención de Metadatos y Datos de Plantilla**: + + ```C + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + ``` + Obtener los metadatos y los datos de la plantilla para el procesamiento del activo. + +2. **Obtención de los Valores Requeridos**: + + ```C + const float& mining_rate = std::get(template_idata["miningRate"]); + const uint8_t& current_lvl = std::get(mdata["level"]); + const std::string& resource_name = std::get(template_idata["farmResource"]); + ``` + Obtener la tasa de minería, el nivel actual y el nombre del recurso para un activo determinado. + +3. **Verificación de Condiciones**: + + ```C + check(current_lvl < new_level, "El nuevo nivel debe ser mayor que el nivel actual"); + check(new_level <= std::get(template_idata["maxLevel"]), "El nuevo nivel no puede ser superior al nivel máximo"); + check(std::get(mdata["lastClaim"]) < time_now, "El objeto está siendo mejorado"); + ``` + Verificar varias condiciones que deben cumplirse antes de mejorar un activo. + +4. **Cálculo de los Nuevos Valores de Velocidad de Minería**: + + ```C + float miningRate_according2lvl = mining_rate; + for(uint8_t i = 1; i < new_level; ++i) + miningRate_according2lvl = miningRate_according2lvl + (miningRate_according2lvl * upgrade_percentage / 100); + ``` + Calcular la nueva velocidad de minería, teniendo en cuenta el porcentaje de mejora para cada nivel. + +5. **Cálculo del Costo de la Mejora**: + + ```C + const int32_t& upgrade_time = get_upgrading_time(new_level) - get_upgrading_time(current_lvl); + const float& resource_price = upgrade_time * miningRate_according2lvl; + ``` + Calcular el tiempo de mejora y el costo de la mejora en función de la diferencia entre los tiempos de mejora de los niveles nuevo y actual. + +6. **Actualización de Datos y Reducción del Balance del Propietario**: + + ```C + std::get(mdata["level"]) = new_level; + std::get(mdata["lastClaim"]) = time_now + upgrade_time; + reduce_owner_resources_balance(owner, std::map({{resource_name, resource_price}})); + update_mdata(assets_itr, mdata, get_self()); + ``` + Actualizar los datos del activo, establecer el nuevo nivel y el tiempo del último reclamo. Reducir el balance del propietario por el costo de la mejora. Actualizar la tabla de datos del activo. + +### Reducción del Balance de Recursos del Propietario + +El siguiente código incluye un mecanismo para deducir los recursos del balance del jugador como pago por la mejora: + +```C +void game::reduce_owner_resources_balance(const name& owner, const std::map& resources) +{ + resources_t resources_table(get_self(), owner.value); + + for(const auto& map_itr : resources) + { + const uint64_t& key_id = stringToUint64(map_itr.first); + auto resources_table_itr = resources_table.require_find(key_id, + ("No se encontró el balance de " + map_itr.first).c_str()); + check(resources_table_itr->amount >= map_itr.second, ("Balance insuficiente: " + map_itr.first).c_str()); + + if(resources_table_itr->amount == map_itr.second) + resources_table.erase(resources_table_itr); + else + { + resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) + { + new_row.amount -= map_itr.second; + }); + } + } +} +``` + +Este código detalla el proceso para reducir el balance de recursos del propietario, asegurando que el pago por la mejora se realice de forma adecuada antes de proceder con la actualización del objeto. + +### Explicación Detallada de la Mejora del Objeto de Granja + +1. **Declaración e Inicialización de la Tabla de Recursos**: + + ```C + resources_t resources_table(get_self(), owner.value); + ``` + Crear un objeto de tabla `resources_table` para almacenar el balance de recursos de un propietario específico (`owner`). + +2. **Bucle de Recursos**: + + ```C + for(const auto& map_itr : resources) + { + // ... + } + ``` + El proceso de mejora implica un bucle que recorre cada recurso listado en un mapa (`resources`). En este mapa, el nombre de cada recurso es la clave, y la cantidad a deducir del balance del jugador es el valor. Este paso es crucial para ajustar con precisión el balance de recursos del jugador según los costos de las mejoras que desean aplicar a sus objetos. + +3. **Obtención de la Clave y Búsqueda en la Tabla de Recursos**: + + ```C + const uint64_t& key_id = stringToUint64(map_itr.first); + auto resources_table_itr = resources_table.require_find(key_id, ("No se encontró el balance de " + map_itr.first).c_str()); + ``` + Obtener un identificador único (`key_id`) a partir de una cadena y usarlo para encontrar la entrada correspondiente en la tabla `resources_table`. + +4. **Verificación de Suficiencia del Balance**: + + ```C + check(resources_table_itr->amount >= map_itr.second, ("Balance insuficiente: " + map_itr.first).c_str()); + ``` + Verificar si hay suficientes fondos en el balance para realizar el retiro. + +5. **Actualización de la Tabla de Recursos**: + + ```C + if(resources_table_itr->amount == map_itr.second) + resources_table.erase(resources_table_itr); + else + { + resources_table.modify(resources_table_itr, get_self(), [&](auto &new_row) + { + new_row.amount -= map_itr.second; + }); + } + ``` + - Si la cantidad del recurso en el balance es igual a la cantidad a restar (`map_itr.second`), entonces se elimina el registro correspondiente de la tabla. + - De lo contrario, se actualiza la cantidad del recurso, reduciéndola en la cantidad adecuada. La función `modify` actualiza una entrada en la tabla. + +### Mejora del Objeto de Granja + +El siguiente código describe cómo mejorar un objeto de granja: + +```C +void game::upgfarmitem(const name& owner, const uint64_t& farmingitem_to_upgrade, const bool& staked) +{ + require_auth(owner); + + if(staked) + { + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("No se encontró el objeto apostado [" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); + + staked_t staked_table(get_self(), owner.value); + staked_table.require_find(farmingitem_to_upgrade, "No se encontró el objeto de granja apostado"); + + upgrade_farmingitem(asset_itr, get_self()); + } + else + { + auto assets = atomicassets::get_assets(owner); + auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("No posees el objeto de granja [" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); + upgrade_farmingitem(asset_itr, owner); + } +} +``` + +### Pasos Principales de la Función de Mejora + +1. **Confirmación de Autenticación**: + + ```C + require_auth(owner); + ``` + Verificar que la transacción esté autorizada por el propietario (`owner`). + +2. **Comprobación de si el Objeto Está Apostado**: + + ```C + if(staked) + { + auto assets = atomicassets::get_assets(get_self()); + auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("No se encontró el objeto apostado [" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); + + staked_t staked_table(get_self(), owner.value); + staked_table.require_find(farmingitem_to_upgrade, "No se encontró el objeto de granja apostado"); + } + else + { + auto assets = atomicassets::get_assets(owner); + auto asset_itr = assets.require_find(farmingitem_to_upgrade, ("No posees el objeto de granja [" + std::to_string(farmingitem_to_upgrade) +"]").c_str()); + } + ``` + Dependiendo de si el objeto de granja está apostado, se realizan diferentes pasos para verificar la propiedad o el estado de apuesta del objeto. + +3. **Mejora del Objeto de Granja**: + + ```C + upgrade_farmingitem(asset_itr, get_self()); + ``` + Llamar a la función `upgrade_farmingitem` para mejorar el objeto de granja. La función toma un iterador al NFT y el nombre del propietario. + +### Lógica de Mejora del Objeto de Granja + +```C +void pixelfarm::upgrade_farmingitem(atomicassets::assets_t::const_iterator& assets_itr, const name& owner) +{ + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + + check(std::get(mdata["slots"])++ < std::get(template_idata["maxSlots"]), "El objeto de granja tiene el máximo de espacios"); + + update_mdata(assets_itr, mdata, owner); +} +``` + +1. **Obtención de Metadatos y Datos de Plantilla**: + + ```C + auto mdata = get_mdata(assets_itr); + auto template_idata = get_template_idata(assets_itr->template_id, assets_itr->collection_name); + ``` + Obtener los metadatos y los datos de la plantilla para el procesamiento del activo. + +2. **Comprobación del Número de Espacios**: + + ```C + check(std::get(mdata["slots"])++ < std::get(template_idata["maxSlots"]), "El objeto de granja tiene el máximo de espacios"); + ``` + Verificar si el número de espacios del objeto de granja después del incremento no excede el número máximo de espacios definido en la plantilla del NFT. Ten en cuenta que aquí se usa un incremento posterior (`++` antes de `std::get(mdata["slots"])`), por lo que el valor de espacios se incrementará antes de la comparación. + +3. **Actualización de los Datos del Objeto de Granja**: + + ```C + update_mdata(assets_itr, mdata, owner); + ``` + Actualizar los metadatos del objeto de granja en la tabla utilizando la función `update_mdata`. Esta función se usa para guardar los nuevos datos después de una mejora. + +### Próximos Pasos + +En el siguiente artículo, cubriremos una función similar, que es la creación de nuevos objetos, también conocidos como "blends". + +PS. [El siguiente enlace](https://github.com/dapplicaio/GamItemUpgrades) lleva a un repositorio que corresponde a todo lo descrito, para que puedas simplemente construir ese código y usarlo como desees. + diff --git a/docs/es/build/tutorials/howto-create_farming_game/Part9.md b/docs/es/build/tutorials/howto-create_farming_game/Part9.md new file mode 100644 index 0000000..898be8f --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/Part9.md @@ -0,0 +1,255 @@ +--- +title: Parte 9. Mezclas de NFTs para juegos en WAX +order: 45 +--- + +En este artículo, siguiendo el anterior sobre la mejora de los elementos de agricultura, nos adentraremos en la creación de mezclas. Mezclar implica combinar elementos específicos para crear nuevos o mejorados dentro del juego. Esta característica agrega profundidad y estrategia al juego, ofreciendo a los jugadores la oportunidad de elaborar elementos únicos con un valor o utilidad potencialmente más altos. + +Vamos a delinear las adiciones de código necesarias para implementar esta funcionalidad, enriqueciendo los elementos interactivos del juego y el compromiso del jugador. + +```C + //scope:contract + struct [[eosio::table]] blends_j + { + uint64_t blend_id; + std::vector blend_components; + int32_t resulting_item; + + uint64_t primary_key() const { return blend_id; } + }; + typedef multi_index< "blends"_n, blends_j > blends_t; +``` + +Introducir una nueva tabla para guardar recetas de mezclas enriquece el juego al permitir la creación de elementos únicos mediante combinaciones. Los administradores pueden definir recetas, que los jugadores luego usan para mezclar elementos, especificando: + +- **blend_id**: Un identificador único para cada receta. + +- **blend_components**: La lista de plantillas de NFT requeridas para la mezcla. + +- **resulting_item**: El ID de la nueva plantilla de NFT creada a partir de la mezcla. + +Esta característica añade una capa estratégica, alentando a los jugadores a recolectar NFTs específicos para crear elementos más valiosos o poderosos. + +Agregar una función para que el administrador del contrato cree nuevas mezclas es un paso estratégico de desarrollo. + +```C +void game::addblend( + const std::vector blend_components, + const int32_t resulting_item +) +{ + require_auth(get_self()); + + blends_t blends_table(get_self(), get_self().value); + + const uint64_t new_blend_id = blends_table.available_primary_key(); + + blends_table.emplace(get_self(), [&](auto new_row) + { + new_row.blend_id = new_blend_id; + new_row.blend_components = blend_components; + new_row.resulting_item = resulting_item; + }); +} +``` + +```C + require_auth(get_self()); +``` + +Implementar esta función asegura que solo el administrador del contrato tenga la autoridad para crear nuevas mezclas. + +```C +const uint64_t new_blend_id = blends_table.available_primary_key(); +``` + +La función `available_primary_key` está diseñada para proporcionar una clave incremental única para nuevas entradas en una tabla. Si las claves existentes en la tabla son 1, 5 y 8, la función devolverá 9, asegurando que cada nueva entrada reciba un identificador distinto. + +```C + blends_table.emplace(get_self(), [&](auto new_row) + { + new_row.blend_id = new_blend_id; + new_row.blend_components = blend_components; + new_row.resulting_item = resulting_item; + }); +``` + +Crear un nuevo registro en la tabla implica especificar los campos necesarios como se describió anteriormente. + +Para integrar la mezcla en el juego, expandiremos la función `receive_asset_transfer`, que se utilizó previamente para apostar elementos. Esta mejora implica agregar condiciones a la declaración if para reconocer y procesar las transacciones de mezcla. + +```C + else if(memo.find("blend:") != std::string::npos) + { + const uint64_t blend_id = std::stoll(memo.substr(6)); + blend(from, asset_ids, blend_id); + } +``` + +Para mezclar, los jugadores necesitan transferir sus NFTs al contrato, de manera similar al staking, pero deben incluir un memo específico que indique la acción de mezcla y el blend_id, como "blend:blend_id" (por ejemplo, "blend:18"). Este método asegura que el contrato reconozca la intención del jugador de mezclar elementos utilizando la receta especificada. + +```C + const uint64_t blend_id = std::stoll(memo.substr(6)); +``` + +Extraer el ID de mezcla del memo es un paso crucial en el proceso de mezcla, permitiendo que el contrato identifique qué receta de mezcla el jugador pretende usar. + +```C +blend(from, asset_ids, blend_id); +``` +Después de extraer el ID de mezcla del memo, el siguiente paso implica llamar a una función auxiliar. + +```C +void game::blend(const name& owner, const std::vector asset_ids, const uint64_t& blend_id) +{ + auto assets = atomicassets::get_assets(get_self()); + auto templates = atomicassets::get_templates(get_self()); + blends_t blends_table(get_self(), get_self().value); + auto blends_table_itr = blends_table.require_find(blend_id, "Could not find blend id"); + check(blends_table_itr->blend_components.size() == asset_ids.size(), "Blend components count mismatch"); + + std::vector temp = blends_table_itr->blend_components; + for(const uint64_t& asset_id : asset_ids) + { + auto assets_itr = assets.find(asset_id); + check(assets_itr->collection_name == name("collname"), // reemplaza "collname" con el nombre de tu colección para verificar NFTs falsos + ("Collection of asset [" + std::to_string(asset_id) + "] mismatch").c_str()); + auto found = std::find(std::begin(temp), std::end(temp), assets_itr->template_id); + if(found != std::end(temp)) + temp.erase(found); + + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "burnasset"_n, + std::make_tuple + ( + get_self(), + asset_id + ) + ).send(); + } + check(temp.size() == 0, "Invalid blend components"); + + auto templates_itr = templates.find(blends_table_itr->resulting_item); + + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "mintasset"_n, + std::make_tuple + ( + get_self(), + get_self(), + templates_itr->schema_name, + blends_table_itr->resulting_item, + owner, + (atomicassets::ATTRIBUTE_MAP) {}, // datos inmutables + (atomicassets::ATTRIBUTE_MAP) {}, // datos mutables + (std::vector ) {} // token de respaldo + ) + ).send(); +} +``` + +### Explicación Detallada de la Función de Mezcla + +1. **Extracción de Información de Activos y Plantillas**: + + ```C + auto assets = atomicassets::get_assets(get_self()); + auto templates = atomicassets::get_templates(get_self()); + ``` + El proceso implica extraer información detallada sobre los NFTs actualmente en poder del contrato. Este paso es esencial para identificar más adelante los NFTs enviados por el usuario, permitiendo la extracción de todos los datos relevantes de la plantilla necesarios para el proceso de mezcla. + +2. **Verificación de la Existencia de la Mezcla y del Conteo de Componentes**: + + ```C + blends_t blends_table(get_self(), get_self().value); + auto blends_table_itr = blends_table.require_find(blend_id, "Could not find blend id"); + check(blends_table_itr->blend_components.size() == asset_ids.size(), "Blend components count mismatch"); + ``` + Primero, el proceso implica verificar la tabla de mezclas para comprobar si la mezcla especificada por el jugador existe; de no ser así, se lanza un error. Luego, es esencial confirmar que el jugador ha enviado la cantidad correcta de NFTs requeridos para la mezcla, asegurando el cumplimiento de las especificaciones de la receta de mezcla. + +3. **Verificación de los Componentes de la Mezcla**: + + ```C + std::vector temp = blends_table_itr->blend_components; + for(const uint64_t& asset_id : asset_ids) + { + // ... + } + ``` + Se crea una variable temporal para asegurar que el jugador ha enviado los componentes correctos para la mezcla. Este paso es seguido por una iteración sobre todos los NFTs proporcionados por el jugador, verificando cada uno contra los requisitos de la mezcla. + +4. **Verificación de la Colección de NFTs y Quema de Activos**: + + ```C + auto assets_itr = assets.find(asset_id); + check(assets_itr->collection_name == name("collname"), // reemplaza "collname" con el nombre de tu colección para verificar NFTs falsos + ("Collection of asset [" + std::to_string(asset_id) + "] mismatch").c_str()); + ``` + Durante el proceso de verificación de la mezcla, se extrae la información de cada NFT enviado para asegurar que pertenece a la colección del juego. Esto implica verificar si el nombre de la colección del NFT coincide con el nombre especificado de la colección del juego, lo cual requiere reemplazar "collname" con el nombre real de tu colección en el código. + +5. **Eliminación de Plantillas Coincidentes y Quema de los NFTs**: + + ```C + auto found = std::find(std::begin(temp), std::end(temp), assets_itr->template_id); + if(found != std::end(temp)) + temp.erase(found); + + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "burnasset"_n, + std::make_tuple + ( + get_self(), + asset_id + ) + ).send(); + ``` + El proceso de mezcla implica una verificación meticulosa contra la receta de mezcla, usando una variable temporal que contiene los ID de plantillas de la mezcla. Para cada NFT enviado, el sistema verifica su plantilla contra la variable temporal. Si se encuentra una coincidencia, esa plantilla se elimina de la variable para evitar duplicados e identificar cualquier plantilla incorrecta enviada por el jugador. Tras esta verificación, se llama a la función de quema. + +6. **Verificación Final de la Mezcla**: + + ```C + check(temp.size() == 0, "Invalid blend components"); + ``` + El siguiente paso en el proceso de mezcla es asegurar que el vector temporal, que contiene los componentes de la receta de mezcla, esté vacío. Esto verifica que el jugador haya enviado correctamente todos los componentes requeridos para la mezcla. Si el vector está vacío, se confirma que todos los componentes fueron enviados y procesados correctamente, permitiendo que la mezcla se complete con éxito. + +7. **Creación del NFT Resultante**: + + ```C + auto templates_itr = templates.find(blends_table_itr->resulting_item); + + action + ( + permission_level{get_self(),"active"_n}, + atomicassets::ATOMICASSETS_ACCOUNT, + "mintasset"_n, + std::make_tuple + ( + get_self(), + get_self(), + templates_itr->schema_name, + blends_table_itr->resulting_item, + owner, + (atomicassets::ATTRIBUTE_MAP) {}, // datos inmutables + (atomicassets::ATTRIBUTE_MAP) {}, // datos mutables + (std::vector) {} // token de respaldo + ) + ).send(); + ``` + Una vez que se completa toda la verificación, el sistema crea un nuevo NFT con la plantilla especificada, representando el resultado de la mezcla. + +### Conclusión + +Este artículo te ha guiado a través del proceso de crear mezclas en un juego, desde configurar recetas de mezcla hasta verificar las entregas de los jugadores y crear nuevos NFTs. Cubre la verificación de los componentes de los NFTs contra los requisitos de la mezcla, asegurando que todos los componentes sean enviados correctamente y concluyendo con la creación de un nuevo elemento que resulta de la mezcla. Este mecanismo de mezcla enriquece la jugabilidad al permitir a los jugadores combinar NFTs en nuevos activos más valiosos, fomentando un compromiso más profundo con el ecosistema del juego. + +**PD.** El [enlace siguiente](https://github.com/dapplicaio/GamingItemBlend) lleva a un repositorio que corresponde a todo lo descrito, por lo que puedes simplemente construir ese código y usarlo como desees. + + diff --git a/docs/es/build/tutorials/howto-create_farming_game/index.md b/docs/es/build/tutorials/howto-create_farming_game/index.md new file mode 100644 index 0000000..912b55b --- /dev/null +++ b/docs/es/build/tutorials/howto-create_farming_game/index.md @@ -0,0 +1,36 @@ +Cómo crear un juego de agricultura +=== + +## Bienvenido a la Guía Definitiva para Crear un Juego en WAX + +¿Estás listo para desatar tu creatividad y construir un juego único de agricultura de recursos en la blockchain de WAX? ¡No busques más! Esta guía completa te llevará a través de cada paso del proceso, desde la configuración de tu kit de herramientas Atomic Assets hasta la implementación de gobernanza DAO y tablas de líderes. + +### Los Componentes Fundamentales de tu Juego en WAX + +En esta serie, desglosaremos los componentes esenciales de un exitoso juego de agricultura de recursos en WAX. Desde la creación de objetos del juego como elementos agrícolas y avatares, hasta la gestión de recursos y tokens, staking de NFT, tipos de agricultura y más. + +### Prepárate para un Profundo Viaje al Desarrollo de Juegos en WAX + +A lo largo de los próximos artículos, profundizaremos en cada uno de estos temas en detalle. Ya seas un desarrollador experimentado o estés empezando, nuestra guía te proporcionará el conocimiento y las herramientas necesarias para construir un destacado juego de agricultura de recursos en WAX. ¡Así que empecemos! + + +