#include <algorithm>
#include <array>
#include <cstdint>
#include <iostream>
#include <optional>
#include <ranges>
#include <span>
#include <type_traits>
// already C++23
namespace compat {
template<typename E>
requires std::is_enum_v<E>
constexpr auto to_underlying(E e) noexcept
{
return static_cast<std::underlying_type_t<E>>(e);
}
} // namespace compat
using skill_id_t = std::uint8_t;
using skill_level_t = std::uint8_t;
static constexpr std::size_t MAX_SKILL_NUM{255};
struct TPlayerSkill
{
std::uint8_t bMasterType{};
skill_level_t bLevel{};
};
using player_skill_span_t = std::span<const TPlayerSkill>;
namespace skill_system {
static constexpr skill_level_t level_threshold_perfect{30};
static constexpr skill_level_t level_threshold_legendary{40};
static constexpr std::size_t skill_group_size{6};
static constexpr std::size_t skill_group_max{2};
enum class JobType : std::uint8_t {
WARRIOR = 0U,
ASSASSIN,
SURA,
SHAMAN,
#ifdef ENABLE_WOLFMAN_CHARACTER
WOLFMAN,
#endif
MAX_NUM
};
static constexpr auto job_count{compat::to_underlying(JobType::MAX_NUM)};
struct SkillGroup
{
std::array<skill_id_t, skill_group_size> ids{};
[[nodiscard]] constexpr auto active_skills() const noexcept
{
return ids | std::views::filter([](skill_id_t id) {
return 0 != id;
});
}
};
using job_skill_table_t = std::array<std::array<SkillGroup, skill_group_max>, job_count>;
template<std::integral... Args>
consteval SkillGroup make_group(Args... args) noexcept
{
static_assert(sizeof...(Args) == skill_group_size, "Skill group size mismatch");
return SkillGroup{.ids = {static_cast<skill_id_t>(args)...}};
}
static constexpr job_skill_table_t job_skill_table{{
{{// WARRIOR
make_group(1, 2, 3, 4, 5, 6),
make_group(16, 17, 18, 19, 20, 21)
}},
{{// ASSASSIN
make_group(31, 32, 33, 34, 35, 36),
make_group(46, 47, 48, 49, 50, 51)
}},
{{// SURA
make_group(61, 62, 63, 64, 65, 66),
make_group(76, 77, 78, 79, 80, 81)
}},
{{// SHAMAN
make_group(91, 92, 93, 94, 95, 96),
make_group(106, 107, 108, 109, 110, 111)
}},
#ifdef ENABLE_WOLFMAN_CHARACTER
{{// WOLFMAN
make_group(170, 171, 172, 173, 174, 175),
make_group(0, 0, 0, 0, 0, 0)
}},
#endif
}};
static_assert(job_skill_table.size() == job_count, "Job skill table size mismatch!");
[[nodiscard]] bool is_group_valid(
JobType job, std::uint8_t group_idx, skill_level_t min_level, player_skill_span_t player_skills
) noexcept
{
const auto job_idx{compat::to_underlying(job)};
if (job_skill_table.size() <= job_idx or skill_group_max <= group_idx)
{
return false;
}
auto group_skills{job_skill_table[job_idx][group_idx].active_skills()};
return std::ranges::all_of(group_skills, [&player_skills, min_level](skill_id_t id) {
if (player_skills.size() <= id)
{
return false;
}
return player_skills[id].bLevel >= min_level;
});
}
enum class BonusType : std::uint8_t {
PERFECT,
LEGENDARY,
MAX
};
struct BonusPoint
{
std::uint8_t type;
std::uint16_t value;
};
struct BonusData
{
static constexpr std::size_t bonus_max_num{5ULL};
std::array<BonusPoint, bonus_max_num> points{};
};
// template <BonusType type>
consteval BonusData get_bonus_config(BonusType type) noexcept
{
using enum BonusType;
// all values mock
if (PERFECT == type)
{
return {.points = {{{1, 10}, {2, 20}, {3, 30}, {4, 40}, {5, 50}}}};
}
if (LEGENDARY == type)
{
return {.points = {{{1, 100}, {2, 200}, {3, 300}, {4, 400}, {5, 500}}}};
}
return {};
}
static constexpr auto bonus_config_table{
std::to_array({get_bonus_config(BonusType::PERFECT), get_bonus_config(BonusType::LEGENDARY)})
};
void apply_bonus_effect(BonusType type, bool is_adding)
{
const auto idx{compat::to_underlying(type)};
if (bonus_config_table.size() <= idx)
{
return;
}
const auto& bonus_data{bonus_config_table[idx]};
const auto type_str{(type == BonusType::PERFECT) ? "PERFECT" : "LEGENDARY"};
const auto action_str{is_adding ? "[APPLY]" : "[REMOVE]"};
std::cout << ">> " << action_str << " Bonus Type: " << type_str << "\n";
auto valid_points{bonus_data.points | std::views::filter([](const auto& elm) {
static constexpr std::uint16_t zero_value{0U};
return zero_value != elm.value;
})};
for (const auto& [pt_type, value]: valid_points)
{
const char sign{is_adding ? '+' : '-'};
std::cout << " -> Point: " << static_cast<int>(pt_type) << " Value: " << sign << value << "\n";
}
}
[[nodiscard]] std::optional<BonusType> calculate_state_bonus(
JobType job, std::uint8_t group, player_skill_span_t skills
)
{
using enum BonusType;
if (is_group_valid(job, group, level_threshold_legendary, skills))
{
return LEGENDARY;
}
if (is_group_valid(job, group, level_threshold_perfect, skills))
{
return PERFECT;
}
return std::nullopt;
}
void update_bonus_state(
JobType job, std::uint8_t group, player_skill_span_t skills, std::optional<BonusType>& current_state
)
{
const auto new_state{calculate_state_bonus(job, group, skills)};
if (new_state == current_state)
{
std::cout << "\n[NO CHANGE] Status is stable.\n";
return;
}
std::cout << "\n[STATUS CHANGE DETECTED]\n";
if (current_state.has_value())
{
apply_bonus_effect(current_state.value(), false);
}
if (new_state.has_value())
{
apply_bonus_effect(new_state.value(), true);
}
current_state = new_state;
}
} // namespace skill_system
// mock datas
static std::array<TPlayerSkill, MAX_SKILL_NUM> g_player_skills{};
static std::optional<skill_system::BonusType> g_current_bonus_state{std::nullopt};
int main()
{
using namespace skill_system;
std::cout << "--- SYSTEM START ---\n";
constexpr auto my_job{JobType::WARRIOR};
constexpr std::uint8_t my_group{0U};
const auto set_all_skills{[](skill_level_t level) {
for (int i{1}; i <= 6; ++i)
{
g_player_skills[i].bLevel = level;
}
}};
std::cout << "\n--- Scenario 1: Low Skills (All 20) ---";
set_all_skills(20);
update_bonus_state(my_job, my_group, g_player_skills, g_current_bonus_state);
std::cout << "\n--- Scenario 2: Skills Upgraded to Perfect (All 35) ---";
set_all_skills(35);
update_bonus_state(my_job, my_group, g_player_skills, g_current_bonus_state);
std::cout << "\n--- Scenario 3: Skills Upgraded to Legendary (All 45) ---";
set_all_skills(45);
update_bonus_state(my_job, my_group, g_player_skills, g_current_bonus_state);
std::cout << "\n--- Scenario 4: One Skill Downgraded (Skill[6] = 25) ---";
g_player_skills[6].bLevel = 25;
update_bonus_state(my_job, my_group, g_player_skills, g_current_bonus_state);
std::cout << "\n--- Scenario 5: Skills Restored to Perfect (All 30) ---";
set_all_skills(30);
update_bonus_state(my_job, my_group, g_player_skills, g_current_bonus_state);
return EXIT_SUCCESS;
}