Repository navigation
refactor(items): build the item tooltip's lines apart from drawing them #821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e0f7011
b892f33
b245598
17dc19f
1f9d999
c2ab350
5ac570c
4227e4c
e248712
7755756
bb0cc91
1fabfd7
d90e1e2
7f3effa
980b397
bfad7de
b83bb46
8deb35a
c4f64c5
95150fa
4cec8be
7d315dc
b2e1749
71458a6
78e5861
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,11 @@ | |
| #include "Core/Utilities/Benchmark/BenchRecorder.h" | ||
| #include "Core/Text/Utf8.h" | ||
| #include "Engine/Object/ZzzCharacter.h" | ||
| #include "Data/GameData/ItemData/ItemDatabase.h" | ||
| #include "Data/GameData/ItemData/ItemType.h" | ||
| #include "Engine/Object/ZzzInventory.h" | ||
| #include "UI/Core/WindowSystem.h" | ||
| #include "UI/Inventory/ItemMng.h" | ||
| #include "Network/Server/WSclient.h" | ||
| #include "Scenes/SceneCore.h" | ||
| #include "Scenes/SceneManager.h" | ||
|
|
@@ -20,6 +25,7 @@ | |
| #include <cmath> | ||
| #include <cstdint> | ||
| #include <filesystem> | ||
| #include <fstream> | ||
| #include <map> | ||
| #include <memory> | ||
| #include <optional> | ||
|
|
@@ -228,7 +234,8 @@ std::string& MutableBuildIdentifier() | |
| } | ||
|
|
||
| // Absolute path for a caller-given one, so the answer names a file the | ||
| // caller can open regardless of the client's working directory. | ||
| // caller can open regardless of the client's working directory. Used by every | ||
| // command that writes a file (`screenshot`, `tooltip-snapshot`). | ||
| std::filesystem::path ResolveScreenshotPath(const std::string& requested) | ||
| { | ||
| // Through the wide form: a std::string given to std::filesystem::path is | ||
|
|
@@ -476,6 +483,213 @@ std::string State(const Request& request, std::unique_ptr<Act>&) | |
| return EncodeResult(request.EncodedId(), WorldStateObject()); | ||
| } | ||
|
|
||
| namespace | ||
| { | ||
| // An item as the server would send it: the fields not set are 0, the durability is full. | ||
| ItemCreationParams FullItem(int group, int number) | ||
| { | ||
| ItemCreationParams params{}; | ||
| params.Group = group; | ||
| params.Number = number; | ||
| params.Durability = static_cast<BYTE>(ItemAttribute[group * MAX_ITEM_INDEX + number].Durability); | ||
| return params; | ||
| } | ||
|
|
||
| // The tooltip lines of such an item and the hero as it is; nothing when the item can't be made. | ||
| std::vector<ItemTooltipLine> TooltipOf(const ItemCreationParams& params) | ||
| { | ||
| ITEM* item = g_pNewItemMng->CreateItemByParameters(¶ms); | ||
| std::vector<ItemTooltipLine> lines = BuildItemTooltipLines(item, false); | ||
| g_pNewItemMng->DeleteItem(item); | ||
| return lines; | ||
| } | ||
|
|
||
| // The kinds of item `tooltip-snapshot` records at every level: plain; with luck, skill, the +4 option and every | ||
| // excellent option; ancient (set 1); with a Jewel of Harmony option (option 1 at level 13). | ||
| struct TooltipVariant | ||
| { | ||
| const char* name; | ||
| void (*apply)(ItemCreationParams& params); | ||
| }; | ||
| constexpr TooltipVariant TooltipVariants[] = { | ||
| {"plain", [](ItemCreationParams&) {}}, | ||
| {"full", | ||
| [](ItemCreationParams& params) | ||
| { | ||
| params.WithLuck = true; | ||
| params.WithSkill = true; | ||
| params.WithOption = true; | ||
| params.OptionLevel = 4; | ||
| params.HasExcellentOption = true; | ||
| params.ExcellentFlags = 63; | ||
| }}, | ||
| {"ancient", | ||
| [](ItemCreationParams& params) | ||
| { | ||
| params.IsAncient = true; | ||
| params.AncientDiscriminator = 1; | ||
| }}, | ||
| {"harmony", | ||
| [](ItemCreationParams& params) | ||
| { | ||
| params.HasHarmonyOption = true; | ||
| params.HarmonyOptionType = 1; | ||
| params.HarmonyOptionLevel = 13; | ||
| }}, | ||
| }; | ||
|
|
||
| // Reads the integer `key` into `value` when the request has it; false when it is not a number from 0 to `max`. | ||
| bool ReadBounded(const Request& request, const char* key, int max, int& value) | ||
| { | ||
| return !request.Has(key) || (request.GetInt(key, value) && value >= 0 && value <= max); | ||
| } | ||
| } // namespace | ||
|
|
||
| std::string ItemTooltip(const Request& request, std::unique_ptr<Act>&) | ||
| { | ||
| int group = -1; | ||
| int number = -1; | ||
| if (!request.GetInt("group", group) || !request.GetInt("number", number) || group < 0 || group >= MAX_ITEM_TYPE || | ||
| number < 0 || number >= MAX_ITEM_INDEX) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::BadRequest, | ||
| "`tooltip` needs `group` and `number` of an item"); | ||
| } | ||
|
|
||
| // The item's fields hold these values (as the item packet has them); a value they can't hold is a `bad_request`, | ||
| // not a wrapped one. Checked before the item is made from the item data. -1: left out. | ||
| int level = -1; | ||
| int durability = -1; | ||
| int option = 0; | ||
| int excellent = 0; | ||
| int ancient = 0; | ||
| int harmony = 0; | ||
| int harmonyLevel = -1; | ||
| if (!ReadBounded(request, "level", Data::Items::ItemLevelCount - 1, level) || | ||
| !ReadBounded(request, "durability", 255, durability) || !ReadBounded(request, "option", 15, option) || | ||
| !ReadBounded(request, "excellent", 63, excellent) || !ReadBounded(request, "ancient", 255, ancient) || | ||
| !ReadBounded(request, "harmony", 15, harmony) || !ReadBounded(request, "harmony_level", 15, harmonyLevel)) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::BadRequest, | ||
| "`level`, `option`, `harmony` and `harmony_level` are 0 to 15, `excellent` the excellent " | ||
| "options' bits 0 to 63, `durability` and `ancient` 0 to 255"); | ||
| } | ||
| // A harmony level counts only with a harmony option; alone it would be ignored. | ||
| if (harmonyLevel >= 0 && harmony == 0) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::BadRequest, | ||
| "`harmony_level` needs `harmony`, the Jewel of Harmony option"); | ||
| } | ||
| // An item number no item uses would answer with the tooltip of an empty item. | ||
| if (g_ItemDatabase.Find(group, number) == nullptr) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::BadRequest, | ||
| "no item has group " + std::to_string(group) + " and number " + std::to_string(number)); | ||
| } | ||
|
|
||
| ItemCreationParams params = FullItem(group, number); | ||
| if (level >= 0) | ||
| params.Level = static_cast<BYTE>(level); | ||
| if (durability >= 0) | ||
| params.Durability = static_cast<BYTE>(durability); | ||
| bool flag = false; | ||
| if (request.GetBool("luck", flag)) | ||
| params.WithLuck = flag; | ||
| if (request.GetBool("skill", flag)) | ||
| params.WithSkill = flag; | ||
| if (option > 0) | ||
| { | ||
| params.WithOption = true; | ||
| params.OptionLevel = static_cast<BYTE>(option); | ||
| } | ||
| if (excellent > 0) | ||
| { | ||
| params.HasExcellentOption = true; | ||
| params.ExcellentFlags = static_cast<BYTE>(excellent); | ||
| } | ||
| if (ancient > 0) | ||
| { | ||
| params.IsAncient = true; | ||
| params.AncientDiscriminator = static_cast<BYTE>(ancient); | ||
| } | ||
| if (harmony > 0) | ||
| { | ||
| params.HasHarmonyOption = true; | ||
| params.HarmonyOptionType = static_cast<BYTE>(harmony); | ||
| if (harmonyLevel >= 0) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 78e5861: |
||
| params.HarmonyOptionLevel = static_cast<BYTE>(harmonyLevel); | ||
| } | ||
|
|
||
| json result; | ||
| result["lines"] = json::array(); | ||
| for (const ItemTooltipLine& line : TooltipOf(params)) | ||
| { | ||
| result["lines"].push_back( | ||
| {{"text", Core::Text::ToUtf8(line.text.c_str())}, {"color", line.color}, {"bold", line.bold}}); | ||
| } | ||
| return EncodeResult(request.EncodedId(), result.dump()); | ||
| } | ||
|
|
||
| std::string ItemTooltipSnapshot(const Request& request, std::unique_ptr<Act>&) | ||
| { | ||
| std::string path; | ||
| if (!request.GetString("out", path) || path.empty()) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::BadRequest, | ||
| "`tooltip-snapshot` needs `out`, the file to write"); | ||
| } | ||
| // One group per request keeps the frame that builds them short. Checked before `out` is opened: opening it | ||
| // empties it, and a mistyped request must not wipe a recorded snapshot. | ||
| int onlyGroup = -1; | ||
| if (request.Has("group") && (!request.GetInt("group", onlyGroup) || onlyGroup < 0 || onlyGroup >= MAX_ITEM_TYPE)) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::BadRequest, "`group` is an item group, 0 to 15"); | ||
| } | ||
|
|
||
| const std::filesystem::path file = ResolveScreenshotPath(path); | ||
| std::ofstream out(file, std::ios::binary); | ||
| if (!out) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::Failed, "the file could not be written"); | ||
| } | ||
|
|
||
| int tooltips = 0; | ||
| for (const Data::Items::ItemDefinition& definition : g_ItemDatabase.GetAllSlots()) | ||
| { | ||
| if (!definition.Exists() || (onlyGroup >= 0 && definition.group != onlyGroup)) | ||
| { | ||
| continue; | ||
| } | ||
| for (const TooltipVariant& variant : TooltipVariants) | ||
| { | ||
| for (int level = 0; level < Data::Items::ItemLevelCount; ++level) | ||
| { | ||
| ItemCreationParams params = FullItem(definition.group, definition.number); | ||
| params.Level = static_cast<BYTE>(level); | ||
| variant.apply(params); | ||
| out << definition.group << '/' << definition.number << ' ' << variant.name << " +" << level << '\n'; | ||
| for (const ItemTooltipLine& line : TooltipOf(params)) | ||
| { | ||
| const std::string text = line.text == L"\n" ? "<blank>" : Core::Text::ToUtf8(line.text.c_str()); | ||
| out << " " << line.color << (line.bold ? " bold " : " ") << text << '\n'; | ||
| } | ||
| ++tooltips; | ||
| } | ||
| } | ||
| } | ||
| // A full disk or a failed write leaves the file short: not a snapshot. | ||
| out.flush(); | ||
| if (!out) | ||
| { | ||
| return EncodeError(request.EncodedId(), ErrorCode::Failed, "the file could not be written completely"); | ||
| } | ||
|
|
||
| json result; | ||
| result["tooltips"] = tooltips; | ||
| result["out"] = Core::Text::ToUtf8(file.wstring().c_str()); | ||
| return EncodeResult(request.EncodedId(), result.dump()); | ||
| } | ||
|
|
||
| std::string Nearby(const Request& request, std::unique_ptr<Act>&) | ||
| { | ||
| json result; | ||
|
|
||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking. This only range-checks
groupandnumber. For a number no item uses,CreateItemByParametersstill makes an item, and the reply is a tooltip of an empty item rather than an error. That looks like a valid answer to a script with a typo'd number. The "the item could not be made" branch can't trigger either, becauseCreateItemByParametersreturns null only for a null pointer. Refusing withbad_requestwheng_ItemDatabase.Find(type)is null, as the snapshot skips items that don'tExists(), would make that branch real.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 78e5861:
tooltipanswersbad_request("no item has group G and number N") wheng_ItemDatabase.Find(group, number)is null. SinceCreateItemByParameterscan't fail for an item that exists, I dropped the unreachable "could not be made" branch andTooltipOfreturns the lines directly. Tested.