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
2 changes: 2 additions & 0 deletions HeavenClient/Audio/Audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ namespace ms
add_sound(Sound::Name::JOBCHANGED, gamesrc["JobChanged"]);
add_sound(Sound::Name::QUESTCLEAR, gamesrc["QuestClear"]);
add_sound(Sound::Name::TOMBSTONE, gamesrc["Tombstone"]);
add_sound(Sound::Name::ENCHANTSUCCESS, gamesrc["EnchantSuccess"]);
add_sound(Sound::Name::ENCHANTFAILURE, gamesrc["EnchantFailure"]);

nl::node itemsrc = nl::nx::sound["Item.img"];

Expand Down
2 changes: 2 additions & 0 deletions HeavenClient/Audio/Audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ namespace ms
JOBCHANGED,
QUESTCLEAR,
TOMBSTONE,
ENCHANTSUCCESS,
ENCHANTFAILURE,
LENGTH
};

Expand Down
2 changes: 2 additions & 0 deletions HeavenClient/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ if(HEAVENCLIENT_BUILD_TESTS)
add_test(NAME skill_recommendation COMMAND skill_recommendation_tests)
add_executable(emote_cooldown_tests tests/emote_cooldown.cpp)
add_test(NAME emote_cooldown COMMAND emote_cooldown_tests)
add_executable(fame_cooldown_tests tests/fame_cooldown.cpp)
add_test(NAME fame_cooldown COMMAND fame_cooldown_tests)
add_executable(chat_flood_guard_tests tests/chat_flood_guard.cpp)
add_test(NAME chat_flood_guard COMMAND chat_flood_guard_tests)
add_executable(char_click_tests tests/char_click.cpp)
Expand Down
11 changes: 11 additions & 0 deletions HeavenClient/Character/Char.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ namespace ms
effects.add(effect);
}

void Char::show_skill_book(bool success)
{
effects.add(skill_book[success][0], DrawArgument(), 3);
effects.add(skill_book[success][1], DrawArgument(), -1);
}

void Char::show_iron_body()
{
ironbody.set_for(500);
Expand Down Expand Up @@ -612,6 +618,10 @@ namespace ms
for (auto iter : CharEffect::PATHS)
chareffects.emplace(iter.first, src.resolve(iter.second));

for (int success = 0; success < 2; success++)
for (int layer = 0; layer < 2; layer++)
skill_book[success][layer] = src["SkillBook"][success ? "Success" : "Failure"][std::to_string(layer)];

nl::node tomb = nl::nx::effect["Tomb.img"];
tomb_fall = tomb["fall"];
tomb_land = tomb["land"];
Expand All @@ -620,4 +630,5 @@ namespace ms
EnumMap<CharEffect::Id, Animation> Char::chareffects;
Animation Char::tomb_fall;
Animation Char::tomb_land;
Animation Char::skill_book[2][2];
}
6 changes: 6 additions & 0 deletions HeavenClient/Character/Char.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ namespace ms
// Display the iron body skill animation.
void show_iron_body();

// Display a skill or mastery book's glow: its first layer over the
// character, its second behind (v83 FUN_0043cba9, z 3 and -1).
void show_skill_book(bool success);

// Display damage over the characters head, with the hit face for
// 1.5 s if it hurt, as v83 does for every user (0x9832e3).
void show_damage(int32_t damage);
Expand Down Expand Up @@ -298,5 +302,7 @@ namespace ms
static EnumMap<CharEffect::Id, Animation> chareffects;
static Animation tomb_fall;
static Animation tomb_land;
// BasicEff.img/SkillBook: [failure, success][front, back]
static Animation skill_book[2][2];
};
}
4 changes: 3 additions & 1 deletion HeavenClient/Character/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ namespace ms

if (int16_t slot = inventory.find_item(type, itemid))
{
if (type == InventoryType::Id::USE)
if (type == InventoryType::Id::USE && (itemid / 10000 == 228 || itemid / 10000 == 229))
request(SkillBookPacket(slot, itemid), ExclRequest::USE_WAIT);
else if (type == InventoryType::Id::USE)
request(UseItemPacket(slot, itemid), ExclRequest::USE_WAIT);
else if (type == InventoryType::Id::SETUP && itemid / 10000 == 301)
sit_on_chair(itemid);
Expand Down
30 changes: 30 additions & 0 deletions HeavenClient/Gameplay/FameCooldown.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <cstdint>

namespace ms
{
// v83 sends GIVE_FAME at most once every 5 minutes (FUN_00a23eb5); a
// press in between says "Please try it again later." and sends nothing.
class FameCooldown
{
public:
// Whether fame may be given at now_ms; if so, it starts the wait.
bool try_give(int64_t now_ms)
{
if (given && now_ms - last < DELAY)
return false;

given = true;
last = now_ms;

return true;
}

private:
static constexpr int64_t DELAY = 300000;

bool given = false;
int64_t last = 0;
};
}
6 changes: 6 additions & 0 deletions HeavenClient/Gameplay/Stage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ namespace ms

start = ContinuousTimer::get().start();
emote_cooldown = {};
fame_cooldown = {};

CharStats stats = player.get_stats();
levelBefore = stats.get_stat(MapleStat::Id::LEVEL);
Expand Down Expand Up @@ -679,6 +680,11 @@ namespace ms
effect = MapEffect(path);
}

