mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-27 18:37:09 +01:00
Customization: outfit.worn lists a full-body costume instead of filtering it out as a body part
reskate.outfit.worn() is meant to list full-body costumes: it has a "Costume" slot for them, between
the head and the feet. But no costume ever reached it. With a costume on, the list came back without
it, so a plugin that goes with a costume mod could not tell whether its costume was worn. Now it is
listed like any other item: {slot = "Costume", item = "Own_Hobkin", title = "Hobkin"}.
Why: the list leaves out the body's own parts (head shape, nose, skin...) by their catalog categories,
which start with "cust" (custhead, custnoses, custbodies...) or name the body. A full-body costume's
category is "cust_fullBodyCostume", so it matched both and was dropped before its slot was looked up.
A category that names a costume is now kept; everything else is decided as before.
The rule (category to slot) moves out of worn_items.cpp into outfit_kind.h as it was, so it can be
tested without the game: outfit_kind_tests (with the plugin tests) checks clothes by their slots, the
costume, the body's own parts, and an unknown category.
Checked in the game: with a costume mod's costume on, outfit.worn() returns
Costume | Own_Hobkin | Hobkin, where it returned nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7fcb7ea798
commit
61092a4950
4 files changed
+112
-40
No files matched your search
@@ -468,6 +468,10 @@ if(DINGOSDK_BUILD_PLUGIN_TESTS AND WIN32)
|
||||
add_executable(dingosdk_board_material_tests Extension/Customization/Test/board_material_tests.cpp)
|
||||
target_include_directories(dingosdk_board_material_tests PRIVATE "${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME board_material COMMAND dingosdk_board_material_tests)
|
||||
# What a catalog category is worn as (Extension/Customization/outfit_kind.h, reskate.outfit.worn).
|
||||
add_executable(dingosdk_outfit_kind_tests Extension/Customization/Test/outfit_kind_tests.cpp)
|
||||
target_include_directories(dingosdk_outfit_kind_tests PRIVATE "${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME outfit_kind COMMAND dingosdk_outfit_kind_tests)
|
||||
endif()
|
||||
get_property(project_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
|
||||
foreach(target IN LISTS project_targets)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// What a catalog category is worn as (Extension/Customization/outfit_kind.h): clothes by their slot,
|
||||
// full-body costumes too, and the body's own parts left out.
|
||||
#include "Extension/Customization/outfit_kind.h"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
using namespace dingosdk;
|
||||
|
||||
void check(bool ok, const std::string& message) {
|
||||
if (ok) return;
|
||||
std::cerr << "FAILED: " << message << '\n';
|
||||
std::exit(1);
|
||||
}
|
||||
void worn_as(const char* category, const char* label) {
|
||||
const auto kind = outfit_kind::of(category);
|
||||
check(kind && kind->label == label, std::string(category) + " is worn as " + label);
|
||||
}
|
||||
void not_worn(const char* category) { check(!outfit_kind::of(category), std::string(category) + " is not worn"); }
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
// Clothes and accessories, by the slot they are worn in.
|
||||
worn_as("tops", "Top");
|
||||
worn_as("headgear", "Headwear");
|
||||
worn_as("eyewear", "Eyewear");
|
||||
worn_as("bottoms", "Bottoms");
|
||||
worn_as("shoes", "Shoes");
|
||||
// A full-body costume (a mod's costume is one, Own_... in cust_fullBodyCostume): worn, though its
|
||||
// category starts like the body's own parts and names the body.
|
||||
worn_as("cust_fullBodyCostume", "Costume");
|
||||
// The body's own parts, and what is not clothing.
|
||||
not_worn("custhead");
|
||||
not_worn("custbodies");
|
||||
not_worn("custnoses");
|
||||
not_worn("hair");
|
||||
not_worn("tattoos");
|
||||
not_worn("gestures");
|
||||
not_worn("");
|
||||
// A category this does not know: worn, its name made readable, after what it knows.
|
||||
const auto unknown = outfit_kind::of("patches_x");
|
||||
check(unknown && unknown->label == "Patches x" && unknown->order == 20, "an unknown category is its name made readable");
|
||||
// From head to foot.
|
||||
check(outfit_kind::of("headgear")->order < outfit_kind::of("cust_fullBodyCostume")->order &&
|
||||
outfit_kind::of("cust_fullBodyCostume")->order < outfit_kind::of("shoes")->order,
|
||||
"a costume sits between the head and the feet");
|
||||
std::cout << "outfit_kind tests passed\n";
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
// The kind of item a catalog category is, for what the skater wears (worn_items.cpp, reskate.outfit.worn):
|
||||
// clothes and accessories by the slot they are worn in, from head to foot; nothing for the body's own
|
||||
// parts, hair, tattoos, gestures or the board. Pure, so it is tested (Test/outfit_kind_tests.cpp).
|
||||
namespace dingosdk::outfit_kind {
|
||||
struct Kind {
|
||||
std::string label; // "Headwear", "Top", ... "Shoes"; a category this does not know, made readable
|
||||
int order; // from head to foot
|
||||
};
|
||||
namespace detail {
|
||||
struct Word { const char* word; const char* label; int order; };
|
||||
constexpr Word words[] = {
|
||||
{"head", "Headwear", 0}, {"hat", "Headwear", 0}, {"cap", "Headwear", 0}, {"beanie", "Headwear", 0}, {"helmet", "Headwear", 0},
|
||||
{"eye", "Eyewear", 1}, {"glass", "Eyewear", 1}, {"mask", "Face", 1},
|
||||
{"neck", "Neckwear", 2}, {"jewel", "Jewellery", 2}, {"earring", "Earrings", 2},
|
||||
{"costume", "Costume", 3}, {"outfit", "Outfit", 3},
|
||||
{"outer", "Outerwear", 4}, {"jacket", "Outerwear", 4}, {"top", "Top", 5}, {"shirt", "Top", 5}, {"hood", "Top", 5},
|
||||
{"glove", "Gloves", 6}, {"wrist", "Wrist", 6}, {"watch", "Wrist", 6}, {"bag", "Bag", 7}, {"backpack", "Bag", 7},
|
||||
{"bottom", "Bottoms", 8}, {"pant", "Bottoms", 8}, {"short", "Bottoms", 8}, {"leg", "Bottoms", 8},
|
||||
{"sock", "Socks", 9}, {"foot", "Shoes", 10}, {"shoe", "Shoes", 10}};
|
||||
constexpr const char* not_outfit[] = {"skin", "face_", "body", "hair", "beard", "brow", "makeup", "eyecolo", "eye_colo",
|
||||
"tattoo", "gesture", "emote", "voice", "bkobject", "board", "deck", "truck", "wheel", "grip",
|
||||
"locomotion", "lipcolo", "mole", "stubble", "freckle", "scar", "nose", "mouth", "teeth"};
|
||||
} // namespace detail
|
||||
|
||||
inline std::optional<Kind> of(std::string_view category) {
|
||||
std::string folded(category);
|
||||
for (auto& c : folded) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
const auto has = [&](const char* word) { return folded.find(word) != std::string::npos; };
|
||||
if (folded.empty()) return std::nullopt;
|
||||
// A full-body costume is worn over the whole skater, and its category is the body's kind of name
|
||||
// ("cust_fullBodyCostume"): taken before the body's own parts are left out.
|
||||
if (!has("costume")) {
|
||||
// The body's own parts are "cust..." (custhead, custeye, custnoses, custbodies, custmoles...).
|
||||
if (folded.starts_with("cust") || std::any_of(std::begin(detail::not_outfit), std::end(detail::not_outfit), has))
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto word = std::find_if(std::begin(detail::words), std::end(detail::words), [&](const detail::Word& w) { return has(w.word); });
|
||||
if (word != std::end(detail::words)) return Kind{word->label, word->order};
|
||||
// A category this does not know: its name made readable.
|
||||
std::string label(category);
|
||||
std::replace(label.begin(), label.end(), '_', ' ');
|
||||
label[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(label[0])));
|
||||
return Kind{label, 20};
|
||||
}
|
||||
} // namespace dingosdk::outfit_kind
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "worn_items.h"
|
||||
#include "item_browser.h"
|
||||
#include "outfit_kind.h"
|
||||
#include "local_customization.h"
|
||||
#include "local_customization_runtime.h"
|
||||
#include "Engine/Core/Platform/memory.h"
|
||||
@@ -9,8 +10,6 @@
|
||||
#include <Windows.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <string_view>
|
||||
|
||||
// What the skater wears, read from the game's own presets: ported from Fit Check (pull request 326,
|
||||
// turnaround.cpp), for plugins (reskate.outfit.worn, board, title).
|
||||
@@ -76,28 +75,8 @@ std::vector<Worn> worn_board() {
|
||||
|
||||
// What the skater wears now: every item of the selected preset
|
||||
// but the board's, labelled by its catalog category (the game's names for its grids: "tops",
|
||||
// "headgear"...), clothes and accessories only (not the body, hair, tattoos or gestures), in the
|
||||
// order they are worn from head to foot. Game thread.
|
||||
namespace {
|
||||
struct OutfitKind { const char* word; const char* label; int order; };
|
||||
constexpr OutfitKind outfit_kinds[] = {
|
||||
{"head", "Headwear", 0}, {"hat", "Headwear", 0}, {"cap", "Headwear", 0}, {"beanie", "Headwear", 0}, {"helmet", "Headwear", 0},
|
||||
{"eye", "Eyewear", 1}, {"glass", "Eyewear", 1}, {"mask", "Face", 1},
|
||||
{"neck", "Neckwear", 2}, {"jewel", "Jewellery", 2}, {"earring", "Earrings", 2},
|
||||
{"costume", "Costume", 3}, {"outfit", "Outfit", 3},
|
||||
{"outer", "Outerwear", 4}, {"jacket", "Outerwear", 4}, {"top", "Top", 5}, {"shirt", "Top", 5}, {"hood", "Top", 5},
|
||||
{"glove", "Gloves", 6}, {"wrist", "Wrist", 6}, {"watch", "Wrist", 6}, {"bag", "Bag", 7}, {"backpack", "Bag", 7},
|
||||
{"bottom", "Bottoms", 8}, {"pant", "Bottoms", 8}, {"short", "Bottoms", 8}, {"leg", "Bottoms", 8},
|
||||
{"sock", "Socks", 9}, {"foot", "Shoes", 10}, {"shoe", "Shoes", 10}};
|
||||
constexpr const char* not_outfit[] = {"skin", "face_", "body", "hair", "beard", "brow", "makeup", "eyecolo", "eye_colo",
|
||||
"tattoo", "gesture", "emote", "voice", "bkobject", "board", "deck", "truck", "wheel", "grip",
|
||||
"locomotion", "lipcolo", "mole", "stubble", "freckle", "scar", "nose", "mouth", "teeth"};
|
||||
std::string folded(std::string_view text) {
|
||||
std::string out(text);
|
||||
for (auto& c : out) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
// "headgear"...), clothes, accessories and full-body costumes only (not the body, hair, tattoos or
|
||||
// gestures; outfit_kind.h), in the order they are worn from head to foot. Game thread.
|
||||
std::vector<Worn> worn_outfit() {
|
||||
std::vector<Worn> parts;
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(GetModuleHandleW(nullptr));
|
||||
@@ -119,22 +98,9 @@ std::vector<Worn> worn_outfit() {
|
||||
for (const auto& item : recipe.items) {
|
||||
if (item.asset.empty()) continue;
|
||||
const auto entry = known.find(item.asset);
|
||||
const auto category = folded(entry != known.end() ? entry->second.category : std::string());
|
||||
const auto has = [&](const char* word) { return category.find(word) != std::string::npos; };
|
||||
// The body's own parts are "cust..." (custhead, custeye, custnoses, custbodies, custmoles...).
|
||||
if (category.empty() || category.starts_with("cust") || std::any_of(std::begin(not_outfit), std::end(not_outfit), has))
|
||||
continue;
|
||||
const auto kind = std::find_if(std::begin(outfit_kinds), std::end(outfit_kinds), [&](const OutfitKind& k) { return has(k.word); });
|
||||
std::string label;
|
||||
int order = 20;
|
||||
if (kind != std::end(outfit_kinds)) label = kind->label, order = kind->order;
|
||||
else {
|
||||
// A category this does not know: its name made readable.
|
||||
label = entry->second.category;
|
||||
std::replace(label.begin(), label.end(), '_', ' ');
|
||||
if (!label.empty()) label[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(label[0])));
|
||||
}
|
||||
found.push_back({order, {label, item.asset, item_title(item.asset)}});
|
||||
const auto kind = outfit_kind::of(entry != known.end() ? entry->second.category : std::string());
|
||||
if (!kind) continue;
|
||||
found.push_back({kind->order, {kind->label, item.asset, item_title(item.asset)}});
|
||||
}
|
||||
}
|
||||
std::stable_sort(found.begin(), found.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
|
||||
Reference in new issue
Block a user