diff --git a/CMakeLists.txt b/CMakeLists.txt index 74bf21b..b839e05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,11 @@ add_executable(L2GameServer src/game/server/game_server.cpp src/game/server/character_database_manager.cpp + # Game entities + src/game/entities/world_object.cpp + src/game/entities/creature.cpp + src/game/entities/player.cpp + # Game-specific networking src/game/network/game_connection_manager.cpp src/game/network/game_client_connection.cpp @@ -116,11 +121,16 @@ add_executable(L2GameServer src/game/packets/requests/create_char_request_packet.cpp src/game/packets/requests/request_game_start.cpp src/game/packets/requests/logout_packet.cpp - src/game/packets/requests/delete_char_packet.cpp + src/game/packets/requests/request_delete_character_packet.cpp src/game/packets/requests/restore_char_packet.cpp src/game/packets/requests/select_char_packet.cpp src/game/packets/requests/enter_world_packet.cpp src/game/packets/requests/no_op_packet.cpp + src/game/packets/requests/request_skill_cool_time.cpp + src/game/packets/requests/request_answer_join_pledge.cpp + src/game/packets/requests/request_item_list.cpp + src/game/packets/requests/request_show_mini_map.cpp + src/game/packets/requests/extended/request_manor_list.cpp # Game response packets src/game/packets/responses/ping_response.cpp @@ -128,6 +138,32 @@ add_executable(L2GameServer src/game/packets/responses/character_selection_info.cpp src/game/packets/responses/new_character_success.cpp src/game/packets/responses/character_create_success.cpp + src/game/packets/responses/character_selected.cpp + src/game/packets/responses/user_info.cpp + src/game/packets/responses/validate_location.cpp + src/game/packets/responses/item_list.cpp + src/game/packets/responses/skill_cool_time.cpp + src/game/packets/responses/shortcut_init.cpp + src/game/packets/responses/etc_status_update.cpp + src/game/packets/responses/ex_storage_max_count.cpp + src/game/packets/responses/quest_list.cpp + # Phase 3: Social/Clan packets + src/game/packets/responses/henna_info.cpp + src/game/packets/responses/pledge_skill_list.cpp + src/game/packets/responses/friend_list.cpp + src/game/packets/responses/pledge_show_member_list_all.cpp + src/game/packets/responses/pledge_status_changed.cpp + src/game/packets/responses/ask_join_pledge.cpp + src/game/packets/responses/npc_html_message.cpp + src/game/packets/responses/ex_send_manor_list.cpp + src/game/packets/responses/skill_list.cpp + src/game/packets/responses/status_update.cpp + src/game/packets/responses/char_info.cpp + # Phase 4: Welcome/System packets + src/game/packets/responses/system_message.cpp + src/game/packets/responses/ex_show_screen_message.cpp + src/game/packets/responses/action_failed.cpp + src/game/packets/responses/show_mini_map.cpp ) target_link_libraries(L2GameServer L2Core) diff --git a/src/core/packets/packet.cpp b/src/core/packets/packet.cpp index eb42f04..93e4333 100644 --- a/src/core/packets/packet.cpp +++ b/src/core/packets/packet.cpp @@ -13,10 +13,31 @@ std::vector SendablePacket::serialize(bool withPadding) std::vector SendablePacket::serialize(bool withPadding, size_t alignment) { SendablePacketBuffer buffer; + + // Write opcode automatically (handles both standard and extended packets) + writeOpcode(buffer); + + // Write packet data write(buffer); + return buffer.getData(withPadding, alignment); } +// Helper method to write opcode (handles extended packets automatically) +void SendablePacket::writeOpcode(SendablePacketBuffer &buffer) const +{ + uint16_t extendedPacketId = getExtendedPacketId(); + + if (extendedPacketId != 0) { + // Extended packet: write 0xFE prefix + 16-bit sub-opcode + buffer.writeUInt8(0xFE); + buffer.writeUInt16(extendedPacketId); + } else { + // Standard packet: write 8-bit opcode + buffer.writeUInt8(getPacketId()); + } +} + // ReadablePacket factory method (basic implementation) std::unique_ptr ReadablePacket::createFromData(const std::vector &data) { diff --git a/src/core/packets/packet.hpp b/src/core/packets/packet.hpp index bf4ae74..c8322a4 100644 --- a/src/core/packets/packet.hpp +++ b/src/core/packets/packet.hpp @@ -56,11 +56,19 @@ class SendablePacket // Packet identification virtual uint8_t getPacketId() const = 0; - virtual std::optional getExPacketId() const = 0; + + // Extended packet identification (returns 0 for standard packets) + virtual uint16_t getExtendedPacketId() const { return 0; } + + // Legacy method for backward compatibility (deprecated - use getExtendedPacketId instead) + virtual std::optional getExPacketId() const { return std::nullopt; } // Write packet data to buffer virtual void write(SendablePacketBuffer &buffer) = 0; + // Helper method to write opcode (handles extended packets automatically) + void writeOpcode(SendablePacketBuffer &buffer) const; + // Get serialized packet data virtual std::vector serialize(bool withPadding = false); virtual std::vector serialize(bool withPadding, size_t alignment); diff --git a/src/game/entities/creature.cpp b/src/game/entities/creature.cpp new file mode 100644 index 0000000..80f4f23 --- /dev/null +++ b/src/game/entities/creature.cpp @@ -0,0 +1,22 @@ +#include "creature.hpp" + +Creature::Creature(uint32_t objectId, const std::string& name) + : WorldObject(objectId) + , name_(name) + , level_(1) + , exp_(0) + , sp_(0) + , currentHp_(100.0) + , maxHp_(100.0) + , currentMp_(100.0) + , maxMp_(100.0) + , currentCp_(0.0) + , maxCp_(0.0) + , str_(10) + , dex_(10) + , con_(10) + , int_(10) + , wit_(10) + , men_(10) +{ +} \ No newline at end of file diff --git a/src/game/entities/creature.hpp b/src/game/entities/creature.hpp new file mode 100644 index 0000000..a22a431 --- /dev/null +++ b/src/game/entities/creature.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "world_object.hpp" +#include +#include + +// Base class for all living entities (players, NPCs, monsters) +class Creature : public WorldObject +{ +protected: + std::string name_; + uint32_t level_; + uint64_t exp_; + uint64_t sp_; + + // Stats + double currentHp_; + double maxHp_; + double currentMp_; + double maxMp_; + double currentCp_; + double maxCp_; + + // Base stats + uint32_t str_; + uint32_t dex_; + uint32_t con_; + uint32_t int_; + uint32_t wit_; + uint32_t men_; + +public: + Creature(uint32_t objectId, const std::string& name); + virtual ~Creature() = default; + + // Getters + const std::string& getName() const { return name_; } + uint32_t getLevel() const { return level_; } + uint64_t getExp() const { return exp_; } + uint64_t getSp() const { return sp_; } + + double getCurrentHp() const { return currentHp_; } + double getMaxHp() const { return maxHp_; } + double getCurrentMp() const { return currentMp_; } + double getMaxMp() const { return maxMp_; } + double getCurrentCp() const { return currentCp_; } + double getMaxCp() const { return maxCp_; } + + uint32_t getStr() const { return str_; } + uint32_t getDex() const { return dex_; } + uint32_t getCon() const { return con_; } + uint32_t getInt() const { return int_; } + uint32_t getWit() const { return wit_; } + uint32_t getMen() const { return men_; } + + // Setters + void setName(const std::string& name) { name_ = name; } + void setLevel(uint32_t level) { level_ = level; } + void setExp(uint64_t exp) { exp_ = exp; } + void setSp(uint64_t sp) { sp_ = sp; } + + void setCurrentHp(double hp) { currentHp_ = hp; } + void setMaxHp(double hp) { maxHp_ = hp; } + void setCurrentMp(double mp) { currentMp_ = mp; } + void setMaxMp(double mp) { maxMp_ = mp; } + void setCurrentCp(double cp) { currentCp_ = cp; } + void setMaxCp(double cp) { maxCp_ = cp; } + + void setStr(uint32_t str) { str_ = str; } + void setDex(uint32_t dex) { dex_ = dex; } + void setCon(uint32_t con) { con_ = con; } + void setInt(uint32_t int_val) { int_ = int_val; } + void setWit(uint32_t wit) { wit_ = wit; } + void setMen(uint32_t men) { men_ = men; } +}; \ No newline at end of file diff --git a/src/game/entities/player.cpp b/src/game/entities/player.cpp new file mode 100644 index 0000000..f2a0d5f --- /dev/null +++ b/src/game/entities/player.cpp @@ -0,0 +1,256 @@ +#include "player.hpp" +#include +#include + +Player::Player(uint32_t objectId, const std::string& name, const std::string& accountName) + : Creature(objectId, name) + , accountName_(accountName) + , sessionId_(0) + , clanId_(0) + , race_(0) + , classId_(0) + , sex_(0) + , face_(0) + , hairStyle_(0) + , hairColor_(0) + , karma_(0) + , pvpKills_(0) + , pkKills_(0) + , fame_(0) + , baseClassId_(0) + , deleteTimer_(0) + , enchantEffect_(0) + , augmentationId_(0) + , paperdollObjectIds_(16, 0) + , paperdollItemIds_(16, 0) + , clan_(nullptr) + , party_(nullptr) + , isOnline_(false) + , isInDuel_(false) + , isNoble_(false) + , isHero_(false) + , accessLevel_(0) + , mountNpcId_(0) + , mountLevel_(0) + , mountObjectId_(0) + , agathionId_(0) + , vitalityPoints_(0) + , pcCafePoints_(0) + , onlineTime_(0) + , lastAccess_(0) + , createDate_(0) + , lastRecomUpdate_(0) + , recomHave_(0) + , recomLeft_(0) + , deathPenaltyBuffLevel_(0) + , charges_(0) + , powerGrade_(0) + , pledgeClass_(0) + , pledgeType_(0) + , apprentice_(0) + , sponsor_(0) + , clanJoinExpiryTime_(0) + , clanCreateExpiryTime_(0) + , lvlJoinedAcademy_(0) + , wantsPeace_(0) + , partyRoom_(0) + , siegeState_(0) + , siegeSide_(0) + , olympiadGameId_(0) + , olympiadSide_(0) + , olympiadBuffCount_(0) + , duelId_(0) + , duelState_(0) + , pvpFlag_(0) + , pvpFlagLasts_(0) + , teleMode_(0) + , partyDistributionType_(0) + , privateStoreType_(0) + , dietMode_(0) + , tradeRefusal_(0) + , exchangeRefusal_(0) + , messageRefusal_(0) + , silenceMode_(0) + , inventoryBlockingStatus_(0) + , expertiseArmorPenalty_(0) + , expertiseWeaponPenalty_(0) + , expertisePenaltyBonus_(0) + , weightPenalty_(0) + , curWeightPenalty_(0) + , inventoryLimit_(0) + , warehouseLimit_(0) + , privateSellStoreLimit_(0) + , privateBuyStoreLimit_(0) + , dwarfRecipeLimit_(0) + , commonRecipeLimit_(0) + , questInventoryLimit_(0) + , bookmarkslot_(0) + , language_(0) + , faction_(0) + , newbie_(0) + , nobless_(0) + , isIn7sDungeon_(0) + , clanPrivileges_(0) + , subpledge_(0) + , titleColor_(0) + , title_(0) + , cancraft_(0) + , onlinetime_(0) + , isin7sdungeon_(0) + , last_recom_date_(0) + , rec_have_(0) + , rec_left_(0) + , death_penalty_level_(0) + , vitality_points_(0) + , pccafe_points_(0) +{ +} + +void Player::setPaperdollObjectId(size_t slot, uint32_t objectId) +{ + if (slot < paperdollObjectIds_.size()) { + paperdollObjectIds_[slot] = objectId; + } +} + +void Player::setPaperdollItemId(size_t slot, uint32_t itemId) +{ + if (slot < paperdollItemIds_.size()) { + paperdollItemIds_[slot] = itemId; + } +} + +void Player::dump() const +{ + std::cout << "\n=== Player Dump ===" << std::endl; + std::cout << "Object ID: " << getObjectId() << std::endl; + std::cout << "Name: " << getName() << std::endl; + std::cout << "Account: " << accountName_ << std::endl; + std::cout << "Session ID: " << sessionId_ << std::endl; + std::cout << "Position: (" << getX() << ", " << getY() << ", " << getZ() << ") heading=" << getHeading() << std::endl; + + std::cout << "\n--- Stats ---" << std::endl; + std::cout << "Level: " << getLevel() << std::endl; + std::cout << "EXP: " << getExp() << std::endl; + std::cout << "SP: " << getSp() << std::endl; + std::cout << "HP: " << getCurrentHp() << "/" << getMaxHp() << std::endl; + std::cout << "MP: " << getCurrentMp() << "/" << getMaxMp() << std::endl; + std::cout << "CP: " << getCurrentCp() << "/" << getMaxCp() << std::endl; + + std::cout << "\n--- Base Stats ---" << std::endl; + std::cout << "STR: " << getStr() << " DEX: " << getDex() << " CON: " << getCon() << std::endl; + std::cout << "INT: " << getInt() << " WIT: " << getWit() << " MEN: " << getMen() << std::endl; + + std::cout << "\n--- Character Info ---" << std::endl; + std::cout << "Race: " << race_ << " (0=human, 1=elf, 2=dark_elf, 3=orc, 4=dwarf)" << std::endl; + std::cout << "Class ID: " << classId_ << std::endl; + std::cout << "Base Class ID: " << baseClassId_ << std::endl; + std::cout << "Sex: " << sex_ << " (0=male, 1=female)" << std::endl; + std::cout << "Face: " << face_ << std::endl; + std::cout << "Hair Style: " << hairStyle_ << std::endl; + std::cout << "Hair Color: " << hairColor_ << std::endl; + + std::cout << "\n--- Status ---" << std::endl; + std::cout << "Karma: " << karma_ << std::endl; + std::cout << "PvP Kills: " << pvpKills_ << std::endl; + std::cout << "PK Kills: " << pkKills_ << std::endl; + std::cout << "Fame: " << fame_ << std::endl; + std::cout << "Clan ID: " << clanId_ << std::endl; + std::cout << "Delete Timer: " << deleteTimer_ << std::endl; + std::cout << "Enchant Effect: " << enchantEffect_ << std::endl; + std::cout << "Augmentation ID: " << augmentationId_ << std::endl; + + std::cout << "\n--- Equipment (Paperdoll) ---" << std::endl; + for (size_t i = 0; i < paperdollObjectIds_.size(); ++i) { + if (paperdollObjectIds_[i] != 0 || paperdollItemIds_[i] != 0) { + std::cout << "Slot " << i << ": ObjectID=" << paperdollObjectIds_[i] + << " ItemID=" << paperdollItemIds_[i] << std::endl; + } + } + + std::cout << "\n--- Stubbed Properties (All return defaults) ---" << std::endl; + std::cout << "Skills count: " << skills_.size() << std::endl; + std::cout << "Clan: " << (clan_ ? "exists" : "nullptr") << std::endl; + std::cout << "Party: " << (party_ ? "exists" : "nullptr") << std::endl; + std::cout << "Inventory count: " << inventory_.size() << std::endl; + std::cout << "Online: " << (isOnline_ ? "true" : "false") << std::endl; + std::cout << "In Duel: " << (isInDuel_ ? "true" : "false") << std::endl; + std::cout << "Noble: " << (isNoble_ ? "true" : "false") << std::endl; + std::cout << "Hero: " << (isHero_ ? "true" : "false") << std::endl; + std::cout << "Access Level: " << accessLevel_ << std::endl; + std::cout << "Mount NPC ID: " << mountNpcId_ << std::endl; + std::cout << "Mount Level: " << mountLevel_ << std::endl; + std::cout << "Mount Object ID: " << mountObjectId_ << std::endl; + std::cout << "Agathion ID: " << agathionId_ << std::endl; + std::cout << "Vitality Points: " << vitalityPoints_ << std::endl; + std::cout << "PC Cafe Points: " << pcCafePoints_ << std::endl; + std::cout << "Online Time: " << onlineTime_ << std::endl; + std::cout << "Last Access: " << lastAccess_ << std::endl; + std::cout << "Create Date: " << createDate_ << std::endl; + std::cout << "Last Recom Update: " << lastRecomUpdate_ << std::endl; + std::cout << "Recom Have: " << recomHave_ << std::endl; + std::cout << "Recom Left: " << recomLeft_ << std::endl; + std::cout << "Death Penalty Buff Level: " << deathPenaltyBuffLevel_ << std::endl; + std::cout << "Charges: " << charges_ << std::endl; + std::cout << "Power Grade: " << powerGrade_ << std::endl; + std::cout << "Pledge Class: " << pledgeClass_ << std::endl; + std::cout << "Pledge Type: " << pledgeType_ << std::endl; + std::cout << "Apprentice: " << apprentice_ << std::endl; + std::cout << "Sponsor: " << sponsor_ << std::endl; + std::cout << "Clan Join Expiry Time: " << clanJoinExpiryTime_ << std::endl; + std::cout << "Clan Create Expiry Time: " << clanCreateExpiryTime_ << std::endl; + std::cout << "Level Joined Academy: " << lvlJoinedAcademy_ << std::endl; + std::cout << "Wants Peace: " << wantsPeace_ << std::endl; + std::cout << "Party Room: " << partyRoom_ << std::endl; + std::cout << "Siege State: " << siegeState_ << std::endl; + std::cout << "Siege Side: " << siegeSide_ << std::endl; + std::cout << "Olympiad Game ID: " << olympiadGameId_ << std::endl; + std::cout << "Olympiad Side: " << olympiadSide_ << std::endl; + std::cout << "Olympiad Buff Count: " << olympiadBuffCount_ << std::endl; + std::cout << "Duel ID: " << duelId_ << std::endl; + std::cout << "Duel State: " << duelState_ << std::endl; + std::cout << "PvP Flag: " << pvpFlag_ << std::endl; + std::cout << "PvP Flag Lasts: " << pvpFlagLasts_ << std::endl; + std::cout << "Tele Mode: " << teleMode_ << std::endl; + std::cout << "Party Distribution Type: " << partyDistributionType_ << std::endl; + std::cout << "Private Store Type: " << privateStoreType_ << std::endl; + std::cout << "Diet Mode: " << dietMode_ << std::endl; + std::cout << "Trade Refusal: " << tradeRefusal_ << std::endl; + std::cout << "Exchange Refusal: " << exchangeRefusal_ << std::endl; + std::cout << "Message Refusal: " << messageRefusal_ << std::endl; + std::cout << "Silence Mode: " << silenceMode_ << std::endl; + std::cout << "Inventory Blocking Status: " << inventoryBlockingStatus_ << std::endl; + std::cout << "Expertise Armor Penalty: " << expertiseArmorPenalty_ << std::endl; + std::cout << "Expertise Weapon Penalty: " << expertiseWeaponPenalty_ << std::endl; + std::cout << "Expertise Penalty Bonus: " << expertisePenaltyBonus_ << std::endl; + std::cout << "Weight Penalty: " << weightPenalty_ << std::endl; + std::cout << "Current Weight Penalty: " << curWeightPenalty_ << std::endl; + std::cout << "Inventory Limit: " << inventoryLimit_ << std::endl; + std::cout << "Warehouse Limit: " << warehouseLimit_ << std::endl; + std::cout << "Private Sell Store Limit: " << privateSellStoreLimit_ << std::endl; + std::cout << "Private Buy Store Limit: " << privateBuyStoreLimit_ << std::endl; + std::cout << "Dwarf Recipe Limit: " << dwarfRecipeLimit_ << std::endl; + std::cout << "Common Recipe Limit: " << commonRecipeLimit_ << std::endl; + std::cout << "Quest Inventory Limit: " << questInventoryLimit_ << std::endl; + std::cout << "Bookmark Slot: " << bookmarkslot_ << std::endl; + std::cout << "Language: " << language_ << std::endl; + std::cout << "Faction: " << faction_ << std::endl; + std::cout << "Newbie: " << newbie_ << std::endl; + std::cout << "Nobless: " << nobless_ << std::endl; + std::cout << "Is In 7s Dungeon: " << isIn7sDungeon_ << std::endl; + std::cout << "Clan Privileges: " << clanPrivileges_ << std::endl; + std::cout << "Subpledge: " << subpledge_ << std::endl; + std::cout << "Title Color: " << titleColor_ << std::endl; + std::cout << "Title: " << title_ << std::endl; + std::cout << "Can Craft: " << cancraft_ << std::endl; + std::cout << "Online Time: " << onlinetime_ << std::endl; + std::cout << "Is In 7s Dungeon: " << isin7sdungeon_ << std::endl; + std::cout << "Last Recom Date: " << last_recom_date_ << std::endl; + std::cout << "Rec Have: " << rec_have_ << std::endl; + std::cout << "Rec Left: " << rec_left_ << std::endl; + std::cout << "Death Penalty Level: " << death_penalty_level_ << std::endl; + std::cout << "Vitality Points: " << vitality_points_ << std::endl; + std::cout << "PC Cafe Points: " << pccafe_points_ << std::endl; + + std::cout << "=== End Player Dump ===\n" << std::endl; +} \ No newline at end of file diff --git a/src/game/entities/player.hpp b/src/game/entities/player.hpp new file mode 100644 index 0000000..015caaa --- /dev/null +++ b/src/game/entities/player.hpp @@ -0,0 +1,374 @@ +#pragma once + +#include "creature.hpp" +#include +#include +#include +#include + +// Forward declarations for stubbed classes +class Clan; +class Party; +class Skill; +class Item; + +// Player class - represents a player character in the game +class Player : public Creature +{ +private: + // Core player properties (implemented for character selection) + std::string accountName_; + uint32_t sessionId_; + uint32_t clanId_; + uint32_t race_; // 0=human, 1=elf, 2=dark_elf, 3=orc, 4=dwarf + uint32_t classId_; + uint32_t sex_; // 0=male, 1=female + uint32_t face_; + uint32_t hairStyle_; + uint32_t hairColor_; + uint32_t karma_; + uint32_t pvpKills_; + uint32_t pkKills_; + uint32_t fame_; + uint32_t baseClassId_; + uint32_t deleteTimer_; + uint32_t enchantEffect_; + uint32_t augmentationId_; + + // Equipment (paperdoll) - 16 slots each + std::vector paperdollObjectIds_; + std::vector paperdollItemIds_; + + // Stubbed properties (return defaults) + std::vector skills_; // Stub: return empty vector + Clan *clan_; // Stub: return nullptr + Party *party_; // Stub: return nullptr + std::vector inventory_; // Stub: return empty vector + bool isOnline_; // Stub: return false + bool isInDuel_; // Stub: return false + bool isNoble_; // Stub: return false + bool isHero_; // Stub: return false + uint32_t accessLevel_; // Stub: return 0 + uint32_t mountNpcId_; // Stub: return 0 + uint32_t mountLevel_; // Stub: return 0 + uint32_t mountObjectId_; // Stub: return 0 + uint32_t agathionId_; // Stub: return 0 + uint32_t vitalityPoints_; // Stub: return 0 + uint32_t pcCafePoints_; // Stub: return 0 + uint32_t onlineTime_; // Stub: return 0 + uint32_t lastAccess_; // Stub: return 0 + uint32_t createDate_; // Stub: return 0 + uint32_t lastRecomUpdate_; // Stub: return 0 + uint32_t recomHave_; // Stub: return 0 + uint32_t recomLeft_; // Stub: return 0 + uint32_t deathPenaltyBuffLevel_; // Stub: return 0 + uint32_t charges_; // Stub: return 0 + uint32_t powerGrade_; // Stub: return 0 + uint32_t pledgeClass_; // Stub: return 0 + uint32_t pledgeType_; // Stub: return 0 + uint32_t apprentice_; // Stub: return 0 + uint32_t sponsor_; // Stub: return 0 + uint32_t clanJoinExpiryTime_; // Stub: return 0 + uint32_t clanCreateExpiryTime_; // Stub: return 0 + uint32_t lvlJoinedAcademy_; // Stub: return 0 + uint32_t wantsPeace_; // Stub: return 0 + uint32_t partyRoom_; // Stub: return 0 + uint32_t siegeState_; // Stub: return 0 + uint32_t siegeSide_; // Stub: return 0 + uint32_t olympiadGameId_; // Stub: return 0 + uint32_t olympiadSide_; // Stub: return 0 + uint32_t olympiadBuffCount_; // Stub: return 0 + uint32_t duelId_; // Stub: return 0 + uint32_t duelState_; // Stub: return 0 + uint32_t pvpFlag_; // Stub: return 0 + uint32_t pvpFlagLasts_; // Stub: return 0 + uint32_t teleMode_; // Stub: return 0 + uint32_t partyDistributionType_; // Stub: return 0 + uint32_t privateStoreType_; // Stub: return 0 + uint32_t dietMode_; // Stub: return 0 + uint32_t tradeRefusal_; // Stub: return 0 + uint32_t exchangeRefusal_; // Stub: return 0 + uint32_t messageRefusal_; // Stub: return 0 + uint32_t silenceMode_; // Stub: return 0 + uint32_t inventoryBlockingStatus_; // Stub: return 0 + uint32_t expertiseArmorPenalty_; // Stub: return 0 + uint32_t expertiseWeaponPenalty_; // Stub: return 0 + uint32_t expertisePenaltyBonus_; // Stub: return 0 + uint32_t weightPenalty_; // Stub: return 0 + uint32_t curWeightPenalty_; // Stub: return 0 + uint32_t inventoryLimit_; // Stub: return 0 + uint32_t warehouseLimit_; // Stub: return 0 + uint32_t privateSellStoreLimit_; // Stub: return 0 + uint32_t privateBuyStoreLimit_; // Stub: return 0 + uint32_t dwarfRecipeLimit_; // Stub: return 0 + uint32_t commonRecipeLimit_; // Stub: return 0 + uint32_t questInventoryLimit_; // Stub: return 0 + uint32_t bookmarkslot_; // Stub: return 0 + uint32_t language_; // Stub: return 0 + uint32_t faction_; // Stub: return 0 + uint32_t newbie_; // Stub: return 0 + uint32_t nobless_; // Stub: return 0 + uint32_t isIn7sDungeon_; // Stub: return 0 + uint32_t clanPrivileges_; // Stub: return 0 + uint32_t subpledge_; // Stub: return 0 + uint32_t titleColor_; // Stub: return 0 + uint32_t title_; // Stub: return 0 + uint32_t cancraft_; // Stub: return 0 + uint32_t onlinetime_; // Stub: return 0 + uint32_t isin7sdungeon_; // Stub: return 0 + uint32_t last_recom_date_; // Stub: return 0 + uint32_t rec_have_; // Stub: return 0 + uint32_t rec_left_; // Stub: return 0 + uint32_t death_penalty_level_; // Stub: return 0 + uint32_t vitality_points_; // Stub: return 0 + uint32_t pccafe_points_; // Stub: return 0 + +public: + // Constructors + Player(uint32_t objectId, const std::string &name, const std::string &accountName); + virtual ~Player() = default; + + // Core methods (implemented for character selection) + const std::string &getAccountName() const { return accountName_; } + uint32_t getSessionId() const { return sessionId_; } + uint32_t getClanId() const { return clanId_; } + uint32_t getRace() const { return race_; } + uint32_t getClassId() const { return classId_; } + uint32_t getSex() const { return sex_; } + uint32_t getFace() const { return face_; } + uint32_t getHairStyle() const { return hairStyle_; } + uint32_t getHairColor() const { return hairColor_; } + uint32_t getKarma() const { return karma_; } + uint32_t getPvpKills() const { return pvpKills_; } + uint32_t getPkKills() const { return pkKills_; } + uint32_t getFame() const { return fame_; } + uint32_t getBaseClassId() const { return baseClassId_; } + uint32_t getDeleteTimer() const { return deleteTimer_; } + uint32_t getEnchantEffect() const { return enchantEffect_; } + uint32_t getAugmentationId() const { return augmentationId_; } + + const std::vector &getPaperdollObjectIds() const { return paperdollObjectIds_; } + const std::vector &getPaperdollItemIds() const { return paperdollItemIds_; } + + void setAccountName(const std::string &accountName) { accountName_ = accountName; } + void setSessionId(uint32_t sessionId) { sessionId_ = sessionId; } + void setClanId(uint32_t clanId) { clanId_ = clanId; } + void setRace(uint32_t race) { race_ = race; } + void setClassId(uint32_t classId) { classId_ = classId; } + void setSex(uint32_t sex) { sex_ = sex; } + void setFace(uint32_t face) { face_ = face; } + void setHairStyle(uint32_t hairStyle) { hairStyle_ = hairStyle; } + void setHairColor(uint32_t hairColor) { hairColor_ = hairColor; } + void setKarma(uint32_t karma) { karma_ = karma; } + void setPvpKills(uint32_t pvpKills) { pvpKills_ = pvpKills; } + void setPkKills(uint32_t pkKills) { pkKills_ = pkKills; } + void setFame(uint32_t fame) { fame_ = fame; } + void setBaseClassId(uint32_t baseClassId) { baseClassId_ = baseClassId; } + void setDeleteTimer(uint32_t deleteTimer) { deleteTimer_ = deleteTimer; } + void setEnchantEffect(uint32_t enchantEffect) { enchantEffect_ = enchantEffect; } + void setAugmentationId(uint32_t augmentationId) { augmentationId_ = augmentationId; } + + void setPaperdollObjectId(size_t slot, uint32_t objectId); + void setPaperdollItemId(size_t slot, uint32_t itemId); + + // Debug method + void dump() const; + + // Stubbed methods (return defaults) + [[maybe_unused]] const std::vector &getSkills() const { return skills_; } + [[maybe_unused]] Clan *getClan() const { return clan_; } + [[maybe_unused]] Party *getParty() const { return party_; } + [[maybe_unused]] const std::vector &getInventory() const { return inventory_; } + [[maybe_unused]] bool isOnline() const { return isOnline_; } + [[maybe_unused]] bool isInDuel() const { return isInDuel_; } + [[maybe_unused]] bool isNoble() const { return isNoble_; } + [[maybe_unused]] bool isHero() const { return isHero_; } + [[maybe_unused]] uint32_t getAccessLevel() const { return accessLevel_; } + [[maybe_unused]] uint32_t getMountNpcId() const { return mountNpcId_; } + [[maybe_unused]] uint32_t getMountLevel() const { return mountLevel_; } + [[maybe_unused]] uint32_t getMountObjectId() const { return mountObjectId_; } + [[maybe_unused]] uint32_t getAgathionId() const { return agathionId_; } + [[maybe_unused]] uint32_t getVitalityPoints() const { return vitalityPoints_; } + [[maybe_unused]] uint32_t getPcCafePoints() const { return pcCafePoints_; } + [[maybe_unused]] uint32_t getOnlineTime() const { return onlineTime_; } + [[maybe_unused]] uint32_t getLastAccess() const { return lastAccess_; } + [[maybe_unused]] uint32_t getCreateDate() const { return createDate_; } + [[maybe_unused]] uint32_t getLastRecomUpdate() const { return lastRecomUpdate_; } + [[maybe_unused]] uint32_t getRecomHave() const { return recomHave_; } + [[maybe_unused]] uint32_t getRecomLeft() const { return recomLeft_; } + [[maybe_unused]] uint32_t getDeathPenaltyBuffLevel() const { return deathPenaltyBuffLevel_; } + [[maybe_unused]] uint32_t getCharges() const { return charges_; } + [[maybe_unused]] uint32_t getPowerGrade() const { return powerGrade_; } + [[maybe_unused]] uint32_t getPledgeClass() const { return pledgeClass_; } + [[maybe_unused]] uint32_t getPledgeType() const { return pledgeType_; } + [[maybe_unused]] uint32_t getApprentice() const { return apprentice_; } + [[maybe_unused]] uint32_t getSponsor() const { return sponsor_; } + [[maybe_unused]] uint32_t getClanJoinExpiryTime() const { return clanJoinExpiryTime_; } + [[maybe_unused]] uint32_t getClanCreateExpiryTime() const { return clanCreateExpiryTime_; } + [[maybe_unused]] uint32_t getLvlJoinedAcademy() const { return lvlJoinedAcademy_; } + [[maybe_unused]] uint32_t getWantsPeace() const { return wantsPeace_; } + [[maybe_unused]] uint32_t getPartyRoom() const { return partyRoom_; } + [[maybe_unused]] uint32_t getSiegeState() const { return siegeState_; } + [[maybe_unused]] uint32_t getSiegeSide() const { return siegeSide_; } + [[maybe_unused]] uint32_t getOlympiadGameId() const { return olympiadGameId_; } + [[maybe_unused]] uint32_t getOlympiadSide() const { return olympiadSide_; } + [[maybe_unused]] uint32_t getOlympiadBuffCount() const { return olympiadBuffCount_; } + [[maybe_unused]] uint32_t getDuelId() const { return duelId_; } + [[maybe_unused]] uint32_t getDuelState() const { return duelState_; } + [[maybe_unused]] uint32_t getPvpFlag() const { return pvpFlag_; } + [[maybe_unused]] uint32_t getPvpFlagLasts() const { return pvpFlagLasts_; } + [[maybe_unused]] uint32_t getTeleMode() const { return teleMode_; } + [[maybe_unused]] uint32_t getPartyDistributionType() const { return partyDistributionType_; } + [[maybe_unused]] uint32_t getPrivateStoreType() const { return privateStoreType_; } + [[maybe_unused]] uint32_t getDietMode() const { return dietMode_; } + [[maybe_unused]] uint32_t getTradeRefusal() const { return tradeRefusal_; } + [[maybe_unused]] uint32_t getExchangeRefusal() const { return exchangeRefusal_; } + [[maybe_unused]] uint32_t getMessageRefusal() const { return messageRefusal_; } + [[maybe_unused]] uint32_t getSilenceMode() const { return silenceMode_; } + [[maybe_unused]] uint32_t getInventoryBlockingStatus() const { return inventoryBlockingStatus_; } + [[maybe_unused]] uint32_t getExpertiseArmorPenalty() const { return expertiseArmorPenalty_; } + [[maybe_unused]] uint32_t getExpertiseWeaponPenalty() const { return expertiseWeaponPenalty_; } + [[maybe_unused]] uint32_t getExpertisePenaltyBonus() const { return expertisePenaltyBonus_; } + [[maybe_unused]] uint32_t getWeightPenalty() const { return weightPenalty_; } + [[maybe_unused]] uint32_t getCurWeightPenalty() const { return curWeightPenalty_; } + [[maybe_unused]] uint32_t getInventoryLimit() const { return inventoryLimit_; } + [[maybe_unused]] uint32_t getWarehouseLimit() const { return warehouseLimit_; } + [[maybe_unused]] uint32_t getPrivateSellStoreLimit() const { return privateSellStoreLimit_; } + [[maybe_unused]] uint32_t getPrivateBuyStoreLimit() const { return privateBuyStoreLimit_; } + [[maybe_unused]] uint32_t getDwarfRecipeLimit() const { return dwarfRecipeLimit_; } + [[maybe_unused]] uint32_t getCommonRecipeLimit() const { return commonRecipeLimit_; } + [[maybe_unused]] uint32_t getQuestInventoryLimit() const { return questInventoryLimit_; } + [[maybe_unused]] uint32_t getBookmarkslot() const { return bookmarkslot_; } + [[maybe_unused]] uint32_t getLanguage() const { return language_; } + [[maybe_unused]] uint32_t getFaction() const { return faction_; } + [[maybe_unused]] uint32_t getNewbie() const { return newbie_; } + [[maybe_unused]] uint32_t getNobless() const { return nobless_; } + [[maybe_unused]] uint32_t getIsIn7sDungeon() const { return isIn7sDungeon_; } + [[maybe_unused]] uint32_t getClanPrivileges() const { return clanPrivileges_; } + [[maybe_unused]] uint32_t getSubpledge() const { return subpledge_; } + [[maybe_unused]] uint32_t getTitleColor() const { return titleColor_; } + [[maybe_unused]] uint32_t getTitleId() const { return title_; } + [[maybe_unused]] uint32_t getCancraft() const { return cancraft_; } + [[maybe_unused]] uint32_t getOnlinetime() const { return onlinetime_; } + [[maybe_unused]] uint32_t getIsin7sdungeon() const { return isin7sdungeon_; } + [[maybe_unused]] uint32_t getLastRecomDate() const { return last_recom_date_; } + [[maybe_unused]] uint32_t getRecHave() const { return rec_have_; } + [[maybe_unused]] uint32_t getRecLeft() const { return rec_left_; } + [[maybe_unused]] uint32_t getDeathPenaltyLevel() const { return death_penalty_level_; } + [[maybe_unused]] uint32_t getPccafePoints() const { return pccafe_points_; } + + // Additional methods needed for CharInfo packet + bool isFemale() const { return sex_ == 1; } + uint32_t getBaseClass() const { return baseClassId_; } + uint32_t getMAtkSpd() const { return 500; } // Magic attack speed + uint32_t getPAtkSpd() const { return 500; } // Physical attack speed + double getMovementSpeedMultiplier() const { return 1.0; } + uint32_t getRunSpeed() const { return 120; } + uint32_t getWalkSpeed() const { return 50; } + uint32_t getSwimRunSpeed() const { return 50; } + uint32_t getSwimWalkSpeed() const { return 20; } + bool isFlying() const { return false; } + double getAttackSpeedMultiplier() const { return 1.0; } + double getCollisionRadius() const { return 9.0; } + double getCollisionHeight() const { return 16.0; } + std::string getTitle() const { return ""; } + uint32_t getAllyId() const { return 0; } + uint32_t getClanCrestId() const { return 0; } + uint32_t getAllyCrestId() const { return 0; } + bool isSitting() const { return false; } + bool isRunning() const { return true; } + bool isInCombat() const { return false; } + bool isAlikeDead() const { return false; } + bool isInvisible() const { return false; } + uint32_t getAbnormalVisualEffects() const { return 0; } + uint32_t getPlayerClass() const { return classId_; } + uint32_t getClanCrestLargeId() const { return 0; } + bool isFishing() const { return false; } + uint32_t getFishX() const { return 0; } + uint32_t getFishY() const { return 0; } + uint32_t getFishZ() const { return 0; } + uint32_t getNameColor() const { return 0xFFFFFF; } + + // Stubbed setters + [[maybe_unused]] void setSkills(const std::vector &skills) { skills_ = skills; } + [[maybe_unused]] void setClan(Clan *clan) { clan_ = clan; } + [[maybe_unused]] void setParty(Party *party) { party_ = party; } + [[maybe_unused]] void setInventory(const std::vector &inventory) { inventory_ = inventory; } + [[maybe_unused]] void setOnline(bool online) { isOnline_ = online; } + [[maybe_unused]] void setInDuel(bool inDuel) { isInDuel_ = inDuel; } + [[maybe_unused]] void setNoble(bool noble) { isNoble_ = noble; } + [[maybe_unused]] void setHero(bool hero) { isHero_ = hero; } + [[maybe_unused]] void setAccessLevel(uint32_t accessLevel) { accessLevel_ = accessLevel; } + [[maybe_unused]] void setMountNpcId(uint32_t mountNpcId) { mountNpcId_ = mountNpcId; } + [[maybe_unused]] void setMountLevel(uint32_t mountLevel) { mountLevel_ = mountLevel; } + [[maybe_unused]] void setMountObjectId(uint32_t mountObjectId) { mountObjectId_ = mountObjectId; } + [[maybe_unused]] void setAgathionId(uint32_t agathionId) { agathionId_ = agathionId; } + [[maybe_unused]] void setVitalityPoints(uint32_t vitalityPoints) { vitalityPoints_ = vitalityPoints; } + [[maybe_unused]] void setPcCafePoints(uint32_t pcCafePoints) { pcCafePoints_ = pcCafePoints; } + [[maybe_unused]] void setOnlineTime(uint32_t onlineTime) { onlineTime_ = onlineTime; } + [[maybe_unused]] void setLastAccess(uint32_t lastAccess) { lastAccess_ = lastAccess; } + [[maybe_unused]] void setCreateDate(uint32_t createDate) { createDate_ = createDate; } + [[maybe_unused]] void setLastRecomUpdate(uint32_t lastRecomUpdate) { lastRecomUpdate_ = lastRecomUpdate; } + [[maybe_unused]] void setRecomHave(uint32_t recomHave) { recomHave_ = recomHave; } + [[maybe_unused]] void setRecomLeft(uint32_t recomLeft) { recomLeft_ = recomLeft; } + [[maybe_unused]] void setDeathPenaltyBuffLevel(uint32_t deathPenaltyBuffLevel) { deathPenaltyBuffLevel_ = deathPenaltyBuffLevel; } + [[maybe_unused]] void setCharges(uint32_t charges) { charges_ = charges; } + [[maybe_unused]] void setPowerGrade(uint32_t powerGrade) { powerGrade_ = powerGrade; } + [[maybe_unused]] void setPledgeClass(uint32_t pledgeClass) { pledgeClass_ = pledgeClass; } + [[maybe_unused]] void setPledgeType(uint32_t pledgeType) { pledgeType_ = pledgeType; } + [[maybe_unused]] void setApprentice(uint32_t apprentice) { apprentice_ = apprentice; } + [[maybe_unused]] void setSponsor(uint32_t sponsor) { sponsor_ = sponsor; } + [[maybe_unused]] void setClanJoinExpiryTime(uint32_t clanJoinExpiryTime) { clanJoinExpiryTime_ = clanJoinExpiryTime; } + [[maybe_unused]] void setClanCreateExpiryTime(uint32_t clanCreateExpiryTime) { clanCreateExpiryTime_ = clanCreateExpiryTime; } + [[maybe_unused]] void setLvlJoinedAcademy(uint32_t lvlJoinedAcademy) { lvlJoinedAcademy_ = lvlJoinedAcademy; } + [[maybe_unused]] void setWantsPeace(uint32_t wantsPeace) { wantsPeace_ = wantsPeace; } + [[maybe_unused]] void setPartyRoom(uint32_t partyRoom) { partyRoom_ = partyRoom; } + [[maybe_unused]] void setSiegeState(uint32_t siegeState) { siegeState_ = siegeState; } + [[maybe_unused]] void setSiegeSide(uint32_t siegeSide) { siegeSide_ = siegeSide; } + [[maybe_unused]] void setOlympiadGameId(uint32_t olympiadGameId) { olympiadGameId_ = olympiadGameId; } + [[maybe_unused]] void setOlympiadSide(uint32_t olympiadSide) { olympiadSide_ = olympiadSide; } + [[maybe_unused]] void setOlympiadBuffCount(uint32_t olympiadBuffCount) { olympiadBuffCount_ = olympiadBuffCount; } + [[maybe_unused]] void setDuelId(uint32_t duelId) { duelId_ = duelId; } + [[maybe_unused]] void setDuelState(uint32_t duelState) { duelState_ = duelState; } + [[maybe_unused]] void setPvpFlag(uint32_t pvpFlag) { pvpFlag_ = pvpFlag; } + [[maybe_unused]] void setPvpFlagLasts(uint32_t pvpFlagLasts) { pvpFlagLasts_ = pvpFlagLasts; } + [[maybe_unused]] void setTeleMode(uint32_t teleMode) { teleMode_ = teleMode; } + [[maybe_unused]] void setPartyDistributionType(uint32_t partyDistributionType) { partyDistributionType_ = partyDistributionType; } + [[maybe_unused]] void setPrivateStoreType(uint32_t privateStoreType) { privateStoreType_ = privateStoreType; } + [[maybe_unused]] void setDietMode(uint32_t dietMode) { dietMode_ = dietMode; } + [[maybe_unused]] void setTradeRefusal(uint32_t tradeRefusal) { tradeRefusal_ = tradeRefusal; } + [[maybe_unused]] void setExchangeRefusal(uint32_t exchangeRefusal) { exchangeRefusal_ = exchangeRefusal; } + [[maybe_unused]] void setMessageRefusal(uint32_t messageRefusal) { messageRefusal_ = messageRefusal; } + [[maybe_unused]] void setSilenceMode(uint32_t silenceMode) { silenceMode_ = silenceMode; } + [[maybe_unused]] void setInventoryBlockingStatus(uint32_t inventoryBlockingStatus) { inventoryBlockingStatus_ = inventoryBlockingStatus; } + [[maybe_unused]] void setExpertiseArmorPenalty(uint32_t expertiseArmorPenalty) { expertiseArmorPenalty_ = expertiseArmorPenalty; } + [[maybe_unused]] void setExpertiseWeaponPenalty(uint32_t expertiseWeaponPenalty) { expertiseWeaponPenalty_ = expertiseWeaponPenalty; } + [[maybe_unused]] void setExpertisePenaltyBonus(uint32_t expertisePenaltyBonus) { expertisePenaltyBonus_ = expertisePenaltyBonus; } + [[maybe_unused]] void setWeightPenalty(uint32_t weightPenalty) { weightPenalty_ = weightPenalty; } + [[maybe_unused]] void setCurWeightPenalty(uint32_t curWeightPenalty) { curWeightPenalty_ = curWeightPenalty; } + [[maybe_unused]] void setInventoryLimit(uint32_t inventoryLimit) { inventoryLimit_ = inventoryLimit; } + [[maybe_unused]] void setWarehouseLimit(uint32_t warehouseLimit) { warehouseLimit_ = warehouseLimit; } + [[maybe_unused]] void setPrivateSellStoreLimit(uint32_t privateSellStoreLimit) { privateSellStoreLimit_ = privateSellStoreLimit; } + [[maybe_unused]] void setPrivateBuyStoreLimit(uint32_t privateBuyStoreLimit) { privateBuyStoreLimit_ = privateBuyStoreLimit; } + [[maybe_unused]] void setDwarfRecipeLimit(uint32_t dwarfRecipeLimit) { dwarfRecipeLimit_ = dwarfRecipeLimit; } + [[maybe_unused]] void setCommonRecipeLimit(uint32_t commonRecipeLimit) { commonRecipeLimit_ = commonRecipeLimit; } + [[maybe_unused]] void setQuestInventoryLimit(uint32_t questInventoryLimit) { questInventoryLimit_ = questInventoryLimit; } + [[maybe_unused]] void setBookmarkslot(uint32_t bookmarkslot) { bookmarkslot_ = bookmarkslot; } + [[maybe_unused]] void setLanguage(uint32_t language) { language_ = language; } + [[maybe_unused]] void setFaction(uint32_t faction) { faction_ = faction; } + [[maybe_unused]] void setNewbie(uint32_t newbie) { newbie_ = newbie; } + [[maybe_unused]] void setNobless(uint32_t nobless) { nobless_ = nobless; } + [[maybe_unused]] void setIsIn7sDungeon(uint32_t isIn7sDungeon) { isIn7sDungeon_ = isIn7sDungeon; } + [[maybe_unused]] void setClanPrivileges(uint32_t clanPrivileges) { clanPrivileges_ = clanPrivileges; } + [[maybe_unused]] void setSubpledge(uint32_t subpledge) { subpledge_ = subpledge; } + [[maybe_unused]] void setTitleColor(uint32_t titleColor) { titleColor_ = titleColor; } + [[maybe_unused]] void setTitleId(uint32_t title) { title_ = title; } + [[maybe_unused]] void setCancraft(uint32_t cancraft) { cancraft_ = cancraft; } + [[maybe_unused]] void setOnlinetime(uint32_t onlinetime) { onlinetime_ = onlinetime; } + [[maybe_unused]] void setIsin7sdungeon(uint32_t isin7sdungeon) { isin7sdungeon_ = isin7sdungeon; } + [[maybe_unused]] void setLastRecomDate(uint32_t lastRecomDate) { last_recom_date_ = lastRecomDate; } + [[maybe_unused]] void setRecHave(uint32_t recHave) { rec_have_ = recHave; } + [[maybe_unused]] void setRecLeft(uint32_t recLeft) { rec_left_ = recLeft; } + [[maybe_unused]] void setDeathPenaltyLevel(uint32_t deathPenaltyLevel) { death_penalty_level_ = deathPenaltyLevel; } + [[maybe_unused]] void setPccafePoints(uint32_t pccafePoints) { pccafe_points_ = pccafePoints; } +}; \ No newline at end of file diff --git a/src/game/entities/world_object.cpp b/src/game/entities/world_object.cpp new file mode 100644 index 0000000..2eea069 --- /dev/null +++ b/src/game/entities/world_object.cpp @@ -0,0 +1,22 @@ +#include "world_object.hpp" + +WorldObject::WorldObject(uint32_t objectId) + : objectId_(objectId) + , x_(0) + , y_(0) + , z_(0) + , heading_(0) +{ +} + +void WorldObject::setPosition(int32_t x, int32_t y, int32_t z) +{ + x_ = x; + y_ = y; + z_ = z; +} + +void WorldObject::setHeading(int32_t heading) +{ + heading_ = heading; +} \ No newline at end of file diff --git a/src/game/entities/world_object.hpp b/src/game/entities/world_object.hpp new file mode 100644 index 0000000..596c96d --- /dev/null +++ b/src/game/entities/world_object.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +// Base class for all game objects (players, NPCs, items, etc.) +class WorldObject +{ +protected: + uint32_t objectId_; + int32_t x_; + int32_t y_; + int32_t z_; + int32_t heading_; + +public: + WorldObject(uint32_t objectId); + virtual ~WorldObject() = default; + + // Getters + uint32_t getObjectId() const { return objectId_; } + int32_t getX() const { return x_; } + int32_t getY() const { return y_; } + int32_t getZ() const { return z_; } + int32_t getHeading() const { return heading_; } + + // Setters + void setPosition(int32_t x, int32_t y, int32_t z); + void setHeading(int32_t heading); + void setObjectId(uint32_t objectId) { objectId_ = objectId; } +}; \ No newline at end of file diff --git a/src/game/network/game_client_connection.cpp b/src/game/network/game_client_connection.cpp index 92ec263..18e2777 100644 --- a/src/game/network/game_client_connection.cpp +++ b/src/game/network/game_client_connection.cpp @@ -10,6 +10,31 @@ #include "../packets/responses/character_selection_info.hpp" #include "../packets/responses/new_character_success.hpp" #include "../packets/responses/character_create_success.hpp" +#include "../packets/responses/character_selected.hpp" +#include "../packets/responses/user_info.hpp" +#include "../packets/responses/validate_location.hpp" +#include "../packets/responses/item_list.hpp" +#include "../packets/responses/skill_cool_time.hpp" +#include "../packets/responses/shortcut_init.hpp" +#include "../packets/responses/etc_status_update.hpp" +#include "../packets/responses/ex_storage_max_count.hpp" +#include "../packets/responses/quest_list.hpp" +// Phase 3: Social/Clan packets +#include "../packets/responses/henna_info.hpp" +#include "../packets/responses/pledge_skill_list.hpp" +#include "../packets/responses/friend_list.hpp" +#include "../packets/responses/pledge_show_member_list_all.hpp" +#include "../packets/responses/pledge_status_changed.hpp" +// Phase 4: Welcome/System packets +#include "../packets/responses/system_message.hpp" +#include "../packets/responses/ex_show_screen_message.hpp" +#include "../packets/responses/action_failed.hpp" +#include "../packets/responses/skill_list.hpp" +#include "../packets/responses/status_update.hpp" +#include "../packets/responses/char_info.hpp" +#include "../packets/responses/show_mini_map.hpp" + +#include "../packets/requests/enter_world_packet.hpp" #include "../packets/requests/request_game_start.hpp" #include "../server/game_server.hpp" #include "../server/character_database_manager.hpp" @@ -93,6 +118,7 @@ void GameClientConnection::handle_complete_packet(std::vector packet_da catch (const PacketException &e) { log_connection_event("Packet creation error: " + std::string(e.what())); + log_connection_event("CRITICAL: Client may be waiting for response to unknown packet - this could cause loading screen hang"); // Continue processing other packets - don't disconnect for unknown packets } } @@ -218,7 +244,7 @@ void GameClientConnection::handle_game_packet(std::unique_ptr pa log_connection_event("Say packet received"); break; case 0x03: // RequestEnterWorld - log_connection_event("RequestEnterWorld packet received"); + handle_enter_world_packet(packet); break; case 0x04: // Action (not unknown - this is an action packet!) log_connection_event("Action packet received (attack, pickup, etc)"); @@ -235,8 +261,8 @@ void GameClientConnection::handle_game_packet(std::unique_ptr pa case 0x0B: // RequestCharacterCreate - Packet to create a new character in database/memory handle_character_create_packet(packet); break; - case 0x0C: // RequestCharacterDelete - log_connection_event("RequestCharacterDelete packet received"); + case 0x0C: // RequestCharacterDeletePacket + log_connection_event("RequestCharacterDeletePacket packet received"); break; case 0x0D: // RequestGameStart (character selection) handle_request_game_start_packet(packet); @@ -244,14 +270,32 @@ void GameClientConnection::handle_game_packet(std::unique_ptr pa case 0x0E: // RequestNewCharacter - Packet to show the character creation screen handle_request_new_character_packet(packet); break; + case 0x0F: // RequestItemList - Client requesting inventory item list + handle_request_item_list_packet(packet); + break; + case 0x25: // RequestAnswerJoinPledge + handle_request_answer_join_pledge_packet(packet); + break; + case 0x9D: // RequestSkillCoolTime + handle_request_skill_cool_time_packet(packet); + break; + case 0xCD: // RequestShowMiniMap + handle_request_show_mini_map_packet(packet); + break; + case 0xD0: // Extended packets (already handled by packet factory, but log here) + log_connection_event("Extended packet processed by factory"); + // Check if this is a RequestManorList packet + if (auto* manor_packet = dynamic_cast(packet.get())) { + handle_request_manor_list_packet(packet); + } + break; default: char hex_unknown[8]; snprintf(hex_unknown, sizeof(hex_unknown), "0x%02X", actual_opcode); log_connection_event("Received unknown packet opcode: " + std::string(hex_unknown)); + log_connection_event("WARNING: Unknown packet may require response - client could be waiting"); break; } - - } catch (const std::exception &e) { @@ -380,7 +424,8 @@ void GameClientConnection::handle_request_login_packet(const std::unique_ptr &packet) { try @@ -486,7 +529,7 @@ void GameClientConnection::handle_request_new_character_packet(const std::unique log_connection_event("Error sending NewCharacterSuccess response: " + std::string(e.what())); } } - +// This uses the 0x0D opcode => SelectCharPacket void GameClientConnection::handle_request_game_start_packet(const std::unique_ptr &packet) { try @@ -526,23 +569,28 @@ void GameClientConnection::handle_request_game_start_packet(const std::unique_pt } // Verify the character belongs to this player's account - if (character_info->login_name != player_name_) + if ((*character_info)->getAccountName() != player_name_) { - log_connection_event("Character ID " + std::to_string(character_id) + - " does not belong to account: " + player_name_); + log_connection_event("Character ID " + std::to_string(character_id) + + " does not belong to account: " + player_name_); return; } // Character validation successful - store the selected character set_character_id(static_cast(character_id)); - - log_connection_event("Character '" + character_info->name + "' (ID: " + - std::to_string(character_id) + ") selected for account: " + player_name_); - // TODO: Send appropriate response packet (e.g., enter world confirmation) - // TODO: Transition to IN_GAME state - // For now, just log successful character selection - log_connection_event("Character selection successful - ready to enter game world"); + log_connection_event("Character '" + (*character_info)->getName() + "' (ID: " + + std::to_string(character_id) + ") selected for account: " + player_name_); + + // Send CharSelected response packet to confirm character selection + auto char_selected_response = std::make_unique(*character_info, session_id_); + send_packet(std::move(char_selected_response)); + + log_connection_event("CharSelected response sent for character: " + (*character_info)->getName()); + + // Transition to IN_GAME state + set_game_state(GameState::IN_GAME); + log_connection_event("Character selection successful - player now IN_GAME"); } catch (const std::exception &e) { @@ -550,6 +598,181 @@ void GameClientConnection::handle_request_game_start_packet(const std::unique_pt } } +void GameClientConnection::handle_enter_world_packet(const std::unique_ptr &packet) +{ + try + { + auto *enter_world_packet = dynamic_cast(packet.get()); + if (!enter_world_packet) + { + log_connection_event("Failed to cast packet to EnterWorldPacket"); + return; + } + + // Validate the packet + if (!enter_world_packet->isValid()) + { + log_connection_event("Invalid EnterWorld packet received"); + return; + } + + log_connection_event("EnterWorld packet received: " + enter_world_packet->toString()); + + // Verify player is in correct state + if (!is_game_state(GameState::IN_GAME)) + { + log_connection_event("EnterWorld received but player not in IN_GAME state"); + return; + } + + // Verify character is selected + if (character_id_ < 0) + { + log_connection_event("EnterWorld received but no character selected"); + return; + } + + log_connection_event("Player entering world - character ID: " + std::to_string(character_id_)); + + // Get character data for spawning + auto *char_db = getCharacterDatabaseManager(); + if (!char_db) + { + log_connection_event("Character database manager not available during EnterWorld"); + return; + } + + auto character_info = char_db->getCharacterBySlot(player_name_, character_id_); + if (!character_info) + { + log_connection_event("Character not found during EnterWorld - ID: " + std::to_string(character_id_)); + return; + } + + log_connection_event("Sending spawn packets for character: " + (*character_info)->getName()); + + // Send essential response packets for player spawning (Phase 1) + + // 1. UserInfo - Player stats, gear, location + auto user_info_response = std::make_unique(*character_info); + send_packet(std::move(user_info_response)); + log_connection_event("UserInfo packet sent"); + + // 2. CharInfo - Character object info for world visibility + auto char_info_response = std::make_unique(*character_info); + send_packet(std::move(char_info_response)); + log_connection_event("CharInfo packet sent"); + + // 3. SkillList - Player skills + auto skill_list_response = std::make_unique(*character_info); + send_packet(std::move(skill_list_response)); + log_connection_event("SkillList packet sent"); + + // 4. StatusUpdate - HP/MP/CP status + auto status_update_response = std::make_unique(*character_info); + send_packet(std::move(status_update_response)); + log_connection_event("StatusUpdate packet sent"); + + // 5. ValidateLocation - Resync position + auto validate_location_response = std::make_unique(*character_info); + send_packet(std::move(validate_location_response)); + log_connection_event("ValidateLocation packet sent"); + + // 6. ItemList - Inventory + auto item_list_response = std::make_unique(*character_info, false); + send_packet(std::move(item_list_response)); + log_connection_event("ItemList packet sent"); + + // 7. SkillCoolTime - Cooldowns + auto skill_cool_time_response = std::make_unique(*character_info); + send_packet(std::move(skill_cool_time_response)); + log_connection_event("SkillCoolTime packet sent"); + + // Phase 2: Basic UI Packets + + // 8. ShortcutInit - Skill/item shortcuts + auto shortcut_init_response = std::make_unique(*character_info); + send_packet(std::move(shortcut_init_response)); + log_connection_event("ShortcutInit packet sent"); + + // 9. EtcStatusUpdate - Status icons (weight, soulshots, etc.) + auto etc_status_response = std::make_unique(*character_info); + send_packet(std::move(etc_status_response)); + log_connection_event("EtcStatusUpdate packet sent"); + + // 10. ExStorageMaxCount - Inventory/warehouse limits + auto storage_max_response = std::make_unique(*character_info); + send_packet(std::move(storage_max_response)); + log_connection_event("ExStorageMaxCount packet sent"); + + // 11. QuestList - Active quests (empty for now) + auto quest_list_response = std::make_unique(*character_info); + send_packet(std::move(quest_list_response)); + log_connection_event("QuestList packet sent"); + + // Send Phase 3: Social/Clan Packets + + // 9. HennaInfo - Active henna (dye) information + auto henna_info_response = std::make_unique(*character_info); + send_packet(std::move(henna_info_response)); + log_connection_event("HennaInfo packet sent"); + + // 10. PledgeSkillList - Clan skills (extended packet) + auto pledge_skill_list_response = std::make_unique(*character_info); + send_packet(std::move(pledge_skill_list_response)); + log_connection_event("PledgeSkillList packet sent"); + + // 11. FriendList - Friend list information + auto friend_list_response = std::make_unique(*character_info); + send_packet(std::move(friend_list_response)); + log_connection_event("FriendList packet sent"); + + // 12. PledgeShowMemberListAll - Clan member list + auto pledge_member_list_response = std::make_unique(*character_info); + send_packet(std::move(pledge_member_list_response)); + log_connection_event("PledgeShowMemberListAll packet sent"); + + // 13. PledgeStatusChanged - Clan status updates + auto pledge_status_response = std::make_unique(*character_info); + send_packet(std::move(pledge_status_response)); + log_connection_event("PledgeStatusChanged packet sent"); + + // Send Phase 4: Welcome/System Packets + + // 14. SystemMessage - Welcome message + auto system_message_response = std::make_unique("Welcome to the server!"); + send_packet(std::move(system_message_response)); + log_connection_event("SystemMessage packet sent"); + + // 15. ExShowScreenMessage - Welcome popup + auto screen_message_response = std::make_unique("Welcome to Lineage 2!", 5, 5000); + send_packet(std::move(screen_message_response)); + log_connection_event("ExShowScreenMessage packet sent"); + + // 16. NpcHtmlMessage - Welcome message + auto html_message = std::make_unique(0, "Hola Mundo! Welcome to our Lineage 2 server!"); + send_packet(std::move(html_message)); + log_connection_event("NpcHtmlMessage packet sent"); + + log_connection_event("Player successfully spawned in world"); + + // Send final sequence matching L2J Mobius + auto final_validate_location = std::make_unique(*character_info); + send_packet(std::move(final_validate_location)); + log_connection_event("Final ValidateLocation sent"); + + // ActionFailed must be the absolute LAST packet + auto final_action_failed = std::make_unique(); + send_packet(std::move(final_action_failed)); + log_connection_event("ActionFailed sent - spawn sequence complete"); + + } + catch (const std::exception &e) + { + log_connection_event("Error processing EnterWorld packet: " + std::string(e.what())); + } +} + // ============================================================================= // Encryption Management // ============================================================================= @@ -634,6 +857,159 @@ void GameClientConnection::on_disconnect() set_game_state(GameState::DISCONNECTED); } +// ============================================================================= +// New Packet Handlers +// ============================================================================= + +void GameClientConnection::handle_request_skill_cool_time_packet(const std::unique_ptr &packet) +{ + try + { + log_connection_event("RequestSkillCoolTime packet received"); + + // Get character info from database + auto *db_manager = getCharacterDatabaseManager(); + if (!db_manager) + { + log_connection_event("No database manager available for skill cooldown request"); + return; + } + + auto character_info = db_manager->getCharacterBySlot(player_name_, character_id_); + if (!character_info) + { + log_connection_event("No character info available for skill cooldown request"); + return; + } + + // Send SkillCoolTime response with current cooldown information + auto skill_cool_time_response = std::make_unique(*character_info); + send_packet(std::move(skill_cool_time_response)); + log_connection_event("SkillCoolTime response sent"); + } + catch (const std::exception &e) + { + log_connection_event("Error handling RequestSkillCoolTime packet: " + std::string(e.what())); + } +} + +void GameClientConnection::handle_request_answer_join_pledge_packet(const std::unique_ptr &packet) +{ + try + { + // Cast to RequestAnswerJoinPledge to access pledge data + auto *pledge_packet = dynamic_cast(packet.get()); + if (!pledge_packet) + { + log_connection_event("Failed to cast packet to RequestAnswerJoinPledge"); + return; + } + + uint32_t response = pledge_packet->getResponse(); + + log_connection_event("RequestAnswerJoinPledge received - Response: " + std::string(response == 1 ? "Accept" : "Decline")); + + // TODO: Process pledge join response (matches L2J Mobius implementation) + // In a real implementation, this would: + // 1. Get the requestor from player's request partner + // 2. If response == 0: Send decline messages to both players + // 3. If response == 1: + // - Add player to clan + // - Set pledge type and power grade + // - Send JoinPledge, PledgeShowMemberListAdd, PledgeShowInfoUpdate packets + // - Broadcast clan updates + // 4. Clear the request + + // For now, send a simple response to acknowledge the packet was received + // This prevents the client from getting stuck waiting for a response + if (response == 1) + { + // Send a system message indicating the pledge join was accepted + auto system_message = std::make_unique("Pledge join request accepted (stub)"); + send_packet(std::move(system_message)); + } + else + { + // Send a system message indicating the pledge join was declined + auto system_message = std::make_unique("Pledge join request declined (stub)"); + send_packet(std::move(system_message)); + } + + log_connection_event("Pledge join response processed (stub implementation)"); + } + catch (const std::exception &e) + { + log_connection_event("Error handling RequestAnswerJoinPledge packet: " + std::string(e.what())); + } +} + +void GameClientConnection::handle_request_item_list_packet(const std::unique_ptr &packet) +{ + try + { + log_connection_event("RequestItemList packet received - client wants inventory refresh"); + + // Get character info from database + auto *db_manager = getCharacterDatabaseManager(); + if (!db_manager) + { + log_connection_event("No database manager available for inventory request"); + return; + } + + auto character_info = db_manager->getCharacterBySlot(player_name_, character_id_); + if (!character_info) + { + log_connection_event("No character info available for inventory request"); + return; + } + + // Send ItemList response with current inventory (showWindow=true to test if it prevents crash) + auto item_list_response = std::make_unique(*character_info, true); + send_packet(std::move(item_list_response)); + log_connection_event("ItemList response sent - inventory refreshed (testing showWindow=true)"); + } + catch (const std::exception &e) + { + log_connection_event("Error handling RequestItemList packet: " + std::string(e.what())); + } +} + +void GameClientConnection::handle_request_manor_list_packet(const std::unique_ptr &packet) +{ + try + { + log_connection_event("RequestManorList packet received"); + + // Send ExSendManorList response (matches L2J Mobius implementation) + auto manor_list_response = std::make_unique(); + send_packet(std::move(manor_list_response)); + log_connection_event("ExSendManorList response sent"); + } + catch (const std::exception &e) + { + log_connection_event("Error handling RequestManorList packet: " + std::string(e.what())); + } +} + +void GameClientConnection::handle_request_show_mini_map_packet(const std::unique_ptr &packet) +{ + try + { + log_connection_event("RequestShowMiniMap packet received (0xCD) - client requesting minimap display"); + + // Send ShowMiniMap response (matches L2J Mobius implementation) + // player.sendPacket(new ShowMiniMap(1665)); + auto show_mini_map_response = std::make_unique(1665); + send_packet(std::move(show_mini_map_response)); + log_connection_event("ShowMiniMap response sent with map ID 1665"); + } + catch (const std::exception &e) + { + log_connection_event("Error handling RequestShowMiniMap packet: " + std::string(e.what())); + } +} + // ============================================================================= // Utility Functions // ============================================================================= diff --git a/src/game/network/game_client_connection.hpp b/src/game/network/game_client_connection.hpp index c24087f..8f7c326 100644 --- a/src/game/network/game_client_connection.hpp +++ b/src/game/network/game_client_connection.hpp @@ -4,6 +4,14 @@ #include "../../core/encryption/game_client_encryption.hpp" #include "../../core/encryption/login_encryption.hpp" #include "../packets/requests/no_op_packet.hpp" +#include "../packets/requests/request_skill_cool_time.hpp" +#include "../packets/requests/request_answer_join_pledge.hpp" +#include "../packets/requests/extended/request_manor_list.hpp" +#include "../packets/responses/skill_cool_time.hpp" +#include "../packets/responses/ask_join_pledge.hpp" +#include "../packets/responses/system_message.hpp" +#include "../packets/responses/npc_html_message.hpp" +#include "../packets/responses/ex_send_manor_list.hpp" // Forward declarations class GameConnectionManager; @@ -89,6 +97,12 @@ class GameClientConnection : public BaseClientConnection void handle_request_new_character_packet(const std::unique_ptr& packet); void handle_character_create_packet(const std::unique_ptr& packet); void handle_request_game_start_packet(const std::unique_ptr& packet); + void handle_enter_world_packet(const std::unique_ptr& packet); + void handle_request_skill_cool_time_packet(const std::unique_ptr& packet); + void handle_request_answer_join_pledge_packet(const std::unique_ptr& packet); + void handle_request_item_list_packet(const std::unique_ptr& packet); + void handle_request_manor_list_packet(const std::unique_ptr& packet); + void handle_request_show_mini_map_packet(const std::unique_ptr& packet); // Game-specific disconnect handling void on_disconnect() override; diff --git a/src/game/packets/packet_factory.cpp b/src/game/packets/packet_factory.cpp index 5555ad9..76b1d68 100644 --- a/src/game/packets/packet_factory.cpp +++ b/src/game/packets/packet_factory.cpp @@ -3,6 +3,10 @@ #include "requests/no_op_packet.hpp" #include "requests/create_char_request_packet.hpp" #include "requests/request_game_start.hpp" +#include "requests/request_skill_cool_time.hpp" +#include "requests/request_answer_join_pledge.hpp" +#include "requests/request_item_list.hpp" +#include "requests/request_show_mini_map.hpp" #include #include #include @@ -67,7 +71,7 @@ std::unique_ptr GamePacketFactory::createFromClientData( case GameClientPacketType::RequestCharacterDelete: // 0x0C - Character deletion - return createDeleteCharPacket(packetData); + return createRequestCharacterDeletePacket(packetData); case GameClientPacketType::RequestGameStart: // 0x0D - Game start (character selection) @@ -77,6 +81,22 @@ std::unique_ptr GamePacketFactory::createFromClientData( // 0x0E - New character info request return createNewCharRequestPacket(packetData); + case GameClientPacketType::RequestItemList: + // 0x0F - Request inventory item list + return createRequestItemListPacket(packetData); + + case GameClientPacketType::RequestAnswerJoinPledge: + // 0x25 - Answer pledge join request + return createRequestAnswerJoinPledgePacket(packetData); + + case GameClientPacketType::RequestSkillCoolTime: + // 0x9D - Request skill cooldown info + return createRequestSkillCoolTimePacket(packetData); + + case GameClientPacketType::RequestShowMiniMap: + // 0xCD - Request show minimap + return createRequestShowMiniMapPacket(packetData); + default: return createNoOpPacket(packetData); } @@ -185,12 +205,12 @@ std::unique_ptr GamePacketFactory::createLogoutPacket(const std: } } -std::unique_ptr GamePacketFactory::createDeleteCharPacket(const std::vector &rawData) +std::unique_ptr GamePacketFactory::createRequestCharacterDeletePacket(const std::vector &rawData) { try { ReadablePacketBuffer buffer(rawData); - auto packet = std::make_unique(); + auto packet = std::make_unique(); packet->read(buffer); return packet; } @@ -276,6 +296,9 @@ std::unique_ptr GamePacketFactory::createExtendedPacket(const st switch (static_cast(sub_opcode)) { + case ExtendedGamePacketType::RequestManorList: + return createRequestManorListPacket(extPacketData); + case ExtendedGamePacketType::GoLobby: return createGoLobbyPacket(extPacketData); @@ -289,11 +312,37 @@ std::unique_ptr GamePacketFactory::createExtendedPacket(const st return createRequestUserBanInfoPacket(extPacketData); default: + std::cout << "[ExtendedPacket] Unknown sub-opcode: 0x" << std::hex << sub_opcode + << " (decimal: " << std::dec << sub_opcode << ")" << std::endl; + std::cout << "[ExtendedPacket] Raw data size: " << rawData.size() << " bytes" << std::endl; + if (!rawData.empty()) { + std::cout << "[ExtendedPacket] Raw data: "; + for (size_t i = 0; i < std::min(rawData.size(), size_t(16)); ++i) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast(rawData[i]) << " "; + } + std::cout << std::dec << std::endl; + } throw PacketException("Unknown extended game packet sub-opcode: 0x" + std::to_string(sub_opcode)); } } +std::unique_ptr GamePacketFactory::createRequestManorListPacket(const std::vector &rawData) +{ + // RequestManorList - Manor (farming) system request + try + { + ReadablePacketBuffer buffer(rawData); + auto packet = std::make_unique(); + packet->read(buffer); + return packet; + } + catch (const std::exception &e) + { + throw PacketException("Failed to create RequestManorList packet: " + std::string(e.what())); + } +} + std::unique_ptr GamePacketFactory::createGoLobbyPacket(const std::vector &rawData) { // TODO: Create GoLobbyPacket class - using NoOpPacket for now @@ -356,4 +405,64 @@ std::unique_ptr GamePacketFactory::createRequestUserBanInfoPacke { throw PacketException("Failed to create RequestUserBanInfo packet: " + std::string(e.what())); } +} + +std::unique_ptr GamePacketFactory::createRequestAnswerJoinPledgePacket(const std::vector &rawData) +{ + try + { + ReadablePacketBuffer buffer(rawData); + auto packet = std::make_unique(); + packet->read(buffer); + return packet; + } + catch (const std::exception &e) + { + throw PacketException("Failed to create RequestAnswerJoinPledge packet: " + std::string(e.what())); + } +} + +std::unique_ptr GamePacketFactory::createRequestItemListPacket(const std::vector &rawData) +{ + try + { + ReadablePacketBuffer buffer(rawData); + auto packet = std::make_unique(); + packet->read(buffer); + return packet; + } + catch (const std::exception &e) + { + throw PacketException("Failed to create RequestItemList packet: " + std::string(e.what())); + } +} + +std::unique_ptr GamePacketFactory::createRequestSkillCoolTimePacket(const std::vector &rawData) +{ + try + { + ReadablePacketBuffer buffer(rawData); + auto packet = std::make_unique(); + packet->read(buffer); + return packet; + } + catch (const std::exception &e) + { + throw PacketException("Failed to create RequestSkillCoolTime packet: " + std::string(e.what())); + } +} + +std::unique_ptr GamePacketFactory::createRequestShowMiniMapPacket(const std::vector &rawData) +{ + try + { + ReadablePacketBuffer buffer(rawData); + auto packet = std::make_unique(); + packet->read(buffer); + return packet; + } + catch (const std::exception &e) + { + throw PacketException("Failed to create RequestShowMiniMap packet: " + std::string(e.what())); + } } \ No newline at end of file diff --git a/src/game/packets/packet_factory.hpp b/src/game/packets/packet_factory.hpp index b1796b8..096c2d9 100644 --- a/src/game/packets/packet_factory.hpp +++ b/src/game/packets/packet_factory.hpp @@ -8,7 +8,7 @@ #include "requests/new_char_request_packet.hpp" #include "requests/create_char_request_packet.hpp" #include "requests/logout_packet.hpp" -#include "requests/delete_char_packet.hpp" +#include "requests/request_delete_character_packet.hpp" #include "requests/restore_char_packet.hpp" #include "requests/select_char_packet.hpp" #include "requests/enter_world_packet.hpp" @@ -17,6 +17,12 @@ #include "requests/extended/check_char_name_packet.hpp" #include "requests/extended/send_client_ini_packet.hpp" #include "requests/extended/request_user_ban_info_packet.hpp" +#include "requests/extended/request_manor_list.hpp" +#include "requests/request_skill_cool_time.hpp" +#include "requests/request_answer_join_pledge.hpp" +#include "requests/request_item_list.hpp" +#include "requests/request_show_mini_map.hpp" +#include "responses/char_info.hpp" #include #include @@ -38,6 +44,10 @@ enum class GameClientPacketType : uint8_t RequestCharacterDelete = 0x0C, // Character deletion RequestGameStart = 0x0D, // Game start (character selection) RequestNewCharacter = 0x0E, // New character info request + RequestItemList = 0x0F, // Request inventory item list + RequestAnswerJoinPledge = 0x25, // Answer pledge join request + RequestSkillCoolTime = 0x9D, // Request skill cooldown info + RequestShowMiniMap = 0xCD, // Request show minimap // Extended packets (0xD0 + sub-opcode) ExtendedPacket = 0xD0 @@ -46,6 +56,7 @@ enum class GameClientPacketType : uint8_t // Extended packet sub-opcodes (16-bit values after 0xD0) enum class ExtendedGamePacketType : uint16_t { + RequestManorList = 0x0008, GoLobby = 0x001B, CheckCharName = 0x008F, SendClientIni = 0x00CD, @@ -76,14 +87,19 @@ class GamePacketFactory static std::unique_ptr createNewCharRequestPacket(const std::vector &rawData); static std::unique_ptr createCreateCharRequestPacket(const std::vector &rawData); static std::unique_ptr createLogoutPacket(const std::vector &rawData); - static std::unique_ptr createDeleteCharPacket(const std::vector &rawData); + static std::unique_ptr createRequestCharacterDeletePacket(const std::vector &rawData); static std::unique_ptr createRestoreCharPacket(const std::vector &rawData); static std::unique_ptr createRequestGameStartPacket(const std::vector &rawData); static std::unique_ptr createSelectCharPacket(const std::vector &rawData); static std::unique_ptr createEnterWorldPacket(const std::vector &rawData); + static std::unique_ptr createRequestItemListPacket(const std::vector &rawData); + static std::unique_ptr createRequestAnswerJoinPledgePacket(const std::vector &rawData); + static std::unique_ptr createRequestSkillCoolTimePacket(const std::vector &rawData); + static std::unique_ptr createRequestShowMiniMapPacket(const std::vector &rawData); // Create extended packet types (NEW - game server complexity) static std::unique_ptr createExtendedPacket(const std::vector &rawData); + static std::unique_ptr createRequestManorListPacket(const std::vector &rawData); static std::unique_ptr createGoLobbyPacket(const std::vector &rawData); static std::unique_ptr createCheckCharNamePacket(const std::vector &rawData); static std::unique_ptr createSendClientIniPacket(const std::vector &rawData); diff --git a/src/game/packets/requests/delete_char_packet.cpp b/src/game/packets/requests/delete_char_packet.cpp deleted file mode 100644 index fe30ca1..0000000 --- a/src/game/packets/requests/delete_char_packet.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "delete_char_packet.hpp" - -uint8_t DeleteCharPacket::getPacketId() const -{ - return PACKET_ID; -} - -std::optional DeleteCharPacket::getExPacketId() const -{ - return std::nullopt; -} - -void DeleteCharPacket::read(ReadablePacketBuffer &buffer) -{ - // TODO: Implement select char reading -} - -bool DeleteCharPacket::isValid() const -{ - return true; -} \ No newline at end of file diff --git a/src/game/packets/requests/enter_world_packet.cpp b/src/game/packets/requests/enter_world_packet.cpp index 97f8a34..ec5b8f8 100644 --- a/src/game/packets/requests/enter_world_packet.cpp +++ b/src/game/packets/requests/enter_world_packet.cpp @@ -1,4 +1,7 @@ #include "enter_world_packet.hpp" +#include +#include +#include uint8_t EnterWorldPacket::getPacketId() const { @@ -12,13 +15,76 @@ std::optional EnterWorldPacket::getExPacketId() const void EnterWorldPacket::read(ReadablePacketBuffer &buffer) { - // NoOp packet - consume any remaining data but don't process it - // This is a fallback for unknown packets + // 1. Read 32 bytes of unknown data + unknown_data_1_.resize(32); + for (int i = 0; i < 32; i++) + { + unknown_data_1_[i] = buffer.readByte(); + } + + // 2. Read 4 unknown integers + unknown_value_1_ = buffer.readInt32(); + unknown_value_2_ = buffer.readInt32(); + unknown_value_3_ = buffer.readInt32(); + unknown_value_4_ = buffer.readInt32(); + + // 3. Read another 32 bytes of unknown data + unknown_data_2_.resize(32); + for (int i = 0; i < 32; i++) + { + unknown_data_2_[i] = buffer.readByte(); + } + + // 4. Read 1 unknown integer + unknown_value_5_ = buffer.readInt32(); + + // 5. Read 5x4 bytes of tracert data (network routing information) + tracert_data_.resize(5); + for (int i = 0; i < 5; i++) + { + tracert_data_[i].resize(4); + for (int j = 0; j < 4; j++) + { + tracert_data_[i][j] = buffer.readByte(); + } + } } -// Validation bool EnterWorldPacket::isValid() const { - // NoOp packets are always "valid" since they're fallbacks - return true; + // Basic validation - check if we have the expected data sizes + return unknown_data_1_.size() == 32 && + unknown_data_2_.size() == 32 && + tracert_data_.size() == 5 && + tracert_data_[0].size() == 4; +} + +std::string EnterWorldPacket::toString() const +{ + std::stringstream ss; + ss << "EnterWorldPacket ["; + ss << "Unknown1: " << unknown_value_1_ << ", "; + ss << "Unknown2: " << unknown_value_2_ << ", "; + ss << "Unknown3: " << unknown_value_3_ << ", "; + ss << "Unknown4: " << unknown_value_4_ << ", "; + ss << "Unknown5: " << unknown_value_5_; + + // Add tracert data summary + ss << ", Tracert: "; + for (size_t i = 0; i < tracert_data_.size() && i < 2; i++) // Show first 2 entries + { + ss << "["; + for (size_t j = 0; j < tracert_data_[i].size(); j++) + { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(tracert_data_[i][j]); + if (j < tracert_data_[i].size() - 1) ss << "."; + } + ss << "]"; + if (i < tracert_data_.size() - 1 && i < 1) ss << " "; + } + if (tracert_data_.size() > 2) ss << "..."; + + ss << "]"; + return ss.str(); } \ No newline at end of file diff --git a/src/game/packets/requests/enter_world_packet.hpp b/src/game/packets/requests/enter_world_packet.hpp index a2ab06c..fa5900b 100644 --- a/src/game/packets/requests/enter_world_packet.hpp +++ b/src/game/packets/requests/enter_world_packet.hpp @@ -3,11 +3,23 @@ #include "../../../core/packets/packet.hpp" #include "../../../core/network/packet_buffer.hpp" #include +#include +// EnterWorld - Client request to enter the game world after character selection class EnterWorldPacket : public ReadablePacket { private: - static constexpr uint8_t PACKET_ID = 0x11; + static constexpr uint8_t PACKET_ID = 0x03; // EnterWorld - Interlude Update 3 + + // Packet data fields (following L2J structure) + std::vector unknown_data_1_; // 32 bytes + uint32_t unknown_value_1_; // Unknown integer + uint32_t unknown_value_2_; // Unknown integer + uint32_t unknown_value_3_; // Unknown integer + uint32_t unknown_value_4_; // Unknown integer + std::vector unknown_data_2_; // 32 bytes + uint32_t unknown_value_5_; // Unknown integer + std::vector> tracert_data_; // 5x4 bytes (network routing) public: EnterWorldPacket() = default; @@ -17,4 +29,12 @@ class EnterWorldPacket : public ReadablePacket void read(ReadablePacketBuffer &buffer) override; bool isValid() const; + + // Getters for packet data (for debugging/logging) + const std::vector& getUnknownData1() const { return unknown_data_1_; } + const std::vector& getUnknownData2() const { return unknown_data_2_; } + const std::vector>& getTracertData() const { return tracert_data_; } + + // Debug method + std::string toString() const; }; \ No newline at end of file diff --git a/src/game/packets/requests/extended/request_manor_list.cpp b/src/game/packets/requests/extended/request_manor_list.cpp new file mode 100644 index 0000000..e604511 --- /dev/null +++ b/src/game/packets/requests/extended/request_manor_list.cpp @@ -0,0 +1,22 @@ +#include "request_manor_list.hpp" + +uint8_t RequestManorList::getPacketId() const +{ + return PACKET_ID; +} + +std::optional RequestManorList::getExPacketId() const +{ + return EX_PACKET_ID; +} + +void RequestManorList::read(ReadablePacketBuffer &buffer) +{ + // RequestManorList has empty readImpl() in L2J Mobius - no data to read + // The packet is just a request for manor information +} + +bool RequestManorList::isValid() const +{ + return true; +} \ No newline at end of file diff --git a/src/game/packets/requests/extended/request_manor_list.hpp b/src/game/packets/requests/extended/request_manor_list.hpp new file mode 100644 index 0000000..08ceb7b --- /dev/null +++ b/src/game/packets/requests/extended/request_manor_list.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../../../../core/packets/packet.hpp" +#include "../../../../core/network/packet_buffer.hpp" +#include + +class RequestManorList : public ReadablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xD0; + static constexpr uint16_t EX_PACKET_ID = 0x08; + +public: + RequestManorList() = default; + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void read(ReadablePacketBuffer &buffer) override; + + bool isValid() const; +}; \ No newline at end of file diff --git a/src/game/packets/requests/request_answer_join_pledge.cpp b/src/game/packets/requests/request_answer_join_pledge.cpp new file mode 100644 index 0000000..36d2ad4 --- /dev/null +++ b/src/game/packets/requests/request_answer_join_pledge.cpp @@ -0,0 +1,23 @@ +#include "request_answer_join_pledge.hpp" + +uint8_t RequestAnswerJoinPledge::getPacketId() const +{ + return PACKET_ID; +} + +std::optional RequestAnswerJoinPledge::getExPacketId() const +{ + return std::nullopt; +} + +void RequestAnswerJoinPledge::read(ReadablePacketBuffer &buffer) +{ + // Read pledge join response data (matches L2J Mobius: _answer = readInt()) + response_ = buffer.readUInt32(); +} + +bool RequestAnswerJoinPledge::isValid() const +{ + // Response should be 0 (decline) or 1 (accept) + return response_ <= 1; +} \ No newline at end of file diff --git a/src/game/packets/requests/request_answer_join_pledge.hpp b/src/game/packets/requests/request_answer_join_pledge.hpp new file mode 100644 index 0000000..872aaee --- /dev/null +++ b/src/game/packets/requests/request_answer_join_pledge.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include + +class RequestAnswerJoinPledge : public ReadablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x25; + + // Pledge join response data + uint32_t response_ = 0; // 0 = decline, 1 = accept + +public: + RequestAnswerJoinPledge() = default; + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void read(ReadablePacketBuffer &buffer) override; + + bool isValid() const; + + // Getters for pledge response data + uint32_t getResponse() const { return response_; } +}; \ No newline at end of file diff --git a/src/game/packets/requests/request_delete_character_packet.cpp b/src/game/packets/requests/request_delete_character_packet.cpp new file mode 100644 index 0000000..5fb74cb --- /dev/null +++ b/src/game/packets/requests/request_delete_character_packet.cpp @@ -0,0 +1,21 @@ +#include "request_delete_character_packet.hpp" + +uint8_t RequestCharacterDeletePacket::getPacketId() const +{ + return PACKET_ID; +} + +std::optional RequestCharacterDeletePacket::getExPacketId() const +{ + return std::nullopt; +} + +void RequestCharacterDeletePacket::read(ReadablePacketBuffer &buffer) +{ + // TODO: Implement select char reading +} + +bool RequestCharacterDeletePacket::isValid() const +{ + return true; +} \ No newline at end of file diff --git a/src/game/packets/requests/delete_char_packet.hpp b/src/game/packets/requests/request_delete_character_packet.hpp similarity index 68% rename from src/game/packets/requests/delete_char_packet.hpp rename to src/game/packets/requests/request_delete_character_packet.hpp index f0f3269..bf9e4c1 100644 --- a/src/game/packets/requests/delete_char_packet.hpp +++ b/src/game/packets/requests/request_delete_character_packet.hpp @@ -4,13 +4,13 @@ #include "../../../core/network/packet_buffer.hpp" #include -class DeleteCharPacket : public ReadablePacket +class RequestCharacterDeletePacket : public ReadablePacket { private: - static constexpr uint8_t PACKET_ID = 0x0D; + static constexpr uint8_t PACKET_ID = 0x0C; public: - DeleteCharPacket() = default; + RequestCharacterDeletePacket() = default; uint8_t getPacketId() const override; std::optional getExPacketId() const override; diff --git a/src/game/packets/requests/request_item_list.cpp b/src/game/packets/requests/request_item_list.cpp new file mode 100644 index 0000000..efbac00 --- /dev/null +++ b/src/game/packets/requests/request_item_list.cpp @@ -0,0 +1,20 @@ +#include "request_item_list.hpp" +#include + +void RequestItemList::read(ReadablePacketBuffer &buffer) +{ + // RequestItemList typically has no additional data beyond the opcode + // Just consume any remaining bytes if present + std::cout << "[RequestItemList] Client requesting inventory item list" << std::endl; +} + +bool RequestItemList::isValid() const +{ + // Always valid - simple request packet + return true; +} + +std::string RequestItemList::toString() const +{ + return "RequestItemList [Client requesting inventory refresh]"; +} \ No newline at end of file diff --git a/src/game/packets/requests/request_item_list.hpp b/src/game/packets/requests/request_item_list.hpp new file mode 100644 index 0000000..f0d56a6 --- /dev/null +++ b/src/game/packets/requests/request_item_list.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include + +// RequestItemList - Client packet requesting inventory item list +// Sent when client opens/closes inventory window +class RequestItemList : public ReadablePacket +{ +public: + // Constructor + RequestItemList() = default; + + // ReadablePacket interface implementation + void read(ReadablePacketBuffer &buffer) override; + uint8_t getPacketId() const override { return 0x0F; } + std::optional getExPacketId() const override { return std::nullopt; } + bool isValid() const; + + // Getters (if packet contains data) + std::string toString() const; +}; \ No newline at end of file diff --git a/src/game/packets/requests/request_show_mini_map.cpp b/src/game/packets/requests/request_show_mini_map.cpp new file mode 100644 index 0000000..bbe2f17 --- /dev/null +++ b/src/game/packets/requests/request_show_mini_map.cpp @@ -0,0 +1,25 @@ +#include "request_show_mini_map.hpp" +#include + +uint8_t RequestShowMiniMap::getPacketId() const +{ + return PACKET_ID; +} + +std::optional RequestShowMiniMap::getExPacketId() const +{ + return std::nullopt; +} + +void RequestShowMiniMap::read(ReadablePacketBuffer &buffer) +{ + // RequestShowMiniMap has no additional data to read (like L2J Mobius - empty readImpl()) + // Client is simply requesting to show/toggle the minimap + std::cout << "[RequestShowMiniMap] Client requesting minimap display" << std::endl; +} + +bool RequestShowMiniMap::isValid() const +{ + // Simple request packet - always valid + return true; +} \ No newline at end of file diff --git a/src/game/packets/requests/request_show_mini_map.hpp b/src/game/packets/requests/request_show_mini_map.hpp new file mode 100644 index 0000000..aa20c14 --- /dev/null +++ b/src/game/packets/requests/request_show_mini_map.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include + +// RequestShowMiniMap - Client packet requesting minimap display (0xCD) +// Simple request packet with no additional data to read +class RequestShowMiniMap : public ReadablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xCD; + +public: + RequestShowMiniMap() = default; + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void read(ReadablePacketBuffer &buffer) override; + + bool isValid() const; +}; \ No newline at end of file diff --git a/src/game/packets/requests/request_skill_cool_time.cpp b/src/game/packets/requests/request_skill_cool_time.cpp new file mode 100644 index 0000000..c09b21c --- /dev/null +++ b/src/game/packets/requests/request_skill_cool_time.cpp @@ -0,0 +1,24 @@ +#include "request_skill_cool_time.hpp" + +uint8_t RequestSkillCoolTime::getPacketId() const +{ + return PACKET_ID; +} + +std::optional RequestSkillCoolTime::getExPacketId() const +{ + return std::nullopt; +} + +void RequestSkillCoolTime::read(ReadablePacketBuffer &buffer) +{ + // RequestSkillCoolTime is typically a simple request packet + // that doesn't contain additional data - client just wants to know + // current skill cooldown status + // If there are any fields, they would be read here +} + +bool RequestSkillCoolTime::isValid() const +{ + return true; +} \ No newline at end of file diff --git a/src/game/packets/requests/request_skill_cool_time.hpp b/src/game/packets/requests/request_skill_cool_time.hpp new file mode 100644 index 0000000..10947e0 --- /dev/null +++ b/src/game/packets/requests/request_skill_cool_time.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include + +class RequestSkillCoolTime : public ReadablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x9D; + +public: + RequestSkillCoolTime() = default; + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void read(ReadablePacketBuffer &buffer) override; + + bool isValid() const; +}; \ No newline at end of file diff --git a/src/game/packets/responses/action_failed.cpp b/src/game/packets/responses/action_failed.cpp new file mode 100644 index 0000000..2053ac1 --- /dev/null +++ b/src/game/packets/responses/action_failed.cpp @@ -0,0 +1,21 @@ +#include "action_failed.hpp" +#include + +// Constructor +ActionFailed::ActionFailed() +{ +} + +void ActionFailed::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + // No additional data needed for this packet (matches L2J Mobius exactly) + + std::cout << "[ActionFailed] Sending action completion signal" << std::endl; +} + +size_t ActionFailed::getSize() const +{ + // Packet ID only (1 byte) + return 1; +} \ No newline at end of file diff --git a/src/game/packets/responses/action_failed.hpp b/src/game/packets/responses/action_failed.hpp new file mode 100644 index 0000000..ba7bac6 --- /dev/null +++ b/src/game/packets/responses/action_failed.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include + +// ActionFailed - Server response to indicate action completion +// Tells the client that an action has completed (successfully or failed) +// This is crucial for removing loading screens and updating client state +class ActionFailed : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x25; // ActionFailed - Interlude Update 3 + +public: + // Constructor + explicit ActionFailed(); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/ask_join_pledge.cpp b/src/game/packets/responses/ask_join_pledge.cpp new file mode 100644 index 0000000..24cb418 --- /dev/null +++ b/src/game/packets/responses/ask_join_pledge.cpp @@ -0,0 +1,48 @@ +#include "ask_join_pledge.hpp" + +AskJoinPledge::AskJoinPledge(uint32_t requestorObjId, const std::string& subPledgeName, + uint32_t pledgeType, const std::string& pledgeName) + : requestor_obj_id_(requestorObjId) + , sub_pledge_name_(subPledgeName) + , pledge_type_(pledgeType) + , pledge_name_(pledgeName) +{ +} + +uint8_t AskJoinPledge::getPacketId() const +{ + return PACKET_ID; +} + +std::optional AskJoinPledge::getExPacketId() const +{ + return std::nullopt; +} + +void AskJoinPledge::write(SendablePacketBuffer &buffer) +{ + // Write requestor object ID + buffer.writeUInt32(requestor_obj_id_); + + // Write sub-pledge name or main pledge name based on pledge type + if (!sub_pledge_name_.empty()) + { + if (pledge_type_ > 0) + { + buffer.writeCUtf16leString(sub_pledge_name_); + } + else + { + buffer.writeCUtf16leString(pledge_name_); + } + } + + // Write pledge type if not 0 + if (pledge_type_ != 0) + { + buffer.writeUInt32(pledge_type_); + } + + // Write main pledge name + buffer.writeCUtf16leString(pledge_name_); +} \ No newline at end of file diff --git a/src/game/packets/responses/ask_join_pledge.hpp b/src/game/packets/responses/ask_join_pledge.hpp new file mode 100644 index 0000000..8e99370 --- /dev/null +++ b/src/game/packets/responses/ask_join_pledge.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include +#include + +class AskJoinPledge : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x32; + + // Pledge invitation data + uint32_t requestor_obj_id_; + std::string sub_pledge_name_; + uint32_t pledge_type_; + std::string pledge_name_; + +public: + AskJoinPledge(uint32_t requestorObjId, const std::string& subPledgeName, + uint32_t pledgeType, const std::string& pledgeName); + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void write(SendablePacketBuffer &buffer) override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/char_info.cpp b/src/game/packets/responses/char_info.cpp new file mode 100644 index 0000000..2d040a6 --- /dev/null +++ b/src/game/packets/responses/char_info.cpp @@ -0,0 +1,245 @@ +#include "char_info.hpp" +#include + +CharInfo::CharInfo(const Player* player, bool gm_see_invis) + : player_(player), gm_see_invis_(gm_see_invis) { + + obj_id_ = player->getObjectId(); + x_ = player->getX(); + y_ = player->getY(); + z_ = player->getZ(); + heading_ = player->getHeading(); + + // Combat stats + m_atk_spd_ = player->getMAtkSpd(); + p_atk_spd_ = static_cast(player->getPAtkSpd()); + + // Movement speeds + move_multiplier_ = player->getMovementSpeedMultiplier(); + run_spd_ = static_cast(player->getRunSpeed() / move_multiplier_); + walk_spd_ = static_cast(player->getWalkSpeed() / move_multiplier_); + swim_run_spd_ = static_cast(player->getSwimRunSpeed() / move_multiplier_); + swim_walk_spd_ = static_cast(player->getSwimWalkSpeed() / move_multiplier_); + fly_run_spd_ = player->isFlying() ? run_spd_ : 0; + fly_walk_spd_ = player->isFlying() ? walk_spd_ : 0; + + vehicle_id_ = 0; // No vehicle support yet +} + +void CharInfo::write(SendablePacketBuffer& buffer) { + buffer.writeInt32(x_); + buffer.writeInt32(y_); + buffer.writeInt32(z_); + buffer.writeInt32(vehicle_id_); + buffer.writeInt32(obj_id_); + + // Character appearance + buffer.writeCUtf16leString(player_->getName()); + buffer.writeInt32(static_cast(player_->getRace())); + buffer.writeInt32(player_->isFemale() ? 1 : 0); + buffer.writeInt32(player_->getBaseClass()); + + // Equipment paperdoll slots (12 items) + buffer.writeInt32(0); // PAPERDOLL_UNDER + buffer.writeInt32(0); // PAPERDOLL_HEAD + buffer.writeInt32(0); // PAPERDOLL_RHAND + buffer.writeInt32(0); // PAPERDOLL_LHAND + buffer.writeInt32(0); // PAPERDOLL_GLOVES + buffer.writeInt32(0); // PAPERDOLL_CHEST + buffer.writeInt32(0); // PAPERDOLL_LEGS + buffer.writeInt32(0); // PAPERDOLL_FEET + buffer.writeInt32(0); // PAPERDOLL_CLOAK + buffer.writeInt32(0); // PAPERDOLL_RHAND (duplicate) + buffer.writeInt32(0); // PAPERDOLL_HAIR + buffer.writeInt32(0); // PAPERDOLL_HAIR2 + + // C6 new shorts (8 shorts) + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + + // Augmentation + buffer.writeInt32(0); // PAPERDOLL_RHAND augmentation + + // More shorts (12 shorts) + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + + // Another augmentation + buffer.writeInt32(0); // PAPERDOLL_RHAND augmentation (duplicate) + + // Final shorts (4 shorts) + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + buffer.writeUInt16(0); + + // PvP and Karma + buffer.writeInt32(player_->getPvpFlag()); + buffer.writeInt32(player_->getKarma()); + + // Combat stats + buffer.writeInt32(m_atk_spd_); + buffer.writeInt32(p_atk_spd_); + + // PvP and Karma (duplicate) + buffer.writeInt32(player_->getPvpFlag()); + buffer.writeInt32(player_->getKarma()); + + // Movement speeds + buffer.writeInt32(run_spd_); + buffer.writeInt32(walk_spd_); + buffer.writeInt32(swim_run_spd_); + buffer.writeInt32(swim_walk_spd_); + buffer.writeInt32(fly_run_spd_); + buffer.writeInt32(fly_walk_spd_); + buffer.writeInt32(fly_run_spd_); // duplicate + buffer.writeInt32(fly_walk_spd_); // duplicate + + // Multipliers + buffer.writeFloat64(move_multiplier_); + buffer.writeFloat64(player_->getAttackSpeedMultiplier()); + buffer.writeFloat64(player_->getCollisionRadius()); + buffer.writeFloat64(player_->getCollisionHeight()); + + // Appearance + buffer.writeInt32(player_->getHairStyle()); + buffer.writeInt32(player_->getHairColor()); + buffer.writeInt32(player_->getFace()); + buffer.writeCUtf16leString(gm_see_invis_ ? "Invisible" : player_->getTitle()); + + // Clan info (no cursed weapon support) + buffer.writeInt32(player_->getClanId()); + buffer.writeInt32(player_->getClanCrestId()); + buffer.writeInt32(player_->getAllyId()); + buffer.writeInt32(player_->getAllyCrestId()); + + // Relation (placeholder) + buffer.writeInt32(0); + + // Status flags + buffer.writeUInt8(player_->isSitting() ? 0 : 1); // standing = 1, sitting = 0 + buffer.writeUInt8(player_->isRunning() ? 1 : 0); // running = 1, walking = 0 + buffer.writeUInt8(player_->isInCombat() ? 1 : 0); + buffer.writeUInt8(player_->isAlikeDead() ? 1 : 0); + buffer.writeUInt8((!gm_see_invis_ && player_->isInvisible()) ? 1 : 0); + buffer.writeUInt8(0); // mount type (0 = no mount) + buffer.writeUInt8(0); // private store type + + // Cubics + buffer.writeUInt16(0); // cubic count + + // More status + buffer.writeUInt8(0); // party match room + buffer.writeInt32(gm_see_invis_ ? (player_->getAbnormalVisualEffects() | 0x800000) : player_->getAbnormalVisualEffects()); + buffer.writeUInt8(player_->getRecomLeft()); + buffer.writeUInt16(player_->getRecomHave()); + buffer.writeInt32(player_->getPlayerClass()); + buffer.writeInt32(player_->getMaxCp()); + buffer.writeInt32(static_cast(player_->getCurrentCp())); + buffer.writeUInt8(0); // enchant effect + buffer.writeUInt8(0); // team id + buffer.writeInt32(player_->getClanCrestLargeId()); + buffer.writeUInt8(player_->isNoble() ? 1 : 0); + buffer.writeUInt8(player_->isHero() ? 1 : 0); + + // Fishing + buffer.writeUInt8(player_->isFishing() ? 1 : 0); + buffer.writeInt32(player_->getFishX()); + buffer.writeInt32(player_->getFishY()); + buffer.writeInt32(player_->getFishZ()); + + // Final appearance + buffer.writeInt32(player_->getNameColor()); + buffer.writeInt32(heading_); + buffer.writeInt32(player_->getPledgeClass()); + buffer.writeInt32(player_->getPledgeType()); + buffer.writeInt32(player_->getTitleColor()); + buffer.writeInt32(0); // cursed weapon level +} + +size_t CharInfo::getSize() const { + // Calculate packet size based on L2J Mobius CharInfo structure + size_t size = 0; + + // Basic position and object data + size += 4 * 5; // x, y, z, vehicle_id, obj_id + + // Character name (string) + size += 2 + (player_->getName().length() * 2); // UTF-16 string + + // Character basic info + size += 4 * 4; // race, sex, base_class + + // Equipment paperdoll (12 items) + size += 4 * 12; + + // C6 shorts and augmentation + size += 2 * 4; // 4 shorts + size += 4; // augmentation + size += 2 * 12; // 12 shorts + size += 4; // augmentation duplicate + size += 2 * 4; // 4 final shorts + + // PvP, karma, combat stats + size += 4 * 6; // pvp_flag, karma, m_atk_spd, p_atk_spd, pvp_flag2, karma2 + + // Movement speeds + size += 4 * 8; // 8 movement speed values + + // Multipliers + size += 8 * 4; // 4 double values + + // Appearance + size += 4 * 3; // hair_style, hair_color, face + + // Title (string) + size += 2 + (player_->getTitle().length() * 2); // UTF-16 string + + // Clan info + size += 4 * 4; // clan_id, clan_crest_id, ally_id, ally_crest_id + + // Relation + size += 4; + + // Status flags + size += 1 * 7; // 7 status bytes + + // Cubics + size += 2; // cubic count (0 for now) + + // More status + size += 1; // party match room + size += 4; // abnormal visual effects + size += 1; // recom left + size += 2; // recom have + size += 4; // player class + size += 4; // max cp + size += 4; // current cp + size += 1; // enchant effect + size += 1; // team id + size += 4; // clan crest large id + size += 1; // noble + size += 1; // hero + + // Fishing + size += 1; // is fishing + size += 4 * 3; // fish x, y, z + + // Final appearance + size += 4 * 5; // name_color, heading, pledge_class, pledge_type, title_color + size += 4; // cursed weapon level + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/char_info.hpp b/src/game/packets/responses/char_info.hpp new file mode 100644 index 0000000..1ea1d19 --- /dev/null +++ b/src/game/packets/responses/char_info.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" + +class CharInfo : public SendablePacket { +private: + const Player* player_; + int obj_id_; + int x_; + int y_; + int z_; + int heading_; + int m_atk_spd_; + int p_atk_spd_; + int run_spd_; + int walk_spd_; + int swim_run_spd_; + int swim_walk_spd_; + int fly_run_spd_; + int fly_walk_spd_; + double move_multiplier_; + int vehicle_id_; + bool gm_see_invis_; + +public: + explicit CharInfo(const Player* player, bool gm_see_invis = false); + + void write(SendablePacketBuffer& buffer) override; + size_t getSize() const override; + + uint8_t getPacketId() const override { return 0x03; } + std::optional getExPacketId() const override { return std::nullopt; } + + static constexpr uint8_t PACKET_ID = 0x03; +}; \ No newline at end of file diff --git a/src/game/packets/responses/character_create_success.cpp b/src/game/packets/responses/character_create_success.cpp index dfea9b7..1ff83c0 100644 --- a/src/game/packets/responses/character_create_success.cpp +++ b/src/game/packets/responses/character_create_success.cpp @@ -12,8 +12,8 @@ void CharacterCreateSuccess::write(SendablePacketBuffer &buffer) { try { - // Write packet ID (same as received ping) - buffer.writeUInt8(PACKET_ID); + // Opcode is written automatically by base class + // No additional data needed for this packet } catch (const std::exception &e) { diff --git a/src/game/packets/responses/character_selected.cpp b/src/game/packets/responses/character_selected.cpp new file mode 100644 index 0000000..8e8af42 --- /dev/null +++ b/src/game/packets/responses/character_selected.cpp @@ -0,0 +1,161 @@ +#include "character_selected.hpp" +#include + +// Constructor +CharacterSelected::CharacterSelected(const Player* player, uint32_t sessionId) + : player_(player), sessionId_(sessionId) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for CharacterSelected packet"); + } +} + +void CharacterSelected::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[CharacterSelected] Sending character data for: " << player_->getName() + << " (ID: " << player_->getObjectId() << ")" << std::endl; + + // Write character data following L2J Mobius CharSelected.java format exactly + // 1. Character name as null-terminated UTF-16LE string + buffer.writeCUtf16leString(player_->getName()); + + // 2. Character ID + buffer.writeUInt32(player_->getObjectId()); + + // 3. Character title as null-terminated UTF-16LE string + buffer.writeCUtf16leString(std::string("")); // TODO: Implement title system + + // 4. Session ID + buffer.writeUInt32(sessionId_); + + // 5. Clan ID + buffer.writeUInt32(player_->getClanId()); + + // 6. Builder level (0x00 = normal player) + buffer.writeUInt32(0); + + // 7. Sex (0=male, 1=female) + buffer.writeUInt32(player_->getSex()); + + // 8. Race (0=human, 1=elf, 2=dark_elf, 3=orc, 4=dwarf) + buffer.writeUInt32(player_->getRace()); + + // 9. Class ID (current class) + buffer.writeUInt32(player_->getClassId()); + + // 10. Active status (1 = active) + buffer.writeUInt32(1); + + // 11. Position coordinates + buffer.writeUInt32(player_->getX()); + buffer.writeUInt32(player_->getY()); + buffer.writeUInt32(player_->getZ()); + + // 12. Current HP (double) + buffer.writeFloat64(player_->getCurrentHp()); + + // 13. Current MP (double) + buffer.writeFloat64(player_->getCurrentMp()); + + // 14. SP (as int, not long for Interlude) + buffer.writeUInt32(static_cast(player_->getSp())); + + // 15. EXP (long) + buffer.writeUInt64(player_->getExp()); + + // 16. Level + buffer.writeUInt32(player_->getLevel()); + + // 17. Karma + buffer.writeUInt32(player_->getKarma()); + + // 18. PK kills + buffer.writeUInt32(player_->getPkKills()); + + // 19. INT stat + buffer.writeUInt32(player_->getInt()); + + // 20. STR stat + buffer.writeUInt32(player_->getStr()); + + // 21. CON stat + buffer.writeUInt32(player_->getCon()); + + // 22. MEN stat + buffer.writeUInt32(player_->getMen()); + + // 23. DEX stat + buffer.writeUInt32(player_->getDex()); + + // 24. WIT stat + buffer.writeUInt32(player_->getWit()); + + // 25. 30 reserved integers (matching Java implementation) + for (int i = 0; i < 30; i++) + { + buffer.writeUInt32(0); + } + + // 26. Additional reserved integer + buffer.writeUInt32(0); + + // 27. Another reserved integer + buffer.writeUInt32(0); + + // 28. Game time (minutes since server start, reset every 24 hours) + // TODO: Implement proper game time system + buffer.writeUInt32(0); + + // 29. Reserved integer + buffer.writeUInt32(0); + + // 30. Class ID again + buffer.writeUInt32(player_->getClassId()); + + // 31-40. 10 more reserved integers (matching Java implementation) + for (int i = 0; i < 10; i++) + { + buffer.writeUInt32(0); + } + + std::cout << "[CharacterSelected] Character data sent successfully" << std::endl; +} + +size_t CharacterSelected::getSize() const +{ + // Calculate packet size based on the data being sent + size_t size = 1; // opcode + + // Character name (UTF-16LE + null terminator) + size += (player_->getName().length() + 1) * 2; + + // Fixed-size fields + size += 4; // Character ID + size += 2; // Title (empty string, UTF-16LE + null) + size += 4; // Session ID + size += 4; // Clan ID + size += 4; // Builder level + size += 4; // Sex + size += 4; // Race + size += 4; // Class ID + size += 4; // Active status + size += 4 * 3; // X, Y, Z coordinates + size += 8 * 2; // Current HP, MP (doubles) + size += 4; // SP + size += 8; // EXP (long) + size += 4; // Level + size += 4; // Karma + size += 4; // PK kills + size += 4 * 6; // INT, STR, CON, MEN, DEX, WIT + size += 4 * 30; // 30 reserved integers + size += 4 * 2; // 2 more reserved integers + size += 4; // Game time + size += 4; // Reserved + size += 4; // Class ID again + size += 4 * 10; // 10 more reserved integers + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/character_selected.hpp b/src/game/packets/responses/character_selected.hpp new file mode 100644 index 0000000..c4c05fd --- /dev/null +++ b/src/game/packets/responses/character_selected.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// CharSelected - Server response after successful character selection (RequestGameStart) +// Confirms character selection and provides character data for entering the game world +class CharacterSelected : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x15; // CharacterSelected - Interlude Update 3 + const Player* player_; + uint32_t sessionId_; + +public: + // Constructor - create with selected character data + explicit CharacterSelected(const Player* player, uint32_t sessionId); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/character_selection_info.cpp b/src/game/packets/responses/character_selection_info.cpp index 8e0e3d1..67bab4a 100644 --- a/src/game/packets/responses/character_selection_info.cpp +++ b/src/game/packets/responses/character_selection_info.cpp @@ -3,149 +3,154 @@ #include // Constructor -CharacterSelectionInfo::CharacterSelectionInfo(const std::vector& characters) +CharacterSelectionInfo::CharacterSelectionInfo(const std::vector &characters) : characters_(characters) { } // Static factory method for creating character list from database -std::unique_ptr CharacterSelectionInfo::createFromDatabase(CharacterDatabaseManager* char_db, const std::string& account_name) +std::unique_ptr CharacterSelectionInfo::createFromDatabase(CharacterDatabaseManager *char_db, const std::string &account_name) { - std::vector characters; - - if (char_db) { + std::vector characters; + + if (char_db) + { // Get all characters for this account from the database characters = char_db->getCharactersForAccount(account_name); } - + return std::make_unique(characters); } void CharacterSelectionInfo::write(SendablePacketBuffer &buffer) { - // Write packet header - using 0x13 for Interlude Update 3 - buffer.writeUInt8(PACKET_ID); // 0x13 CharacterSelectionInfo - + // Opcode is written automatically by base class + // Character count uint32_t char_count = static_cast(characters_.size()); buffer.writeUInt32(char_count); - + std::cout << "[CharSelectionInfo] Sending " << char_count << " characters" << std::endl; - + // Find most recently accessed character for active selection int active_char_index = -1; - if (!characters_.empty()) { + if (!characters_.empty()) + { active_char_index = 0; // Default to first character } - + // Write each character's data using EXACT L2 Interlude format - for (size_t i = 0; i < characters_.size(); ++i) { - const auto& character = characters_[i]; - - std::cout << "[CharSelectionInfo] Character " << i << ": " << character.name - << " ID=" << character.char_id - << " HP=" << character.current_hp << "/" << character.max_hp - << " MP=" << character.current_mp << "/" << character.max_mp << std::endl; - + for (size_t i = 0; i < characters_.size(); ++i) + { + const auto *character = characters_[i]; + + std::cout << "[CharSelectionInfo] Character " << i << ": " << character->getName() + << " ID=" << character->getObjectId() + << " HP=" << character->getCurrentHp() << "/" << character->getMaxHp() + << " MP=" << character->getCurrentMp() << "/" << character->getMaxMp() << std::endl; + // Character name as null-terminated UTF-16LE string - buffer.writeCUtf16leString(character.name); - + buffer.writeCUtf16leString(character->getName()); + // Character ID - buffer.writeUInt32(character.char_id); - - // Login name as null-terminated UTF-16LE string - buffer.writeCUtf16leString(character.login_name); - + buffer.writeUInt32(character->getObjectId()); + + // Login name as null-terminated UTF-16LE string + buffer.writeCUtf16leString(character->getAccountName()); + // Session ID - buffer.writeUInt32(character.session_id); - + buffer.writeUInt32(character->getSessionId()); + // Clan ID - buffer.writeUInt32(character.clan_id); - + buffer.writeUInt32(character->getClanId()); + // Builder level (0x00 = normal player) - buffer.writeUInt32(character.builder_level); - + buffer.writeUInt32(0); + // Sex (0=male, 1=female) - buffer.writeUInt32(character.sex); - + buffer.writeUInt32(character->getSex()); + // Race (0=human, 1=elf, 2=dark_elf, 3=orc, 4=dwarf) - buffer.writeUInt32(character.race); - + buffer.writeUInt32(character->getRace()); + // Class ID (current class) - buffer.writeUInt32(character.class_id); - + buffer.writeUInt32(character->getClassId()); + // Server ID (always 1 for single server) buffer.writeUInt32(1); - + // Position - buffer.writeUInt32(character.x); - buffer.writeUInt32(character.y); - buffer.writeUInt32(character.z); - + buffer.writeUInt32(character->getX()); + buffer.writeUInt32(character->getY()); + buffer.writeUInt32(character->getZ()); + // Current HP (double) - buffer.writeFloat64(character.current_hp); - + buffer.writeFloat64(character->getCurrentHp()); + // Current MP (double) - buffer.writeFloat64(character.current_mp); - + buffer.writeFloat64(character->getCurrentMp()); + // SP (as int, not long for Interlude) - buffer.writeUInt32(static_cast(character.sp)); - + buffer.writeUInt32(static_cast(character->getSp())); + // EXP (long) - buffer.writeUInt64(character.exp); - + buffer.writeUInt64(character->getExp()); + // Level - buffer.writeUInt32(character.level); - + buffer.writeUInt32(character->getLevel()); + // Karma - buffer.writeUInt32(character.karma); - + buffer.writeUInt32(character->getKarma()); + // PK kills - buffer.writeUInt32(character.pk_kills); - - // PvP kills - buffer.writeUInt32(character.pv_kills); - + buffer.writeUInt32(character->getPkKills()); + + // PvP kills + buffer.writeUInt32(character->getPvpKills()); + // Reserved bytes (9 zeros matching Java) - for (int j = 0; j < 9; ++j) { + for (int j = 0; j < 9; ++j) + { buffer.writeUInt32(0); } - + // CRITICAL: Equipment paperdoll - Send 16 object IDs first - for (uint32_t obj_id : character.paperdoll_object_ids) { + for (uint32_t obj_id : character->getPaperdollObjectIds()) + { buffer.writeUInt32(obj_id); } - - // CRITICAL: Equipment paperdoll - Send 16 item IDs second - for (uint32_t item_id : character.paperdoll_item_ids) { + + // CRITICAL: Equipment paperdoll - Send 16 item IDs second + for (uint32_t item_id : character->getPaperdollItemIds()) + { buffer.writeUInt32(item_id); } - + // Appearance data (essential for character display) - buffer.writeUInt32(character.hair_style); - buffer.writeUInt32(character.hair_color); - buffer.writeUInt32(character.face); - + buffer.writeUInt32(character->getHairStyle()); + buffer.writeUInt32(character->getHairColor()); + buffer.writeUInt32(character->getFace()); + // Maximum HP (double) - CRITICAL missing field - buffer.writeFloat64(character.max_hp); - + buffer.writeFloat64(character->getMaxHp()); + // Maximum MP (double) - CRITICAL missing field - buffer.writeFloat64(character.max_mp); - + buffer.writeFloat64(character->getMaxMp()); + // Delete timer (0 = not being deleted, >0 = deletion time) buffer.writeUInt32(0); - + // Base class ID (for subclass system) - buffer.writeUInt32(character.base_class_id); - + buffer.writeUInt32(character->getBaseClassId()); + // Is this character selected/active? buffer.writeUInt32(static_cast(i == active_char_index ? 1 : 0)); - + // Enchant effect (visual weapon glow) - buffer.writeUInt8(static_cast(std::min(character.enchant_effect, 127u))); - + buffer.writeUInt8(static_cast(std::min(character->getEnchantEffect(), 127u))); + // Augmentation ID (weapon augmentation) - buffer.writeUInt32(character.augmentation_id); + buffer.writeUInt32(character->getAugmentationId()); } } @@ -154,23 +159,24 @@ size_t CharacterSelectionInfo::getSize() const // Packet size calculation - this is complex due to variable string lengths // For now, return a reasonable estimate size_t base_size = 1 + 4; // opcode + char_count - + // Each character has many more fields now - for (const auto& character : characters_) { + for (const auto *character : characters_) + { size_t char_size = 0; - char_size += (character.name.length() + 1) * 2; // Name (UTF-16LE + null) - char_size += 4; // Character ID - char_size += (character.login_name.length() + 1) * 2; // Login name (UTF-16LE + null) - char_size += 4 * 8; // Session, clan, builder, sex, race, class, server, x, y, z - char_size += 8 * 4; // current_hp, current_mp, max_hp, max_mp (doubles) - char_size += 4 + 8 + 4 * 4; // sp, exp, level, karma, pk, pvp - char_size += 4 * 9; // Reserved bytes - char_size += 4 * 32; // Equipment (16 object + 16 item IDs) - char_size += 4 * 3; // Hair style, color, face - char_size += 4 * 3; // Delete timer, base class, selected - char_size += 1 + 4; // Enchant effect + augmentation + char_size += (character->getName().length() + 1) * 2; // Name (UTF-16LE + null) + char_size += 4; // Character ID + char_size += (character->getAccountName().length() + 1) * 2; // Login name (UTF-16LE + null) + char_size += 4 * 8; // Session, clan, builder, sex, race, class, server, x, y, z + char_size += 8 * 4; // current_hp, current_mp, max_hp, max_mp (doubles) + char_size += 4 + 8 + 4 * 4; // sp, exp, level, karma, pk, pvp + char_size += 4 * 9; // Reserved bytes + char_size += 4 * 32; // Equipment (16 object + 16 item IDs) + char_size += 4 * 3; // Hair style, color, face + char_size += 4 * 3; // Delete timer, base class, selected + char_size += 1 + 4; // Enchant effect + augmentation base_size += char_size; } - + return base_size; -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/game/packets/responses/character_selection_info.hpp b/src/game/packets/responses/character_selection_info.hpp index 7c9a606..b810b14 100644 --- a/src/game/packets/responses/character_selection_info.hpp +++ b/src/game/packets/responses/character_selection_info.hpp @@ -2,99 +2,22 @@ #include "../../../core/packets/packet.hpp" #include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" #include #include #include -// Character data structure matching L2 Interlude packet format -struct CharacterInfo -{ - std::string name; - uint32_t char_id; - std::string login_name; - uint32_t session_id; - uint32_t clan_id; - uint32_t builder_level; - uint32_t sex; // 0 = male, 1 = female - uint32_t race; // 0 = human, 1 = elf, 2 = dark_elf, 3 = orc, 4 = dwarf - uint32_t class_id; - uint32_t active; // 0 = inactive, 1 = active - - // Position - uint32_t x; - uint32_t y; - uint32_t z; - - // Stats - double current_hp; - double current_mp; - double max_hp; - double max_mp; - uint64_t sp; - uint64_t exp; - uint32_t level; - uint32_t karma; - uint32_t pk_kills; - uint32_t pv_kills; - - // Appearance data (essential for L2 character display) - uint32_t hair_style; - uint32_t hair_color; - uint32_t face; - - // Base stats (often required for character display) - uint32_t str_stat; - uint32_t dex_stat; - uint32_t con_stat; - uint32_t int_stat; - uint32_t wit_stat; - uint32_t men_stat; - - // Equipment (paperdoll) - L2 needs both object IDs and item IDs - std::vector paperdoll_object_ids; // object IDs - std::vector paperdoll_item_ids; // item IDs - - // Additional L2 fields - long delete_timer; - uint32_t augmentation_id; - uint32_t base_class_id; - uint32_t is_selected; // Whether this character is the active/selected one - uint32_t enchant_effect; // Visual enchant effect - - CharacterInfo() - { - paperdoll_object_ids.resize(16, 0); // Initialize 16 equipment object slots - paperdoll_item_ids.resize(16, 0); // Initialize 16 equipment item slots - hair_style = 0; - hair_color = 0; - face = 0; - str_stat = 10; - dex_stat = 10; - con_stat = 10; - int_stat = 10; - wit_stat = 10; - men_stat = 10; - max_hp = 100.0; - max_mp = 100.0; - delete_timer = 0; - base_class_id = 0; - is_selected = 0; - enchant_effect = 0; - augmentation_id = 0; - } -}; - // CharacterSelectionInfo - Server response after successful RequestLogin // Shows available characters for the authenticated account class CharacterSelectionInfo : public SendablePacket { private: static constexpr uint8_t PACKET_ID = 0x13; // CharacterSelectionInfo - Interlude Update 3 - std::vector characters_; + std::vector characters_; public: // Constructor - create with character data - explicit CharacterSelectionInfo(const std::vector &characters); + explicit CharacterSelectionInfo(const std::vector &characters); // Create with characters from database static std::unique_ptr createFromDatabase(class CharacterDatabaseManager *char_db, const std::string &account_name); diff --git a/src/game/packets/responses/etc_status_update.cpp b/src/game/packets/responses/etc_status_update.cpp new file mode 100644 index 0000000..bb998ac --- /dev/null +++ b/src/game/packets/responses/etc_status_update.cpp @@ -0,0 +1,43 @@ +#include "etc_status_update.hpp" +#include + +EtcStatusUpdate::EtcStatusUpdate(const Player* player) + : player_(player) +{ + if (!player_) { + throw std::invalid_argument("Player cannot be null for EtcStatusUpdate packet"); + } + calculateStatusFlags(); +} + +void EtcStatusUpdate::write(SendablePacketBuffer& buffer) +{ + // Packet structure: 7 status flags as uint32 values (matches L2J Mobius exactly) + buffer.writeInt32(static_cast(charges_)); + buffer.writeInt32(static_cast(weightPenalty_)); + buffer.writeInt32(static_cast(messageRefusal_)); + buffer.writeInt32(static_cast(dangerArea_)); + buffer.writeInt32(static_cast(expertisePenalty_)); + buffer.writeInt32(static_cast(charmOfCourage_)); + buffer.writeInt32(static_cast(deathPenalty_)); +} + +size_t EtcStatusUpdate::getSize() const +{ + // Fixed size: 7 uint32 fields = 28 bytes + return 28; +} + +void EtcStatusUpdate::calculateStatusFlags() +{ + // For now, set all flags to 0 (no active status effects) + // TODO: Implement actual status calculation based on player state + + charges_ = 0; // TODO: player_.getCharges() + weightPenalty_ = 0; // TODO: player_.getWeightPenalty() + messageRefusal_ = 0; // TODO: player_.getMessageRefusal() || player_.isChatBanned() || player_.isSilenceMode() + dangerArea_ = 0; // TODO: player_.isInsideZone(ZoneId.DANGER_AREA) + expertisePenalty_ = 0; // TODO: (player_.getExpertiseWeaponPenalty() > 0) || (player_.getExpertiseArmorPenalty() > 0) + charmOfCourage_ = 0; // TODO: player_.hasCharmOfCourage() + deathPenalty_ = 0; // TODO: player_.getDeathPenaltyBuffLevel() +} \ No newline at end of file diff --git a/src/game/packets/responses/etc_status_update.hpp b/src/game/packets/responses/etc_status_update.hpp new file mode 100644 index 0000000..bdb72a9 --- /dev/null +++ b/src/game/packets/responses/etc_status_update.hpp @@ -0,0 +1,36 @@ +// src/game/packets/responses/etc_status_update.hpp +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include + +// EtcStatusUpdate packet (opcode 0xF3) +// Sends status icons (weight, soulshots, etc.) to the client +class EtcStatusUpdate : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xF3; + const Player* player_; + + // Status flags (matches L2J Mobius exactly - only 7 fields) + uint32_t charges_; // 1-7 increase force, level + uint32_t weightPenalty_; // 1-4 weight penalty, level (1=50%, 2=66.6%, 3=80%, 4=100%) + uint32_t messageRefusal_; // 1 = block all chat + uint32_t dangerArea_; // 1 = danger area + uint32_t expertisePenalty_; // Weapon Grade Penalty [1-4] - Armor Grade Penalty [1-4] + uint32_t charmOfCourage_; // 1 = charm of courage (allows resurrection on the same spot upon death on the siege battlefield) + uint32_t deathPenalty_; // 1-15 death penalty, level (combat ability decreased due to death) + + void calculateStatusFlags(); + +public: + explicit EtcStatusUpdate(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer& buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/ex_send_manor_list.cpp b/src/game/packets/responses/ex_send_manor_list.cpp new file mode 100644 index 0000000..4f78af3 --- /dev/null +++ b/src/game/packets/responses/ex_send_manor_list.cpp @@ -0,0 +1,60 @@ +#include "ex_send_manor_list.hpp" +#include + +ExSendManorList::ExSendManorList() +{ + // Based on L2J Mobius: send castle list sorted by residence ID + // Castle names are sent in lowercase as per L2J Mobius implementation + castles_.clear(); + + // Standard Interlude castles (sorted by residence ID) + castles_.push_back({1, "gludio"}); + castles_.push_back({2, "dion"}); + castles_.push_back({3, "giran"}); + castles_.push_back({4, "oren"}); + castles_.push_back({5, "aden"}); + castles_.push_back({6, "innadril"}); + castles_.push_back({7, "goddard"}); + castles_.push_back({8, "rune"}); + castles_.push_back({9, "schuttgart"}); +} + +uint8_t ExSendManorList::getPacketId() const +{ + return PACKET_ID; +} + +std::optional ExSendManorList::getExPacketId() const +{ + return EX_PACKET_ID; +} + +void ExSendManorList::write(SendablePacketBuffer &buffer) +{ + // Write castle count (matches L2J Mobius: buffer.writeInt(castles.size())) + buffer.writeUInt32(static_cast(castles_.size())); + + // Write each castle (matches L2J Mobius structure exactly) + for (const auto& castle : castles_) + { + buffer.writeUInt32(castle.residence_id); // castle.getResidenceId() + buffer.writeCUtf16leString(castle.castle_name); // castle.getName().toLowerCase() + } + + std::cout << "[ExSendManorList] Sending castle list with " << castles_.size() << " castles" << std::endl; +} + +size_t ExSendManorList::getSize() const +{ + // Calculate packet size: extended opcode + castle count + castle data + size_t size = 3; // 0xFE + 16-bit sub-opcode + size += 4; // castle count (uint32) + + for (const auto& castle : castles_) + { + size += 4; // residence_id (uint32) + size += (castle.castle_name.length() + 1) * 2; // castle_name (UTF-16LE + null) + } + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/ex_send_manor_list.hpp b/src/game/packets/responses/ex_send_manor_list.hpp new file mode 100644 index 0000000..7239450 --- /dev/null +++ b/src/game/packets/responses/ex_send_manor_list.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +class ExSendManorList : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xFE; + static constexpr uint16_t EX_PACKET_ID = 0x1B; + + // Castle data structure (matches L2J Mobius Castle.getResidenceId() and Castle.getName()) + struct CastleData { + uint32_t residence_id; // castle.getResidenceId() + std::string castle_name; // castle.getName().toLowerCase() + }; + + std::vector castles_; + +public: + explicit ExSendManorList(); + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/ex_show_screen_message.cpp b/src/game/packets/responses/ex_show_screen_message.cpp new file mode 100644 index 0000000..e2b8285 --- /dev/null +++ b/src/game/packets/responses/ex_show_screen_message.cpp @@ -0,0 +1,130 @@ +#include "ex_show_screen_message.hpp" +#include + +// Constructor - simple text message +ExShowScreenMessage::ExShowScreenMessage(const std::string& text, int32_t time) + : type_(1), sysMessageId_(-1), position_(static_cast(Position::TOP_CENTER)), + unk1_(0), size_(0), unk2_(0), unk3_(0), effect_(false), time_(time), fade_(false), text_(text) +{ +} + +// Constructor - text with position +ExShowScreenMessage::ExShowScreenMessage(const std::string& text, int32_t position, int32_t time) + : type_(1), sysMessageId_(-1), position_(position), + unk1_(0), size_(0), unk2_(0), unk3_(0), effect_(false), time_(time), fade_(false), text_(text) +{ +} + +// Constructor - full control +ExShowScreenMessage::ExShowScreenMessage(const std::string& text, int32_t position, int32_t time, + int32_t size, bool fade, bool showEffect) + : type_(1), sysMessageId_(-1), position_(position), + unk1_(0), size_(size), unk2_(0), unk3_(0), effect_(showEffect), time_(time), fade_(fade), text_(text) +{ +} + +// Constructor - system message +ExShowScreenMessage::ExShowScreenMessage(int32_t sysMessageId, int32_t position, int32_t time, + const std::vector& params) + : type_(2), sysMessageId_(sysMessageId), position_(position), + unk1_(0), size_(0), unk2_(0), unk3_(0), effect_(false), time_(time), fade_(false), text_(""), parameters_(params) +{ +} + +void ExShowScreenMessage::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[ExShowScreenMessage] Sending screen message: " << text_ << std::endl; + + // Following L2J Mobius ExShowScreenMessage.java structure EXACTLY + + // 1. Type (int) + buffer.writeUInt32(static_cast(type_)); + + // 2. System message ID (int) + buffer.writeUInt32(static_cast(sysMessageId_)); + + // 3. Position (int) + buffer.writeUInt32(static_cast(position_)); + + // 4. Unknown 1 (int) + buffer.writeUInt32(static_cast(unk1_)); + + // 5. Size (int) + buffer.writeUInt32(static_cast(size_)); + + // 6. Unknown 2 (int) + buffer.writeUInt32(static_cast(unk2_)); + + // 7. Unknown 3 (int) + buffer.writeUInt32(static_cast(unk3_)); + + // 8. Effect (int) + buffer.writeUInt32(static_cast(effect_ ? 1 : 0)); + + // 9. Time (int) + buffer.writeUInt32(static_cast(time_)); + + // 10. Fade (int) + buffer.writeUInt32(static_cast(fade_ ? 1 : 0)); + + // 11. Text (string) + buffer.writeCUtf16leString(text_); + + // 12. Additional text or parameters + if (sysMessageId_ == -1) + { + // For text messages, write the text again + buffer.writeCUtf16leString(text_); + } + else if (!parameters_.empty()) + { + // For system messages, write parameters + for (const auto& param : parameters_) + { + buffer.writeCUtf16leString(param); + } + } + + std::cout << "[ExShowScreenMessage] Screen message sent successfully" << std::endl; +} + +size_t ExShowScreenMessage::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 3; // Extended opcode (0xFE + 16-bit sub-opcode) + + // Fixed-size fields (11 ints) + size += 4 * 11; // type, sysMessageId, position, unk1, size, unk2, unk3, effect, time, fade + + // Text + size += (text_.length() + 1) * 2; // UTF-16LE + null + + // Additional text or parameters + if (sysMessageId_ == -1) + { + // For text messages, write the text again + size += (text_.length() + 1) * 2; // UTF-16LE + null + } + else if (!parameters_.empty()) + { + // For system messages, write parameters + for (const auto& param : parameters_) + { + size += (param.length() + 1) * 2; // UTF-16LE + null + } + } + + return size; +} + +void ExShowScreenMessage::addStringParameter(const std::string& param) +{ + parameters_.push_back(param); +} + +void ExShowScreenMessage::addStringParameter(const std::vector& params) +{ + parameters_.insert(parameters_.end(), params.begin(), params.end()); +} \ No newline at end of file diff --git a/src/game/packets/responses/ex_show_screen_message.hpp b/src/game/packets/responses/ex_show_screen_message.hpp new file mode 100644 index 0000000..764b600 --- /dev/null +++ b/src/game/packets/responses/ex_show_screen_message.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include +#include +#include + +// ExShowScreenMessage - Server response with screen messages +// Shows popup messages on the client screen (welcome messages, announcements, etc.) +class ExShowScreenMessage : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xFE; // Extended packet prefix + static constexpr uint16_t EX_PACKET_ID = 0x38; // Extended packet sub-opcode + + // Positions (matches L2J Mobius) + enum class Position { + TOP_LEFT = 1, + TOP_CENTER = 2, + TOP_RIGHT = 3, + MIDDLE_LEFT = 4, + MIDDLE_CENTER = 5, + MIDDLE_RIGHT = 6, + BOTTOM_CENTER = 7, + BOTTOM_RIGHT = 8 + }; + + int32_t type_; // 0 - System Message, 1 - Text, 2 - NPC String + int32_t sysMessageId_; // System message ID (-1 for text) + int32_t position_; // Position on screen + int32_t unk1_; // Unknown field + int32_t size_; // Font size (0 - normal, 1 - small) + int32_t unk2_; // Unknown field + int32_t unk3_; // Unknown field + bool effect_; // Show effect (upper effect) + int32_t time_; // Display time + bool fade_; // Fade effect + std::string text_; // Text to display + std::vector parameters_; // String parameters + +public: + // Constructor - simple text message + explicit ExShowScreenMessage(const std::string& text, int32_t time); + + // Constructor - text with position + explicit ExShowScreenMessage(const std::string& text, int32_t position, int32_t time); + + // Constructor - full control + explicit ExShowScreenMessage(const std::string& text, int32_t position, int32_t time, + int32_t size, bool fade, bool showEffect); + + // Constructor - system message + explicit ExShowScreenMessage(int32_t sysMessageId, int32_t position, int32_t time, + const std::vector& params = {}); + + // Add string parameter + void addStringParameter(const std::string& param); + void addStringParameter(const std::vector& params); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + uint16_t getExtendedPacketId() const override { return EX_PACKET_ID; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/ex_storage_max_count.cpp b/src/game/packets/responses/ex_storage_max_count.cpp new file mode 100644 index 0000000..301924b --- /dev/null +++ b/src/game/packets/responses/ex_storage_max_count.cpp @@ -0,0 +1,51 @@ +// src/game/packets/responses/ex_storage_max_count.cpp +#include "ex_storage_max_count.hpp" +#include + +ExStorageMaxCount::ExStorageMaxCount(const Player* player) + : player_(player) +{ + if (!player_) { + throw std::invalid_argument("Player cannot be null for ExStorageMaxCount packet"); + } + calculateStorageLimits(); +} + +void ExStorageMaxCount::write(SendablePacketBuffer& buffer) +{ + // Packet structure: 9 storage limits as uint32 values (matches L2J Mobius exactly) + buffer.writeInt32(static_cast(inventory_)); + buffer.writeInt32(static_cast(warehouse_)); + buffer.writeInt32(static_cast(clan_)); + buffer.writeInt32(static_cast(privateSell_)); + buffer.writeInt32(static_cast(privateBuy_)); + buffer.writeInt32(static_cast(recipeD_)); + buffer.writeInt32(static_cast(recipe_)); + buffer.writeInt32(static_cast(inventoryExtraSlots_)); // Belt inventory slots increase count + buffer.writeInt32(static_cast(inventoryQuestItems_)); +} + +size_t ExStorageMaxCount::getSize() const +{ + // Extended opcode (0xFE + 16-bit sub-opcode) + 9 uint32 fields + return 3 + 36; // 3 bytes opcode + 36 bytes data +} + +void ExStorageMaxCount::calculateStorageLimits() +{ + // For now, use default values + // TODO: Calculate actual limits based on player level, class, and bonuses + + inventory_ = 80; // TODO: player_.getInventoryLimit() + warehouse_ = 80; // TODO: player_.getWareHouseLimit() + clan_ = 200; // TODO: Config.WAREHOUSE_SLOTS_CLAN + privateSell_ = 80; // TODO: player_.getPrivateSellStoreLimit() + privateBuy_ = 80; // TODO: player_.getPrivateBuyStoreLimit() + recipeD_ = 50; // TODO: player_.getDwarfRecipeLimit() + recipe_ = 50; // TODO: player_.getCommonRecipeLimit() + inventoryExtraSlots_ = 0; // TODO: player_.getStat().calcStat(Stat.INV_LIM, 0, null, null) + inventoryQuestItems_ = 100; // TODO: Config.INVENTORY_MAXIMUM_QUEST_ITEMS + + // TODO: Implement actual limit calculation + // Example: inventory_ = 80 + (player_.getLevel() / 10) * 4; // +4 slots per 10 levels +} \ No newline at end of file diff --git a/src/game/packets/responses/ex_storage_max_count.hpp b/src/game/packets/responses/ex_storage_max_count.hpp new file mode 100644 index 0000000..fffbadc --- /dev/null +++ b/src/game/packets/responses/ex_storage_max_count.hpp @@ -0,0 +1,39 @@ +// src/game/packets/responses/ex_storage_max_count.hpp +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include + +// ExStorageMaxCount packet (opcode 0x2FE) +// Sends inventory and warehouse limits to the client +class ExStorageMaxCount : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xFE; // Extended packet prefix + static constexpr uint16_t EX_PACKET_ID = 0x2E; // Extended packet sub-opcode + const Player* player_; + + // Storage limits (matches L2J Mobius exactly - 9 fields) + uint32_t inventory_; // player.getInventoryLimit() + uint32_t warehouse_; // player.getWareHouseLimit() + uint32_t clan_; // Config.WAREHOUSE_SLOTS_CLAN + uint32_t privateSell_; // player.getPrivateSellStoreLimit() + uint32_t privateBuy_; // player.getPrivateBuyStoreLimit() + uint32_t recipeD_; // player.getDwarfRecipeLimit() + uint32_t recipe_; // player.getCommonRecipeLimit() + uint32_t inventoryExtraSlots_; // player.getStat().calcStat(Stat.INV_LIM, 0, null, null) + uint32_t inventoryQuestItems_; // Config.INVENTORY_MAXIMUM_QUEST_ITEMS + + void calculateStorageLimits(); + +public: + explicit ExStorageMaxCount(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + uint16_t getExtendedPacketId() const override { return EX_PACKET_ID; } + void write(SendablePacketBuffer& buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/friend_list.cpp b/src/game/packets/responses/friend_list.cpp new file mode 100644 index 0000000..159acbf --- /dev/null +++ b/src/game/packets/responses/friend_list.cpp @@ -0,0 +1,82 @@ +#include "friend_list.hpp" +#include + +// Constructor +FriendList::FriendList(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for FriendList packet"); + } + buildFriendList(); +} + +void FriendList::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[FriendList] Sending friend list for: " << player_->getName() << std::endl; + + // Following L2J Mobius FriendList.java structure EXACTLY + + // 1. Friend count (int) + buffer.writeUInt32(static_cast(friends_.size())); + + // 2. Friend data + for (const auto& friend_data : friends_) + { + buffer.writeUInt32(friend_data.objId); // character id + buffer.writeCUtf16leString(friend_data.name); // character name + buffer.writeUInt32(friend_data.online ? 1 : 0); // online status + buffer.writeUInt32(friend_data.online ? friend_data.objId : 0); // object id if online + } + + std::cout << "[FriendList] Friend list sent successfully (" << friends_.size() << " friends)" << std::endl; +} + +size_t FriendList::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 1; // opcode + + // Fixed-size fields + size += 4; // Friend count (int) + + // Friend data + for (const auto& friend_data : friends_) + { + size += 4; // objId (int) + size += (friend_data.name.length() + 1) * 2; // name (UTF-16LE + null) + size += 4; // online status (int) + size += 4; // online objId (int) + } + + return size; +} + +void FriendList::buildFriendList() +{ + // For now, return empty friend list since we don't have a friend system implemented + // TODO: Implement actual friend loading from database/player data + friends_.clear(); + + // Example friends (commented out for now): + /* + // Online friend + friends_.push_back({ + 12345, // objId + "FriendName1", // name + true, // online + 12345 // onlineObjId (same as objId when online) + }); + + // Offline friend + friends_.push_back({ + 67890, // objId + "FriendName2", // name + false, // online + 0 // onlineObjId (0 when offline) + }); + */ +} \ No newline at end of file diff --git a/src/game/packets/responses/friend_list.hpp b/src/game/packets/responses/friend_list.hpp new file mode 100644 index 0000000..020bc58 --- /dev/null +++ b/src/game/packets/responses/friend_list.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include +#include + +// FriendList - Server response with friend list information +// Shows friends and their online status +class FriendList : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xFA; // FriendList - Interlude Update 3 + const Player* player_; + + // Friend data structure (matches L2J Mobius) + struct FriendData { + uint32_t objId; + std::string name; + bool online; + uint32_t onlineObjId; + }; + + std::vector friends_; + + void buildFriendList(); + +public: + // Constructor - create with player data + explicit FriendList(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/henna_info.cpp b/src/game/packets/responses/henna_info.cpp new file mode 100644 index 0000000..237b002 --- /dev/null +++ b/src/game/packets/responses/henna_info.cpp @@ -0,0 +1,83 @@ +#include "henna_info.hpp" +#include + +// Constructor +HennaInfo::HennaInfo(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for HennaInfo packet"); + } + buildHennaList(); +} + +void HennaInfo::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[HennaInfo] Sending henna info for: " << player_->getName() << std::endl; + + // Following L2J Mobius HennaInfo.java structure EXACTLY + + // 1. Henna stat bonuses (6 bytes - INT, STR, CON, MEN, DEX, WIT) + buffer.writeUInt8(0); // INT bonus (TODO: player_.getHennaStatINT()) + buffer.writeUInt8(0); // STR bonus (TODO: player_.getHennaStatSTR()) + buffer.writeUInt8(0); // CON bonus (TODO: player_.getHennaStatCON()) + buffer.writeUInt8(0); // MEN bonus (TODO: player_.getHennaStatMEN()) + buffer.writeUInt8(0); // DEX bonus (TODO: player_.getHennaStatDEX()) + buffer.writeUInt8(0); // WIT bonus (TODO: player_.getHennaStatWIT()) + + // 2. Slots count (int) + buffer.writeUInt32(3); // TODO: player_.getHennaSlots() + + // 3. Henna count (int) + buffer.writeUInt32(static_cast(hennas_.size())); + + // 4. Henna data + for (const auto& henna : hennas_) + { + buffer.writeUInt32(henna.dyeId); + buffer.writeUInt32(henna.count); + } + + std::cout << "[HennaInfo] Henna info sent successfully (" << hennas_.size() << " hennas)" << std::endl; +} + +size_t HennaInfo::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 1; // opcode + + // Fixed-size fields + size += 6; // Henna stat bonuses (6 bytes) + size += 4; // Slots count (int) + size += 4; // Henna count (int) + + // Henna data + size += hennas_.size() * 8; // Each henna: dyeId(4) + count(4) + + return size; +} + +void HennaInfo::buildHennaList() +{ + // For now, return empty henna list since we don't have a henna system implemented + // TODO: Implement actual henna loading from database/player data + hennas_.clear(); + + // Example hennas (commented out for now): + /* + // Basic henna dye + hennas_.push_back({ + 4445, // dyeId (Basic Henna) + 1 // count + }); + + // Advanced henna dye + hennas_.push_back({ + 4446, // dyeId (Advanced Henna) + 1 // count + }); + */ +} \ No newline at end of file diff --git a/src/game/packets/responses/henna_info.hpp b/src/game/packets/responses/henna_info.hpp new file mode 100644 index 0000000..7f2b81b --- /dev/null +++ b/src/game/packets/responses/henna_info.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// HennaInfo - Server response with active henna (dye) information +// Shows equipped henna dyes and their stat bonuses +class HennaInfo : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xE4; // HennaInfo - Interlude Update 3 + const Player* player_; + + // Henna data structure (matches L2J Mobius) + struct HennaData { + uint32_t dyeId; + uint32_t count; + }; + + std::vector hennas_; + + void buildHennaList(); + +public: + // Constructor - create with player data + explicit HennaInfo(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/item_list.cpp b/src/game/packets/responses/item_list.cpp new file mode 100644 index 0000000..fe975e5 --- /dev/null +++ b/src/game/packets/responses/item_list.cpp @@ -0,0 +1,72 @@ +#include "item_list.hpp" +#include + +// Constructor +ItemList::ItemList(const Player* player, bool showWindow) + : player_(player), show_window_(showWindow) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for ItemList packet"); + } +} + +void ItemList::write(SendablePacketBuffer &buffer) +{ + // Following L2J Mobius ItemList.java structure EXACTLY: + // buffer.writeShort(_showWindow); + // buffer.writeShort(_items.size()); + // for (Item item : _items) { writeItem(item, buffer); } + + std::cout << "[ItemList] Sending inventory for: " << player_->getName() + << " (show window: " << (show_window_ ? "yes" : "no") << ")" << std::endl; + + // 1. Show window flag (matches L2J: buffer.writeShort(_showWindow)) + buffer.writeUInt16(show_window_ ? 1 : 0); + + // 2. Item count (matches L2J: buffer.writeShort(_items.size())) + uint16_t item_count = 0; // Empty inventory for now + buffer.writeUInt16(item_count); + + // 3. Item data - for each item, L2J calls writeItem() which writes: + // Based on AbstractItemPacket.writeItem(): + // buffer.writeShort(item.getItem().getType1()); // Item type 1 + // buffer.writeInt(item.getObjectId()); // Object ID + // buffer.writeInt(item.getItem().getDisplayId()); // Item ID + // buffer.writeInt(item.getCount()); // Quantity + // buffer.writeShort(item.getItem().getType2()); // Item type 2 + // buffer.writeShort(item.getCustomType1()); // Custom type 1 (always 0) + // buffer.writeShort(item.getEquipped()); // Equipped flag + // buffer.writeInt(item.getItem().getBodyPart()); // Body part slot + // buffer.writeShort(item.getEnchant()); // Enchant level + // buffer.writeShort(item.getCustomType2()); // Custom type 2 + // buffer.writeInt(item.getAugmentationBonus()); // Augmentation + // buffer.writeInt(item.getMana()); // Mana + + // TODO: When inventory system is implemented, iterate through items and call writeItem() + + std::cout << "[ItemList] Inventory sent successfully (" << item_count << " items)" << std::endl; +} + +size_t ItemList::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + // Note: Opcode is handled by base class, not included here + + size_t size = 0; + + // Fixed-size fields (exactly like L2J Mobius) + size += 2; // Show window flag (short) + size += 2; // Item count (short) + + // Item data size calculation (based on AbstractItemPacket.writeItem()) + // Each item is 38 bytes total: + // - Type1 (2) + ObjectId (4) + ItemId (4) + Count (4) + Type2 (2) + // - CustomType1 (2) + Equipped (2) + BodyPart (4) + Enchant (2) + // - CustomType2 (2) + Augmentation (4) + Mana (4) = 38 bytes per item + + uint16_t item_count = 0; // Empty inventory for now + size += item_count * 38; // 38 bytes per item (L2J structure) + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/item_list.hpp b/src/game/packets/responses/item_list.hpp new file mode 100644 index 0000000..79073d0 --- /dev/null +++ b/src/game/packets/responses/item_list.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// ItemList - Server response with player inventory contents +// Shows all items in player's inventory +class ItemList : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x1B; // ItemList - Interlude Update 3 (FIXED: was 0x11=BuyList) + const Player* player_; + bool show_window_; // Whether to show inventory window + +public: + // Constructor - create with player data + explicit ItemList(const Player* player, bool showWindow = false); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/new_character_success.cpp b/src/game/packets/responses/new_character_success.cpp index 565e1bc..994138e 100644 --- a/src/game/packets/responses/new_character_success.cpp +++ b/src/game/packets/responses/new_character_success.cpp @@ -12,11 +12,12 @@ void NewCharacterSuccess::write(SendablePacketBuffer &buffer) { try { - buffer.writeUInt8(PACKET_ID); + // Opcode is written automatically by base class + // No additional data needed for this packet } catch (const std::exception &e) { - throw std::runtime_error("Failed to write PingResponse packet: " + std::string(e.what())); + throw std::runtime_error("Failed to write NewCharacterSuccess packet: " + std::string(e.what())); } } diff --git a/src/game/packets/responses/npc_html_message.cpp b/src/game/packets/responses/npc_html_message.cpp new file mode 100644 index 0000000..1daf371 --- /dev/null +++ b/src/game/packets/responses/npc_html_message.cpp @@ -0,0 +1,95 @@ +#include "npc_html_message.hpp" +#include + +// Default constructor +NpcHtmlMessage::NpcHtmlMessage() + : npc_obj_id_(0) + , html_content_("") + , item_id_(0) +{ +} + +// Constructor with NPC object ID +NpcHtmlMessage::NpcHtmlMessage(uint32_t npcObjId) + : npc_obj_id_(npcObjId) + , html_content_("") + , item_id_(0) +{ +} + +// Constructor with HTML content +NpcHtmlMessage::NpcHtmlMessage(const std::string& html) + : npc_obj_id_(0) + , html_content_(html) + , item_id_(0) +{ +} + +// Constructor with NPC object ID and HTML content +NpcHtmlMessage::NpcHtmlMessage(uint32_t npcObjId, const std::string& html) + : npc_obj_id_(npcObjId) + , html_content_(html) + , item_id_(0) +{ +} + +// Constructor with NPC object ID and item ID +NpcHtmlMessage::NpcHtmlMessage(uint32_t npcObjId, uint32_t itemId) + : npc_obj_id_(npcObjId) + , html_content_("") + , item_id_(itemId) +{ + if (itemId < 0) + { + throw std::invalid_argument("Item ID cannot be negative"); + } +} + +// Constructor with NPC object ID, item ID, and HTML content +NpcHtmlMessage::NpcHtmlMessage(uint32_t npcObjId, uint32_t itemId, const std::string& html) + : npc_obj_id_(npcObjId) + , html_content_(html) + , item_id_(itemId) +{ + if (itemId < 0) + { + throw std::invalid_argument("Item ID cannot be negative"); + } +} + +uint8_t NpcHtmlMessage::getPacketId() const +{ + return PACKET_ID; +} + +std::optional NpcHtmlMessage::getExPacketId() const +{ + return std::nullopt; +} + +void NpcHtmlMessage::write(SendablePacketBuffer &buffer) +{ + // Write packet structure matching L2J Mobius exactly: + // 1. NPC object ID + buffer.writeUInt32(npc_obj_id_); + + // 2. HTML content as string + buffer.writeCUtf16leString(html_content_); + + // 3. Item ID + buffer.writeUInt32(item_id_); + + std::cout << "[NpcHtmlMessage] Sending HTML message - NPC ID: " << npc_obj_id_ + << ", Item ID: " << item_id_ + << ", Content: " << html_content_ << std::endl; +} + +size_t NpcHtmlMessage::getSize() const +{ + // Calculate packet size: opcode + npc_obj_id + html_content + item_id + size_t size = 1; // opcode + size += 4; // npc_obj_id (uint32) + size += (html_content_.length() + 1) * 2; // html_content (UTF-16LE + null terminator) + size += 4; // item_id (uint32) + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/npc_html_message.hpp b/src/game/packets/responses/npc_html_message.hpp new file mode 100644 index 0000000..8529697 --- /dev/null +++ b/src/game/packets/responses/npc_html_message.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include +#include + +class NpcHtmlMessage : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x0F; + + // HTML message data + uint32_t npc_obj_id_; + std::string html_content_; + uint32_t item_id_; + +public: + // Constructors matching L2J Mobius + NpcHtmlMessage(); + explicit NpcHtmlMessage(uint32_t npcObjId); + explicit NpcHtmlMessage(const std::string& html); + NpcHtmlMessage(uint32_t npcObjId, const std::string& html); + NpcHtmlMessage(uint32_t npcObjId, uint32_t itemId); + NpcHtmlMessage(uint32_t npcObjId, uint32_t itemId, const std::string& html); + + uint8_t getPacketId() const override; + std::optional getExPacketId() const override; + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/ping_response.cpp b/src/game/packets/responses/ping_response.cpp index 2c7c183..e2f999d 100644 --- a/src/game/packets/responses/ping_response.cpp +++ b/src/game/packets/responses/ping_response.cpp @@ -13,8 +13,7 @@ void PingResponse::write(SendablePacketBuffer &buffer) { try { - // Write packet ID (same as received ping) - buffer.writeUInt8(PACKET_ID); + // Opcode is written automatically by base class // Echo back the ping data buffer.writeBytes(ping_data_); diff --git a/src/game/packets/responses/pledge_show_member_list_all.cpp b/src/game/packets/responses/pledge_show_member_list_all.cpp new file mode 100644 index 0000000..24ac99a --- /dev/null +++ b/src/game/packets/responses/pledge_show_member_list_all.cpp @@ -0,0 +1,38 @@ +#include "pledge_show_member_list_all.hpp" +#include + +// Constructor +PledgeShowMemberListAll::PledgeShowMemberListAll(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for PledgeShowMemberListAll packet"); + } +} + +void PledgeShowMemberListAll::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[PledgeShowMemberListAll] Sending clan member list for: " << player_->getName() << std::endl; + + // TODO: Implement actual clan member list + // For now, send empty list since we don't have a clan system implemented + + // Clan member count (int) + buffer.writeUInt32(0); + + std::cout << "[PledgeShowMemberListAll] Clan member list sent successfully (0 members)" << std::endl; +} + +size_t PledgeShowMemberListAll::getSize() const +{ + // Calculate packet size + size_t size = 1; // opcode + size += 4; // Member count (int) + + // TODO: Add size calculation for actual clan members when implemented + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/pledge_show_member_list_all.hpp b/src/game/packets/responses/pledge_show_member_list_all.hpp new file mode 100644 index 0000000..07d935f --- /dev/null +++ b/src/game/packets/responses/pledge_show_member_list_all.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include + +// PledgeShowMemberListAll - Server response with clan member list +// Shows all clan members and their information +class PledgeShowMemberListAll : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x53; // PledgeShowMemberListAll - Interlude Update 3 + const Player* player_; + +public: + // Constructor - create with player data + explicit PledgeShowMemberListAll(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/pledge_skill_list.cpp b/src/game/packets/responses/pledge_skill_list.cpp new file mode 100644 index 0000000..a748172 --- /dev/null +++ b/src/game/packets/responses/pledge_skill_list.cpp @@ -0,0 +1,76 @@ +#include "pledge_skill_list.hpp" +#include + +// Constructor +PledgeSkillList::PledgeSkillList(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for PledgeSkillList packet"); + } + buildSkillList(); +} + +void PledgeSkillList::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[PledgeSkillList] Sending clan skills for: " << player_->getName() << std::endl; + + // Following L2J Mobius PledgeSkillList.java structure EXACTLY + + // 1. Skill count (int) + buffer.writeUInt32(static_cast(skills_.size())); + + // 2. Skill data + for (const auto& skill : skills_) + { + buffer.writeUInt32(skill.displayId); + buffer.writeUInt32(skill.displayLevel); + } + + std::cout << "[PledgeSkillList] Clan skills sent successfully (" << skills_.size() << " skills)" << std::endl; +} + +size_t PledgeSkillList::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 3; // Extended opcode (0xFE + 16-bit sub-opcode) + + // Fixed-size fields + size += 4; // Skill count (int) + + // Skill data + size += skills_.size() * 8; // Each skill: displayId(4) + displayLevel(4) + + return size; +} + +void PledgeSkillList::buildSkillList() +{ + // For now, return empty skill list since we don't have a clan system implemented + // TODO: Implement actual clan skill loading from database/player data + skills_.clear(); + + // Example clan skills (commented out for now): + /* + // Clan skill: Clan Harmony + skills_.push_back({ + 190, // displayId (Clan Harmony) + 1 // displayLevel + }); + + // Clan skill: Clan Vitality + skills_.push_back({ + 191, // displayId (Clan Vitality) + 1 // displayLevel + }); + + // Clan skill: Clan Agility + skills_.push_back({ + 192, // displayId (Clan Agility) + 1 // displayLevel + }); + */ +} \ No newline at end of file diff --git a/src/game/packets/responses/pledge_skill_list.hpp b/src/game/packets/responses/pledge_skill_list.hpp new file mode 100644 index 0000000..9159dba --- /dev/null +++ b/src/game/packets/responses/pledge_skill_list.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// PledgeSkillList - Server response with clan skills +// Shows available clan skills to the client +class PledgeSkillList : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xFE; // Extended packet prefix + static constexpr uint16_t EX_PACKET_ID = 0x39; // Extended packet sub-opcode + const Player* player_; + + // Skill data structure (matches L2J Mobius) + struct SkillData { + uint32_t displayId; + uint32_t displayLevel; + }; + + std::vector skills_; + + void buildSkillList(); + +public: + // Constructor - create with player data + explicit PledgeSkillList(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + uint16_t getExtendedPacketId() const override { return EX_PACKET_ID; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/pledge_status_changed.cpp b/src/game/packets/responses/pledge_status_changed.cpp new file mode 100644 index 0000000..bdb4c7d --- /dev/null +++ b/src/game/packets/responses/pledge_status_changed.cpp @@ -0,0 +1,40 @@ +#include "pledge_status_changed.hpp" +#include + +// Constructor +PledgeStatusChanged::PledgeStatusChanged(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for PledgeStatusChanged packet"); + } +} + +void PledgeStatusChanged::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[PledgeStatusChanged] Sending clan status update for: " << player_->getName() << std::endl; + + // TODO: Implement actual clan status update + // For now, send minimal data since we don't have a clan system implemented + + // Clan ID + buffer.writeUInt32(player_->getClanId()); + + // Clan status (0 = no clan) + buffer.writeUInt32(0); + + std::cout << "[PledgeStatusChanged] Clan status update sent successfully" << std::endl; +} + +size_t PledgeStatusChanged::getSize() const +{ + // Calculate packet size + size_t size = 1; // opcode + size += 4; // Clan ID (int) + size += 4; // Clan status (int) + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/pledge_status_changed.hpp b/src/game/packets/responses/pledge_status_changed.hpp new file mode 100644 index 0000000..b64b1d6 --- /dev/null +++ b/src/game/packets/responses/pledge_status_changed.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include + +// PledgeStatusChanged - Server response for clan status updates +// Notifies client of clan-related status changes +class PledgeStatusChanged : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xCD; // PledgeStatusChanged - Interlude Update 3 + const Player* player_; + +public: + // Constructor - create with player data + explicit PledgeStatusChanged(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/quest_list.cpp b/src/game/packets/responses/quest_list.cpp new file mode 100644 index 0000000..24b86e6 --- /dev/null +++ b/src/game/packets/responses/quest_list.cpp @@ -0,0 +1,62 @@ +// src/game/packets/responses/quest_list.cpp +#include "quest_list.hpp" +#include + +QuestList::QuestList(const Player* player) + : player_(player) +{ + if (!player_) { + throw std::invalid_argument("Player cannot be null for QuestList packet"); + } + buildQuestList(); +} + +void QuestList::write(SendablePacketBuffer& buffer) +{ + // Packet structure: questCount + quests + // Each quest: questId(4) + state(4) + + // Write quest count (2 bytes - matches L2J Mobius) + buffer.writeInt16(static_cast(quests_.size())); + + // Write each quest + for (const auto& quest : quests_) + { + buffer.writeInt32(static_cast(quest.questId)); + buffer.writeInt32(static_cast(quest.state)); + } +} + +size_t QuestList::getSize() const +{ + // Calculate packet size: 2 bytes for count + 8 bytes per quest + return 2 + (quests_.size() * 8); +} + +void QuestList::buildQuestList() +{ + // For now, return empty quest list since we don't have a quest system implemented + // TODO: Implement actual quest loading from database/player data + quests_.clear(); + + // Example quests (commented out for now): + /* + // Tutorial quest + quests_.push_back({ + 1, // questId (Tutorial Quest) + 1 // state (active) + }); + + // First hunting quest + quests_.push_back({ + 2, // questId (Hunt Wolves) + 1 // state (active) + }); + + // Available quest + quests_.push_back({ + 3, // questId (Deliver Package) + 3 // state (available) + }); + */ +} \ No newline at end of file diff --git a/src/game/packets/responses/quest_list.hpp b/src/game/packets/responses/quest_list.hpp new file mode 100644 index 0000000..fb77ec4 --- /dev/null +++ b/src/game/packets/responses/quest_list.hpp @@ -0,0 +1,36 @@ +// src/game/packets/responses/quest_list.hpp +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// QuestList packet (opcode 0x80) +// Sends active quests to the client +class QuestList : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x80; + const Player* player_; + + // Quest data structure + struct QuestData { + uint32_t questId; + uint32_t state; // 1=active, 2=completed, 3=available + }; + + std::vector quests_; + + void buildQuestList(); + +public: + explicit QuestList(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer& buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/shortcut_init.cpp b/src/game/packets/responses/shortcut_init.cpp new file mode 100644 index 0000000..d838a77 --- /dev/null +++ b/src/game/packets/responses/shortcut_init.cpp @@ -0,0 +1,125 @@ +// src/game/packets/responses/shortcut_init.cpp +#include "shortcut_init.hpp" +#include + +ShortcutInit::ShortcutInit(const Player* player) + : player_(player) +{ + if (!player_) { + throw std::invalid_argument("Player cannot be null for ShortcutInit packet"); + } + buildShortcuts(); +} + +void ShortcutInit::write(SendablePacketBuffer& buffer) +{ + // Packet structure: shortcutCount + shortcuts + // Each shortcut has different structure based on type (matches L2J Mobius) + + // Write shortcut count + buffer.writeInt32(static_cast(shortcuts_.size())); + + // Write each shortcut + for (const auto& shortcut : shortcuts_) + { + // Write type and slot (common for all types) + buffer.writeInt32(static_cast(shortcut.type)); + buffer.writeInt32(static_cast(shortcut.slot)); + + // Write type-specific data + switch (shortcut.type) + { + case ShortcutType::ITEM: + { + buffer.writeInt32(static_cast(shortcut.id)); + buffer.writeInt32(static_cast(shortcut.level)); // item count + buffer.writeInt32(-1); // shared reuse group (not used for items) + buffer.writeInt32(static_cast(shortcut.characterType)); + buffer.writeInt32(0); // unknown + buffer.writeInt16(static_cast(shortcut.enchantLevel)); + buffer.writeInt16(static_cast(shortcut.augmentation)); + break; + } + case ShortcutType::SKILL: + { + buffer.writeInt32(static_cast(shortcut.id)); + buffer.writeInt32(static_cast(shortcut.level)); + buffer.write(static_cast(shortcut.subLevel)); // C5 + buffer.writeInt32(static_cast(shortcut.sharedReuseGroup)); + break; + } + case ShortcutType::ACTION: + case ShortcutType::MACRO: + case ShortcutType::RECIPE: + { + buffer.writeInt32(static_cast(shortcut.id)); + buffer.writeInt32(static_cast(shortcut.level)); + break; + } + } + } +} + +size_t ShortcutInit::getSize() const +{ + // Calculate packet size: 4 bytes for count + variable size for shortcuts + size_t size = 4; // shortcut count + + for (const auto& shortcut : shortcuts_) + { + size += 8; // type + slot (common for all types) + + switch (shortcut.type) + { + case ShortcutType::ITEM: + size += 28; // id(4) + level(4) + -1(4) + charType(4) + 0(4) + enchant(2) + augment(2) + break; + case ShortcutType::SKILL: + size += 13; // id(4) + level(4) + subLevel(1) + reuseGroup(4) + break; + case ShortcutType::ACTION: + case ShortcutType::MACRO: + case ShortcutType::RECIPE: + size += 8; // id(4) + level(4) + break; + } + } + + return size; +} + +void ShortcutInit::buildShortcuts() +{ + // For now, return empty shortcuts since we don't have a shortcut system implemented + // TODO: Implement actual shortcut loading from database/player data + shortcuts_.clear(); + + // Example shortcuts (commented out for now): + /* + // Basic attack skill shortcut + shortcuts_.push_back({ + ShortcutType::SKILL, // type + 0, // slot (slot 0, page 0) + 1, // id (skill ID for basic attack) + 1, // level (skill level) + 0, // subLevel + 0, // sharedReuseGroup + 0, // characterType (not used for skills) + 0, // enchantLevel (not used for skills) + 0 // augmentation (not used for skills) + }); + + // Health potion shortcut + shortcuts_.push_back({ + ShortcutType::ITEM, // type + 1, // slot (slot 1, page 0) + 1539, // id (item ID for health potion) + 1, // level (item count) + 0, // subLevel (not used for items) + 0, // sharedReuseGroup (not used for items) + 0, // characterType (player) + 0, // enchantLevel + 0 // augmentation + }); + */ +} \ No newline at end of file diff --git a/src/game/packets/responses/shortcut_init.hpp b/src/game/packets/responses/shortcut_init.hpp new file mode 100644 index 0000000..fee5631 --- /dev/null +++ b/src/game/packets/responses/shortcut_init.hpp @@ -0,0 +1,52 @@ +// src/game/packets/responses/shortcut_init.hpp +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// ShortcutInit packet (opcode 0x45) +// Sends skill/item shortcuts to the client +class ShortcutInit : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x45; + const Player* player_; + + // Shortcut types (matches L2J Mobius) + enum class ShortcutType { + ITEM = 1, + SKILL = 2, + ACTION = 3, + MACRO = 4, + RECIPE = 5 + }; + + // Shortcut data structure (matches L2J Mobius) + struct ShortcutData { + ShortcutType type; + uint32_t slot; // slot + (page * 12) + uint32_t id; // item/skill/action ID + uint32_t level; // skill level or item count + uint8_t subLevel; // skill sublevel (only for skills) + uint32_t sharedReuseGroup; // shared reuse group (only for skills) + uint32_t characterType; // character type (only for items) + uint16_t enchantLevel; // enchant level (only for items) + uint16_t augmentation; // augmentation (only for items) + }; + + std::vector shortcuts_; + + void buildShortcuts(); + +public: + explicit ShortcutInit(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer& buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/show_mini_map.cpp b/src/game/packets/responses/show_mini_map.cpp new file mode 100644 index 0000000..a4cc170 --- /dev/null +++ b/src/game/packets/responses/show_mini_map.cpp @@ -0,0 +1,20 @@ +#include "show_mini_map.hpp" +#include + +ShowMiniMap::ShowMiniMap(int mapId) : map_id_(mapId) +{ +} + +void ShowMiniMap::write(SendablePacketBuffer& buffer) +{ + std::cout << "[ShowMiniMap] Sending minimap display packet with map ID: " << map_id_ << std::endl; + + // Opcode is written automatically by base class + buffer.writeInt32(map_id_); // Map ID (default 1665 from L2J Mobius) +} + +size_t ShowMiniMap::getSize() const +{ + // Packet ID (1 byte) + Map ID (4 bytes) + return 5; +} \ No newline at end of file diff --git a/src/game/packets/responses/show_mini_map.hpp b/src/game/packets/responses/show_mini_map.hpp new file mode 100644 index 0000000..0e286ce --- /dev/null +++ b/src/game/packets/responses/show_mini_map.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include + +// ShowMiniMap - Server response to show/enable minimap (sent when client requests 0xCD) +// Based on L2J Mobius: player.sendPacket(new ShowMiniMap(1665)); +class ShowMiniMap : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x9D; // ShowMiniMap packet ID + int map_id_; + +public: + // Constructor + explicit ShowMiniMap(int mapId = 1665); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer& buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/skill_cool_time.cpp b/src/game/packets/responses/skill_cool_time.cpp new file mode 100644 index 0000000..cf0d5f6 --- /dev/null +++ b/src/game/packets/responses/skill_cool_time.cpp @@ -0,0 +1,55 @@ +#include "skill_cool_time.hpp" +#include + +// Constructor +SkillCoolTime::SkillCoolTime(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for SkillCoolTime packet"); + } +} + +void SkillCoolTime::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[SkillCoolTime] Sending skill cooldowns for: " << player_->getName() << std::endl; + + // Following L2J Mobius SkillCoolTime.java structure EXACTLY + + // 1. Skill count (as int, not uint32) + int32_t skill_count = 0; // TODO: Implement actual skill system + buffer.writeInt32(skill_count); + + // 2. Skill cooldown data (none for now) + // TODO: When skill system is implemented, iterate through skills with cooldowns: + // for each skill with cooldown: + // - Skill ID (int) + // - Skill level (int) + // - Reuse delay (remaining time in seconds) + // - Reuse group (remaining time in seconds) + // This matches L2J Mobius structure: + // buffer.writeInt(ts.getSkillId()); + // buffer.writeInt(ts.getSkillLevel()); + // buffer.writeInt((int) (reuse > 0 ? reuse : remaining) / 1000); + // buffer.writeInt(Math.max(1, (int) remaining / 1000)); + + std::cout << "[SkillCoolTime] Skill cooldowns sent successfully (" << skill_count << " skills)" << std::endl; +} + +size_t SkillCoolTime::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 1; // opcode + + // Fixed-size fields (exactly like L2J Mobius) + size += 4; // Skill count (int) + + // Skill cooldown data (none for now) + // TODO: Add size calculation for actual skills when skill system is implemented + // Each skill entry: 4 ints = 16 bytes + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/skill_cool_time.hpp b/src/game/packets/responses/skill_cool_time.hpp new file mode 100644 index 0000000..034df71 --- /dev/null +++ b/src/game/packets/responses/skill_cool_time.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// SkillCoolTime - Server response with skill cooldown information +// Manages skill cooldowns and reuse delays +class SkillCoolTime : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0xC7; // SkillCoolTime - Interlude Update 3 + const Player* player_; + +public: + // Constructor - create with player data + explicit SkillCoolTime(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/skill_list.cpp b/src/game/packets/responses/skill_list.cpp new file mode 100644 index 0000000..62df196 --- /dev/null +++ b/src/game/packets/responses/skill_list.cpp @@ -0,0 +1,60 @@ +#include "skill_list.hpp" +#include + +SkillList::SkillList(const Player* player) +{ + // For now, add some basic skills that every player should have + // In a real implementation, this would read from player's skill database + + // Basic skills every character should have (examples from L2J) + addSkill(1001, 1, true, false); // Divine Protection (passive) + addSkill(1002, 1, true, false); // Blessing of Eva (passive) + addSkill(1003, 1, false, false); // Power Strike (active) + addSkill(1004, 1, false, false); // Mortal Blow (active) + + std::cout << "[SkillList] Created skill list with " << skills_.size() << " skills for player" << std::endl; +} + +void SkillList::addSkill(uint32_t skillId, uint32_t level, bool passive, bool disabled) +{ + skills_.push_back({skillId, level, passive, disabled}); +} + +void SkillList::write(SendablePacketBuffer &buffer) +{ + // SkillList packet format (matches L2J Mobius exactly): + // - uint32: skill_count + // For each skill: + // - uint32: passive flag (0=active, 1=passive) + // - uint32: skill_level + // - uint32: skill_id + // - uint8: disabled flag (0=enabled, 1=disabled) + + buffer.writeUInt32(static_cast(skills_.size())); + + for (const auto& skill : skills_) + { + buffer.writeUInt32(skill.is_passive ? 1 : 0); // passive flag + buffer.writeUInt32(skill.skill_level); // skill level + buffer.writeUInt32(skill.skill_id); // skill ID + buffer.writeUInt8(skill.is_disabled ? 1 : 0); // disabled flag + } + + std::cout << "[SkillList] Sending skill list with " << skills_.size() << " skills" << std::endl; +} + +size_t SkillList::getSize() const +{ + // Calculate packet size: skill count + skill data + size_t size = 4; // skill count (uint32) + + for (const auto& skill : skills_) + { + size += 4; // passive flag (uint32) + size += 4; // skill level (uint32) + size += 4; // skill ID (uint32) + size += 1; // disabled flag (uint8) + } + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/skill_list.hpp b/src/game/packets/responses/skill_list.hpp new file mode 100644 index 0000000..2fe660f --- /dev/null +++ b/src/game/packets/responses/skill_list.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// SkillList - Server packet to send player's skill list +// Critical packet that client expects to initialize skill UI and functionality +class SkillList : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x58; // SkillList - Interlude Update 3 + + // Skill data structure (matches L2J Mobius exactly) + struct SkillData { + uint32_t skill_id; + uint32_t skill_level; + bool is_passive; + bool is_disabled; + }; + + std::vector skills_; + +public: + // Constructor - create with player data + explicit SkillList(const Player* player); + + // Add individual skill (for testing/manual construction) + void addSkill(uint32_t skillId, uint32_t level, bool passive, bool disabled); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/status_update.cpp b/src/game/packets/responses/status_update.cpp new file mode 100644 index 0000000..7ca006d --- /dev/null +++ b/src/game/packets/responses/status_update.cpp @@ -0,0 +1,44 @@ +#include "status_update.hpp" +#include + +StatusUpdate::StatusUpdate(const Player* player) + : player_(player) +{ +} + +void StatusUpdate::write(SendablePacketBuffer &buffer) +{ + // StatusUpdate packet format (based on L2J Mobius): + // - uint32: object_id + // - uint32: attribute_count (how many attributes we're updating) + // For each attribute: + // - uint32: attribute_id (09=HP, 0A=MP, 0B=CP) + // - uint32: attribute_value + + buffer.writeUInt32(player_->getObjectId()); + + // Send 3 attributes: HP, MP, CP + buffer.writeUInt32(3); + + // HP (attribute ID 0x09) + buffer.writeUInt32(0x09); + buffer.writeUInt32(static_cast(player_->getCurrentHp())); + + // MP (attribute ID 0x0A) + buffer.writeUInt32(0x0A); + buffer.writeUInt32(static_cast(player_->getCurrentMp())); + + // CP (attribute ID 0x0B) + buffer.writeUInt32(0x0B); + buffer.writeUInt32(static_cast(player_->getCurrentCp())); + + std::cout << "[StatusUpdate] Sending HP/MP/CP status for: " << player_->getName() + << " (HP:" << player_->getCurrentHp() << ", MP:" << player_->getCurrentMp() + << ", CP:" << player_->getCurrentCp() << ")" << std::endl; +} + +size_t StatusUpdate::getSize() const +{ + // object_id(4) + attribute_count(4) + 3 * (attribute_id(4) + value(4)) = 32 bytes + return 32; +} \ No newline at end of file diff --git a/src/game/packets/responses/status_update.hpp b/src/game/packets/responses/status_update.hpp new file mode 100644 index 0000000..25877a7 --- /dev/null +++ b/src/game/packets/responses/status_update.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include + +// StatusUpdate - Server packet to update player's HP/MP/CP status +// Critical packet that client expects to know current health/mana status +class StatusUpdate : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x0E; // StatusUpdate - Interlude Update 3 + const Player* player_; + +public: + // Constructor - create with player data + explicit StatusUpdate(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/system_message.cpp b/src/game/packets/responses/system_message.cpp new file mode 100644 index 0000000..714ea08 --- /dev/null +++ b/src/game/packets/responses/system_message.cpp @@ -0,0 +1,211 @@ +#include "system_message.hpp" +#include + +// Constructor with message ID +SystemMessage::SystemMessage(int32_t messageId) + : messageId_(messageId) +{ +} + +// Constructor with text message +SystemMessage::SystemMessage(const std::string& text) + : messageId_(1) // Default message ID for text messages +{ + addString(text); +} + +void SystemMessage::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[SystemMessage] Sending system message ID: " << messageId_ << std::endl; + + // Following L2J Mobius SystemMessage.java structure EXACTLY + + // 1. Message ID (int) + buffer.writeUInt32(static_cast(messageId_)); + + // 2. Parameter count (int) + buffer.writeUInt32(static_cast(parameters_.size())); + + // 3. Parameters + for (const auto& param : parameters_) + { + // Parameter type (int) + buffer.writeUInt32(static_cast(param.type)); + + // Parameter value based on type + switch (param.type) + { + case ParamType::TYPE_TEXT: + case ParamType::TYPE_PLAYER_NAME: + { + buffer.writeCUtf16leString(param.stringValue); + break; + } + case ParamType::TYPE_LONG_NUMBER: + { + buffer.writeUInt64(static_cast(param.longValue)); + break; + } + case ParamType::TYPE_SKILL_NAME: + { + if (param.intArrayValue.size() >= 2) + { + buffer.writeUInt32(static_cast(param.intArrayValue[0])); // SkillId + buffer.writeUInt32(static_cast(param.intArrayValue[1])); // SkillLevel + } + break; + } + case ParamType::TYPE_ZONE_NAME: + { + if (param.intArrayValue.size() >= 3) + { + buffer.writeUInt32(static_cast(param.intArrayValue[0])); // x + buffer.writeUInt32(static_cast(param.intArrayValue[1])); // y + buffer.writeUInt32(static_cast(param.intArrayValue[2])); // z + } + break; + } + default: // All other types use int value + { + buffer.writeUInt32(static_cast(param.intValue)); + break; + } + } + } + + std::cout << "[SystemMessage] System message sent successfully (" << parameters_.size() << " parameters)" << std::endl; +} + +size_t SystemMessage::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 1; // opcode + + // Fixed-size fields + size += 4; // Message ID (int) + size += 4; // Parameter count (int) + + // Parameters + for (const auto& param : parameters_) + { + size += 4; // Parameter type (int) + + switch (param.type) + { + case ParamType::TYPE_TEXT: + case ParamType::TYPE_PLAYER_NAME: + { + size += (param.stringValue.length() + 1) * 2; // UTF-16LE + null + break; + } + case ParamType::TYPE_LONG_NUMBER: + { + size += 8; // long value + break; + } + case ParamType::TYPE_SKILL_NAME: + { + size += 8; // 2 int values + break; + } + case ParamType::TYPE_ZONE_NAME: + { + size += 12; // 3 int values + break; + } + default: // All other types use int value + { + size += 4; // int value + break; + } + } + } + + return size; +} + +// Parameter helper methods +void SystemMessage::addParameter(ParamType type, const std::string& value) +{ + ParamData param; + param.type = type; + param.stringValue = value; + parameters_.push_back(param); +} + +void SystemMessage::addParameter(ParamType type, int32_t value) +{ + ParamData param; + param.type = type; + param.intValue = value; + parameters_.push_back(param); +} + +void SystemMessage::addParameter(ParamType type, int64_t value) +{ + ParamData param; + param.type = type; + param.longValue = value; + parameters_.push_back(param); +} + +void SystemMessage::addParameter(ParamType type, const std::vector& values) +{ + ParamData param; + param.type = type; + param.intArrayValue = values; + parameters_.push_back(param); +} + +// Public parameter methods +SystemMessage& SystemMessage::addString(const std::string& text) +{ + addParameter(ParamType::TYPE_TEXT, text); + return *this; +} + +SystemMessage& SystemMessage::addInt(int32_t number) +{ + addParameter(ParamType::TYPE_INT_NUMBER, number); + return *this; +} + +SystemMessage& SystemMessage::addLong(int64_t number) +{ + addParameter(ParamType::TYPE_LONG_NUMBER, number); + return *this; +} + +SystemMessage& SystemMessage::addPlayerName(const std::string& playerName) +{ + addParameter(ParamType::TYPE_PLAYER_NAME, playerName); + return *this; +} + +SystemMessage& SystemMessage::addNpcName(int32_t npcId) +{ + addParameter(ParamType::TYPE_NPC_NAME, 1000000 + npcId); // L2J Mobius format + return *this; +} + +SystemMessage& SystemMessage::addItemName(int32_t itemId) +{ + addParameter(ParamType::TYPE_ITEM_NAME, itemId); + return *this; +} + +SystemMessage& SystemMessage::addSkillName(int32_t skillId, int32_t skillLevel) +{ + std::vector values = {skillId, skillLevel}; + addParameter(ParamType::TYPE_SKILL_NAME, values); + return *this; +} + +SystemMessage& SystemMessage::addZoneName(int32_t x, int32_t y, int32_t z) +{ + std::vector values = {x, y, z}; + addParameter(ParamType::TYPE_ZONE_NAME, values); + return *this; +} \ No newline at end of file diff --git a/src/game/packets/responses/system_message.hpp b/src/game/packets/responses/system_message.hpp new file mode 100644 index 0000000..03ef272 --- /dev/null +++ b/src/game/packets/responses/system_message.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include +#include +#include + +// SystemMessage - Server response with system messages +// Shows system messages to the client (welcome messages, notifications, etc.) +class SystemMessage : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x64; // SystemMessage - Interlude Update 3 + + // Parameter types (matches L2J Mobius) + enum class ParamType { + TYPE_TEXT = 0, + TYPE_INT_NUMBER = 1, + TYPE_NPC_NAME = 2, + TYPE_ITEM_NAME = 3, + TYPE_SKILL_NAME = 4, + TYPE_CASTLE_NAME = 5, + TYPE_LONG_NUMBER = 6, + TYPE_ZONE_NAME = 7, + TYPE_ELEMENT_NAME = 9, + TYPE_INSTANCE_NAME = 10, + TYPE_DOOR_NAME = 11, + TYPE_PLAYER_NAME = 12, + TYPE_SYSTEM_STRING = 13 + }; + + // Parameter data structure + struct ParamData { + ParamType type; + std::string stringValue; + int32_t intValue; + int64_t longValue; + std::vector intArrayValue; + }; + + int32_t messageId_; + std::vector parameters_; + + void addParameter(ParamType type, const std::string& value); + void addParameter(ParamType type, int32_t value); + void addParameter(ParamType type, int64_t value); + void addParameter(ParamType type, const std::vector& values); + +public: + // Constructor - create with message ID + explicit SystemMessage(int32_t messageId); + + // Constructor - create with text message + explicit SystemMessage(const std::string& text); + + // Parameter methods (matches L2J Mobius) + SystemMessage& addString(const std::string& text); + SystemMessage& addInt(int32_t number); + SystemMessage& addLong(int64_t number); + SystemMessage& addPlayerName(const std::string& playerName); + SystemMessage& addNpcName(int32_t npcId); + SystemMessage& addItemName(int32_t itemId); + SystemMessage& addSkillName(int32_t skillId, int32_t skillLevel); + SystemMessage& addZoneName(int32_t x, int32_t y, int32_t z); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/user_info.cpp b/src/game/packets/responses/user_info.cpp new file mode 100644 index 0000000..540e86d --- /dev/null +++ b/src/game/packets/responses/user_info.cpp @@ -0,0 +1,315 @@ +#include "user_info.hpp" +#include + +// Constructor +UserInfo::UserInfo(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for UserInfo packet"); + } +} + +void UserInfo::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[UserInfo] Sending user info for: " << player_->getName() + << " (ID: " << player_->getObjectId() << ")" << std::endl; + + // Following L2J Mobius UserInfo.java structure EXACTLY + + // 1. Position coordinates + buffer.writeUInt32(player_->getX()); + buffer.writeUInt32(player_->getY()); + buffer.writeUInt32(player_->getZ()); + + // 2. Vehicle ID (for mounted players) + buffer.writeUInt32(0); // TODO: Implement mount system + + // 3. Object ID + buffer.writeUInt32(player_->getObjectId()); + + // 4. Character name (regular string, not UTF-16) + buffer.writeCUtf16leString(player_->getName()); + + // 5. Race + buffer.writeUInt32(player_->getRace()); + + // 6. Sex (0=male, 1=female) + buffer.writeUInt32(player_->getSex()); + + // 7. Base class + buffer.writeUInt32(player_->getClassId()); + + // 8. Level + buffer.writeUInt32(player_->getLevel()); + + // 9. EXP (long) + buffer.writeUInt64(player_->getExp()); + + // 10. Stats + buffer.writeUInt32(player_->getStr()); + buffer.writeUInt32(player_->getDex()); + buffer.writeUInt32(player_->getCon()); + buffer.writeUInt32(player_->getInt()); + buffer.writeUInt32(player_->getWit()); + buffer.writeUInt32(player_->getMen()); + + // 11. HP/MP + buffer.writeUInt32(player_->getMaxHp()); + buffer.writeUInt32(static_cast(player_->getCurrentHp())); + buffer.writeUInt32(player_->getMaxMp()); + buffer.writeUInt32(static_cast(player_->getCurrentMp())); + + // 12. SP + buffer.writeUInt32(static_cast(player_->getSp())); + + // 13. Load (stub values) + buffer.writeUInt32(0); // Current load + buffer.writeUInt32(80); // Max load + + // 14. Weapon equipped (20=no weapon, 40=weapon equipped) + buffer.writeUInt32(20); // TODO: Check if weapon is equipped + + // 15. Paperdoll object IDs (equipment slots) + for (int i = 0; i < 18; ++i) { + buffer.writeUInt32(0); // TODO: Implement equipment system + } + + // 16. Paperdoll display IDs (equipment visual IDs) + for (int i = 0; i < 18; ++i) { + buffer.writeUInt32(0); // TODO: Implement equipment system + } + + // 17. C6 new fields (14 shorts) + for (int i = 0; i < 14; ++i) { + buffer.writeUInt16(0); + } + + // 18. Right hand augmentation + buffer.writeUInt32(0); // TODO: Implement augmentation system + + // 19. More C6 fields (13 shorts) + for (int i = 0; i < 13; ++i) { + buffer.writeUInt16(0); + } + + // 20. Right hand augmentation (duplicate) + buffer.writeUInt32(0); // TODO: Implement augmentation system + + // 21. More C6 fields (4 shorts) + for (int i = 0; i < 4; ++i) { + buffer.writeUInt16(0); + } + + // 22. Combat stats (stub values) + buffer.writeUInt32(100); // PAtk + buffer.writeUInt32(300); // PAtkSpd + buffer.writeUInt32(50); // PDef + buffer.writeUInt32(10); // EvasionRate + buffer.writeUInt32(80); // Accuracy + buffer.writeUInt32(5); // CriticalHit + buffer.writeUInt32(50); // MAtk + buffer.writeUInt32(200); // MAtkSpd + buffer.writeUInt32(300); // PAtkSpd (duplicate) + buffer.writeUInt32(30); // MDef + + // 23. PvP info + buffer.writeUInt32(player_->getPvpFlag()); + buffer.writeUInt32(player_->getKarma()); + + // 24. Movement speeds (stub values) + buffer.writeUInt32(120); // Run speed + buffer.writeUInt32(80); // Walk speed + buffer.writeUInt32(100); // Swim run speed + buffer.writeUInt32(60); // Swim walk speed + buffer.writeUInt32(0); // Fly run speed + buffer.writeUInt32(0); // Fly walk speed + buffer.writeUInt32(0); // Fly run speed (duplicate) + buffer.writeUInt32(0); // Fly walk speed (duplicate) + + // 25. Multipliers (stub values) + buffer.writeFloat64(1.0); // Movement speed multiplier + buffer.writeFloat64(1.0); // Attack speed multiplier + buffer.writeFloat64(20.0); // Collision radius + buffer.writeFloat64(30.0); // Collision height + + // 26. Appearance + buffer.writeUInt32(player_->getHairStyle()); + buffer.writeUInt32(player_->getHairColor()); + buffer.writeUInt32(player_->getFace()); + buffer.writeUInt32(0); // Builder level (GM level) + + // 27. Title + buffer.writeCUtf16leString(std::string("")); // TODO: Implement title system + + // 28. Clan info + buffer.writeUInt32(player_->getClanId()); + buffer.writeUInt32(0); // Clan crest ID + buffer.writeUInt32(0); // Ally ID + buffer.writeUInt32(0); // Ally crest ID + + // 29. Relation (clan leader, siege flags) + buffer.writeUInt32(0); // TODO: Implement clan relations + + // 30. Mount and store + buffer.writeUInt8(0); // Mount type + buffer.writeUInt8(0); // Private store type + buffer.writeUInt8(0); // Has dwarven craft + + // 31. PvP kills + buffer.writeUInt32(player_->getPkKills()); + buffer.writeUInt32(player_->getPvpKills()); + + // 32. Cubics + buffer.writeUInt16(0); // Cubic count + // TODO: Iterate through cubics if any + + // 33. Party match room + buffer.writeUInt8(0); // Is in party match room + + // 34. Abnormal visual effects + buffer.writeUInt32(0); // TODO: Implement abnormal effects + + // 35. Zone and clan privileges + buffer.writeUInt8(0); // Is in water zone + buffer.writeUInt32(0); // Clan privileges + + // 36. Recommendations + buffer.writeUInt16(0); // Recommendations remaining + buffer.writeUInt16(0); // Recommendations received + + // 37. Mount NPC ID + buffer.writeUInt32(0); // TODO: Implement mount system + + // 38. Inventory and class + buffer.writeUInt16(80); // Inventory limit (default 80) + buffer.writeUInt32(player_->getClassId()); + buffer.writeUInt32(0); // Special effects + + // 39. CP (Combat Points) - stub values + buffer.writeUInt32(100); // Max CP + buffer.writeUInt32(100); // Current CP + + // 40. Enchant effect and team + buffer.writeUInt8(0); // Enchant effect + buffer.writeUInt8(0); // Team ID + + // 41. Clan crest large + buffer.writeUInt32(0); // TODO: Implement clan crest system + + // 42. Noble and hero + buffer.writeUInt8(0); // Is noble + buffer.writeUInt8(0); // Is hero + + // 43. Fishing + buffer.writeUInt8(0); // Is fishing + buffer.writeUInt32(0); // Fishing X + buffer.writeUInt32(0); // Fishing Y + buffer.writeUInt32(0); // Fishing Z + + // 44. Colors + buffer.writeUInt32(0); // Name color + buffer.writeUInt8(0); // Is running + buffer.writeUInt32(0); // Pledge class + buffer.writeUInt32(0); // Pledge type + buffer.writeUInt32(0); // Title color + + // 45. Cursed weapon + buffer.writeUInt32(0); // TODO: Implement cursed weapon system + + std::cout << "[UserInfo] User info sent successfully" << std::endl; +} + +size_t UserInfo::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 1; // opcode + + // Position and vehicle + size += 4 * 4; // X, Y, Z, Vehicle ID + + // Object ID and name + size += 4; // Object ID + size += (player_->getName().length() + 1) * 2; // Name (UTF-16LE + null) + + // Basic info + size += 4 * 6; // Race, Sex, BaseClass, Level, EXP (8 bytes), Stats (6 * 4) + + // HP/MP/SP/Load + size += 4 * 6; // MaxHP, CurrentHP, MaxMP, CurrentMP, SP, CurrentLoad, MaxLoad + + // Weapon and equipment + size += 4; // Weapon equipped + size += 4 * 18; // Paperdoll object IDs + size += 4 * 18; // Paperdoll display IDs + + // C6 fields + size += 2 * 14; // 14 shorts + size += 4; // Right hand augmentation + size += 2 * 13; // 13 shorts + size += 4; // Right hand augmentation (duplicate) + size += 2 * 4; // 4 shorts + + // Combat stats + size += 4 * 10; // Combat stats + + // PvP and movement + size += 4 * 2; // PvP flag, Karma + size += 4 * 8; // Movement speeds + + // Multipliers and collision + size += 8 * 4; // Multipliers and collision (doubles) + + // Appearance and title + size += 4 * 3; // Hair style, color, face + size += 4; // Builder level + size += 2; // Title (empty string, UTF-16LE + null) + + // Clan info + size += 4 * 4; // Clan ID, crest, ally ID, ally crest + size += 4; // Relation + + // Mount and store + size += 1 * 3; // Mount type, store type, dwarven craft + + // PvP kills and cubics + size += 4 * 2; // PK kills, PvP kills + size += 2; // Cubic count + + // Party and effects + size += 1; // Party match room + size += 4; // Abnormal effects + size += 1; // Water zone + size += 4; // Clan privileges + + // Recommendations and mount + size += 2 * 2; // Recommendations + size += 4; // Mount NPC ID + + // Inventory and class + size += 2; // Inventory limit + size += 4 * 2; // Class ID, special effects + + // CP and effects + size += 4 * 2; // Max CP, Current CP + size += 1 * 2; // Enchant effect, team + + // Clan crest and status + size += 4; // Clan crest large + size += 1 * 2; // Noble, hero + + // Fishing + size += 1; // Is fishing + size += 4 * 3; // Fishing coordinates + + // Colors and cursed weapon + size += 4; // Name color + size += 1; // Is running + size += 4 * 3; // Pledge class, type, title color + size += 4; // Cursed weapon + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/user_info.hpp b/src/game/packets/responses/user_info.hpp new file mode 100644 index 0000000..9e9a664 --- /dev/null +++ b/src/game/packets/responses/user_info.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include +#include + +// UserInfo - Server response with player stats, gear, and location +// Critical packet for player spawning - sent after EnterWorld +class UserInfo : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x32; // UserInfo - Interlude Update 3 + const Player* player_; + +public: + // Constructor - create with player data + explicit UserInfo(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/validate_location.cpp b/src/game/packets/responses/validate_location.cpp new file mode 100644 index 0000000..5161d76 --- /dev/null +++ b/src/game/packets/responses/validate_location.cpp @@ -0,0 +1,54 @@ +#include "validate_location.hpp" +#include + +// Constructor +ValidateLocation::ValidateLocation(const Player* player) + : player_(player) +{ + if (!player_) + { + throw std::invalid_argument("Player cannot be null for ValidateLocation packet"); + } +} + +void ValidateLocation::write(SendablePacketBuffer &buffer) +{ + // Opcode is written automatically by base class + + std::cout << "[ValidateLocation] Sending position validation for: " << player_->getName() + << " at (" << player_->getX() << ", " << player_->getY() << ", " << player_->getZ() << ")" << std::endl; + + // Following L2J Mobius ValidateLocation.java structure EXACTLY + + // 1. Object ID + buffer.writeUInt32(player_->getObjectId()); + + // 2. X coordinate + buffer.writeUInt32(player_->getX()); + + // 3. Y coordinate + buffer.writeUInt32(player_->getY()); + + // 4. Z coordinate + buffer.writeUInt32(player_->getZ()); + + // 5. Heading (facing direction) + buffer.writeUInt32(0); // TODO: Implement heading system + + std::cout << "[ValidateLocation] Position validation sent successfully" << std::endl; +} + +size_t ValidateLocation::getSize() const +{ + // Calculate packet size based on L2J Mobius structure + size_t size = 1; // opcode + + // Fixed-size fields (exactly like L2J Mobius) + size += 4; // Object ID + size += 4; // X coordinate + size += 4; // Y coordinate + size += 4; // Z coordinate + size += 4; // Heading + + return size; +} \ No newline at end of file diff --git a/src/game/packets/responses/validate_location.hpp b/src/game/packets/responses/validate_location.hpp new file mode 100644 index 0000000..efe0b4c --- /dev/null +++ b/src/game/packets/responses/validate_location.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../../../core/packets/packet.hpp" +#include "../../../core/network/packet_buffer.hpp" +#include "../../entities/player.hpp" +#include + +// ValidateLocation - Server response to resync player position +// Critical for ensuring client/server position synchronization +class ValidateLocation : public SendablePacket +{ +private: + static constexpr uint8_t PACKET_ID = 0x79; // ValidateLocation - Interlude Update 3 + const Player* player_; + +public: + // Constructor - create with player data + explicit ValidateLocation(const Player* player); + + // SendablePacket interface implementation + uint8_t getPacketId() const override { return PACKET_ID; } + std::optional getExPacketId() const override { return std::nullopt; } + void write(SendablePacketBuffer &buffer) override; + size_t getSize() const override; +}; \ No newline at end of file diff --git a/src/game/packets/responses/version_check_response.cpp b/src/game/packets/responses/version_check_response.cpp index e483435..a3ebc38 100644 --- a/src/game/packets/responses/version_check_response.cpp +++ b/src/game/packets/responses/version_check_response.cpp @@ -22,8 +22,7 @@ void VersionCheckResponse::write(SendablePacketBuffer &buffer) { // Write VersionCheck packet structure for Interlude Update 3 // According to protocol: must include dynamic key and opcode obfuscation key - - buffer.writeUInt8(PACKET_ID); // Opcode: 0x00 (VersionCheck) + // Opcode is written automatically by base class if (protocol_ok_) { // Protocol accepted flag (1 byte) diff --git a/src/game/server/character_database_manager.cpp b/src/game/server/character_database_manager.cpp index 02117d4..a0c09ff 100644 --- a/src/game/server/character_database_manager.cpp +++ b/src/game/server/character_database_manager.cpp @@ -18,9 +18,9 @@ uint32_t CharacterDatabaseManager::createCharacter(const std::string &accountNam } // Check if character name already exists - for (const auto &[charId, character] : m_characters) + for (const auto &[charId, player] : m_characters) { - if (character.name == characterName) + if (player->getName() == characterName) { return 0; // Character name already exists } @@ -33,14 +33,14 @@ uint32_t CharacterDatabaseManager::createCharacter(const std::string &accountNam return 0; // Account has too many characters } - // Generate character ID and create character + // Generate character ID and create player uint32_t characterId = generateNextCharacterId(); - CharacterInfo newCharacter = createDefaultCharacter(accountName, characterName, race, sex, classId, - hairStyle, hairColor, face); - newCharacter.char_id = characterId; + auto newPlayer = createDefaultPlayer(accountName, characterName, race, sex, classId, + hairStyle, hairColor, face); + newPlayer->setObjectId(characterId); - // Store character and update account mapping - m_characters[characterId] = newCharacter; + // Store player and update account mapping + m_characters[characterId] = std::move(newPlayer); addCharacterToAccount(accountName, characterId); return characterId; @@ -57,7 +57,7 @@ bool CharacterDatabaseManager::deleteCharacter(uint32_t characterId, const std:: } // Verify the character belongs to this account - if (charIter->second.login_name != accountName) + if (charIter->second->getAccountName() != accountName) { return false; // Character doesn't belong to this account } @@ -73,9 +73,9 @@ bool CharacterDatabaseManager::characterExists(const std::string &characterName) { std::lock_guard lock(m_charactersMutex); - for (const auto &[charId, character] : m_characters) + for (const auto &[charId, player] : m_characters) { - if (character.name == characterName) + if (player->getName() == characterName) { return true; } @@ -98,20 +98,20 @@ bool CharacterDatabaseManager::isValidCharacterName(const std::string &name) con } // Character retrieval -std::optional CharacterDatabaseManager::getCharacterById(uint32_t characterId) const +std::optional CharacterDatabaseManager::getCharacterById(uint32_t characterId) const { std::lock_guard lock(m_charactersMutex); auto it = m_characters.find(characterId); if (it != m_characters.end()) { - return it->second; + return it->second.get(); } return std::nullopt; } -std::optional CharacterDatabaseManager::getCharacterBySlot(const std::string &accountName, uint32_t slotIndex) const +std::optional CharacterDatabaseManager::getCharacterBySlot(const std::string &accountName, uint32_t slotIndex) const { auto characters = getCharactersForAccount(accountName); @@ -123,26 +123,26 @@ std::optional CharacterDatabaseManager::getCharacterBySlot(const return std::nullopt; } -std::optional CharacterDatabaseManager::getCharacterByName(const std::string &characterName) const +std::optional CharacterDatabaseManager::getCharacterByName(const std::string &characterName) const { std::lock_guard lock(m_charactersMutex); - for (const auto &[charId, character] : m_characters) + for (const auto &[charId, player] : m_characters) { - if (character.name == characterName) + if (player->getName() == characterName) { - return character; + return player.get(); } } return std::nullopt; } -std::vector CharacterDatabaseManager::getCharactersForAccount(const std::string &accountName) const +std::vector CharacterDatabaseManager::getCharactersForAccount(const std::string &accountName) const { std::lock_guard lock(m_charactersMutex); - std::vector accountCharacters; + std::vector accountCharacters; auto accountIter = m_accountCharacters.find(accountName); if (accountIter != m_accountCharacters.end()) @@ -154,16 +154,16 @@ std::vector CharacterDatabaseManager::getCharactersForAccount(con auto charIter = m_characters.find(characterId); if (charIter != m_characters.end()) { - accountCharacters.push_back(charIter->second); + accountCharacters.push_back(charIter->second.get()); } } } // Sort by character ID for consistent ordering std::sort(accountCharacters.begin(), accountCharacters.end(), - [](const CharacterInfo &a, const CharacterInfo &b) + [](const Player *a, const Player *b) { - return a.char_id < b.char_id; + return a->getObjectId() < b->getObjectId(); }); return accountCharacters; @@ -211,7 +211,7 @@ bool CharacterDatabaseManager::updateCharacterLevel(uint32_t characterId, uint32 auto it = m_characters.find(characterId); if (it != m_characters.end()) { - it->second.level = newLevel; + it->second->setLevel(newLevel); return true; } @@ -225,9 +225,7 @@ bool CharacterDatabaseManager::updateCharacterPosition(uint32_t characterId, uin auto it = m_characters.find(characterId); if (it != m_characters.end()) { - it->second.x = x; - it->second.y = y; - it->second.z = z; + it->second->setPosition(x, y, z); return true; } @@ -242,7 +240,7 @@ std::vector CharacterDatabaseManager::getAllCharacterIds() const std::vector characterIds; characterIds.reserve(m_characters.size()); - for (const auto &[charId, character] : m_characters) + for (const auto &[charId, player] : m_characters) { characterIds.push_back(charId); } @@ -273,73 +271,64 @@ uint32_t CharacterDatabaseManager::generateNextCharacterId() return m_nextCharacterId.fetch_add(1); } -CharacterInfo CharacterDatabaseManager::createDefaultCharacter(const std::string &accountName, const std::string &characterName, - uint32_t race, uint32_t sex, uint32_t classId, - uint32_t hairStyle, uint32_t hairColor, uint32_t face) +std::unique_ptr CharacterDatabaseManager::createDefaultPlayer(const std::string &accountName, const std::string &characterName, + uint32_t race, uint32_t sex, uint32_t classId, + uint32_t hairStyle, uint32_t hairColor, uint32_t face) { - CharacterInfo character; + auto player = std::make_unique(0, characterName, accountName); // ObjectId will be set later // Basic info - character.name = characterName; - character.login_name = accountName; - character.session_id = 0; // Will be set during login - character.clan_id = 0; // No clan initially - character.builder_level = 0; // Normal player - character.sex = sex; - character.race = race; - character.class_id = classId; - character.active = 1; // Character is active + player->setSessionId(0); // Will be set during login + player->setClanId(0); // No clan initially + player->setRace(race); + player->setSex(sex); + player->setClassId(classId); + player->setBaseClassId(classId); // Base class same as current class initially // Default starting position (safer coordinates for all races) - character.x = -84318; - character.y = 244579; - character.z = -3730; + player->setPosition(-84318, 244579, -3730); // Default starting stats - Set proper starting HP/MP based on class/race - character.level = 1; - character.current_hp = 100.0; - character.current_mp = 100.0; - character.max_hp = 100.0; // Set max HP - character.max_mp = 100.0; // Set max MP - character.sp = 0; - character.exp = 0; - character.karma = 0; - character.pk_kills = 0; - character.pv_kills = 0; + player->setLevel(1); + player->setCurrentHp(100.0); + player->setMaxHp(100.0); + player->setCurrentMp(100.0); + player->setMaxMp(100.0); + player->setSp(0); + player->setExp(0); + player->setKarma(0); + player->setPkKills(0); + player->setPvpKills(0); // Appearance data from character creation - character.hair_style = hairStyle; - character.hair_color = hairColor; - character.face = face; + player->setHairStyle(hairStyle); + player->setHairColor(hairColor); + player->setFace(face); // Initialize base stats that were missing // These should be set based on race/class but for now use defaults - character.str_stat = 10; - character.dex_stat = 10; - character.con_stat = 10; - character.int_stat = 10; - character.wit_stat = 10; - character.men_stat = 10; + player->setStr(10); + player->setDex(10); + player->setCon(10); + player->setInt(10); + player->setWit(10); + player->setMen(10); - // Equipment slots - Initialize both object and item arrays - character.paperdoll_object_ids.resize(16, 0); // Object IDs (all empty) - character.paperdoll_item_ids.resize(16, 0); // Item IDs (all empty) + // Equipment slots - Initialize both object and item arrays (already done in Player constructor) + // The Player constructor already initializes 16 empty slots // Character state and deletion - character.delete_timer = 0; // Not scheduled for deletion - character.base_class_id = classId; // Base class same as current class initially - character.is_selected = 0; // Not selected by default - character.enchant_effect = 0; // No enchant effect - character.augmentation_id = 0; // No weapon augmentation + player->setDeleteTimer(0); // Not scheduled for deletion + player->setEnchantEffect(0); // No enchant effect + player->setAugmentationId(0); // No weapon augmentation // Add logging to validate character creation - std::cout << "[CharDB] Created character: " << characterName - << " ID=" << character.char_id + std::cout << "[CharDB] Created player: " << characterName << " race=" << race << " sex=" << sex << " class=" << classId - << " HP=" << character.current_hp << "/" << character.max_hp - << " MP=" << character.current_mp << "/" << character.max_mp << std::endl; + << " HP=" << player->getCurrentHp() << "/" << player->getMaxHp() + << " MP=" << player->getCurrentMp() << "/" << player->getMaxMp() << std::endl; - return character; + return player; } void CharacterDatabaseManager::addCharacterToAccount(const std::string &accountName, uint32_t characterId) diff --git a/src/game/server/character_database_manager.hpp b/src/game/server/character_database_manager.hpp index 2fd6a34..5a79dbb 100644 --- a/src/game/server/character_database_manager.hpp +++ b/src/game/server/character_database_manager.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../packets/responses/character_selection_info.hpp" +#include "../entities/player.hpp" #include #include #include @@ -16,7 +16,7 @@ class CharacterDatabaseManager { private: - std::unordered_map m_characters; // Character ID -> Character data + std::unordered_map> m_characters; // Character ID -> Player instance std::unordered_map> m_accountCharacters; // Account -> Character IDs mutable std::mutex m_charactersMutex; // Thread safety for character operations std::atomic m_nextCharacterId{1}; // Auto-incrementing character ID @@ -38,11 +38,11 @@ class CharacterDatabaseManager bool characterExists(const std::string &characterName) const; bool isValidCharacterName(const std::string &name) const; - // Character retrieval - std::optional getCharacterById(uint32_t characterId) const; - std::optional getCharacterBySlot(const std::string &accountName, uint32_t slotIndex) const; - std::optional getCharacterByName(const std::string &characterName) const; - std::vector getCharactersForAccount(const std::string &accountName) const; + // Character retrieval methods + std::optional getCharacterById(uint32_t characterId) const; + std::optional getCharacterBySlot(const std::string &accountName, uint32_t slotIndex) const; + std::optional getCharacterByName(const std::string &characterName) const; + std::vector getCharactersForAccount(const std::string &accountName) const; // Character information size_t getCharacterCount() const; @@ -63,9 +63,9 @@ class CharacterDatabaseManager private: // Helper methods uint32_t generateNextCharacterId(); - CharacterInfo createDefaultCharacter(const std::string &accountName, const std::string &characterName, - uint32_t race, uint32_t sex, uint32_t classId, - uint32_t hairStyle, uint32_t hairColor, uint32_t face); + std::unique_ptr createDefaultPlayer(const std::string &accountName, const std::string &characterName, + uint32_t race, uint32_t sex, uint32_t classId, + uint32_t hairStyle, uint32_t hairColor, uint32_t face); void addCharacterToAccount(const std::string &accountName, uint32_t characterId); void removeCharacterFromAccount(const std::string &accountName, uint32_t characterId); }; \ No newline at end of file diff --git a/src/login/packets/responses/auth_gg_response.cpp b/src/login/packets/responses/auth_gg_response.cpp index e1e7cc9..9a0c649 100644 --- a/src/login/packets/responses/auth_gg_response.cpp +++ b/src/login/packets/responses/auth_gg_response.cpp @@ -21,10 +21,7 @@ std::optional AuthGGResponse::getExPacketId() const void AuthGGResponse::write(SendablePacketBuffer &buffer) { - // Write packet structure according to Rust implementation: // Opcode + Session ID + 4 zero int32s (GameGuard placeholders) + padding for 4-byte alignment - - buffer.writeUInt8(OPCODE); // Opcode: 0x0B (GgAuth) buffer.writeInt32(m_sessionId); // Session ID: validated session // Write 4 zero int32s as GameGuard placeholders diff --git a/src/login/packets/responses/init_packet.cpp b/src/login/packets/responses/init_packet.cpp index f1f507a..8d96e6a 100644 --- a/src/login/packets/responses/init_packet.cpp +++ b/src/login/packets/responses/init_packet.cpp @@ -31,7 +31,6 @@ void InitPacket::write(SendablePacketBuffer &buffer) // Write packet structure according to network-steps.md: // Opcode + Session ID + Protocol + RSA Key + GameGuard + Blowfish Key + Null - buffer.writeUInt8(OPCODE); // Opcode: 0x00 (Init) buffer.writeInt32(m_sessionId); // Session ID: random i32 buffer.writeInt32(PROTOCOL_REVISION); // Protocol revision: 0x0000c621 buffer.writeBytes(m_scrambledModulus); // RSA public key: 128 bytes (scrambled modulus) diff --git a/src/login/packets/responses/login_ok_response.cpp b/src/login/packets/responses/login_ok_response.cpp index 1d712a1..1480d59 100644 --- a/src/login/packets/responses/login_ok_response.cpp +++ b/src/login/packets/responses/login_ok_response.cpp @@ -21,10 +21,8 @@ std::optional LoginOkResponse::getExPacketId() const void LoginOkResponse::write(SendablePacketBuffer &buffer) { - // Write packet structure exactly matching Rust implementation: // opcode + login_ok1 + login_ok2 + zeros + 0x03ea + zeros + 16 zero bytes - buffer.writeUInt8(OPCODE); // Opcode: 0x03 (LoginOk) buffer.writeInt32(m_sessionKey.login_ok1); // Session key part 1 buffer.writeInt32(m_sessionKey.login_ok2); // Session key part 2 buffer.writeInt32(0x00); // Zero padding diff --git a/src/login/packets/responses/play_ok_response.cpp b/src/login/packets/responses/play_ok_response.cpp index be42010..3b843b5 100644 --- a/src/login/packets/responses/play_ok_response.cpp +++ b/src/login/packets/responses/play_ok_response.cpp @@ -12,9 +12,7 @@ void PlayOkResponse::write(SendablePacketBuffer &buffer) { try { - // Write packet ID (matches Rust LoginServerOpcodes::PlayOk = 0x07) - buffer.writeUInt8(PACKET_ID); - + // Opcode is written automatically by base class // Write session key for game server connection (matches Rust PlayOk::new implementation) // The client will use these values to authenticate with the game server buffer.writeInt32(session_key_.play_ok1); diff --git a/src/login/packets/responses/server_list_response.cpp b/src/login/packets/responses/server_list_response.cpp index 0dd37f7..4655a7d 100644 --- a/src/login/packets/responses/server_list_response.cpp +++ b/src/login/packets/responses/server_list_response.cpp @@ -30,11 +30,9 @@ std::optional ServerListResponse::getExPacketId() const void ServerListResponse::write(SendablePacketBuffer &buffer) { - // Write packet structure exactly matching Rust implementation: // opcode + server_count + last_server + server_data_for_each + trailer + character_info // Header - buffer.writeUInt8(OPCODE); // Opcode: 0x04 (ServerList) buffer.writeUInt8(static_cast(m_servers.size())); // Server count buffer.writeUInt8(m_lastServer); // Last server selection diff --git a/src/login/server/login_server.cpp b/src/login/server/login_server.cpp index e10548d..4b02652 100644 --- a/src/login/server/login_server.cpp +++ b/src/login/server/login_server.cpp @@ -199,7 +199,6 @@ void LoginServer::initialize_server() connection_manager_ = std::make_unique(io_context_, conn_config, game_server_manager_.get()); - // Add some test servers for demonstration registerTestServers(); log_server_event("Server initialized"); @@ -321,8 +320,7 @@ void LoginServer::registerTestServers() { return; } - // Register 10 test servers with randomized properties - for (int i = 1; i <= 10; i++) { + for (int i = 1; i <= 1; i++) { ServerData testServer; testServer.ip = "127.0.0.1"; testServer.port = 7777 + i - 1; // Increment port for each server