bool Stage::try_give_fame()
{
return fame_cooldown.try_give(get_uptime() / 1000);
}

int64_t Stage::get_uptime()
{
return ContinuousTimer::get().stop(start);
Expand Down
5 changes: 5 additions & 0 deletions HeavenClient/Gameplay/Stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "Combat/Combat.h"
#include "EmoteCooldown.h"
#include "FameCooldown.h"
#include "MapleMap/MapInfo.h"
#include "MapleMap/MapTilesObjs.h"
#include "MapleMap/MapBackgrounds.h"
Expand Down Expand Up @@ -128,6 +129,9 @@ namespace ms
// Set a map effect
void add_effect(std::string path);

// Whether the player may give fame now (once every 5 minutes); if
// so, the wait starts.
bool try_give_fame();
// Get the total up time of the client
int64_t get_uptime();
// Get the level at the start of the client
Expand Down Expand Up @@ -215,6 +219,7 @@ namespace ms

Combat combat;
EmoteCooldown emote_cooldown;
FameCooldown fame_cooldown;

std::chrono::time_point<std::chrono::steady_clock> start;
uint16_t levelBefore;
Expand Down
46 changes: 44 additions & 2 deletions HeavenClient/IO/UITypes/UICharInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

#include "../../Character/Job.h"
#include "../../Gameplay/Stage.h"
#include "../../IO/UI.h"
#include "../../IO/UITypes/UIChatBar.h"
#include "../../Net/Packets/PlayerInteractionPackets.h"
#include "../../Util/NxFiles.h"

#include <nlnx/nx.hpp>
Expand Down Expand Up @@ -93,10 +96,18 @@ namespace ms
buttons[Buttons::BT_EXCEPTION] = std::make_unique<MapleButton>(UserInfo["BtExceptionShow"]);
buttons[Buttons::BT_PANEL_HIDE] = std::make_unique<MapleButton>(Basic["BtHide"], PANEL + PANEL_HIDE);

// There are no families, parties, trades, fame, or pets yet.
for (auto button : { Buttons::BT_FAMILY, Buttons::BT_PARTY, Buttons::BT_TRADE, Buttons::BT_FAME_UP, Buttons::BT_FAME_DOWN, Buttons::BT_EXCEPTION })
// There are no families, parties, trades, or pets yet.
for (auto button : { Buttons::BT_FAMILY, Buttons::BT_PARTY, Buttons::BT_TRADE, Buttons::BT_EXCEPTION })
buttons[button]->set_state(Button::State::DISABLED);

// v83 offers fame only from level 15 (FUN_009007a0), and never for
// oneself (FUN_00903b86).
auto &player = Stage::get().get_player();

if (cid == player.get_oid() || player.get_stats().get_stat(MapleStat::Id::LEVEL) < 15)
for (auto button : { Buttons::BT_FAME_UP, Buttons::BT_FAME_DOWN })
buttons[button]->set_state(Button::State::DISABLED);

name = Text(Text::Font::A12M, Text::Alignment::CENTER, Color::Name::WHITE);
level = Text(Text::Font::A12M, Text::Alignment::LEFT, Color::Name::BLACK);
job = Text(Text::Font::A12M, Text::Alignment::LEFT, Color::Name::BLACK);
Expand Down Expand Up @@ -155,6 +166,12 @@ namespace ms
case Buttons::BT_PANEL_HIDE:
set_panel(Panel::NO_PANEL);
break;
case Buttons::BT_FAME_UP:
give_fame(true);
break;
case Buttons::BT_FAME_DOWN:
give_fame(false);
break;
case Buttons::BT_PET_SHOW:
set_mode(Mode::PET);
break;
Expand Down Expand Up @@ -209,6 +226,31 @@ namespace ms
alliance.change_text(a.empty() ? "-" : a);
}

void UICharInfo::change_fame(int16_t f)
{
fame.change_text(std::to_string(f));
}

void UICharInfo::give_fame(bool up) const
{
const char *refusal = nullptr;

if (!Stage::get().get_character(cid))
refusal = "Unable to find the character.";
else if (!Stage::get().try_give_fame())
refusal = "Please try it again later.";

if (refusal)
{
if (auto chatbar = UI::get().get_element<UIChatBar>())
chatbar->send_chatline(refusal, UIChatBar::LineType::SKILL);

return;
}

GiveFamePacket(cid, up).dispatch();
}

