Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions HeavenClient/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ if(HEAVENCLIENT_BUILD_TESTS)
add_test(NAME party_search COMMAND party_search_tests)
add_executable(rps_game_tests tests/rps_game.cpp Character/RpsGame.cpp)
add_test(NAME rps_game COMMAND rps_game_tests)
add_executable(family_tests tests/family.cpp Character/Family.cpp Net/InPacket.cpp Net/Handlers/Helpers/FamilyParser.cpp)
target_include_directories(family_tests PRIVATE ${PROJECT_SOURCE_DIR}/libs/NoLifeNx)
add_test(NAME family COMMAND family_tests)
add_executable(diseases_tests tests/diseases.cpp)
target_include_directories(diseases_tests PRIVATE ${PROJECT_SOURCE_DIR}/libs/NoLifeNx)
add_test(NAME diseases COMMAND diseases_tests)
Expand Down Expand Up @@ -494,6 +497,8 @@ add_executable(HeavenClient
Character/PartySearch.h
Character/RpsGame.cpp
Character/RpsGame.h
Character/Family.cpp
Character/Family.h
Character/Pets.h
Character/MiniRoomBalloon.cpp
Character/MiniRoomBalloon.h
Expand Down Expand Up @@ -744,6 +749,10 @@ add_executable(HeavenClient
IO/UITypes/UIPartyHP.h
IO/UITypes/UIPartySearch.cpp
IO/UITypes/UIPartySearch.h
IO/UITypes/UIFamily.cpp
IO/UITypes/UIFamily.h
IO/UITypes/UIFamilyTree.cpp
IO/UITypes/UIFamilyTree.h
IO/UITypes/UIPartySearchIcon.cpp
IO/UITypes/UIPartySearchIcon.h
IO/UITypes/UIQuestHelper.h
Expand Down Expand Up @@ -927,6 +936,8 @@ add_executable(HeavenClient
IO/PartyCommands.h
IO/PartySearchActions.cpp
IO/PartySearchActions.h
IO/FamilyActions.cpp
IO/FamilyActions.h
IO/GuildCommands.cpp
IO/GuildCommands.h
IO/ChatCommand.h
Expand Down Expand Up @@ -968,6 +979,8 @@ add_executable(HeavenClient
Net/Handlers/Helpers/AllianceNews.h
Net/Handlers/Helpers/GuildParser.cpp
Net/Handlers/Helpers/GuildParser.h
Net/Handlers/Helpers/FamilyParser.cpp
Net/Handlers/Helpers/FamilyParser.h
Net/Handlers/AttackHandlers.cpp
Net/Handlers/AttackHandlers.h
Net/Handlers/BuddyHandlers.cpp
Expand All @@ -980,6 +993,8 @@ add_executable(HeavenClient
Net/Handlers/CommonHandlers.h
Net/Handlers/GuildHandlers.cpp
Net/Handlers/GuildHandlers.h
Net/Handlers/FamilyHandlers.cpp
Net/Handlers/FamilyHandlers.h
Net/Handlers/InventoryHandlers.cpp
Net/Handlers/InventoryHandlers.h
Net/Handlers/LoginHandlers.cpp
Expand All @@ -1004,6 +1019,7 @@ add_executable(HeavenClient
Net/Packets/CharCreationPackets.h
Net/Packets/CommonPackets.h
Net/Packets/GameplayPackets.h
Net/Packets/FamilyPackets.h
Net/Packets/InventoryPackets.h
Net/Packets/LoginPackets.h
Net/Packets/MessagingPackets.h
Expand Down
189 changes: 189 additions & 0 deletions HeavenClient/Character/Family.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#include "Family.h"

#include <unordered_map>
#include <utility>

namespace ms
{
bool FamilyPrivilege::needs_target() const
{
return type == 0 || type == 1;
}

int32_t FamilyInfo::used_today(int32_t index) const
{
auto iter = used.find(index);

return iter == used.end() ? 0 : iter->second;
}

FamilyChart::FamilyChart()
{
slots.fill(-1);
}

const FamilyNode* FamilyChart::at(size_t slot) const
{
if (slot >= SLOTS || slots[slot] < 0)
return nullptr;

return &nodes[slots[slot]];
}

int32_t FamilyChart::count(int32_t key) const
{
auto iter = counts.find(key);

return iter == counts.end() ? 0 : iter->second;
}

bool FamilyChart::can_add_junior() const
{
const FamilyNode* owner = at(OWNER);

return mine && owner && owner->juniors.size() < max_juniors;
}

bool FamilyChart::passed_senior(size_t slot) const
{
if (!mine || slot < 5 || slot >= SLOTS)
return false;

const FamilyNode* node = at(slot);

if (!node || node->senior < 0)
return false;

return node->level > nodes[node->senior].level;
}

void FamilyChart::arrange(const FamilyNode& self)
{
slots.fill(-1);

int32_t owner = -1;
std::unordered_map<int32_t, int32_t> by_id;

// The leader is never put in the map, so nobody hangs from it.
for (int32_t i = 1; i < static_cast<int32_t>(nodes.size()); i++)
{
FamilyNode& node = nodes[i];

if (node.id == viewed)
{
owner = i;
node.online = false;
}

auto senior = by_id.find(node.senior_id);

if (senior != by_id.end())
{
node.senior = senior->second;
nodes[senior->second].juniors.push_back(i);
}

by_id[node.id] = i;
}

if (nodes.size() > 2)
slots[LEADER] = 0;

if (owner < 0 && mine)
{
FamilyNode local;
local.id = self.id;
local.job = self.job;
local.level = self.level;
local.online = true;
local.name = self.name;

owner = static_cast<int32_t>(nodes.size());
nodes.push_back(local);
}

if (owner < 0)
return;

slots[OWNER] = owner;

auto junior = [&](int32_t node, size_t index)
{
if (node < 0 || index >= nodes[node].juniors.size())
return -1;

return nodes[node].juniors[index];
};

int32_t senior = nodes[owner].senior;

if (senior >= 0)
{
slots[SENIOR] = senior;
slots[1] = nodes[senior].senior;
slots[4] = junior(senior, 1);
}

for (size_t i = 0; i < 2; i++)
{
int32_t first = junior(owner, i);
slots[5 + i] = first;
slots[7 + 2 * i] = junior(first, 0);
slots[8 + 2 * i] = junior(first, 1);
}
}

bool Family::loaded() const
{
return has_info;
}

bool Family::exists() const
{
return has_info && !info.name.empty();
}

const FamilyInfo& Family::get_info() const
{
return info;
}

const std::vector<FamilyPrivilege>& Family::get_privileges() const
{
return privileges;
}

bool Family::is_leader(int32_t self) const
{
return has_info && info.leader_id == self;
}

bool Family::can_use(size_t index) const
{
if (!exists() || index >= privileges.size())
return false;

const FamilyPrivilege& privilege = privileges[index];

return info.rep >= privilege.cost
&& info.used_today(static_cast<int32_t>(index)) < privilege.limit;
}

void Family::set_info(FamilyInfo value)
{
info = std::move(value);
has_info = true;
}

void Family::set_privileges(std::vector<FamilyPrivilege> value)
{
privileges = std::move(value);
}

void Family::clear()
{
has_info = false;
info = {};
privileges.clear();
}
}
128 changes: 128 additions & 0 deletions HeavenClient/Character/Family.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#pragma once

#include <array>
#include <cstdint>
#include <map>
#include <string>
#include <vector>

namespace ms
{
// An entitlement the family window offers, as the server lists them
// (FAMILY_PRIVILEGE_LIST).
struct FamilyPrivilege
{
uint8_t type = 0;
// The Rep it costs and how often it may be used a day.
int32_t cost = 0;
int32_t limit = 0;
std::string name;
std::string description;

// Types 0 and 1 ask for a family member's name first.
bool needs_target() const;
};

// The player's own numbers (FAMILY_INFO_RESULT).
struct FamilyInfo
{
int32_t rep = 0;
int32_t total_rep = 0;
int32_t today = 0;
uint16_t juniors = 0;
uint16_t max_juniors = 0;
int32_t leader_id = 0;
// Empty outside a family.
std::string name;
std::string precepts;
// Times used today, by the privilege's place in the list.
std::map<int32_t, int32_t> used;

int32_t used_today(int32_t index) const;
};

// One member of a Family Tree.
struct FamilyNode
{
int32_t id = 0;
int32_t senior_id = 0;
uint16_t job = 0;
uint8_t level = 0;
bool online = false;
int32_t rep = 0;
int32_t total_rep = 0;
int32_t senior_rep = 0;
int32_t today = 0;
int32_t map_id = 0;
int32_t minutes = 0;
std::string name;

// Places in FamilyChart::nodes, -1 for none.
int32_t senior = -1;
std::vector<int32_t> juniors;
};

// A Family Tree (FAMILY_CHART_RESULT), laid out as the client does
// (0x80a5f7): eleven boxes, four generations around the one viewed.
struct FamilyChart
{
static constexpr size_t SLOTS = 11;
static constexpr size_t LEADER = 0, SENIOR = 2, OWNER = 3;

int32_t viewed = 0;
// Whether the tree is the player's own.
bool mine = false;
std::vector<FamilyNode> nodes;
// Places in `nodes` by box, -1 when empty.
std::array<int32_t, SLOTS> slots;
// -1: everyone in the family, 0: the seniors above the one viewed,
// a junior's id: everyone below that junior.
std::map<int32_t, int32_t> counts;
std::map<int32_t, int32_t> used;
uint16_t max_juniors = 0;

FamilyChart();

// The member in a box, or nullptr.
const FamilyNode* at(size_t slot) const;
int32_t count(int32_t key) const;
// The Add Junior button: the player's own tree, with room below.
bool can_add_junior() const;
// Whether a member's name shows red: a junior of the player's tree
// who passed their senior's level.
bool passed_senior(size_t slot) const;

// Links the members as the client does: the first is the leader at
// the top and nobody's senior, the one viewed is the owner, and the
// others hang from their seniors. Without the owner in the packet,
// the player's own tree gets one from `self`.
void arrange(const FamilyNode& self);
};

// The player's family, as the server last described it.
class Family
{
public:
// The longest precepts the prompt takes.
static constexpr size_t MAX_PRECEPTS = 200;

// Every send but the two openers waits for the first INFO.
bool loaded() const;
bool exists() const;
const FamilyInfo& get_info() const;
const std::vector<FamilyPrivilege>& get_privileges() const;
// Whether `self` may edit the precepts: the leader only.
bool is_leader(int32_t self) const;
// Whether the privilege at `index` may be used now.
bool can_use(size_t index) const;

void set_info(FamilyInfo info);
void set_privileges(std::vector<FamilyPrivilege> privileges);
void clear();

private:
bool has_info = false;
FamilyInfo info;
std::vector<FamilyPrivilege> privileges;
};
}
Loading
Loading