diff --git a/common/database.cpp b/common/database.cpp index cea5ddf2b..74596df23 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -1095,13 +1095,13 @@ void Database::SetLFP(uint32 character_id, bool is_lfp) CharacterDataRepository::UpdateOne(*this, e); } -void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon) +void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 ingame) { auto e = CharacterDataRepository::FindOne(*this, character_id); - e.firstlogon = first_logon; - e.lfg = is_lfg ? 1 : 0; - e.lfp = is_lfp ? 1 : 0; + e.ingame = ingame; + e.lfg = is_lfg ? 1 : 0; + e.lfp = is_lfp ? 1 : 0; CharacterDataRepository::UpdateOne(*this, e); } @@ -1115,11 +1115,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg) CharacterDataRepository::UpdateOne(*this, e); } -void Database::SetFirstLogon(uint32 character_id, uint8 first_logon) +void Database::SetIngame(uint32 character_id, uint8 ingame) { auto e = CharacterDataRepository::FindOne(*this, character_id); - e.firstlogon = first_logon; + e.ingame = ingame; CharacterDataRepository::UpdateOne(*this, e); } diff --git a/common/database.h b/common/database.h index fe08e83fb..9b81f66a0 100644 --- a/common/database.h +++ b/common/database.h @@ -263,7 +263,7 @@ public: bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year); void ClearMerchantTemp(); void ClearPTimers(uint32 character_id); - void SetFirstLogon(uint32 character_id, uint8 first_logon); + void SetIngame(uint32 character_id, uint8 ingame); void SetLFG(uint32 character_id, bool is_lfg); void SetLFP(uint32 character_id, bool is_lfp); void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon); diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 29aad812f..1b2f7bc61 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7098,7 +7098,20 @@ ALTER TABLE `npc_types` ADD COLUMN `npc_tint_id` SMALLINT UNSIGNED NULL DEFAULT '0' AFTER `multiquest_enabled`; )", .content_schema_update = true - } + }, + ManifestEntry{ + .version = 9323, + .description = "2025_04_16_character_data_first_login.sql", + .check = "SHOW COLUMNS FROM `character_data` LIKE 'first_login'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `character_data` +CHANGE COLUMN `firstlogon` `ingame` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`, +ADD COLUMN `first_login` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`; +)", + .content_schema_update = false + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ // .version = 9228, diff --git a/common/repositories/base/base_character_data_repository.h b/common/repositories/base/base_character_data_repository.h index 05d8daa7e..85374f7fc 100644 --- a/common/repositories/base/base_character_data_repository.h +++ b/common/repositories/base/base_character_data_repository.h @@ -115,7 +115,8 @@ public: uint8_t lfg; std::string mailkey; uint8_t xtargets; - int8_t firstlogon; + uint8_t ingame; + uint32_t first_login; uint32_t e_aa_effects; uint32_t e_percent_to_aa; uint32_t e_expended_aa_spent; @@ -230,7 +231,8 @@ public: "lfg", "mailkey", "xtargets", - "firstlogon", + "ingame", + "first_login", "e_aa_effects", "e_percent_to_aa", "e_expended_aa_spent", @@ -341,7 +343,8 @@ public: "lfg", "mailkey", "xtargets", - "firstlogon", + "ingame", + "first_login", "e_aa_effects", "e_percent_to_aa", "e_expended_aa_spent", @@ -486,7 +489,8 @@ public: e.lfg = 0; e.mailkey = ""; e.xtargets = 5; - e.firstlogon = 0; + e.ingame = 0; + e.first_login = 0; e.e_aa_effects = 0; e.e_percent_to_aa = 0; e.e_expended_aa_spent = 0; @@ -627,15 +631,16 @@ public: e.lfg = row[93] ? static_cast(strtoul(row[93], nullptr, 10)) : 0; e.mailkey = row[94] ? row[94] : ""; e.xtargets = row[95] ? static_cast(strtoul(row[95], nullptr, 10)) : 5; - e.firstlogon = row[96] ? static_cast(atoi(row[96])) : 0; - e.e_aa_effects = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; - e.e_percent_to_aa = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; - e.e_expended_aa_spent = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; - e.aa_points_spent_old = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; - e.aa_points_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; - e.e_last_invsnapshot = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; - e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10); - e.illusion_block = row[104] ? static_cast(strtoul(row[104], nullptr, 10)) : 0; + e.ingame = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 0; + e.first_login = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; + e.e_aa_effects = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; + e.e_percent_to_aa = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; + e.e_expended_aa_spent = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; + e.aa_points_spent_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; + e.aa_points_old = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; + e.e_last_invsnapshot = row[103] ? static_cast(strtoul(row[103], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); + e.illusion_block = row[105] ? static_cast(strtoul(row[105], nullptr, 10)) : 0; return e; } @@ -764,15 +769,16 @@ public: v.push_back(columns[93] + " = " + std::to_string(e.lfg)); v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'"); v.push_back(columns[95] + " = " + std::to_string(e.xtargets)); - v.push_back(columns[96] + " = " + std::to_string(e.firstlogon)); - v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects)); - v.push_back(columns[98] + " = " + std::to_string(e.e_percent_to_aa)); - v.push_back(columns[99] + " = " + std::to_string(e.e_expended_aa_spent)); - v.push_back(columns[100] + " = " + std::to_string(e.aa_points_spent_old)); - v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old)); - v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot)); - v.push_back(columns[103] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); - v.push_back(columns[104] + " = " + std::to_string(e.illusion_block)); + v.push_back(columns[96] + " = " + std::to_string(e.ingame)); + v.push_back(columns[97] + " = " + std::to_string(e.first_login)); + v.push_back(columns[98] + " = " + std::to_string(e.e_aa_effects)); + v.push_back(columns[99] + " = " + std::to_string(e.e_percent_to_aa)); + v.push_back(columns[100] + " = " + std::to_string(e.e_expended_aa_spent)); + v.push_back(columns[101] + " = " + std::to_string(e.aa_points_spent_old)); + v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old)); + v.push_back(columns[103] + " = " + std::to_string(e.e_last_invsnapshot)); + v.push_back(columns[104] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(columns[105] + " = " + std::to_string(e.illusion_block)); auto results = db.QueryDatabase( fmt::format( @@ -890,7 +896,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -1024,7 +1031,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -1162,15 +1170,16 @@ public: e.lfg = row[93] ? static_cast(strtoul(row[93], nullptr, 10)) : 0; e.mailkey = row[94] ? row[94] : ""; e.xtargets = row[95] ? static_cast(strtoul(row[95], nullptr, 10)) : 5; - e.firstlogon = row[96] ? static_cast(atoi(row[96])) : 0; - e.e_aa_effects = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; - e.e_percent_to_aa = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; - e.e_expended_aa_spent = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; - e.aa_points_spent_old = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; - e.aa_points_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; - e.e_last_invsnapshot = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; - e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10); - e.illusion_block = row[104] ? static_cast(strtoul(row[104], nullptr, 10)) : 0; + e.ingame = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 0; + e.first_login = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; + e.e_aa_effects = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; + e.e_percent_to_aa = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; + e.e_expended_aa_spent = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; + e.aa_points_spent_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; + e.aa_points_old = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; + e.e_last_invsnapshot = row[103] ? static_cast(strtoul(row[103], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); + e.illusion_block = row[105] ? static_cast(strtoul(row[105], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1291,15 +1300,16 @@ public: e.lfg = row[93] ? static_cast(strtoul(row[93], nullptr, 10)) : 0; e.mailkey = row[94] ? row[94] : ""; e.xtargets = row[95] ? static_cast(strtoul(row[95], nullptr, 10)) : 5; - e.firstlogon = row[96] ? static_cast(atoi(row[96])) : 0; - e.e_aa_effects = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; - e.e_percent_to_aa = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; - e.e_expended_aa_spent = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; - e.aa_points_spent_old = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; - e.aa_points_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; - e.e_last_invsnapshot = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; - e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10); - e.illusion_block = row[104] ? static_cast(strtoul(row[104], nullptr, 10)) : 0; + e.ingame = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 0; + e.first_login = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; + e.e_aa_effects = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; + e.e_percent_to_aa = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; + e.e_expended_aa_spent = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; + e.aa_points_spent_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; + e.aa_points_old = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; + e.e_last_invsnapshot = row[103] ? static_cast(strtoul(row[103], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); + e.illusion_block = row[105] ? static_cast(strtoul(row[105], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1470,7 +1480,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -1597,7 +1608,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); diff --git a/common/version.h b/common/version.h index 5a4bf1632..0ac95fb61 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9322 +#define CURRENT_BINARY_DATABASE_VERSION 9323 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #endif diff --git a/zone/client.h b/zone/client.h index e91353356..e6832bf24 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2082,7 +2082,8 @@ private: uint16 trader_id; uint16 customer_id; uint32 account_creation; - uint8 firstlogon; + bool first_login; + bool ingame; uint32 mercid; // current merc uint8 mercSlot; // selected merc slot time_t m_trader_transaction_date; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 98d5204e3..5328da726 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -807,29 +807,42 @@ void Client::CompleteConnect() m_last_position_before_bulk_update = GetPosition(); /* This sub event is for if a player logs in for the first time since entering world. */ - if (firstlogon == 1) { - TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); - BuyerRepository::DeleteBuyer(database, CharacterID()); - LogTradingDetail( - "Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.", + if (ingame) { + auto e = CharacterDataRepository::FindOne( + database, CharacterID() ); + bool is_first_login = e.first_login == 0; + RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{}); if (parse->PlayerHasQuestSub(EVENT_CONNECT)) { - parse->EventPlayer(EVENT_CONNECT, this, "", 0); + const std::string& export_string = fmt::format( + "{} {} {}", + e.last_login, + time(nullptr) - e.last_login, + is_first_login ? 1 : 0 + ); + parse->EventPlayer(EVENT_CONNECT, this, export_string, 0); } - /** - * Update last login since this doesn't get updated until a late save later so we can update online status - */ - database.QueryDatabase( - StringFormat( - "UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u", + if (is_first_login) { + e.first_login = time(nullptr); + TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + BuyerRepository::DeleteBuyer(database, CharacterID()); + LogTradingDetail( + "Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.", CharacterID() - ) - ); + ); + } + + e.last_login = time(nullptr); + + const int updated = CharacterDataRepository::UpdateOne(database, e); + if (!updated) { + LogError("Failed to update login time for character_id [{}]", CharacterID()); + } if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) { InvokeChangePetName(false); @@ -878,7 +891,7 @@ void Client::CompleteConnect() entity_list.SendFindableNPCList(this); if (IsInAGuild()) { - if (firstlogon == 1) { + if (ingame) { guild_mgr.UpdateDbMemberOnline(CharacterID(), true); SendGuildMembersList(); } @@ -1314,14 +1327,14 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Load Character Data */ query = fmt::format( - "SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}", + "SELECT `lfp`, `lfg`, `xtargets`, `first_login`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block`, `ingame` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}", cid ); auto results = database.QueryDatabase(query); for (auto row : results) { if (row[4] && Strings::ToInt(row[4]) > 0) { - guild_id = Strings::ToInt(row[4]); - guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE; + guild_id = Strings::ToInt(row[4]); + guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE; guild_tribute_opt_in = row[7] ? Strings::ToBool(row[7]) : 0; } @@ -1329,10 +1342,21 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) SetExtraHaste(Strings::ToInt(row[8]), false); SetIllusionBlock(Strings::ToBool(row[9])); - if (LFP) { LFP = Strings::ToInt(row[0]); } - if (LFG) { LFG = Strings::ToInt(row[1]); } - if (row[3]) - firstlogon = Strings::ToInt(row[3]); + if (LFP) { + LFP = Strings::ToInt(row[0]); + } + + if (LFG) { + LFG = Strings::ToInt(row[1]); + } + + if (row[3]) { + first_login = Strings::ToUnsignedInt(row[3]); + } + + if (row[10]) { + ingame = Strings::ToBool(row[10]); + } } if (RuleB(Character, SharedBankPlat)) @@ -1721,7 +1745,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) // Taunt persists when zoning on newer clients, overwrite default. if (m_ClientVersionBit & EQ::versions::maskUFAndLater) { - if (!firstlogon) { + if (!ingame) { pet->SetTaunting(m_petinfo.taunting); } } diff --git a/zone/client_process.cpp b/zone/client_process.cpp index c9de4c160..ec63581d6 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -727,7 +727,7 @@ void Client::OnDisconnect(bool hard_disconnect) { o->trade->Reset(); } - database.SetFirstLogon(CharacterID(), 0); //We change firstlogon status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world. + database.SetIngame(CharacterID(), 0); //We change ingame status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world. /* Remove from all proximities */ ClearAllProximities(); diff --git a/zone/embparser.cpp b/zone/embparser.cpp index e42022146..16a662482 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -2543,6 +2543,14 @@ void PerlembParser::ExportEventVariables( break; } + case EVENT_CONNECT: { + Seperator sep(data); + ExportVar(package_name.c_str(), "last_login", sep.arg[0]); + ExportVar(package_name.c_str(), "seconds_since_last_login", sep.arg[1]); + ExportVar(package_name.c_str(), "is_first_login", sep.arg[2]); + break; + } + default: { break; } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 050657bbc..fa684a490 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -353,6 +353,7 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss; PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked; PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item; + PlayerArgumentDispatch[EVENT_CONNECT] = handle_player_connect; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index d6419189a..8d2517fe3 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -1809,6 +1809,26 @@ void handle_player_read_item( } } +void handle_player_connect( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + Seperator sep(data.c_str()); + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0])); + lua_setfield(L, -2, "last_login"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1])); + lua_setfield(L, -2, "seconds_since_last_login"); + + lua_pushboolean(L, Strings::ToBool(sep.arg[2])); + lua_setfield(L, -2, "is_first_login"); +} + // Item void handle_item_click( QuestInterface *parse, diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 4a9a1bbb9..3cb428c58 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -865,6 +865,15 @@ void handle_player_read_item( std::vector *extra_pointers ); +void handle_player_connect( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + // Item void handle_item_click( QuestInterface *parse,