void UICharInfo::set_mode(Mode m)
{
mode = m;
Expand Down
4 changes: 4 additions & 0 deletions HeavenClient/IO/UITypes/UICharInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ namespace ms
UIElement::Type get_type() const override;

void update_stats(int32_t character_id, int16_t job_id, int8_t level, int16_t fame, std::string guild, std::string alliance);
// Show the fame the player just raised or dropped (FAME_RESPONSE).
void change_fame(int16_t fame);

private:
void give_fame(bool up) const;

// The main window alone, or lengthened to show pets, the mount, or
// the monster book collection.
enum Mode : uint8_t
Expand Down
6 changes: 6 additions & 0 deletions HeavenClient/IO/UITypes/UIItemInventory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ namespace ms
update_slot(slot);
}

void UIItemInventory::refresh_slots()
{
load_icons();
update_slider();
}

void UIItemInventory::update_slider()
{
int16_t total = (inventory.get_slotmax(tab) + COLUMNS - 1) / COLUMNS;
Expand Down
3 changes: 3 additions & 0 deletions HeavenClient/IO/UITypes/UIItemInventory.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ namespace ms

void clear_new();

// Show a tab's new slot count.
void refresh_slots();

protected:
Button::State button_pressed(uint16_t buttonid) override;

Expand Down
11 changes: 11 additions & 0 deletions HeavenClient/Net/Handlers/InventoryHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ namespace ms
}
}

void InventoryGrowHandler::handle(InPacket& recv) const
{
InventoryType::Id type = InventoryType::by_value(recv.read_byte());
uint8_t slots = recv.read_byte();

Stage::get().get_player().get_inventory().set_slotmax(type, slots);

if (auto iteminventory = UI::get().get_element<UIItemInventory>())
iteminventory->refresh_slots();
}

void ModifyInventoryHandler::handle(InPacket& recv) const
{
// Set when this answers the player's item request.
Expand Down
7 changes: 7 additions & 0 deletions HeavenClient/Net/Handlers/InventoryHandlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ namespace ms
void handle(InPacket& recv) const override;
};

// Handler for a packet which gives an inventory tab more slots (v83
// FUN_00a1f881): type, then the new slot count.
class InventoryGrowHandler : public PacketHandler
{
void handle(InPacket& recv) const override;
};

// Handler for a packet which modifies the player's inventory.
class ModifyInventoryHandler : public PacketHandler
{
Expand Down
47 changes: 47 additions & 0 deletions HeavenClient/Net/Handlers/PlayerHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "Helpers/BuffParser.h"
#include "Helpers/LoginParser.h"
#include "../../Audio/Audio.h"

#include "../Packets/LoginPackets.h"

Expand Down Expand Up @@ -253,6 +254,52 @@ namespace ms
UI::get().enable();
}

void SkillUseResultHandler::handle(InPacket& recv) const
{
recv.skip_byte();

Stage::get().get_player().request_answered();
}

void SkillLearnItemResultHandler::handle(InPacket& recv) const
{
int32_t cid = recv.read_int();
auto character = Stage::get().get_character(cid);

if (!character)
return;

bool mastery = recv.read_bool();
recv.skip_int(); // skill
recv.skip_int(); // master level
bool used = recv.read_bool();
bool success = recv.read_bool();

if (used)
character->show_skill_book(success);

if (!Stage::get().is_player(cid))
return;

Stage::get().get_player().request_answered();
Sound(success ? Sound::Name::ENCHANTSUCCESS : Sound::Name::ENCHANTFAILURE).play();

std::string book = mastery ? "Mastery Book" : "Skill Book";
std::string message;

if (!used)
message = "You cannot use " + book + ".";
else if (!success)
message = "Despite using " + book + ", the effect was nowhere to be found.";
else if (mastery)
message = "The Book of Mastery glows brightly, and the current skills have gone through an upgrade.";
else
message = "The Skill Book glows brightly, and new skills have now been added.";

if (auto chatbar = UI::get().get_element<UIChatBar>())
chatbar->send_chatline(message, UIChatBar::LineType::SKILL);
}

void SkillMacrosHandler::handle(InPacket& recv) const
{
uint8_t size = recv.read_byte();
Expand Down
14 changes: 14 additions & 0 deletions HeavenClient/Net/Handlers/PlayerHandlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ namespace ms
void handle(InPacket& recv) const override;
};

// Handler for a packet which answers the player's skill use (v83
// FUN_00a209b2): it lets the player send the next request.
class SkillUseResultHandler : public PacketHandler
{
void handle(InPacket& recv) const override;
};

// Handler for a packet which tells how someone's skill or mastery book
// went (v83 FUN_00a1e5af).
class SkillLearnItemResultHandler : public PacketHandler
{
void handle(InPacket& recv) const override;
};

// Parses skill macros
// Opcode: SKILL_MACROS(124)
class SkillMacrosHandler : public PacketHandler
Expand Down
Loading
Loading