replace the old source

This commit is contained in:
Sky Johnson 2025-08-30 19:44:00 -05:00
parent bc6ba6ed6c
commit 6b0f888fef
265 changed files with 33586 additions and 26312 deletions

View File

@ -391,7 +391,7 @@ func BenchmarkCRC(b *testing.B) {
} }
for b.Loop() { for b.Loop() {
CalculateCRC32(data) //CalculateCRC32(data)
} }
} }

View File

@ -0,0 +1,20 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Character.h"

View File

@ -0,0 +1,25 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _EQ2_CHARACTER_
#define _EQ2_CHARACTER_
class Character{
};
#endif

File diff suppressed because it is too large Load Diff

253
old/LoginServer/LWorld.h Normal file
View File

@ -0,0 +1,253 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef LWORLD_H
#define LWORLD_H
#include "../common/Mutex.h"
#define ERROR_BADPASSWORD "Bad password"
#define INVALID_ACCOUNT "Invalid Server Account."
#define ERROR_BADVERSION "Incorrect version"
#define ERROR_UNNAMED "Unnamed servers not allowed to connect to full login servers"
#define ERROR_NOTMASTER "Not a master server"
#define ERROR_NOTMESH "Not a mesh server"
#define ERROR_GHOST "Ghost kick"
#define ERROR_UnknownServerType "Unknown Server Type"
#define ERROR_BADNAME_SERVER "Bad server name, name may not contain the word \"Server\" (case sensitive)"
#define ERROR_BADNAME "Bad server name. Unknown reason."
#define WORLD_NAME_SUFFIX " Server"
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/http.hpp>
#include <sstream>
#include <string>
#include <iostream>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
#include "../common/linked_list.h"
#include "../WorldServer/MutexList.h"
#include "../WorldServer/MutexMap.h"
#include "../common/timer.h"
#include "../common/types.h"
#include "../common/queue.h"
#include "../common/servertalk.h"
#include "../common/TCPConnection.h"
#include "client.h"
#define MAX_UPDATE_COUNT 20
#define MAX_LOGIN_APPEARANCE_COUNT 100
#ifdef WIN32
void ServerUpdateLoop(void* tmp);
#else
void* ServerUpdateLoop(void* tmp);
#endif
enum ConType { UnknownW, World, Chat, Login };
class LWorld
{
public:
LWorld(TCPConnection* in_con, bool OutgoingLoginUplink = false, int32 iIP = 0, int16 iPort = 0, bool iNeverKick = false);
LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id);
LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID);
~LWorld();
static bool CheckServerName(const char* name);
bool Process();
void SendPacket(ServerPacket* pack);
void Message(const char* to, const char* message, ...);
bool SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version);
void UpdateStatus(sint32 in_status, sint32 in_players, sint32 in_zones, int8 in_level) {
// we don't want the server list to update unless something has changed
if(status != in_status || num_players != in_players || num_zones != in_zones || world_max_level != in_level)
{
status = in_status;
num_players = in_players;
num_zones = in_zones;
world_max_level = in_level;
UpdateWorldList();
}
}
void UpdateWorldList(LWorld* to = 0);
void SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones);
inline bool IsPlaceholder() { return pPlaceholder; }
inline int32 GetAccountID() { return accountid; }
inline char* GetAccount() { return account; }
inline char* GetAddress() { return address; }
inline int16 GetClientPort() { return pClientPort; }
inline bool IsAddressIP() { return isaddressip; }
inline char* GetName() { return worldname; }
inline sint32 GetStatus() { return status; }
bool IsLocked() { return status==-2; }
inline int32 GetIP() { return ip; }
inline int16 GetPort() { return port; }
inline int32 GetID() { return ID; }
inline int32 GetAdmin() { return admin_id; }
inline bool ShowDown() { return pshowdown; }
inline bool ShowDownActive(){ return show_down_active; }
void ShowDownActive(bool show){ show_down_active = show; }
void ShowDown(bool show){ pshowdown = show; }
inline bool Connected() { return pConnected; }
int8 GetWorldStatus();
void ChangeToPlaceholder();
void Kick(const char* message = ERROR_GHOST, bool iSetKickedFlag = true);
inline bool IsKicked() { return kicked; }
inline bool IsNeverKick() { return pNeverKick; }
inline ConType GetType() { return ptype; }
inline bool IsOutgoingUplink() { return OutgoingUplink; }
inline TCPConnection* GetLink() { return Link; }
inline int32 GetRemoteID() { return RemoteID; }
inline int32 GetLinkWorldID() { return LinkWorldID; }
inline sint32 GetZoneNum() { return num_zones; }
inline sint32 GetPlayerNum() { return num_players; }
void SetID(int32 new_id) { ID = new_id; }
void SendDeleteCharacter( int32 char_id, int32 account_id );
bool IsDevelServer(){ return devel_server; }
inline int8 GetMaxWorldLevel() { return world_max_level; }
bool IsInit;
protected:
friend class LWorldList;
TCPConnection* Link;
Timer* pReconnectTimer;
Timer* pStatsTimer;
private:
int32 ID;
int32 ip;
char IPAddr[64];
int16 port;
bool kicked;
bool pNeverKick;
bool pPlaceholder;
bool devel_server;
int32 accountid;
char account[30];
char address[250];
bool isAuthenticated;
int16 pClientPort;
bool isaddressip;
char worldname[200];
sint32 status;
int32 admin_id;
bool pshowdown;
bool show_down_active;
ConType ptype;
bool OutgoingUplink;
bool pConnected;
sint32 num_players;
sint32 num_zones;
int32 RemoteID;
int32 LinkWorldID;
int8 world_max_level;
};
class LWorldList
{
public:
LWorldList();
~LWorldList();
LWorld* FindByID(int32 WorldID);
LWorld* FindByIP(int32 ip);
LWorld* FindByAddress(char* address);
LWorld* FindByLink(TCPConnection* in_link, int32 in_id);
LWorld* FindByAccount(int32 in_accountid, ConType in_type = World);
void Add(LWorld* worldserver);
void AddInitiateWorld ( LWorld* world );
void Process();
void ReceiveData();
void SendPacket(ServerPacket* pack, LWorld* butnotme = 0);
void SendPacketLocal(ServerPacket* pack, LWorld* butnotme = 0);
void SendPacketLogin(ServerPacket* pack, LWorld* butnotme = 0);
void SendWorldChanged(int32 server_id, bool sendtoallclients=false, Client* sendto = 0);
vector<PacketStruct*>* GetServerListUpdate(int16 version);
EQ2Packet* MakeServerListPacket(int8 lsadmin, int16 version);
void UpdateWorldList(LWorld* to = 0);
void UpdateWorldStats();
void KickGhost(ConType in_type, int32 in_accountid = 0, LWorld* ButNotMe = 0);
void KickGhostIP(int32 ip, LWorld* NotMe = 0, int16 iClientPort = 0);
void RemoveByLink(TCPConnection* in_link, int32 in_id = 0, LWorld* ButNotMe = 0);
void RemoveByID(int32 in_id);
void SendWorldStatus(LWorld* chat, char* adminname);
void ConnectUplink();
bool Init();
void InitWorlds();
void Shutdown();
bool WriteXML();
int32 GetCount(ConType type);
void PopulateWorldList(http::response<http::string_body>& res);
void ListWorldsToConsole();
//devn00b temp
void AddServerEquipmentUpdates(LWorld* world, map<int32, LoginEquipmentUpdate> updates);
void ProcessLSEquipUpdates();
void RequestServerEquipUpdates(LWorld* world);
void SetUpdateServerList ( bool var ) { UpdateServerList = var; }
bool ContinueServerUpdates(){ return server_update_thread; }
void ResetServerUpdates(){server_update_thread = true;}
void ProcessServerUpdates();
void RequestServerUpdates(LWorld* world);
void AddServerZoneUpdates(LWorld* world, map<int32, LoginZoneUpdate> updates);
protected:
friend class LWorld;
int32 GetNextID() { return NextID++; }
private:
Mutex MWorldMap;
map<int32, map<int32, bool> > zone_updates_already_used; //used to determine if someone is trying to DOS us
MutexMap<int32, int32> zone_update_timeouts;
MutexMap<int32, int32> awaiting_zone_update;
MutexMap<LWorld*, int32> last_updated;
MutexMap<int32, map<int32, LoginZoneUpdate> > server_zone_updates;
bool server_update_thread;
int32 NextID;
LinkedList<LWorld*> list;
map<int32,LWorld*> worldmap;
TCPServer* tcplistener;
TCPConnection* OutLink;
//devn00b temp
// JohnAdams: login appearances, copied from above
map<int32, map<int32, bool> > equip_updates_already_used;
MutexMap<int32, int32> equip_update_timeouts;
MutexMap<int32, int32> awaiting_equip_update;
MutexMap<LWorld*, int32> last_equip_updated;
MutexMap<int32, map<int32, LoginEquipmentUpdate> > server_equip_updates;
//
///
// holds the world server list so we don't have to create it for every character
// logging in
map<int32,EQ2Packet*> ServerListData;
bool UpdateServerList;
};
#endif

View File

@ -0,0 +1,58 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#include "LoginAccount.h"
LoginAccount::LoginAccount(){
}
LoginAccount::~LoginAccount(){
vector<CharSelectProfile*>::iterator iter;
for(iter = charlist.begin(); iter != charlist.end(); iter++){
safe_delete(*iter);
}
}
void LoginAccount::flushCharacters ( )
{
vector<CharSelectProfile*>::iterator iter;
for(iter = charlist.begin(); iter != charlist.end(); iter++){
safe_delete(*iter);
}
charlist.clear ( );
}
CharSelectProfile* LoginAccount::getCharacter(char* name){
vector<CharSelectProfile*>::iterator char_iterator;
CharSelectProfile* profile = 0;
EQ2_16BitString temp;
for(char_iterator = charlist.begin(); char_iterator != charlist.end(); char_iterator++){
profile = *char_iterator;
temp = profile->packet->getType_EQ2_16BitString_ByName("name");
if(strcmp(temp.data.c_str(), name)==0)
return profile;
}
return 0;
}
void LoginAccount::removeCharacter(char* name, int16 version){
vector<CharSelectProfile*>::iterator iter;
CharSelectProfile* profile = 0;
EQ2_16BitString temp;
for(iter = charlist.begin(); iter != charlist.end(); iter++){
profile = *iter;
temp = profile->packet->getType_EQ2_16BitString_ByName("name");
if(strcmp(temp.data.c_str(), name)==0){
if(version <= 561) {
profile->deleted = true; // workaround for char select crash on old clients
}
else {
safe_delete(*iter);
charlist.erase(iter);
}
return;
}
}
}

View File

@ -0,0 +1,54 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef _LOGINACCOUNT_
#define _LOGINACCOUNT_
#include <iostream>
#include <string>
#include <vector>
#include "../common/linked_list.h"
#include "PacketHeaders.h"
#include "../common/PacketStruct.h"
using namespace std;
class LoginAccount {
public:
LoginAccount();
LoginAccount(int32 id, const char* in_name, const char* in_pass){
account_id = id;
strcpy(name, in_name);
strcpy(password, in_pass);
}
~LoginAccount();
bool SaveAccount(LoginAccount* acct);
vector<CharSelectProfile*> charlist;
void setName(const char* in_name) { strcpy(name, in_name); }
void setPassword(const char* in_pass) { strcpy(password, in_pass); }
void setAuthenticated(bool in_auth) { authenticated=in_auth; }
void setAccountID(int32 id){ account_id = id; }
void addCharacter(CharSelectProfile* profile){
charlist.push_back(profile);
}
void removeCharacter(PacketStruct* profile);
void removeCharacter(char* name, int16 version);
void serializeCharacter(uchar* buffer, CharSelectProfile* profile);
void flushCharacters ( );
CharSelectProfile* getCharacter(char* name);
int32 getLoginAccountID(){ return account_id; }
char* getLoginName() { return name; }
char* getLoginPassword() { return password; }
bool getLoginAuthenticated() { return authenticated; }
private:
int32 account_id;
char name[32];
char password[32];
bool authenticated;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef EQ2LOGIN_EMU_DATABASE_H
#define EQ2LOGIN_EMU_DATABASE_H
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock.h>
#include <windows.h>
#endif
#include <mysql.h>
#include <string>
#include <vector>
#include "../common/database.h"
#include "../common/DatabaseNew.h"
#include "../common/types.h"
#include "../common/MiscFunctions.h"
#include "../common/servertalk.h"
#include "../common/Mutex.h"
#include "PacketHeaders.h"
#include "LoginAccount.h"
#include "LWorld.h"
#include "../common/PacketStruct.h"
using namespace std;
#pragma pack()
class LoginDatabase : public Database
{
public:
void FixBugReport();
void UpdateAccountIPAddress(int32 account_id, int32 address);
void UpdateWorldIPAddress(int32 world_id, int32 address);
void SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id);
LoginAccount* LoadAccount(const char* name, const char* password, bool attemptAccountCreation=true);
int32 GetAccountIDByName(const char* name);
int32 CheckServerAccount(char* name, char* passwd);
bool IsServerAccountDisabled(char* name);
bool IsIPBanned(char* ipaddr);
void GetServerAccounts(vector<LWorld*>* server_list);
char* GetServerAccountName(int32 id);
bool VerifyDelete(int32 account_id, int32 character_id, const char* name);
void SetServerZoneDescriptions(int32 server_id, map<int32, LoginZoneUpdate> zone_descriptions);
int32 GetServer(int32 accountID, int32 charID, string name);
void LoadCharacters(LoginAccount* acct, int16 version);
void CheckCharacterTimeStamps(LoginAccount* acct);
string GetCharacterName(int32 char_id , int32 server_id, int32 account_id);
void SaveCharacterColors(int32 char_id, char* type, EQ2_Color color);
void SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3, float multiplier=100.0f);
int16 GetAppearanceID(string name);
void DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id);
int32 SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version);
void LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet);
bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id);
bool UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id);
bool UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id);
bool UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id);
bool UpdateCharacterName(int32 account_id, int32 character_id, char* newName, int32 server_id);
bool UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id);
bool UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id);
int32 GetRaceID(char* name);
void UpdateRaceID(char* name);
bool DeleteCharacter(int32 account_id, int32 character_id, int32 server_id);
void SaveClientLog(const char* type, const char* message, const char* player_name, int16 version);
bool CheckVersion(char* version);
void GetLatestTableVersions(LatestTableVersions* table_versions);
TableQuery* GetLatestTableQuery(int32 server_ip, char* name, int16 version);
bool VerifyDataTable(char* name);
sint16 GetDataVersion(char* name);
void SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet);
string GetZoneDescription(char* name);
string GetColumnNames(char* name);
TableDataQuery* GetTableDataQuery(int32 server_ip, char* name, int16 version);
void UpdateWorldServerStats( LWorld* world, sint32 status);
bool ResetWorldServerStatsConnectedTime( LWorld* world );
void RemoveOldWorldServerStats();
void ResetWorldStats();
//devn00b temp
bool ConnectNewDatabase();
void SetServerEquipmentAppearances(int32 server_id, map<int32, LoginEquipmentUpdate> equip_updates); // JohnAdams: login appearances
int32 GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id); // JohnAdams: login appearances
void RemoveDeletedCharacterData();
int8 GetMaxCharsSetting();
int16 GetAccountBonus(int32 acct_id);
void UpdateWorldVersion(int32 world_id, char* version);
void UpdateAccountClientDataVersion(int32 account_id, int16 version);
void SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture);
DatabaseNew dbLogin;
};
#endif

View File

@ -0,0 +1,88 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#include "PacketHeaders.h"
#include "../common/MiscFunctions.h"
#include "LoginDatabase.h"
#include "LWorld.h"
extern LWorldList world_list;
extern LoginDatabase database;
void LS_DeleteCharacterRequest::loadData(EQApplicationPacket* packet){
InitializeLoadData(packet->pBuffer, packet->size);
LoadData(character_number);
LoadData(server_id);
LoadData(spacer);
LoadDataString(name);
}
EQ2Packet* LS_CharSelectList::serialize(int16 version){
Clear();
AddData(num_characters);
AddData(char_data);
if (version <= 561) {
LS_CharListAccountInfoEarlyClient account_info;
account_info.account_id = account_id;
account_info.unknown1 = 0xFFFFFFFF;
account_info.unknown2 = 0;
account_info.maxchars = 7; //live has a max of 7 on gold accounts base.
account_info.unknown4 = 0;
AddData(account_info);
}
else {
LS_CharListAccountInfo account_info;
account_info.account_id = account_id;
account_info.unknown1 = 0xFFFFFFFF;
account_info.unknown2 = 0;
account_info.maxchars = database.GetMaxCharsSetting();
account_info.vet_adv_bonus = database.GetAccountBonus(account_id);
account_info.vet_trade_bonus = 0;
account_info.unknown4 = 0;
for (int i = 0; i < 3; i++)
account_info.unknown5[i] = 0xFFFFFFFF;
account_info.unknown5[3] = 0;
AddData(account_info);
}
return new EQ2Packet(OP_AllCharactersDescReplyMsg, getData(), getDataSize());
}
void LS_CharSelectList::addChar(uchar* data, int16 size){
char_data.append((char*)data, size);
}
void LS_CharSelectList::loadData(int32 account, vector<CharSelectProfile*> charlist, int16 version){
vector<CharSelectProfile*>::iterator itr;
account_id = account;
num_characters = 0;
char_data = "";
CharSelectProfile* character = 0;
for(itr = charlist.begin();itr != charlist.end();itr++){
character = *itr;
int32 serverID = character->packet->getType_int32_ByName("server_id");
if(character->deleted) { // workaround for old clients <= 561 that crash if you delete a char (Doesn't refresh the char panel correctly)
character->packet->setDataByName("name", "(deleted)");
character->packet->setDataByName("charid", 0xFFFFFFFF);
character->packet->setDataByName("name", 0xFFFFFFFF);
character->packet->setDataByName("server_id", 0xFFFFFFFF);
character->packet->setDataByName("created_date", 0xFFFFFFFF);
character->packet->setDataByName("unknown1", 0xFFFFFFFF);
character->packet->setDataByName("unknown2", 0xFFFFFFFF);
character->packet->setDataByName("flags", 0xFF);
}
else if(serverID == 0 || !world_list.FindByID(serverID))
continue;
num_characters++;
character->SaveData(version);
addChar(character->getData(), character->getDataSize());
}
}
void CharSelectProfile::SaveData(int16 in_version){
Clear();
AddData(*packet->serializeString());
}

View File

@ -0,0 +1,61 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef __PACKET_HEADERS__
#define __PACKET_HEADERS__
#include "../common/types.h"
#include "../common/EQPacket.h"
#include "../common/EQ2_Common_Structs.h"
#include "login_structs.h"
#include "../common/DataBuffer.h"
#include "../common/GlobalHeaders.h"
#include "../common/ConfigReader.h"
#include <vector>
extern ConfigReader configReader;
class CharSelectProfile : public DataBuffer{
public:
CharSelectProfile(int16 version){
deleted = false;
packet = configReader.getStruct("CharSelectProfile",version);
for(int8 i=0;i<24;i++){
packet->setEquipmentByName("equip",0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,i);
}
}
~CharSelectProfile(){
safe_delete(packet);
}
PacketStruct* packet;
void SaveData(int16 in_version);
void Data();
int16 size;
bool deleted;
};
class LS_CharSelectList : public DataBuffer {
public:
int8 num_characters;
int32 account_id;
EQ2Packet* serialize(int16 version);
void addChar(uchar* data, int16 size);
string char_data;
void loadData(int32 account, vector<CharSelectProfile*> charlist, int16 version);
};
class LS_DeleteCharacterRequest : public DataBuffer{
public:
int32 character_number;
int32 server_id;
int32 spacer;
EQ2_16BitString name;
void loadData(EQApplicationPacket* packet);
};
#endif

View File

@ -0,0 +1,67 @@
#include "../net.h"
#include "../LWorld.h"
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
extern ClientList client_list;
extern LWorldList world_list;
extern NetConnection net;
void NetConnection::Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
res.set(http::field::content_type, "application/json");
boost::property_tree::ptree pt;
pt.put("web_status", "online");
pt.put("login_status", net.login_running ? "online" : "offline");
pt.put("login_uptime", (getCurrentTimestamp() - net.login_uptime));
auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - net.login_uptime));
std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds));
pt.put("login_uptime_string", uptime_str);
pt.put("world_count", world_list.GetCount(ConType::World));
pt.put("client_count", net.numclients);
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
void NetConnection::Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
world_list.PopulateWorldList(res);
}
void LWorldList::PopulateWorldList(http::response<http::string_body>& res) {
struct in_addr in;
res.set(http::field::content_type, "application/json");
boost::property_tree::ptree maintree;
std::ostringstream oss;
map<int32,LWorld*>::iterator map_list;
for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) {
LWorld* world = map_list->second;
in.s_addr = world->GetIP();
if (world->GetType() == World) {
boost::property_tree::ptree pt;
pt.put("id", world->GetID());
pt.put("world_name", world->GetName());
pt.put("status", (world->GetStatus() == 1) ? "online" : "offline");
pt.put("num_players", world->GetPlayerNum());
pt.put("ip_addr", inet_ntoa(in));
maintree.push_back(std::make_pair("", pt));
}
}
boost::property_tree::ptree result;
result.add_child("WorldServers", maintree);
boost::property_tree::write_json(oss, result);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}

813
old/LoginServer/client.cpp Normal file
View File

@ -0,0 +1,813 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#include "../common/debug.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock.h>
#include <process.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include <assert.h>
#include "net.h"
#include "client.h"
#include "../common/EQStream.h"
#include "../common/packet_dump.h"
#include "../common/packet_functions.h"
#include "../common/emu_opcodes.h"
#include "../common/MiscFunctions.h"
#include "LWorld.h"
#include "LoginDatabase.h"
#include "../common/ConfigReader.h"
#include "../common/Log.h"
extern NetConnection net;
extern LWorldList world_list;
extern ClientList client_list;
extern LoginDatabase database;
extern map<int16,OpcodeManager*>EQOpcodeManager;
extern ConfigReader configReader;
using namespace std;
Client::Client(EQStream* ieqnc) {
eqnc = ieqnc;
ip = eqnc->GetrIP();
port = ntohs(eqnc->GetrPort());
account_id = 0;
lsadmin = 0;
worldadmin = 0;
lsstatus = 0;
version = 0;
kicked = false;
verified = false;
memset(bannedreason, 0, sizeof(bannedreason));
//worldresponse_timer = new Timer(10000);
//worldresponse_timer->Disable();
memset(key,0,10);
LoginMode = None;
num_updates = 0;
updatetimer = new Timer(500);
updatelisttimer = new Timer(10000);
//keepalive = new Timer(5000);
//logintimer = new Timer(500); // Give time for the servers to send updates
//keepalive->Start();
//updatetimer->Start();
//logintimer->Disable();
disconnectTimer = 0;
memset(ClientSession,0,25);
request_num = 0;
login_account = 0;
createRequest = 0;
playWaitTimer = NULL;
start = false;
update_position = 0;
update_packets = 0;
needs_world_list = true;
sent_character_list = false;
}
Client::~Client() {
//safe_delete(worldresponse_timer);
//safe_delete(logintimer);
safe_delete(login_account);
eqnc->Close();
safe_delete(playWaitTimer);
safe_delete(createRequest);
safe_delete(disconnectTimer);
safe_delete(updatetimer);
}
bool Client::Process() {
if(!start && !eqnc->CheckActive()){
if(!playWaitTimer)
playWaitTimer = new Timer(5000);
else if(playWaitTimer->Check()){
safe_delete(playWaitTimer);
return false;
}
return true;
}
else if(!start){
safe_delete(playWaitTimer);
start = true;
}
if (disconnectTimer && disconnectTimer->Check())
{
safe_delete(disconnectTimer);
getConnection()->SendDisconnect();
}
if (!kicked) {
/************ Get all packets from packet manager out queue and process them ************/
EQApplicationPacket *app = 0;
/*if(logintimer && logintimer->Check())
{
database.LoadCharacters(GetLoginAccount());
SendLoginAccepted();
logintimer->Disable();
}*/
/*if(worldresponse_timer && worldresponse_timer->Check())
{
FatalError(WorldDownErrorMessage);
worldresponse_timer->Disable();
}*/
if(playWaitTimer != NULL && playWaitTimer->Check ( ) )
{
SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT);
safe_delete(playWaitTimer);
}
if(!needs_world_list && updatetimer && updatetimer->Check()){
if(updatelisttimer && updatelisttimer->Check()){
if(num_updates >= 180){ //30 minutes
getConnection()->SendDisconnect();
}
else{
vector<PacketStruct*>::iterator itr;
if(update_packets){
for(itr = update_packets->begin(); itr != update_packets->end(); itr++){
safe_delete(*itr);
}
}
safe_delete(update_packets);
update_packets = world_list.GetServerListUpdate(version);
}
num_updates++;
}
else{
if(!update_packets){
update_packets = world_list.GetServerListUpdate(version);
}
else{
if(update_position < update_packets->size()){
QueuePacket(update_packets->at(update_position)->serialize());
update_position++;
}
else
update_position = 0;
}
}
}
while(app = eqnc->PopPacket())
{
switch(app->GetOpcode())
{
case OP_LoginRequestMsg:{
DumpPacket(app);
PacketStruct* packet = configReader.getStruct("LS_LoginRequest", 1);
if(packet && packet->LoadPacketData(app->pBuffer,app->size)){
version = packet->getType_int16_ByName("version");
LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version);
if (version == 0 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0)
{
safe_delete(packet);
packet = configReader.getStruct("LS_LoginRequest", 1208);
if (packet && packet->LoadPacketData(app->pBuffer, app->size)) {
version = packet->getType_int16_ByName("version");
}
else
break;
}
//[7:19 PM] Kirmmin: Well, I very quickly learned that unknown3 in LS_LoginRequest packet is the same value as cl_eqversion in the eq2_defaults.ini file.
LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version);
if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version);
SendLoginDenied();
return false;
}
if(EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()){
getConnection()->SetClientVersion(GetVersion());
EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username");
EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password");
LoginAccount* acct = database.LoadAccount(username.data.c_str(),password.data.c_str(), net.IsAllowingAccountCreation());
if(acct){
Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID());
if(otherclient)
otherclient->getConnection()->SendDisconnect(); // This person is already logged in, we don't want them logged in twice, kick the previous client as it might be a ghost
}
if(acct){
SetAccountName(username.data.c_str());
database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP());
database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version);
LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", (char*)username.data.c_str());
}
else
{
if (username.size > 0)
LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", (char*)username.data.c_str());
else
LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!");
}
if(!acct)
SendLoginDenied();
else{
needs_world_list = true;
SetLoginAccount(acct);
SendLoginAccepted();
}
}
else{
cout << "Error bad version: " << version << endl;
SendLoginDeniedBadVersion();
}
}
else{
cout << "Error loading LS_LoginRequest packet: \n";
//DumpPacket(app);
}
safe_delete(packet);
break;
}
case OP_KeymapLoadMsg:{
// cout << "Received OP_KeymapNoneMsg\n";
//dunno what this is for
break;
}
case OP_AllWSDescRequestMsg:{
SendWorldList();
needs_world_list = false;
if(!sent_character_list) {
database.LoadCharacters(GetLoginAccount(), GetVersion());
sent_character_list = true;
}
SendCharList();
break;
}
case OP_LsClientCrashlogReplyMsg:{
// DumpPacket(app);
SaveErrorsToDB(app, "Crash Log", GetVersion());
break;
}
case OP_LsClientVerifylogReplyMsg:{
// DumpPacket(app);
SaveErrorsToDB(app, "Verify Log", GetVersion());
break;
}
case OP_LsClientAlertlogReplyMsg:{
// DumpPacket(app);
SaveErrorsToDB(app, "Alert Log", GetVersion());
break;
}
case OP_LsClientBaselogReplyMsg:{
// DumpPacket(app);
SaveErrorsToDB(app, "Base Log", GetVersion());
break;
}
case OP_AllCharactersDescRequestMsg:{
break;
}
case OP_CreateCharacterRequestMsg:{
PacketStruct* packet = configReader.getStruct("CreateCharacter", GetVersion());
DumpPacket(app);
playWaitTimer = new Timer ( 15000 );
playWaitTimer->Start ( );
LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", GetAccountName());
if(packet->LoadPacketData(app->pBuffer,app->size, GetVersion() <= 561 ? false : true)){
DumpPacket(app->pBuffer, app->size);
packet->setDataByName("account_id",GetAccountID());
LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id"));
if(!world_server)
{
DumpPacket(app->pBuffer, app->size);
cout << GetAccountName() << " attempted creation of character with an invalid server id of: " << packet->getType_int32_ByName("server_id") << "\n";
break;
}
else
{
createRequest = packet;
ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, app->size+sizeof(int16));
int16 out_version = GetVersion();
memcpy(outpack->pBuffer, &out_version, sizeof(int16));
memcpy(outpack->pBuffer + sizeof(int16), app->pBuffer, app->size);
uchar* tmp = outpack->pBuffer;
if(out_version<=283)
tmp+=2;
else if(out_version == 373) {
tmp += 6;
}
else
tmp += 7;
int32 account_id = GetAccountID();
memcpy(tmp, &account_id, sizeof(int32));
world_server->SendPacket(outpack);
safe_delete(outpack);
}
}
else{
LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", GetAccountName());
safe_delete(packet);
}
// world_list.SendWorldChanged(create.profile.server_id, false, this);
break;
}
case OP_PlayCharacterRequestMsg:{
int32 char_id = 0;
int32 server_id = 0;
PacketStruct* request = configReader.getStruct("LS_PlayRequest",GetVersion());
if(request && request->LoadPacketData(app->pBuffer,app->size)){
char_id = request->getType_int32_ByName("char_id");
if (GetVersion() <= 283) {
server_id = database.GetServer(GetAccountID(), char_id, request->getType_EQ2_16BitString_ByName("name").data);
}
else {
server_id = request->getType_int32_ByName("server_id");
}
LWorld* world = world_list.FindByID(server_id);
string name = database.GetCharacterName(char_id,server_id,GetAccountID());
if(world && name.length() > 0){
pending_play_char_id = char_id;
ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct));
UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer;
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
strcpy(req->ip_address, inet_ntoa(in));
world->SendPacket(outpack);
delete outpack;
safe_delete(playWaitTimer);
playWaitTimer = new Timer ( 5000 );
playWaitTimer->Start ( );
}
else{
cout << GetAccountName() << " sent invalid Play Request: \n";
SendPlayFailed(PLAY_ERROR_PROBLEM);
DumpPacket(app);
}
}
safe_delete(request);
break;
}
case OP_DeleteCharacterRequestMsg:{
PacketStruct* request = configReader.getStruct("LS_DeleteCharacterRequest", GetVersion());
PacketStruct* response = configReader.getStruct("LS_DeleteCharacterResponse", GetVersion());
if(request && response && request->LoadPacketData(app->pBuffer,app->size)){
EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name");
int32 acct_id = GetAccountID();
int32 char_id = request->getType_int32_ByName("char_id");
int32 server_id = request->getType_int32_ByName("server_id");
if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){
response->setDataByName("response", 1);
GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion());
LWorld* world_server = world_list.FindByID(server_id);
if(world_server != NULL)
world_server->SendDeleteCharacter ( char_id , acct_id );
}
else
response->setDataByName("response", 0);
response->setDataByName("server_id", server_id);
response->setDataByName("char_id", char_id);
response->setDataByName("account_id", account_id);
response->setMediumStringByName("name", (char*)name.data.c_str());
response->setDataByName("max_characters", 10);
EQ2Packet* outapp = response->serialize();
QueuePacket(outapp);
this->SendCharList();
}
safe_delete(request);
safe_delete(response);
break;
}
default: {
const char* name = app->GetOpcodeName();
if (name)
LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode());
else
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode());
}
}
delete app;
}
}
if (!eqnc->CheckActive()) {
return false;
}
return true;
}
void Client::SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version){
int32 size = 0;
z_stream zstream;
if (version >= 546) {
memcpy(&size, app->pBuffer + sizeof(int32), sizeof(int32));
zstream.next_in = app->pBuffer + 8;
zstream.avail_in = app->size - 8;
}
else { //box set
size = 0xFFFF;
zstream.next_in = app->pBuffer + 2;
zstream.avail_in = app->size - 2;
}
size++;
char* message = new char[size];
memset(message, 0, size);
int zerror = 0;
zstream.next_out = (BYTE*)message;
zstream.avail_out = size;
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
zerror = inflateInit( &zstream);
if(zerror != Z_OK) {
safe_delete_array(message);
return;
}
zerror = inflate( &zstream, 0 );
if(message && strlen(message) > 0)
database.SaveClientLog(type, message, GetLoginAccount()->getLoginName(), GetVersion());
safe_delete_array(message);
}
void Client::CharacterApproved(int32 server_id,int32 char_id)
{
if(createRequest && server_id == createRequest->getType_int32_ByName("server_id")){
LWorld* world_server = world_list.FindByID(server_id);
if(!world_server)
return;
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(packet){
packet->setDataByName("account_id", GetAccountID());
packet->setDataByName("unknown", 0xFFFFFFFF);
packet->setDataByName("response", CREATESUCCESS_REPLY);
packet->setMediumStringByName("name", (char*)createRequest->getType_EQ2_16BitString_ByName("name").data.c_str());
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
database.SaveCharacter(createRequest, GetLoginAccount(),char_id, GetVersion());
// refresh characters for this account
database.LoadCharacters(GetLoginAccount(), GetVersion());
SendCharList();
if (GetVersion() <= 561)
{
pending_play_char_id = char_id;
ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct));
UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer;
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
strcpy(req->ip_address, inet_ntoa(in));
world_server->SendPacket(outpack);
delete outpack;
}
}
}
else{
cout << GetAccountName() << " received invalid CharacterApproval from server: " << server_id << endl;
}
safe_delete(createRequest);
}
void Client::CharacterRejected(int8 reason_number)
{
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(createRequest && packet){
packet->setDataByName("account_id", GetAccountID());
int8 clientReasonNum = reason_number;
// reason numbers change and instead of updating the world server
// the login server will hold the up to date #'s
/*
switch(reason_number)
{
// these error codes seem to be removed now, they shutdown the client rather immediately
// for now we are just going to play a joke on them and say they can't create a new character.
case INVALIDRACE_REPLY:
case INVALIDGENDER_REPLY:
clientReasonNum = 8;
break;
case BADNAMELENGTH_REPLY:
clientReasonNum = 9;
break;
case NAMEINVALID_REPLY:
clientReasonNum = 10;
break;
case NAMEFILTER_REPLY:
clientReasonNum = 11;
break;
case NAMETAKEN_REPLY:
clientReasonNum = 12;
break;
case OVERLOADEDSERVER_REPLY:
clientReasonNum = 13;
break;
}
*/
packet->setDataByName("response", clientReasonNum);
packet->setMediumStringByName("name", "");
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
/*LS_CreateCharacterReply reply(GetAccountID(), reason_number, create.profile.name.data);
EQ2Packet* outapp = reply.serialize();
QueuePacket(outapp);
create.Clear();*/
}
void Client::SendCharList(){
/*PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply");
packet->setDataByName("account_id", GetAccountID());
packet->setDataByName("response", reason_number);
packet->setDataByName("name", &create.profile.name);
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);*/
LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName());
LS_CharSelectList list;
list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion());
EQ2Packet* outapp = list.serialize(GetVersion());
DumpPacket(outapp->pBuffer, outapp->size);
QueuePacket(outapp);
}
void Client::SendLoginDeniedBadVersion(){
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 6;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
void Client::SendLoginDenied(){
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 1;
// reply_codes for AoM:
/* 1 = Login rejected: Invalid username or password. Please try again.
2 = Login rejected: Server thinks your account is currently playing; you may have to wait "
"a few minutes for it to clear, then try again
6 = Login rejected: The client's version does not match the server's. Please re-run the patcher.
7 = Login rejected: You have no scheduled playtimes.
8 = Your account does not have the features required to play on this server.
11 = The client's build does not match the server's. Please re-run the patcher.
12 = You must update your password in order to log in. Pressing OK will op"
"en your web browser to the SOE password management page
Other Value > 1 = Login rejected for an unknown reason.
*/
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
void Client::SendLoginAccepted(int32 account_id, int8 login_response) {
PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion());
int i = 0;
if (packet)
{
packet->setDataByName("account_id", account_id);
packet->setDataByName("login_response", login_response);
packet->setDataByName("do_not_force_soga", 1);
// sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership
// sub_level > 0 = class alignments still required, but portraits are viewable and race selectable
// sub_level = 2 membership, you can 'create characters on time locked servers' vs standard
// sub_level = 0 forces popup on close to web browser
packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
packet->setDataByName("race_flag", 0x1FFFFF);
packet->setDataByName("class_flag", 0x7FFFFFE);
packet->setMediumStringByName("username", GetAccountName());
packet->setMediumStringByName("password", GetAccountName());
// unknown5
// full support = 0x7CFF
// 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city
// 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city
packet->setDataByName("unknown5", net.GetExpansionFlag());
packet->setDataByName("unknown6", 0xFF);
packet->setDataByName("unknown6", 0xFF, 1);
packet->setDataByName("unknown6", 0xFF, 2);
// controls class access / playable characters
packet->setDataByName("unknown10", 0xFF);
// packet->setDataByName("unknown7a", 0x0101);
// packet->setDataByName("race_unknown", 0x01);
packet->setDataByName("unknown7", net.GetEnabledRaces()); // 0x01-0xFF disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits
packet->setDataByName("unknown7a", 0xEE);
packet->setDataByName("unknown8", net.GetCitiesFlag(), 1); // dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas)
/*
1 = city of qeynos
2 = city of freeport
4 = city of kelethin
8 = city of neriak
16 = gorowyn
32 = new halas
64 = queens colony
128 = outpost overlord
*/
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
void Client::SendWorldList(){
EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin, version);
EQ2Packet* dupe = pack->Copy();
DumpPacket(dupe->pBuffer,dupe->size);
QueuePacket(dupe);
SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
return;
}
void Client::QueuePacket(EQ2Packet* app){
eqnc->EQ2QueuePacket(app);
}
void Client::WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key)
{
LWorld* world = world_list.FindByID(worldid);
if(world == 0) {
FatalError(0);
return;
}
if(response != 1){
if(response == PLAY_ERROR_CHAR_NOT_LOADED){
string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID());
if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())){
GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion());
}
}
FatalError(response);
return;
}
PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion());
if(response_packet){
safe_delete(playWaitTimer);
response_packet->setDataByName("response", 1);
response_packet->setSmallStringByName("server", ip_address);
response_packet->setDataByName("port", port);
response_packet->setDataByName("account_id", GetAccountID());
response_packet->setDataByName("access_code", access_key);
EQ2Packet* outapp = response_packet->serialize();
QueuePacket(outapp);
safe_delete(response_packet);
}
return;
}
void Client::FatalError(int8 response) {
safe_delete(playWaitTimer);
SendPlayFailed(response);
}
void Client::SendPlayFailed(int8 response){
PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion());
if(response_packet){
response_packet->setDataByName("response", response);
response_packet->setSmallStringByName("server", "");
response_packet->setDataByName("port", 0);
response_packet->setDataByName("account_id", GetAccountID());
response_packet->setDataByName("access_code", 0);
EQ2Packet* outapp = response_packet->serialize();
QueuePacket(outapp);
safe_delete(response_packet);
}
}
void ClientList::Add(Client* client) {
MClientList.writelock();
client_list[client] = true;
MClientList.releasewritelock();
}
Client* ClientList::Get(int32 ip, int16 port) {
Client* ret = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++){
if(itr->first->GetIP() == ip && itr->first->GetPort() == port){
ret = itr->first;
break;
}
}
MClientList.releasereadlock();
return ret;
}
void ClientList::FindByCreateRequest(){
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++){
if(itr->first->AwaitingCharCreationRequest()){
if(!client)
client = itr->first;
else{
client = 0;//more than 1 character waiting, dont want to send rejection to wrong one
break;
}
}
}
MClientList.releasereadlock();
if(client)
client->CharacterRejected(UNKNOWNERROR_REPLY);
}
Client* ClientList::FindByLSID(int32 lsaccountid) {
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++){
if(itr->first->GetAccountID() == lsaccountid){
client = itr->first;
break;
}
}
MClientList.releasereadlock();
return client;
}
void ClientList::SendPacketToAllClients(EQ2Packet* app){
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
if(client_list.size() > 0){
for(itr = client_list.begin(); itr != client_list.end(); itr++){
itr->first->QueuePacket(app->Copy());
}
}
safe_delete(app);
MClientList.releasereadlock();
}
void ClientList::Process() {
Client* client = 0;
vector<Client*> erase_list;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++){
client = itr->first;
if(!client->Process())
erase_list.push_back(client);
}
MClientList.releasereadlock();
if(erase_list.size() > 0){
vector<Client*>::iterator erase_itr;
MClientList.writelock();
for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++){
client = *erase_itr;
struct in_addr in;
in.s_addr = client->getConnection()->GetRemoteIP();
net.numclients--;
LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName());
client->getConnection()->Close();
net.UpdateWindowTitle();
client_list.erase(client);
}
MClientList.releasewritelock();
}
}
void Client::StartDisconnectTimer() {
if (!disconnectTimer)
{
disconnectTimer = new Timer(1000);
disconnectTimer->Start();
}
}

131
old/LoginServer/client.h Normal file
View File

@ -0,0 +1,131 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef CLIENT_H
#define CLIENT_H
#include "../common/linked_list.h"
#include "../common/timer.h"
#include "../common/TCPConnection.h"
#include "login_structs.h"
#include "LoginAccount.h"
#include "../common/PacketStruct.h"
#include <string>
#include <vector>
enum eLoginMode { None, Normal, Registration };
class DelayQue;
class Client
{
public:
Client(EQStream* ieqnc);
~Client();
void SendLoginDenied();
void SendLoginDeniedBadVersion();
void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0);
void SendWorldList();
void SendCharList();
int16 AddWorldToList2(uchar* buffer, char* name, int32 id, int16* flags);
void GenerateChecksum(EQApplicationPacket* outapp);
int8 LoginKey[10];
int8 ClientSession[25];
bool Process();
void SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version);
void CharacterApproved(int32 server_id,int32 char_id);
void CharacterRejected(int8 reason_number);
EQStream* getConnection() { return eqnc; }
LoginAccount* GetLoginAccount() { return login_account; }
void SetLoginAccount(LoginAccount* in_account) {
login_account = in_account;
if(in_account)
account_id = in_account->getLoginAccountID();
}
int16 GetVersion(){ return version; }
char* GetKey() { return key; }
void SetKey(char* in_key) { strcpy(key,in_key); }
int32 GetIP() { return ip; }
int16 GetPort() { return port; }
int32 GetAccountID() { return account_id; }
const char* GetAccountName(){ return (char*)account_name.c_str(); }
void SetAccountName(const char* name){ account_name = string(name); }
void ProcessLogin(char* name, char* pass,int seq=0);
void QueuePacket(EQ2Packet* app);
void FatalError(int8 response);
void WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key);
bool AwaitingCharCreationRequest(){
if(createRequest)
return true;
else
return false;
}
Timer* updatetimer;
Timer* updatelisttimer;
Timer* disconnectTimer;
//Timer* keepalive;
//Timer* logintimer;
int16 packettotal;
int32 requested_server_id;
int32 request_num;
LinkedList<DelayQue*> delay_que;
void SendPlayFailed(int8 response);
void StartDisconnectTimer();
private:
string pending_play_char_name;
int32 pending_play_char_id;
int8 update_position;
int16 num_updates;
vector<PacketStruct*>* update_packets;
LoginAccount* login_account;
EQStream* eqnc;
int32 ip;
int16 port;
int32 account_id;
string account_name;
char key[10];
int8 lsadmin;
sint16 worldadmin;
int lsstatus;
bool kicked;
bool verified;
bool start;
bool needs_world_list;
int16 version;
char bannedreason[30];
bool sent_character_list;
eLoginMode LoginMode;
PacketStruct* createRequest;
Timer* playWaitTimer;
};
class ClientList
{
public:
ClientList() {}
~ClientList() {}
void Add(Client* client);
Client* Get(int32 ip, int16 port);
Client* FindByLSID(int32 lsaccountid);
void FindByCreateRequest();
void SendPacketToAllClients(EQ2Packet* app);
void Process();
private:
Mutex MClientList;
map<Client*, bool> client_list;
};
class DelayQue {
public:
DelayQue(Timer* in_timer, EQApplicationPacket* in_packet){
timer = in_timer;
packet = in_packet;
};
Timer* timer;
EQApplicationPacket* packet;
};
#endif

View File

@ -1,851 +0,0 @@
// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team - GPL License
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <vector>
#include <map>
#include "net.hpp"
#include "lworld.hpp"
#include "login_structs.hpp"
#include "login_account.hpp"
#include "login_database.hpp"
#include "../common/tcp.hpp"
#include "../common/timer.hpp"
#include "../common/linked_list.hpp"
#include "../common/log.hpp"
#include "../common/debug.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/stream/eq_stream.hpp"
#include "../common/packet/packet_dump.hpp"
#include "../common/packet/packet_struct.hpp"
#include "../common/packet/packet_functions.hpp"
#include "../common/opcodes/emu_opcodes.hpp"
using namespace std;
enum eLoginMode { None, Normal, Registration };
extern NetConnection net;
extern LWorldList world_list;
extern ClientList client_list;
extern LoginDatabase database;
extern map<int16,OpcodeManager*> EQOpcodeManager;
extern ConfigReader configReader;
class DelayQue
{
public:
// Constructor initializes timer and packet for delayed queue processing
DelayQue(Timer* in_timer, EQApplicationPacket* in_packet)
{
timer = in_timer;
packet = in_packet;
}
Timer* timer; // Timer for delay processing
EQApplicationPacket* packet; // Packet to be processed after delay
};
class Client
{
public:
// Constructor initializes client connection and default values
Client(EQStream* ieqnc)
{
eqnc = ieqnc;
ip = eqnc->GetrIP();
port = ntohs(eqnc->GetrPort());
account_id = 0;
lsadmin = 0;
worldadmin = 0;
lsstatus = 0;
version = 0;
kicked = false;
verified = false;
memset(bannedreason, 0, sizeof(bannedreason));
memset(key,0,10);
LoginMode = None;
num_updates = 0;
updatetimer = new Timer(500);
updatelisttimer = new Timer(10000);
disconnectTimer = 0;
memset(ClientSession,0,25);
request_num = 0;
login_account = 0;
createRequest = 0;
playWaitTimer = NULL;
start = false;
update_position = 0;
update_packets = 0;
needs_world_list = true;
sent_character_list = false;
}
// Destructor cleans up allocated resources and closes connection
~Client()
{
safe_delete(login_account);
eqnc->Close();
safe_delete(playWaitTimer);
safe_delete(createRequest);
safe_delete(disconnectTimer);
safe_delete(updatetimer);
}
// Main processing loop for client connections and packet handling
bool Process()
{
if(!start && !eqnc->CheckActive()) {
if(!playWaitTimer)
playWaitTimer = new Timer(5000);
else if(playWaitTimer->Check()) {
safe_delete(playWaitTimer);
return false;
}
return true;
}
else if(!start) {
safe_delete(playWaitTimer);
start = true;
}
if(disconnectTimer && disconnectTimer->Check()) {
safe_delete(disconnectTimer);
getConnection()->SendDisconnect();
}
if(!kicked) {
EQApplicationPacket *app = 0;
if(playWaitTimer != NULL && playWaitTimer->Check()) {
SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT);
safe_delete(playWaitTimer);
}
if(!needs_world_list && updatetimer && updatetimer->Check()) {
if(updatelisttimer && updatelisttimer->Check()) {
if(num_updates >= 180) { // 30 minutes
getConnection()->SendDisconnect();
}
else {
vector<PacketStruct*>::iterator itr;
if(update_packets) {
for(itr = update_packets->begin(); itr != update_packets->end(); itr++) {
safe_delete(*itr);
}
}
safe_delete(update_packets);
update_packets = world_list.GetServerListUpdate(version);
}
num_updates++;
}
else {
if(!update_packets) {
update_packets = world_list.GetServerListUpdate(version);
}
else {
if(update_position < update_packets->size()) {
QueuePacket(update_packets->at(update_position)->serialize());
update_position++;
}
else
update_position = 0;
}
}
}
while(app = eqnc->PopPacket()) {
switch(app->GetOpcode()) {
case OP_LoginRequestMsg:
HandleLoginRequest(app);
break;
case OP_KeymapLoadMsg:
// Keymap message - currently unused
break;
case OP_AllWSDescRequestMsg:
HandleWorldServerListRequest();
break;
case OP_LsClientCrashlogReplyMsg:
SaveErrorsToDB(app, "Crash Log", GetVersion());
break;
case OP_LsClientVerifylogReplyMsg:
SaveErrorsToDB(app, "Verify Log", GetVersion());
break;
case OP_LsClientAlertlogReplyMsg:
SaveErrorsToDB(app, "Alert Log", GetVersion());
break;
case OP_LsClientBaselogReplyMsg:
SaveErrorsToDB(app, "Base Log", GetVersion());
break;
case OP_AllCharactersDescRequestMsg:
break;
case OP_CreateCharacterRequestMsg:
HandleCreateCharacterRequest(app);
break;
case OP_PlayCharacterRequestMsg:
HandlePlayCharacterRequest(app);
break;
case OP_DeleteCharacterRequestMsg:
HandleDeleteCharacterRequest(app);
break;
default:
HandleUnknownOpcode(app);
}
delete app;
}
}
if(!eqnc->CheckActive()) {
return false;
}
return true;
}
// Handles login request packets and validates credentials
void HandleLoginRequest(EQApplicationPacket* app)
{
DumpPacket(app);
PacketStruct* packet = configReader.getStruct("LS_LoginRequest", 1);
if(packet && packet->LoadPacketData(app->pBuffer,app->size)) {
version = packet->getType_int16_ByName("version");
LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version);
if(version == 0 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
safe_delete(packet);
packet = configReader.getStruct("LS_LoginRequest", 1208);
if(packet && packet->LoadPacketData(app->pBuffer, app->size)) {
version = packet->getType_int16_ByName("version");
}
else
return;
}
LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version);
if(EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version);
SendLoginDenied();
safe_delete(packet);
return;
}
if(EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()) {
getConnection()->SetClientVersion(GetVersion());
EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username");
EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password");
LoginAccount* acct = database.LoadAccount(username.data.c_str(),password.data.c_str(), net.IsAllowingAccountCreation());
if(acct) {
Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID());
if(otherclient)
otherclient->getConnection()->SendDisconnect(); // Prevent double login
}
if(acct) {
SetAccountName(username.data.c_str());
database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP());
database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version);
LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", (char*)username.data.c_str());
}
else {
if(username.size > 0)
LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", (char*)username.data.c_str());
else
LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!");
}
if(!acct)
SendLoginDenied();
else {
needs_world_list = true;
SetLoginAccount(acct);
SendLoginAccepted();
}
}
else {
cout << "Error bad version: " << version << endl;
SendLoginDeniedBadVersion();
}
}
else {
cout << "Error loading LS_LoginRequest packet: \n";
}
safe_delete(packet);
}
// Handles world server list request and sends character list
void HandleWorldServerListRequest()
{
SendWorldList();
needs_world_list = false;
if(!sent_character_list) {
database.LoadCharacters(GetLoginAccount(), GetVersion());
sent_character_list = true;
}
SendCharList();
}
// Handles character creation requests from clients
void HandleCreateCharacterRequest(EQApplicationPacket* app)
{
PacketStruct* packet = configReader.getStruct("CreateCharacter", GetVersion());
DumpPacket(app);
playWaitTimer = new Timer(15000);
playWaitTimer->Start();
LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", GetAccountName());
if(packet->LoadPacketData(app->pBuffer,app->size, GetVersion() <= 561 ? false : true)) {
DumpPacket(app->pBuffer, app->size);
packet->setDataByName("account_id",GetAccountID());
LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id"));
if(!world_server) {
DumpPacket(app->pBuffer, app->size);
cout << GetAccountName() << " attempted creation of character with an invalid server id of: " << packet->getType_int32_ByName("server_id") << "\n";
}
else {
createRequest = packet;
ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, app->size+sizeof(int16));
int16 out_version = GetVersion();
memcpy(outpack->pBuffer, &out_version, sizeof(int16));
memcpy(outpack->pBuffer + sizeof(int16), app->pBuffer, app->size);
uchar* tmp = outpack->pBuffer;
if(out_version<=283)
tmp+=2;
else if(out_version == 373) {
tmp += 6;
}
else
tmp += 7;
int32 account_id = GetAccountID();
memcpy(tmp, &account_id, sizeof(int32));
world_server->SendPacket(outpack);
safe_delete(outpack);
}
}
else {
LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", GetAccountName());
safe_delete(packet);
}
}
// Handles character play requests and forwards to world server
void HandlePlayCharacterRequest(EQApplicationPacket* app)
{
int32 char_id = 0;
int32 server_id = 0;
PacketStruct* request = configReader.getStruct("LS_PlayRequest",GetVersion());
if(request && request->LoadPacketData(app->pBuffer,app->size)) {
char_id = request->getType_int32_ByName("char_id");
if(GetVersion() <= 283) {
server_id = database.GetServer(GetAccountID(), char_id, request->getType_EQ2_16BitString_ByName("name").data);
}
else {
server_id = request->getType_int32_ByName("server_id");
}
LWorld* world = world_list.FindByID(server_id);
string name = database.GetCharacterName(char_id,server_id,GetAccountID());
if(world && name.length() > 0) {
pending_play_char_id = char_id;
ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct));
UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer;
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
strcpy(req->ip_address, inet_ntoa(in));
world->SendPacket(outpack);
delete outpack;
safe_delete(playWaitTimer);
playWaitTimer = new Timer(5000);
playWaitTimer->Start();
}
else {
cout << GetAccountName() << " sent invalid Play Request: \n";
SendPlayFailed(PLAY_ERROR_PROBLEM);
DumpPacket(app);
}
}
safe_delete(request);
}
// Handles character deletion requests
void HandleDeleteCharacterRequest(EQApplicationPacket* app)
{
PacketStruct* request = configReader.getStruct("LS_DeleteCharacterRequest", GetVersion());
PacketStruct* response = configReader.getStruct("LS_DeleteCharacterResponse", GetVersion());
if(request && response && request->LoadPacketData(app->pBuffer,app->size)) {
EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name");
int32 acct_id = GetAccountID();
int32 char_id = request->getType_int32_ByName("char_id");
int32 server_id = request->getType_int32_ByName("server_id");
if(database.VerifyDelete(acct_id, char_id, name.data.c_str())) {
response->setDataByName("response", 1);
GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion());
LWorld* world_server = world_list.FindByID(server_id);
if(world_server != NULL)
world_server->SendDeleteCharacter(char_id, acct_id);
}
else
response->setDataByName("response", 0);
response->setDataByName("server_id", server_id);
response->setDataByName("char_id", char_id);
response->setDataByName("account_id", account_id);
response->setMediumStringByName("name", (char*)name.data.c_str());
response->setDataByName("max_characters", 10);
EQ2Packet* outapp = response->serialize();
QueuePacket(outapp);
this->SendCharList();
}
safe_delete(request);
safe_delete(response);
}
// Handles unknown opcodes and logs them for debugging
void HandleUnknownOpcode(EQApplicationPacket* app)
{
const char* name = app->GetOpcodeName();
if(name)
LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode());
else
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode());
}
// Saves client error logs to database after decompression
void SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version)
{
int32 size = 0;
z_stream zstream;
if(version >= 546) {
memcpy(&size, app->pBuffer + sizeof(int32), sizeof(int32));
zstream.next_in = app->pBuffer + 8;
zstream.avail_in = app->size - 8;
}
else { // box set
size = 0xFFFF;
zstream.next_in = app->pBuffer + 2;
zstream.avail_in = app->size - 2;
}
size++;
char* message = new char[size];
memset(message, 0, size);
int zerror = 0;
zstream.next_out = (BYTE*)message;
zstream.avail_out = size;
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
zerror = inflateInit(&zstream);
if(zerror != Z_OK) {
safe_delete_array(message);
return;
}
zerror = inflate(&zstream, 0);
if(message && strlen(message) > 0)
database.SaveClientLog(type, message, GetLoginAccount()->getLoginName(), GetVersion());
safe_delete_array(message);
}
// Handles character creation approval from world server
void CharacterApproved(int32 server_id,int32 char_id)
{
if(createRequest && server_id == createRequest->getType_int32_ByName("server_id")) {
LWorld* world_server = world_list.FindByID(server_id);
if(!world_server)
return;
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(packet) {
packet->setDataByName("account_id", GetAccountID());
packet->setDataByName("unknown", 0xFFFFFFFF);
packet->setDataByName("response", CREATESUCCESS_REPLY);
packet->setMediumStringByName("name", (char*)createRequest->getType_EQ2_16BitString_ByName("name").data.c_str());
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
database.SaveCharacter(createRequest, GetLoginAccount(),char_id, GetVersion());
// refresh characters for this account
database.LoadCharacters(GetLoginAccount(), GetVersion());
SendCharList();
if(GetVersion() <= 561) {
pending_play_char_id = char_id;
ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct));
UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer;
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
strcpy(req->ip_address, inet_ntoa(in));
world_server->SendPacket(outpack);
delete outpack;
}
}
}
else {
cout << GetAccountName() << " received invalid CharacterApproval from server: " << server_id << endl;
}
safe_delete(createRequest);
}
// Handles character creation rejection from world server
void CharacterRejected(int8 reason_number)
{
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(createRequest && packet) {
packet->setDataByName("account_id", GetAccountID());
int8 clientReasonNum = reason_number;
packet->setDataByName("response", clientReasonNum);
packet->setMediumStringByName("name", "");
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
// Sends character list to client
void SendCharList()
{
LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName());
LS_CharSelectList list;
list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion());
EQ2Packet* outapp = list.serialize(GetVersion());
DumpPacket(outapp->pBuffer, outapp->size);
QueuePacket(outapp);
}
// Sends login denied response with bad version error
void SendLoginDeniedBadVersion()
{
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 6;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
// Sends login denied response for invalid credentials
void SendLoginDenied()
{
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 1;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
// Sends login accepted response with account details
void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0)
{
PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion());
if(packet) {
packet->setDataByName("account_id", account_id);
packet->setDataByName("login_response", login_response);
packet->setDataByName("do_not_force_soga", 1);
packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
packet->setDataByName("race_flag", 0x1FFFFF);
packet->setDataByName("class_flag", 0x7FFFFFE);
packet->setMediumStringByName("username", GetAccountName());
packet->setMediumStringByName("password", GetAccountName());
packet->setDataByName("unknown5", net.GetExpansionFlag());
packet->setDataByName("unknown6", 0xFF);
packet->setDataByName("unknown6", 0xFF, 1);
packet->setDataByName("unknown6", 0xFF, 2);
packet->setDataByName("unknown10", 0xFF);
packet->setDataByName("unknown7", net.GetEnabledRaces());
packet->setDataByName("unknown7a", 0xEE);
packet->setDataByName("unknown8", net.GetCitiesFlag(), 1);
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
// Sends world server list to client
void SendWorldList()
{
EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin, version);
EQ2Packet* dupe = pack->Copy();
DumpPacket(dupe->pBuffer,dupe->size);
QueuePacket(dupe);
SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
}
// Queues packet for transmission to client
void QueuePacket(EQ2Packet* app)
{
eqnc->EQ2QueuePacket(app);
}
// Handles world server response for character play requests
void WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key)
{
LWorld* world = world_list.FindByID(worldid);
if(world == 0) {
FatalError(0);
return;
}
if(response != 1) {
if(response == PLAY_ERROR_CHAR_NOT_LOADED) {
string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID());
if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())) {
GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion());
}
}
FatalError(response);
return;
}
PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion());
if(response_packet) {
safe_delete(playWaitTimer);
response_packet->setDataByName("response", 1);
response_packet->setSmallStringByName("server", ip_address);
response_packet->setDataByName("port", port);
response_packet->setDataByName("account_id", GetAccountID());
response_packet->setDataByName("access_code", access_key);
EQ2Packet* outapp = response_packet->serialize();
QueuePacket(outapp);
safe_delete(response_packet);
}
}
// Handles fatal errors and sends play failed response
void FatalError(int8 response)
{
safe_delete(playWaitTimer);
SendPlayFailed(response);
}
// Sends play failed response to client
void SendPlayFailed(int8 response)
{
PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion());
if(response_packet) {
response_packet->setDataByName("response", response);
response_packet->setSmallStringByName("server", "");
response_packet->setDataByName("port", 0);
response_packet->setDataByName("account_id", GetAccountID());
response_packet->setDataByName("access_code", 0);
EQ2Packet* outapp = response_packet->serialize();
QueuePacket(outapp);
safe_delete(response_packet);
}
}
// Starts timer for delayed client disconnection
void StartDisconnectTimer()
{
if(!disconnectTimer) {
disconnectTimer = new Timer(1000);
disconnectTimer->Start();
}
}
// Accessor methods
EQStream* getConnection() { return eqnc; }
LoginAccount* GetLoginAccount() { return login_account; }
void SetLoginAccount(LoginAccount* in_account) {
login_account = in_account;
if(in_account)
account_id = in_account->getLoginAccountID();
}
int16 GetVersion() { return version; }
char* GetKey() { return key; }
void SetKey(char* in_key) { strcpy(key,in_key); }
int32 GetIP() { return ip; }
int16 GetPort() { return port; }
int32 GetAccountID() { return account_id; }
const char* GetAccountName() { return (char*)account_name.c_str(); }
void SetAccountName(const char* name) { account_name = string(name); }
bool AwaitingCharCreationRequest() {
if(createRequest)
return true;
else
return false;
}
// Public member variables
int8 LoginKey[10]; // Login encryption key
int8 ClientSession[25]; // Client session identifier
Timer* updatetimer; // Timer for client updates
Timer* updatelisttimer; // Timer for update list processing
Timer* disconnectTimer; // Timer for delayed disconnection
int16 packettotal; // Total packet count
int32 requested_server_id; // Server ID requested by client
int32 request_num; // Request number counter
LinkedList<DelayQue*> delay_que; // Queue for delayed packet processing
private:
string pending_play_char_name; // Character name for pending play request
int32 pending_play_char_id; // Character ID for pending play request
int8 update_position; // Position in update packet list
int16 num_updates; // Number of updates sent
vector<PacketStruct*>* update_packets; // List of update packets
LoginAccount* login_account; // Associated login account
EQStream* eqnc; // Network connection stream
int32 ip; // Client IP address
int16 port; // Client port number
int32 account_id; // Account ID
string account_name; // Account name
char key[10]; // Client authentication key
int8 lsadmin; // Login server admin level
sint16 worldadmin; // World server admin level
int lsstatus; // Login server status
bool kicked; // Whether client has been kicked
bool verified; // Whether client is verified
bool start; // Whether client has started
bool needs_world_list; // Whether client needs world list
int16 version; // Client version
char bannedreason[30]; // Reason for ban if applicable
bool sent_character_list; // Whether character list has been sent
eLoginMode LoginMode; // Current login mode
PacketStruct* createRequest; // Pending character creation request
Timer* playWaitTimer; // Timer for play request timeout
};
class ClientList
{
public:
// Constructor and destructor for client list management
ClientList() {}
~ClientList() {}
// Adds a new client to the list
void Add(Client* client)
{
MClientList.writelock();
client_list[client] = true;
MClientList.releasewritelock();
}
// Finds client by IP address and port
Client* Get(int32 ip, int16 port)
{
Client* ret = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
if(itr->first->GetIP() == ip && itr->first->GetPort() == port) {
ret = itr->first;
break;
}
}
MClientList.releasereadlock();
return ret;
}
// Finds clients waiting for character creation and handles rejections
void FindByCreateRequest()
{
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
if(itr->first->AwaitingCharCreationRequest()) {
if(!client)
client = itr->first;
else {
client = 0; // more than 1 character waiting, dont want to send rejection to wrong one
break;
}
}
}
MClientList.releasereadlock();
if(client)
client->CharacterRejected(UNKNOWNERROR_REPLY);
}
// Finds client by login server account ID
Client* FindByLSID(int32 lsaccountid)
{
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
if(itr->first->GetAccountID() == lsaccountid) {
client = itr->first;
break;
}
}
MClientList.releasereadlock();
return client;
}
// Sends packet to all connected clients
void SendPacketToAllClients(EQ2Packet* app)
{
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
if(client_list.size() > 0) {
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
itr->first->QueuePacket(app->Copy());
}
}
safe_delete(app);
MClientList.releasereadlock();
}
// Processes all clients and removes inactive ones
void Process()
{
Client* client = 0;
vector<Client*> erase_list;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
client = itr->first;
if(!client->Process())
erase_list.push_back(client);
}
MClientList.releasereadlock();
if(erase_list.size() > 0) {
vector<Client*>::iterator erase_itr;
MClientList.writelock();
for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++) {
client = *erase_itr;
struct in_addr in;
in.s_addr = client->getConnection()->GetRemoteIP();
net.numclients--;
LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName());
client->getConnection()->Close();
net.UpdateWindowTitle();
client_list.erase(client);
}
MClientList.releasewritelock();
}
}
private:
Mutex MClientList; // Mutex for thread-safe client list access
map<Client*, bool> client_list; // Map of active clients
};

View File

@ -1,164 +0,0 @@
// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPL License
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <cstring>
#include "packet_headers.hpp"
#include "../common/linked_list.hpp"
#include "../common/packet/packet_struct.hpp"
using namespace std;
/**
* LoginAccount class manages user account data and associated character profiles
* Handles authentication, character management, and account operations
*/
class LoginAccount
{
public:
// Default constructor - initializes account with default values
LoginAccount();
// Parameterized constructor - creates account with specified ID, name, and password
LoginAccount(int32 id, const char* in_name, const char* in_pass);
// Destructor - cleans up all character profiles and allocated memory
~LoginAccount();
// Saves account data to persistent storage
bool SaveAccount(LoginAccount* acct);
// Vector containing all character profiles associated with this account
vector<CharSelectProfile*> charlist;
// Sets the account name from C-string input
void setName(const char* in_name) { strcpy(name, in_name); }
// Sets the account password from C-string input
void setPassword(const char* in_pass) { strcpy(password, in_pass); }
// Sets the authentication status for this account
void setAuthenticated(bool in_auth) { authenticated = in_auth; }
// Sets the unique account identifier
void setAccountID(int32 id) { account_id = id; }
// Adds a character profile to the account's character list
void addCharacter(CharSelectProfile* profile)
{
charlist.push_back(profile);
}
// Removes character by PacketStruct profile reference
void removeCharacter(PacketStruct* profile);
// Removes character by name with version-specific handling
void removeCharacter(char* name, int16 version);
// Serializes character data into provided buffer for network transmission
void serializeCharacter(uchar* buffer, CharSelectProfile* profile);
// Removes and deallocates all character profiles from the account
void flushCharacters();
// Retrieves character profile by name, returns nullptr if not found
CharSelectProfile* getCharacter(char* name);
// Returns the unique account identifier
int32 getLoginAccountID() { return account_id; }
// Returns pointer to account name string
char* getLoginName() { return name; }
// Returns pointer to account password string
char* getLoginPassword() { return password; }
// Returns current authentication status
bool getLoginAuthenticated() { return authenticated; }
private:
int32 account_id; // Unique identifier for this account
char name[32]; // Account name (limited to 31 characters + null terminator)
char password[32]; // Account password (limited to 31 characters + null terminator)
bool authenticated; // Current authentication state
};
// Default constructor implementation - initializes account with default values
inline LoginAccount::LoginAccount()
{
account_id = 0;
memset(name, 0, sizeof(name));
memset(password, 0, sizeof(password));
authenticated = false;
}
// Parameterized constructor implementation - sets up account with provided credentials
inline LoginAccount::LoginAccount(int32 id, const char* in_name, const char* in_pass)
{
account_id = id;
strcpy(name, in_name);
strcpy(password, in_pass);
authenticated = false;
}
// Destructor implementation - properly cleans up all character profiles
inline LoginAccount::~LoginAccount()
{
for(auto iter = charlist.begin(); iter != charlist.end(); iter++) {
delete(*iter); // Clean up character profile memory
}
}
// Removes and deallocates all character profiles from memory
inline void LoginAccount::flushCharacters()
{
for(auto iter = charlist.begin(); iter != charlist.end(); iter++) {
delete(*iter); // Clean up each character profile
}
charlist.clear(); // Clear the vector
}
// Searches for and returns character profile by name
inline CharSelectProfile* LoginAccount::getCharacter(char* name)
{
CharSelectProfile* profile = nullptr;
EQ2_16BitString temp;
// Iterate through character list to find matching name
for(auto char_iterator = charlist.begin(); char_iterator != charlist.end(); char_iterator++) {
profile = *char_iterator;
temp = profile->packet->getType_EQ2_16BitString_ByName("name");
if(strcmp(temp.data.c_str(), name) == 0)
return profile; // Return matching profile
}
return nullptr; // Character not found
}
// Removes character from account by name with client version handling
inline void LoginAccount::removeCharacter(char* name, int16 version)
{
CharSelectProfile* profile = nullptr;
EQ2_16BitString temp;
// Search for character with matching name
for(auto iter = charlist.begin(); iter != charlist.end(); iter++) {
profile = *iter;
temp = profile->packet->getType_EQ2_16BitString_ByName("name");
if(strcmp(temp.data.c_str(), name) == 0) {
if(version <= 561) {
profile->deleted = true; // Workaround for character select crash on legacy clients
}
else {
delete(*iter); // Clean up memory
charlist.erase(iter); // Remove from vector
}
return;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef LOGIN_OPCODES_H
#define LOGIN_OPCODES_H
#define OP_Login2 0x0200
#define OP_GetLoginInfo 0x0300
#define OP_SendServersFragment 0x0D00
#define OP_LoginInfo 0x0100
#define OP_SessionId 0x0900
#define OP_Disconnect 0x0500
//#define OP_Reg_SendPricing 0x0400
#define OP_AllFinish 0x0500
#define OP_Ack5 0x1500
#define OP_Chat_ChannelList 0x0600
#define OP_Chat_JoinChannel 0x0700
#define OP_Chat_PartChannel 0x0800
#define OP_Chat_ChannelMessage 0x0930
#define OP_Chat_Tell 0x0a00
#define OP_Chat_SysMsg 0x0b00
#define OP_Chat_CreateChannel 0x0c00
#define OP_Chat_ChangeChannel 0x0d00
#define OP_Chat_DeleteChannel 0x0e00
#define OP_Chat_UserList 0x1000
#define OP_Reg_GetPricing 0x1a00 // for new account signup
#define OP_Reg_SendPricing 0x1b00
#define OP_RegisterAccount 0x2300
#define OP_Chat_ChannelWelcome 0x2400
#define OP_Chat_PopupMakeWindow 0x3000
#define OP_BillingInfoAccepted 0x3300 // i THINK =p
#define OP_CheckGameCardValid 0x3400
#define OP_GameCardTimeLeft 0x3600
#define OP_AccountExpired 0x4200
#define OP_Reg_GetPricing2 0x4400 // for re-registering
#define OP_ChangePassword 0x4500
#define OP_ServerList 0x4600
#define OP_SessionKey 0x4700
#define OP_RequestServerStatus 0x4800
#define OP_SendServerStatus 0x4A00
#define OP_Reg_ChangeAcctLogin 0x5100
#define OP_LoginBanner 0x5200
#define OP_Chat_GuildsList 0x5500
#define OP_Chat_GuildEdit 0x5700
#define OP_Version 0x5900
#define OP_RenewAccountBillingInfo 0x7a00
#endif /* LOGIN_OPCODES_H */

View File

@ -1,53 +0,0 @@
// Copyright (C) 2007 EQ2EMulator Development Team, GPL License
#pragma once
// Core login and session management opcodes
#define OP_Login2 0x0200 // Primary login authentication
#define OP_GetLoginInfo 0x0300 // Request login information from client
#define OP_LoginInfo 0x0100 // Response with login details
#define OP_SessionId 0x0900 // Session identifier assignment
#define OP_SessionKey 0x4700 // Session key exchange
#define OP_Disconnect 0x0500 // Connection termination
#define OP_AllFinish 0x0500 // Process completion acknowledgment
#define OP_Ack5 0x1500 // Generic acknowledgment packet
// Server list and status management
#define OP_SendServersFragment 0x0D00 // Fragment of server list data
#define OP_ServerList 0x4600 // Complete server list
#define OP_RequestServerStatus 0x4800 // Request current server status
#define OP_SendServerStatus 0x4A00 // Response with server status
#define OP_Version 0x5900 // Client/server version verification
// Chat system opcodes
#define OP_Chat_ChannelList 0x0600 // Available chat channels
#define OP_Chat_JoinChannel 0x0700 // Join a chat channel
#define OP_Chat_PartChannel 0x0800 // Leave a chat channel
#define OP_Chat_ChannelMessage 0x0930 // Message to channel
#define OP_Chat_Tell 0x0a00 // Private message
#define OP_Chat_SysMsg 0x0b00 // System message
#define OP_Chat_CreateChannel 0x0c00 // Create new chat channel
#define OP_Chat_ChangeChannel 0x0d00 // Modify channel settings
#define OP_Chat_DeleteChannel 0x0e00 // Remove chat channel
#define OP_Chat_UserList 0x1000 // Users in channel
#define OP_Chat_ChannelWelcome 0x2400 // Welcome message for channel
#define OP_Chat_PopupMakeWindow 0x3000 // Create popup chat window
#define OP_Chat_GuildsList 0x5500 // Available guilds list
#define OP_Chat_GuildEdit 0x5700 // Guild modification
// Account registration and management
#define OP_RegisterAccount 0x2300 // New account creation
#define OP_Reg_GetPricing 0x1a00 // Get pricing for new account signup
//#define OP_Reg_SendPricing 0x0400 // Legacy pricing send (unused)
#define OP_Reg_SendPricing 0x1b00 // Send pricing information
#define OP_Reg_GetPricing2 0x4400 // Get pricing for re-registration
#define OP_Reg_ChangeAcctLogin 0x5100 // Change account login credentials
#define OP_ChangePassword 0x4500 // Password modification
#define OP_LoginBanner 0x5200 // Login banner display
// Billing and subscription management
#define OP_BillingInfoAccepted 0x3300 // Billing information accepted
#define OP_CheckGameCardValid 0x3400 // Validate game card
#define OP_GameCardTimeLeft 0x3600 // Remaining game card time
#define OP_AccountExpired 0x4200 // Account expiration notification
#define OP_RenewAccountBillingInfo 0x7a00 // Billing renewal information

View File

@ -0,0 +1,61 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifndef LOGIN_STRUCTS_H
#define LOGIN_STRUCTS_H
#include "../common/types.h"
#include "PacketHeaders.h"
#pragma pack(1)
struct LS_LoginRequest{
EQ2_16BitString AccessCode;
EQ2_16BitString unknown1;
EQ2_16BitString username;
EQ2_16BitString password;
EQ2_16BitString unknown2[4];
int16 unknown3;
int32 unknown4[2];
};
struct LS_WorldStatusChanged{
int32 server_id;
int8 up;
int8 locked;
int8 hidden;
};
struct LS_PlayCharacterRequest{
int32 character_id;
int32 server_id;
int16 unknown1;
};
struct LS_OLDPlayCharacterRequest{
int32 character_id;
EQ2_16BitString name;
};
struct LS_CharListAccountInfoEarlyClient {
int32 account_id;
int32 unknown1;
int16 unknown2;
int32 maxchars;
int8 unknown4; // 15 bytes total
// int8 unknown7; // adds 'free' option..
};
struct LS_CharListAccountInfo{
int32 account_id;
int32 unknown1;
int16 unknown2;
int32 maxchars;
// DoF does not have the following data
int8 unknown4;
int32 unknown5[4];
int8 vet_adv_bonus; // sets Veteran Bonus under 'Select Character' yellow (vs greyed out), adventure/tradeskill bonus 200%
int8 vet_trade_bonus; // when 1 (count?) provides free upgrade option for character to lvl 90 (heroic character) -- its a green 'Free' up arrow next to the character that is selected in char select
}; // 33 bytes
#pragma pack()
#endif

View File

@ -1,68 +0,0 @@
// Copyright (C) 2007 EQ2EMulator Development Team - GPL License
#pragma once
#include "packet_headers.hpp"
#include "../common/types.hpp"
#pragma pack(1)
// Login request packet structure containing user credentials and access information
struct LS_LoginRequest
{
EQ2_16BitString AccessCode; // Access code for login validation
EQ2_16BitString unknown1; // Unknown field - possibly session data
EQ2_16BitString username; // Player username
EQ2_16BitString password; // Player password
EQ2_16BitString unknown2[4]; // Unknown array - possibly additional auth data
int16 unknown3; // Unknown 16-bit field
int32 unknown4[2]; // Unknown 32-bit array
};
// World server status change notification structure
struct LS_WorldStatusChanged
{
int32 server_id; // Unique identifier for the world server
int8 up; // Server online status (1 = up, 0 = down)
int8 locked; // Server locked status (1 = locked, 0 = unlocked)
int8 hidden; // Server visibility status (1 = hidden, 0 = visible)
};
// Character play request for newer client versions
struct LS_PlayCharacterRequest
{
int32 character_id; // Unique character identifier
int32 server_id; // Target server identifier
int16 unknown1; // Unknown field - possibly additional flags
};
// Character play request for older client versions
struct LS_OLDPlayCharacterRequest
{
int32 character_id; // Unique character identifier
EQ2_16BitString name; // Character name string
};
// Account information structure for early client versions
struct LS_CharListAccountInfoEarlyClient
{
int32 account_id; // Unique account identifier
int32 unknown1; // Unknown field - possibly subscription data
int16 unknown2; // Unknown 16-bit field
int32 maxchars; // Maximum characters allowed on account
int8 unknown4; // Unknown field - padding or flags (15 bytes total)
};
// Complete account information structure for character list
struct LS_CharListAccountInfo
{
int32 account_id; // Unique account identifier
int32 unknown1; // Unknown field - possibly subscription type
int16 unknown2; // Unknown 16-bit field
int32 maxchars; // Maximum characters allowed on account
int8 unknown4; // Unknown field - DoF compatibility marker
int32 unknown5[4]; // Unknown array - extended account data
int8 vet_adv_bonus; // Veteran adventure bonus flag (enables 200% bonus)
int8 vet_trade_bonus; // Veteran tradeskill bonus flag (enables free lvl 90 upgrade)
}; // Total structure size: 33 bytes
#pragma pack()

View File

@ -1,112 +0,0 @@
// Copyright (C) 2007-2021 EQ2Emulator Development Team, GPL v3 License
#include <iostream>
#include <cstdlib>
#include "net.hpp"
EQStreamFactory eqsf(LoginStream);
std::map<int16,OpcodeManager*> EQOpcodeManager;
NetConnection net;
ClientList client_list;
LWorldList world_list;
LoginDatabase database;
ConfigReader configReader;
std::map<int16, int16> EQOpcodeVersions;
Timer statTimer(60000);
volatile bool RunLoops = true;
void CatchSignal(int sig_num)
{
std::cout << "Got signal " << sig_num << std::endl;
RunLoops = false;
}
int main(int argc, char** argv)
{
if (signal(SIGINT, CatchSignal) == SIG_ERR) {
std::cerr << "Could not set signal handler" << std::endl;
}
LogStart();
LogParseConfigs();
net.WelcomeHeader();
srand(time(NULL));
if (!net.ReadLoginConfig())
return 1;
net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword());
const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" };
for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++) {
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]);
if (configReader.processXML_Elements(structList[s]))
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]);
else {
LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]);
return 1;
}
}
LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
world_list.Init();
if (eqsf.listen_ip_address)
LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort());
else
LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
if (!eqsf.Open(net.GetPort())) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
return 1;
}
net.login_running = true;
net.login_uptime = getCurrentTimestamp();
net.UpdateWindowTitle();
EQStream* eqs;
Timer* TimeoutTimer = new Timer(5000);
TimeoutTimer->Start();
while (RunLoops) {
Timer::SetCurrentTime();
while ((eqs = eqsf.Pop())) {
struct in_addr in;
in.s_addr = eqs->GetRemoteIP();
LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort()));
Client* client = new Client(eqs);
eqs->SetClientVersion(0);
client_list.Add(client);
net.numclients++;
net.UpdateWindowTitle();
}
if (TimeoutTimer->Check()) {
eqsf.CheckTimeout();
}
if (statTimer.Check()) {
world_list.UpdateWorldStats();
database.RemoveOldWorldServerStats();
database.FixBugReport();
}
client_list.Process();
world_list.Process();
Sleep(1);
}
eqsf.Close();
world_list.Shutdown();
delete TimeoutTimer;
return 0;
}

363
old/LoginServer/net.cpp Normal file
View File

@ -0,0 +1,363 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#include "../common/debug.h"
#include <iostream>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sstream>
#include <string>
#include <iostream>
#include "../common/queue.h"
#include "../common/timer.h"
#include "../common/seperator.h"
#include "net.h"
#include "client.h"
#include "LoginDatabase.h"
#include "LWorld.h"
#include "../common/packet_functions.h"
#include "../common/EQStreamFactory.h"
#include "../common/MiscFunctions.h"
#include "../common/version.h"
#include "../common/PacketStruct.h"
#include "../common/DataBuffer.h"
#include "../common/ConfigReader.h"
#include "../common/Log.h"
#include "../common/JsonParser.h"
#include "../common/Common_Defines.h"
#ifdef WIN32
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#include <conio.h>
#else
#include <stdlib.h>
#include "../common/unix.h"
#endif
EQStreamFactory eqsf(LoginStream);
map<int16,OpcodeManager*>EQOpcodeManager;
//TCPServer eqns(5999);
NetConnection net;
ClientList client_list;
LWorldList world_list;
LoginDatabase database;
ConfigReader configReader;
map<int16, int16> EQOpcodeVersions;
Timer statTimer(60000);
volatile bool RunLoops = true;
bool ReadLoginConfig();
#ifdef PUBLICLOGIN
char version[200], consoletitle[200];
#endif
#include "../common/timer.h"
#include "../common/CRC16.h"
#include <fstream>
int main(int argc, char** argv){
#ifdef _DEBUG
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
if (signal(SIGINT, CatchSignal) == SIG_ERR) {
cerr << "Could not set signal handler" << endl;
}
LogStart();
LogParseConfigs();
net.WelcomeHeader();
srand(time(NULL));
if(!net.ReadLoginConfig())
return 1;
net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword());
const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" };
for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++)
{
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]);
if (configReader.processXML_Elements(structList[s]))
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]);
else
{
LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]);
return 1;
}
}
LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
world_list.Init();
if(eqsf.listen_ip_address)
LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort());
else
LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
/*}
else {
cout << "EQNetworkServer.Open() error" << endl;
return 1;
}*/
if (!eqsf.Open(net.GetPort())) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
return 1;
}
net.login_running = true;
net.login_uptime = getCurrentTimestamp();
net.UpdateWindowTitle();
EQStream* eqs;
Timer* TimeoutTimer = new Timer(5000);
TimeoutTimer->Start();
while(RunLoops) {
Timer::SetCurrentTime();
while ((eqs = eqsf.Pop())) {
struct in_addr in;
in.s_addr = eqs->GetRemoteIP();
LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort()));
Client* client = new Client(eqs);
eqs->SetClientVersion(0);
client_list.Add(client);
net.numclients++;
net.UpdateWindowTitle();
}
if(TimeoutTimer->Check()){
eqsf.CheckTimeout();
}
if(statTimer.Check()){
world_list.UpdateWorldStats();
database.RemoveOldWorldServerStats();
database.FixBugReport();
}
client_list.Process();
world_list.Process();
#ifdef WIN32
if(kbhit())
{
int hitkey = getch();
net.HitKey(hitkey);
}
#endif
Sleep(1);
}
//close
//eqns.Close();
eqsf.Close();
world_list.Shutdown();
return 0;
}
#ifdef WIN32
void NetConnection::HitKey(int keyhit)
{
switch(keyhit)
{
case 'l':
case 'L': {
world_list.ListWorldsToConsole();
break;
}
case 'v':
case 'V':
{
printf("========Version Info=========\n");
printf("%s %s\n", EQ2EMU_MODULE, CURRENT_VERSION);
printf("Last Compiled on %s %s\n", COMPILE_DATE, COMPILE_TIME);
printf("=============================\n\n");
break;
}
case 'H':
case 'h': {
printf("===========Help=============\n");
printf("Available Commands:\n");
printf("l = Listing of World Servers\n");
printf("v = Login Version\n");
// printf("0 = Kick all connected world servers\n");
printf("============================\n\n");
break;
}
default:
printf("Invalid Command.\n");
break;
}
}
#endif
void CatchSignal(int sig_num) {
cout << "Got signal " << sig_num << endl;
RunLoops = false;
}
bool NetConnection::ReadLoginConfig() {
JsonParser parser(MAIN_CONFIG_FILE);
if(!parser.IsLoaded()) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
return false;
}
std::string serverport = parser.getValue("loginconfig.serverport");
std::string serverip = parser.getValue("loginconfig.serverip");
if (!parser.convertStringToUnsignedShort(serverport, port)) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
return false;
}
if(serverip.size() > 0) {
eqsf.listen_ip_address = new char[serverip.size() + 1];
strcpy(eqsf.listen_ip_address, serverip.c_str());
}
else {
safe_delete(eqsf.listen_ip_address);
eqsf.listen_ip_address = nullptr;
}
std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
int16 allow_acct = 0;
parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
allowAccountCreation = allow_acct > 0 ? true : false;
std::string expflag_str = parser.getValue("loginconfig.expansionflag");
parser.convertStringToUnsignedInt(expflag_str, expansionFlag);
std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag);
std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel);
std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
parser.convertStringToUnsignedInt(enableraces_str, enabledRaces);
web_loginaddress = parser.getValue("loginconfig.webloginaddress");
web_certfile = parser.getValue("loginconfig.webcertfile");
web_keyfile = parser.getValue("loginconfig.webkeyfile");
web_keypassword = parser.getValue("loginconfig.webkeypassword");
web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser");
web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword");
std::string webloginport_str = parser.getValue("loginconfig.webloginport");
parser.convertStringToUnsignedShort(webloginport_str, web_loginport);
LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE);
LogWrite(INIT__INFO, 0, "Init", "Database init begin..");
//remove this when all database calls are using the new database class
if (!database.Init()) {
LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
LogStop();
return false;
}
LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
EQOpcodeVersions = database.GetVersions();
map<int16,int16>::iterator version_itr2;
int16 version1 = 0;
for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) {
version1 = version_itr2->first;
EQOpcodeManager[version1] = new RegularOpcodeManager();
map<string, uint16> eq = database.GetOpcodes(version1);
if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) {
LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
return false;
}
}
return true;
}
void NetConnection::UpdateWindowTitle(char* iNewTitle) {
#ifdef WIN32
char tmp[500];
if (iNewTitle) {
snprintf(tmp, sizeof(tmp), "Login: %s", iNewTitle);
}
else {
snprintf(tmp, sizeof(tmp), "%s, Version: %s: %i Server(s), %i Client(s) Connected", EQ2EMU_MODULE, CURRENT_VERSION, net.numservers, net.numclients);
}
SetConsoleTitle(tmp);
#endif
}
void NetConnection::WelcomeHeader()
{
#ifdef _WIN32
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD);
#endif
printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION);
#ifdef _WIN32
SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD);
#endif
printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n");
printf("EQ2Emulator is free software: you can redistribute it and/or modify\n");
printf("it under the terms of the GNU General Public License as published by\n");
printf("the Free Software Foundation, either version 3 of the License, or\n");
printf("(at your option) any later version.\n\n");
printf("EQ2Emulator is distributed in the hope that it will be useful,\n");
printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
printf("GNU General Public License for more details.\n\n");
#ifdef _WIN32
SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD);
#endif
printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n");
printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n");
printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n");
printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n");
printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n");
printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n");
printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n");
printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n");
printf(" \\__/ \n\n");
#ifdef _WIN32
SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD);
#endif
printf(" Website : https://eq2emu.com \n");
printf(" Wiki : https://wiki.eq2emu.com \n");
printf(" Git : https://git.eq2emu.com \n");
printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n");
#ifdef _WIN32
SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD);
#endif
printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n");
#ifdef _WIN32
SetConsoleTextAttribute(console, FOREGROUND_WHITE);
#endif
fflush(stdout);
}
void NetConnection::InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password) {
if(web_ipaddr.size() > 0 && web_port > 0) {
try {
login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password);
login_webserver->register_route("/status", NetConnection::Web_loginhandle_status);
login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
login_webserver->run();
LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port);
}
catch (const std::exception& e) {
LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what());
}
}
}

147
old/LoginServer/net.h Normal file
View File

@ -0,0 +1,147 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
//#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <atomic>
#include "../common/types.h"
#include "../common/Web/WebServer.h"
#include "../common/MiscFunctions.h"
void CatchSignal(int sig_num);
enum eServerMode { Standalone, Master, Slave, Mesh };
class NetConnection
{
public:
NetConnection() {
port = 5999;
listening_socket = 0;
memset(masteraddress, 0, sizeof(masteraddress));
uplinkport = 0;
memset(uplinkaccount, 0, sizeof(uplinkaccount));
memset(uplinkpassword, 0, sizeof(uplinkpassword));
LoginMode = Standalone;
Uplink_WrongVersion = false;
numclients = 0;
numservers = 0;
allowAccountCreation = true;
// full support = 0x7CFF
// 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city
// 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city
expansionFlag = 0x7CFF; // 0x4CF5
/* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas)
1 = city of qeynos
2 = city of freeport
4 = city of kelethin
8 = city of neriak
16 = gorowyn
32 = new halas
64 = queens colony
128 = outpost overlord
*/
citiesFlag = 0xFF;
// sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership
// sub_level > 0 = class alignments still required, but portraits are viewable and race selectable
// sub_level = 2 membership, you can 'create characters on time locked servers' vs standard
// sub_level = 0 forces popup on close to web browser
defaultSubscriptionLevel = 0xFFFFFFFF;
// disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits
enabledRaces = 0xFFFF; // 0xCFFF
web_loginport = 0;
login_webserver = nullptr;
login_running = false;
login_uptime = getCurrentTimestamp();
}
~NetConnection() {
safe_delete(login_webserver);
}
void UpdateWindowTitle(char* iNewTitle = 0);
bool Init();
void ListenNewClients();
void HitKey(int keyhit);
char address[1024];
int32 numclients;
int32 numservers;
int16 GetPort() { return port; }
void SetPort(int16 in_port) { port = in_port; }
eServerMode GetLoginMode() { return LoginMode; }
bool ReadLoginConfig();
char* GetMasterAddress() { return masteraddress; }
int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; }
char* GetUplinkAccount() { return uplinkaccount; }
char* GetUplinkPassword() { return uplinkpassword; }
bool IsAllowingAccountCreation() { return allowAccountCreation; }
int32 GetExpansionFlag() { return expansionFlag; }
int8 GetCitiesFlag() { return citiesFlag; }
int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; }
int32 GetEnabledRaces() { return enabledRaces; }
std::string GetWebLoginAddress() { return web_loginaddress; }
inline int16 GetWebLoginPort() { return web_loginport; }
std::string GetWebCertFile() { return web_certfile; }
std::string GetWebKeyFile() { return web_keyfile; }
std::string GetWebKeyPassword() { return web_keypassword; }
std::string GetWebHardcodeUser() { return web_hardcodeuser; }
std::string GetWebHardcodePassword() { return web_hardcodepassword; }
void WelcomeHeader();
void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password);
static void Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res);
bool login_running;
std::atomic<int64> login_uptime;
protected:
friend class LWorld;
bool Uplink_WrongVersion;
private:
int16 port;
int listening_socket;
char masteraddress[300];
int16 uplinkport;
char uplinkaccount[300];
char uplinkpassword[300];
eServerMode LoginMode;
bool allowAccountCreation;
int32 expansionFlag;
int8 citiesFlag;
int32 defaultSubscriptionLevel;
int32 enabledRaces;
std::string web_loginaddress;
std::string web_certfile;
std::string web_keyfile;
std::string web_keypassword;
std::string web_hardcodeuser;
std::string web_hardcodepassword;
int16 web_loginport;
WebServer* login_webserver;
};

View File

@ -1,307 +0,0 @@
#pragma once
// Copyright (C) 2007-2021 EQ2Emulator Development Team, GPL v3 License
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <atomic>
#include <iostream>
#include <string>
#include <cstring>
#include <ctime>
#include <csignal>
#include <sstream>
#include <fstream>
#include <map>
#include "lworld.hpp"
#include "client.hpp"
#include "login_database.hpp"
#include "../common/log.hpp"
#include "../common/unix.hpp"
#include "../common/crc16.hpp"
#include "../common/types.hpp"
#include "../common/debug.hpp"
#include "../common/queue.hpp"
#include "../common/timer.hpp"
#include "../common/version.hpp"
#include "../common/separator.hpp"
#include "../common/web_server.hpp"
#include "../common/json_parser.hpp"
#include "../common/data_buffer.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/common_defines.hpp"
#include "../common/packet/packet_struct.hpp"
#include "../common/packet/packet_functions.hpp"
#include "../common/stream/eq_stream_factory.hpp"
void CatchSignal(int sig_num);
enum eServerMode
{
Standalone,
Master,
Slave,
Mesh
};
class NetConnection
{
public:
// Constructor - initializes all network connection parameters and default values
NetConnection()
{
port = 5999;
listening_socket = 0;
memset(masteraddress, 0, sizeof(masteraddress));
uplinkport = 0;
memset(uplinkaccount, 0, sizeof(uplinkaccount));
memset(uplinkpassword, 0, sizeof(uplinkpassword));
LoginMode = Standalone;
Uplink_WrongVersion = false;
numclients = 0;
numservers = 0;
allowAccountCreation = true;
// Full expansion support flag - controls available expansions
expansionFlag = 0x7CFF;
// Cities availability flag - controls which starting cities are available
citiesFlag = 0xFF;
// Default subscription level - controls character creation restrictions
defaultSubscriptionLevel = 0xFFFFFFFF;
// Enabled races flag - controls which races are selectable
enabledRaces = 0xFFFF;
web_loginport = 0;
login_webserver = nullptr;
login_running = false;
login_uptime = getCurrentTimestamp();
}
// Destructor - cleans up web server resources
~NetConnection()
{
safe_delete(login_webserver);
}
// Updates console window title with server status information
void UpdateWindowTitle(char* iNewTitle = 0)
{
// Linux systems don't support console title updates like Windows
// This is a no-op on Linux systems
}
// Reads and parses the login server configuration from JSON file
bool ReadLoginConfig()
{
JsonParser parser(MAIN_CONFIG_FILE);
if (!parser.IsLoaded()) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
return false;
}
std::string serverport = parser.getValue("loginconfig.serverport");
std::string serverip = parser.getValue("loginconfig.serverip");
if (!parser.convertStringToUnsignedShort(serverport, port)) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
return false;
}
if (serverip.size() > 0) {
eqsf.listen_ip_address = new char[serverip.size() + 1];
strcpy(eqsf.listen_ip_address, serverip.c_str());
}
else {
safe_delete(eqsf.listen_ip_address);
eqsf.listen_ip_address = nullptr;
}
std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
int16 allow_acct = 0;
parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
allowAccountCreation = allow_acct > 0 ? true : false;
std::string expflag_str = parser.getValue("loginconfig.expansionflag");
parser.convertStringToUnsignedInt(expflag_str, expansionFlag);
std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag);
std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel);
std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
parser.convertStringToUnsignedInt(enableraces_str, enabledRaces);
web_loginaddress = parser.getValue("loginconfig.webloginaddress");
web_certfile = parser.getValue("loginconfig.webcertfile");
web_keyfile = parser.getValue("loginconfig.webkeyfile");
web_keypassword = parser.getValue("loginconfig.webkeypassword");
web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser");
web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword");
std::string webloginport_str = parser.getValue("loginconfig.webloginport");
parser.convertStringToUnsignedShort(webloginport_str, web_loginport);
LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE);
LogWrite(INIT__INFO, 0, "Init", "Database init begin..");
if (!database.Init()) {
LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
LogStop();
return false;
}
LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
EQOpcodeVersions = database.GetVersions();
std::map<int16,int16>::iterator version_itr2;
int16 version1 = 0;
for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) {
version1 = version_itr2->first;
EQOpcodeManager[version1] = new RegularOpcodeManager();
std::map<std::string, uint16> eq = database.GetOpcodes(version1);
if (!EQOpcodeManager[version1]->LoadOpcodes(&eq)) {
LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
return false;
}
}
return true;
}
// Displays the EQ2Emulator welcome header with ASCII art and information
void WelcomeHeader()
{
printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION);
printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n");
printf("EQ2Emulator is free software: you can redistribute it and/or modify\n");
printf("it under the terms of the GNU General Public License as published by\n");
printf("the Free Software Foundation, either version 3 of the License, or\n");
printf("(at your option) any later version.\n\n");
printf("EQ2Emulator is distributed in the hope that it will be useful,\n");
printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
printf("GNU General Public License for more details.\n\n");
printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n");
printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n");
printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n");
printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n");
printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n");
printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n");
printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n");
printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n");
printf(" \\__/ \n\n");
printf(" Website : https://eq2emu.com \n");
printf(" Wiki : https://wiki.eq2emu.com \n");
printf(" Git : https://git.eq2emu.com \n");
printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n");
printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n");
fflush(stdout);
}
// Initializes and starts the web server for login status and world information
void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password)
{
if (web_ipaddr.size() > 0 && web_port > 0) {
try {
login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password);
login_webserver->register_route("/status", NetConnection::Web_loginhandle_status);
login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
login_webserver->run();
LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port);
}
catch (const std::exception& e) {
LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what());
}
}
}
// Accessor methods for configuration values
int16 GetPort() { return port; }
void SetPort(int16 in_port) { port = in_port; }
eServerMode GetLoginMode() { return LoginMode; }
char* GetMasterAddress() { return masteraddress; }
int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; }
char* GetUplinkAccount() { return uplinkaccount; }
char* GetUplinkPassword() { return uplinkpassword; }
bool IsAllowingAccountCreation() { return allowAccountCreation; }
int32 GetExpansionFlag() { return expansionFlag; }
int8 GetCitiesFlag() { return citiesFlag; }
int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; }
int32 GetEnabledRaces() { return enabledRaces; }
std::string GetWebLoginAddress() { return web_loginaddress; }
int16 GetWebLoginPort() { return web_loginport; }
std::string GetWebCertFile() { return web_certfile; }
std::string GetWebKeyFile() { return web_keyfile; }
std::string GetWebKeyPassword() { return web_keypassword; }
std::string GetWebHardcodeUser() { return web_hardcodeuser; }
std::string GetWebHardcodePassword() { return web_hardcodepassword; }
// Web server route handlers for status and world information
static void Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res);
// Public server state variables
char address[1024];
int32 numclients;
int32 numservers;
bool login_running;
std::atomic<int64> login_uptime;
protected:
friend class LWorld;
bool Uplink_WrongVersion;
private:
// Network connection parameters
int16 port;
int listening_socket;
char masteraddress[300];
int16 uplinkport;
char uplinkaccount[300];
char uplinkpassword[300];
eServerMode LoginMode;
// Login server configuration flags
bool allowAccountCreation;
int32 expansionFlag;
int8 citiesFlag;
int32 defaultSubscriptionLevel;
int32 enabledRaces;
// Web server configuration
std::string web_loginaddress;
std::string web_certfile;
std::string web_keyfile;
std::string web_keypassword;
std::string web_hardcodeuser;
std::string web_hardcodepassword;
int16 web_loginport;
WebServer* login_webserver;
};
// Global variables - external declarations
extern EQStreamFactory eqsf;
extern std::map<int16,OpcodeManager*> EQOpcodeManager;
extern NetConnection net;
extern ClientList client_list;
extern LWorldList world_list;
extern LoginDatabase database;
extern ConfigReader configReader;
extern std::map<int16, int16> EQOpcodeVersions;
extern Timer statTimer;
extern volatile bool RunLoops;

View File

@ -1,160 +0,0 @@
// Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPLv3
#pragma once
#include <vector>
#include <string>
#include "lworld.hpp"
#include "login_structs.hpp"
#include "login_database.hpp"
#include "../common/types.hpp"
#include "../common/data_buffer.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/global_headers.hpp"
#include "../common/eq_common_structs.hpp"
#include "../common/packet/eq_packet.hpp"
extern ConfigReader configReader;
extern LWorldList world_list;
extern LoginDatabase database;
using std::vector;
using std::string;
// Character profile data for character selection screen
class CharSelectProfile : public DataBuffer
{
public:
// Constructor - initializes character profile with version-specific packet structure
CharSelectProfile(int16 version)
{
deleted = false;
packet = configReader.getStruct("CharSelectProfile", version);
// Initialize all 24 equipment slots to default values
for (int8 i = 0; i < 24; i++) {
packet->setEquipmentByName("equip", 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, i);
}
}
// Destructor - safely cleans up packet structure
~CharSelectProfile()
{
safe_delete(packet);
}
// Serializes character profile data for transmission
void SaveData(int16 in_version)
{
Clear();
AddData(*packet->serializeString());
}
PacketStruct* packet; // Packet structure containing character data
int16 size; // Size of serialized data
bool deleted; // Flag indicating if character is marked for deletion
};
// Character selection list containing multiple character profiles
class LS_CharSelectList : public DataBuffer
{
public:
// Serializes character list data with account information for specified client version
EQ2Packet* serialize(int16 version)
{
Clear();
AddData(num_characters);
AddData(char_data);
if (version <= 561) {
// Early client version uses simplified account info structure
LS_CharListAccountInfoEarlyClient account_info;
account_info.account_id = account_id;
account_info.unknown1 = 0xFFFFFFFF;
account_info.unknown2 = 0;
account_info.maxchars = 7; // Live has a max of 7 on gold accounts base
account_info.unknown4 = 0;
AddData(account_info);
} else {
// Later client versions use extended account info structure
LS_CharListAccountInfo account_info;
account_info.account_id = account_id;
account_info.unknown1 = 0xFFFFFFFF;
account_info.unknown2 = 0;
account_info.maxchars = database.GetMaxCharsSetting();
account_info.vet_adv_bonus = database.GetAccountBonus(account_id);
account_info.vet_trade_bonus = 0;
account_info.unknown4 = 0;
for (int i = 0; i < 3; i++)
account_info.unknown5[i] = 0xFFFFFFFF;
account_info.unknown5[3] = 0;
AddData(account_info);
}
return new EQ2Packet(OP_AllCharactersDescReplyMsg, getData(), getDataSize());
}
// Appends character data to the character list
void addChar(uchar* data, int16 size)
{
char_data.append(reinterpret_cast<char*>(data), size);
}
// Loads character data from database and populates character list for specified account
void loadData(int32 account, vector<CharSelectProfile*> charlist, int16 version)
{
account_id = account;
num_characters = 0;
char_data = "";
CharSelectProfile* character = nullptr;
for (auto itr = charlist.begin(); itr != charlist.end(); itr++) {
character = *itr;
int32 serverID = character->packet->getType_int32_ByName("server_id");
if (character->deleted) {
// Workaround for old clients <= 561 that crash if you delete a char
// Doesn't refresh the char panel correctly
character->packet->setDataByName("name", "(deleted)");
character->packet->setDataByName("charid", 0xFFFFFFFF);
character->packet->setDataByName("name", 0xFFFFFFFF);
character->packet->setDataByName("server_id", 0xFFFFFFFF);
character->packet->setDataByName("created_date", 0xFFFFFFFF);
character->packet->setDataByName("unknown1", 0xFFFFFFFF);
character->packet->setDataByName("unknown2", 0xFFFFFFFF);
character->packet->setDataByName("flags", 0xFF);
} else if (serverID == 0 || !world_list.FindByID(serverID)) {
continue;
}
num_characters++;
character->SaveData(version);
addChar(character->getData(), character->getDataSize());
}
}
int8 num_characters; // Number of characters in the list
int32 account_id; // Account ID for this character list
string char_data; // Serialized character data buffer
};
// Request packet for character deletion operations
class LS_DeleteCharacterRequest : public DataBuffer
{
public:
// Loads character deletion request data from incoming packet
void loadData(EQApplicationPacket* packet)
{
InitializeLoadData(packet->pBuffer, packet->size);
LoadData(character_number);
LoadData(server_id);
LoadData(spacer);
LoadDataString(name);
}
int32 character_number; // Character slot number to delete
int32 server_id; // Server ID where character exists
int32 spacer; // Padding/alignment bytes
EQ2_16BitString name; // Character name to delete
};

View File

@ -1,101 +0,0 @@
// Copyright (C) 2007-2021 EQ2Emulator Development Team, GPL v3
#pragma once
#include <sstream>
#include <string>
#include <map>
#include <arpa/inet.h>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "net.hpp"
#include "lworld.hpp"
extern ClientList client_list;
extern LWorldList world_list;
extern NetConnection net;
/**
* Handles web requests for login server status information.
* Generates JSON response containing login status, uptime, client count, and world count.
* @param req HTTP request object containing the incoming request
* @param res HTTP response object to populate with JSON status data
*/
void NetConnection::Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res)
{
// Set response content type to JSON
res.set(http::field::content_type, "application/json");
boost::property_tree::ptree pt;
// Build status information tree
pt.put("web_status", "online");
pt.put("login_status", net.login_running ? "online" : "offline");
pt.put("login_uptime", (getCurrentTimestamp() - net.login_uptime));
// Convert uptime to human-readable format
auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - net.login_uptime));
std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds));
pt.put("login_uptime_string", uptime_str);
pt.put("world_count", world_list.GetCount(ConType::World));
pt.put("client_count", net.numclients);
// Serialize to JSON and set response body
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
/**
* Handles web requests for world server list information.
* Delegates to LWorldList::PopulateWorldList to generate the response.
* @param req HTTP request object containing the incoming request
* @param res HTTP response object to be populated with world server data
*/
void NetConnection::Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res)
{
world_list.PopulateWorldList(res);
}
/**
* Populates HTTP response with JSON data containing all active world servers.
* Iterates through world map and builds JSON array of world server information
* including ID, name, status, player count, and IP address.
* @param res HTTP response object to populate with world server JSON data
*/
void LWorldList::PopulateWorldList(http::response<http::string_body>& res)
{
struct in_addr in;
res.set(http::field::content_type, "application/json");
boost::property_tree::ptree maintree;
std::ostringstream oss;
// Iterate through all worlds in the world map
std::map<int32,LWorld*>::iterator map_list;
for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) {
LWorld* world = map_list->second;
in.s_addr = world->GetIP();
// Only include World type servers in the response
if (world->GetType() == World) {
boost::property_tree::ptree pt;
pt.put("id", world->GetID());
pt.put("world_name", world->GetName());
pt.put("status", (world->GetStatus() == 1) ? "online" : "offline");
pt.put("num_players", world->GetPlayerNum());
pt.put("ip_addr", inet_ntoa(in)); // Convert IP to string format
maintree.push_back(std::make_pair("", pt));
}
}
// Build final JSON structure and serialize
boost::property_tree::ptree result;
result.add_child("WorldServers", maintree);
boost::property_tree::write_json(oss, result);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}

View File

@ -0,0 +1,332 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Achievements.h"
#include "../../common/Log.h"
#include "../../common/ConfigReader.h"
#include <assert.h>
extern ConfigReader configReader;
extern MasterAchievementList master_achievement_list;
Achievement::Achievement() {
id = 0;
memset(title, 0, sizeof(title));
memset(uncompleted_text, 0, sizeof(uncompleted_text));
memset(completed_text, 0, sizeof(completed_text));
memset(category, 0, sizeof(category));
memset(expansion, 0, sizeof(expansion));
icon = 0;
point_value = 0;
qty_req = 0;
hide = false;
unknown3a = 0;
unknown3b = 0;
}
Achievement::Achievement(Achievement *in) {
vector<struct AchievementRequirements *> *requirements_in;
vector<struct AchievementRewards *> *rewards_in;
vector<struct AchievementRequirements *>::iterator itr;
vector<struct AchievementRewards *>::iterator itr2;
struct AchievementRequirements *achievement_requirement;
struct AchievementRewards *achievement_reward;
assert(in);
id = in->GetID();
strncpy(title, in->GetTitle(), sizeof(title));
strncpy(uncompleted_text, in->GetUncompletedText(), sizeof(uncompleted_text));
strncpy(completed_text, in->GetCompletedText(), sizeof(completed_text));
strncpy(category, in->GetCategory(), sizeof(category));
strncpy(expansion, in->GetExpansion(), sizeof(expansion));
icon = in->GetIcon();
point_value = in->GetPointValue();
qty_req = in->GetQtyReq();
hide = in->GetHide();
unknown3a = in->GetUnknown3a();
unknown3b = in->GetUnknown3b();
requirements_in = in->GetRequirements();
for (itr = requirements_in->begin(); itr != requirements_in->end(); itr++) {
achievement_requirement = new struct AchievementRequirements;
achievement_requirement->achievement_id = (*itr)->achievement_id;
achievement_requirement->name = (*itr)->name;
achievement_requirement->qty_req = (*itr)->qty_req;
requirements.push_back(achievement_requirement);
}
rewards_in = in->GetRewards();
for (itr2 = rewards_in->begin(); itr2 != rewards_in->end(); itr2++) {
achievement_reward = new struct AchievementRewards;
achievement_reward->achievement_id = (*itr2)->achievement_id;
achievement_reward->reward = (*itr2)->reward;
rewards.push_back(achievement_reward);
}
}
Achievement::~Achievement() {
vector<struct AchievementRequirements *>::iterator itr;
vector<struct AchievementRewards *>::iterator itr2;
for (itr = requirements.begin(); itr != requirements.end(); itr++)
safe_delete(*itr);
for (itr2 = rewards.begin(); itr2 != rewards.end(); itr2++)
safe_delete(*itr2);
}
void Achievement::AddAchievementRequirement(struct AchievementRequirements *requirement) {
assert(requirement);
requirements.push_back(requirement);
}
void Achievement::AddAchievementReward(struct AchievementRewards *reward) {
assert(reward);
rewards.push_back(reward);
}
void AchievementUpdate::AddAchievementUpdateItems(struct AchievementUpdateItems *update_item) {
assert(update_item);
update_items.push_back(update_item);
}
MasterAchievementList::MasterAchievementList() {
m_packetsCreated = false;
masterPacket = 0;
mutex_achievements.SetName("MasterAchievementList::achievements");
}
MasterAchievementList::~MasterAchievementList() {
ClearAchievements();
}
bool MasterAchievementList::AddAchievement(Achievement *achievement) {
bool ret = false;
assert(achievement);
mutex_achievements.writelock(__FUNCTION__, __LINE__);
if (achievements.count(achievement->GetID()) == 0) {
achievements[achievement->GetID()] = achievement;
ret = true;
}
mutex_achievements.releasewritelock(__FUNCTION__, __LINE__);
return ret;
}
Achievement * MasterAchievementList::GetAchievement(int32 achievement_id) {
Achievement *achievement = 0;
mutex_achievements.readlock(__FUNCTION__, __LINE__);
if (achievements.count(achievement_id) > 0)
achievement = achievements[achievement_id];
mutex_achievements.releasereadlock(__FUNCTION__, __LINE__);
return achievement;
}
void MasterAchievementList::ClearAchievements() {
map<int32, Achievement *>::iterator itr;
mutex_achievements.writelock(__FUNCTION__, __LINE__);
for (itr = achievements.begin(); itr != achievements.end(); itr++)
safe_delete(itr->second);
achievements.clear();
mutex_achievements.releasewritelock(__FUNCTION__, __LINE__);
}
int32 MasterAchievementList::Size() {
int32 size;
mutex_achievements.readlock(__FUNCTION__, __LINE__);
size = achievements.size();
mutex_achievements.releasereadlock(__FUNCTION__, __LINE__);
return size;
}
PlayerAchievementList::PlayerAchievementList() {
}
PlayerAchievementList::~PlayerAchievementList() {
ClearAchievements();
}
bool PlayerAchievementList::AddAchievement(Achievement *achievement) {
assert(achievement);
if (achievements.count(achievement->GetID()) == 0) {
achievements[achievement->GetID()] = achievement;
return true;
}
return false;
}
Achievement * PlayerAchievementList::GetAchievement(int32 achievement_id) {
if (achievements.count(achievement_id) > 0)
return achievements[achievement_id];
return 0;
}
void PlayerAchievementList::ClearAchievements() {
map<int32, Achievement *>::iterator itr;
for (itr = achievements.begin(); itr != achievements.end(); itr++)
safe_delete(itr->second);
achievements.clear();
}
int32 PlayerAchievementList::Size() {
return achievements.size();
}
AchievementUpdate::AchievementUpdate() {
id = 0;
completed_date = 0;
}
AchievementUpdate::AchievementUpdate(AchievementUpdate *in) {
vector<struct AchievementUpdateItems *> *items_in;
vector<struct AchievementUpdateItems *>::iterator itr;
struct AchievementUpdateItems *items;
assert(in);
id = in->GetID();
completed_date = in->GetCompletedDate();
items_in = in->GetUpdateItems();
for (itr = items_in->begin(); itr != items_in->end(); itr++) {
items = new struct AchievementUpdateItems;
items->achievement_id = (*itr)->achievement_id;
items->item_update = (*itr)->item_update;
update_items.push_back(items);
}
}
AchievementUpdate::~AchievementUpdate() {
vector<struct AchievementUpdateItems *>::iterator itr;
for (itr = update_items.begin(); itr != update_items.end(); itr++)
safe_delete(*itr);
}
PlayerAchievementUpdateList::PlayerAchievementUpdateList() {
}
PlayerAchievementUpdateList::~PlayerAchievementUpdateList() {
ClearAchievementUpdates();
}
bool PlayerAchievementUpdateList::AddAchievementUpdate(AchievementUpdate *update) {
assert(update);
if (achievement_updates.count(update->GetID()) == 0) {
achievement_updates[update->GetID()] = update;
return true;
}
return false;
}
void PlayerAchievementUpdateList::ClearAchievementUpdates() {
map<int32, AchievementUpdate *>::iterator itr;
for (itr = achievement_updates.begin(); itr != achievement_updates.end(); itr++)
safe_delete(itr->second);
achievement_updates.clear();
}
int32 PlayerAchievementUpdateList::Size() {
return achievement_updates.size();
}
void MasterAchievementList::CreateMasterAchievementListPacket() {
map<int32, Achievement *>::iterator itr;
Achievement *achievement;
vector<AchievementRequirements *> *requirements = 0;
vector<AchievementRequirements *>::iterator itr2;
AchievementRequirements *requirement;
vector<AchievementRewards *> *rewards = 0;
vector<AchievementRewards *>::iterator itr3;
AchievementRewards *reward;
PacketStruct *packet;
int16 i = 0;
int16 j = 0;
int16 k = 0;
int16 version = 1096;
if (!(packet = configReader.getStruct("WS_CharacterAchievements", version))) {
return;
}
packet->setArrayLengthByName("num_achievements" , achievements.size());
for (itr = achievements.begin(); itr != achievements.end(); itr++) {
achievement = itr->second;
packet->setArrayDataByName("achievement_id", achievement->GetID(), i);
packet->setArrayDataByName("title", achievement->GetTitle(), i);
packet->setArrayDataByName("uncompleted_text", achievement->GetUncompletedText(), i);
packet->setArrayDataByName("completed_text", achievement->GetCompletedText(), i);
packet->setArrayDataByName("category", achievement->GetCategory(), i);
packet->setArrayDataByName("expansion", achievement->GetExpansion(), i);
packet->setArrayDataByName("icon", achievement->GetIcon(), i);
packet->setArrayDataByName("point_value", achievement->GetPointValue(), i);
packet->setArrayDataByName("qty_req", achievement->GetQtyReq(), i);
packet->setArrayDataByName("hide_achievement", achievement->GetHide(), i);
packet->setArrayDataByName("unknown3", achievement->GetUnknown3a(), i);
packet->setArrayDataByName("unknown3", achievement->GetUnknown3b(), i);
requirements = achievement->GetRequirements();
rewards = achievement->GetRewards();
j = 0;
k = 0;
packet->setSubArrayLengthByName("num_items", requirements->size(), i, j);
for (itr2 = requirements->begin(); itr2 != requirements->end(); itr2++) {
requirement = *itr2;
packet->setSubArrayDataByName("item_name", requirement->name.c_str(), i, j);
packet->setSubArrayDataByName("item_qty_req", requirement->qty_req, i, j);
j++;
}
packet->setSubArrayLengthByName("num_rewards", achievement->GetRewards()->size(), i, k);
for (itr3 = rewards->begin(); itr3 != rewards->end(); itr3++) {
reward = *itr3;
packet->setSubArrayDataByName("reward_item", reward->reward.c_str(), i, k);
k++;
}
i++;
}
//packet->PrintPacket();
EQ2Packet* data = packet->serialize();
masterPacket = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size);
safe_delete(packet);
safe_delete(data);
//DumpPacket(app);
m_packetsCreated = true;
}

View File

@ -0,0 +1,176 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ACHIEVEMENTS_H_
#define ACHIEVEMENTS_H_
#include "../../common/types.h"
#include "../../common/Mutex.h"
#include "../Items/Items.h"
#include <map>
#include <vector>
using namespace std;
struct AchievementRewards
{
int32 achievement_id;
string reward;
};
struct AchievementRequirements
{
int32 achievement_id;
string name;
int32 qty_req;
};
struct AchievementUpdateItems
{
int32 achievement_id;
int32 item_update;
};
class Achievement {
public:
Achievement();
Achievement(Achievement *in);
virtual ~Achievement();
void SetID(int32 id) {this->id = id;}
void SetTitle(const char *title) {strncpy(this->title, title, sizeof(this->title));}
void SetUncompletedText(const char *uncompleted_text) {strncpy(this->uncompleted_text, uncompleted_text, sizeof(this->uncompleted_text));}
void SetCompletedText(const char *completed_text) {strncpy(this->completed_text, completed_text, sizeof(this->completed_text));}
void SetCategory(const char *category) {strncpy(this->category, category, sizeof(this->category));}
void SetExpansion(const char *expansion) {strncpy(this->expansion, expansion, sizeof(this->expansion));}
void SetIcon(int16 icon) {this->icon = icon;}
void SetPointValue(int32 point_value) {this->point_value = point_value;}
void SetQtyReq(int32 qty_req) {this->qty_req = qty_req;}
void SetHide(bool hide) {this->hide = hide;}
void SetUnknown3a(int32 unknown3a) {this->unknown3a = unknown3a;}
void SetUnknown3b(int32 unknown3b) {this->unknown3b = unknown3b;}
void AddAchievementRequirement(struct AchievementRequirements *requirements);
void AddAchievementReward(struct AchievementRewards *reward);
int32 GetID() {return id;}
const char * GetTitle() {return title;}
const char * GetUncompletedText() {return uncompleted_text;}
const char * GetCompletedText() {return completed_text;}
const char * GetCategory() {return category;}
const char * GetExpansion() {return expansion;}
int16 GetIcon() {return icon;}
int32 GetPointValue() {return point_value;}
int32 GetQtyReq() {return qty_req;}
bool GetHide() {return hide;}
int32 GetUnknown3a() {return unknown3a;}
int32 GetUnknown3b() {return unknown3b;}
vector<struct AchievementRequirements *> * GetRequirements() {return &requirements;}
vector<struct AchievementRewards *> * GetRewards() {return &rewards;}
private:
int32 id;
char title[512];
char uncompleted_text[512];
char completed_text[512];
char category[32];
char expansion[32];
int16 icon;
int32 point_value;
int32 qty_req;
bool hide;
int32 unknown3a;
int32 unknown3b;
vector<struct AchievementRequirements *> requirements;
vector<struct AchievementRewards *> rewards;
};
class AchievementUpdate {
public:
AchievementUpdate();
AchievementUpdate(AchievementUpdate *in);
virtual ~AchievementUpdate();
void SetID(int32 id) {this->id = id;}
void SetCompletedDate(int32 completed_date) {this->completed_date = completed_date;}
void AddAchievementUpdateItems(struct AchievementUpdateItems *update_items);
int32 GetID() {return id;}
int32 GetCompletedDate() {return completed_date;}
vector<struct AchievementUpdateItems *> * GetUpdateItems() {return &update_items;}
private:
int32 id;
int32 completed_date;
vector<struct AchievementUpdateItems *> update_items;
};
class MasterAchievementList {
public:
MasterAchievementList();
virtual ~MasterAchievementList();
bool AddAchievement(Achievement *achievement);
Achievement * GetAchievement(int32 achievement_id);
void ClearAchievements();
int32 Size();
void CreateMasterAchievementListPacket();
EQ2Packet * GetAchievementPacket() { return m_packetsCreated ? masterPacket : 0;}
EQ2Packet *masterPacket;
private:
Mutex mutex_achievements;
map<int32, Achievement *> achievements;
bool m_packetsCreated;
};
class PlayerAchievementList {
public:
PlayerAchievementList();
virtual ~PlayerAchievementList();
bool AddAchievement(Achievement *achievement);
Achievement * GetAchievement(int32 achievement_id);
void ClearAchievements();
int32 Size();
map<int32, Achievement *> * GetAchievements() {return &achievements;}
private:
map<int32, Achievement *> achievements;
};
class PlayerAchievementUpdateList {
public:
PlayerAchievementUpdateList();
virtual ~PlayerAchievementUpdateList();
bool AddAchievementUpdate(AchievementUpdate *achievement_update);
void ClearAchievementUpdates();
int32 Size();
map<int32, AchievementUpdate *> * GetAchievementUpdates() {return &achievement_updates;}
private:
map<int32, AchievementUpdate *> achievement_updates;
};
#endif

View File

@ -1,11 +1,32 @@
// Copyright (C) 2007 EQ2EMulator Development Team - GPL v3 License /*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
#endif
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h"
#include "achievements.hpp"
#include "../WorldDatabase.h" #include "../WorldDatabase.h"
#include "../../common/log.hpp" #include "Achievements.h"
extern MasterAchievementList master_achievement_list; extern MasterAchievementList master_achievement_list;
@ -55,6 +76,7 @@ void WorldDatabase::LoadAchievements()
LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievements", master_achievement_list.Size()); LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievements", master_achievement_list.Size());
LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement requirements", aReqs_total); LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement requirements", aReqs_total);
LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards", aRewards_total); LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards", aRewards_total);
} }
int32 WorldDatabase::LoadAchievementRequirements(Achievement *achievement) int32 WorldDatabase::LoadAchievementRequirements(Achievement *achievement)

View File

@ -1,7 +1,25 @@
// Copyright (C) 2007 EQ2EMulator Development Team - GPL v3 License /*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AltAdvancement.h" #include "AltAdvancement.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Spells.h" #include "../Spells.h"
#include "../classes.h" #include "../classes.h"
@ -9,8 +27,8 @@
#include <map> #include <map>
#include <assert.h> #include <assert.h>
#include <mysql.h> #include <mysql.h>
#include "../../common/database_new.hpp" #include "../../common/DatabaseNew.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
extern ConfigReader configReader; extern ConfigReader configReader;
extern MasterSpellList master_spell_list; extern MasterSpellList master_spell_list;
extern Classes classes; extern Classes classes;

View File

@ -22,8 +22,8 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#define __AltAdvancement__ #define __AltAdvancement__
#include <vector> #include <vector>
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/packet/eq_packet.hpp" #include "../../common/EQPacket.h"
#include "../client.h" #include "../client.h"
// defines for AA tabs based on group # from DB // defines for AA tabs based on group # from DB

View File

@ -25,8 +25,8 @@
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/database_new.hpp" #include "../../common/DatabaseNew.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "AltAdvancement.h" #include "AltAdvancement.h"
extern MasterAAList master_aa_list; extern MasterAAList master_aa_list;

View File

@ -2,7 +2,7 @@
#include "BotBrain.h" #include "BotBrain.h"
#include "../Trade.h" #include "../Trade.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../classes.h" #include "../classes.h"
#include "../SpellProcess.h" #include "../SpellProcess.h"

View File

@ -1,5 +1,5 @@
#include "../Commands/Commands.h" #include "../Commands/Commands.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../classes.h" #include "../classes.h"
#include "../races.h" #include "../races.h"
#include "../Bots/Bot.h" #include "../Bots/Bot.h"

View File

@ -1,4 +1,4 @@
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "Bot.h" #include "Bot.h"
#include "../classes.h" #include "../classes.h"

View File

@ -0,0 +1,957 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BrokerManager.h"
#include "../Items/Items.h"
#include "../../common/Log.h"
#include "../WorldDatabase.h"
#include "../World.h"
#include "../Web/PeerManager.h"
#include <cstdlib>
extern WorldDatabase database;
extern ZoneList zone_list;
extern PeerManager peer_manager;
BrokerManager::BrokerManager() {
}
void BrokerManager::AddSeller(int32 cid,
const std::string& name,
int32 hid,
bool enabled,
bool invFlag)
{
int64 prevSession = 0, prevTotal = 0;
{
std::shared_lock lock(mtx_);
auto sit = players_.find(cid);
if (sit != players_.end()) {
prevSession = sit->second.coin_session;
prevTotal = sit->second.coin_total;
}
}
SellerInfo info{cid, name, hid, enabled, invFlag, prevSession, prevTotal};
{
std::unique_lock lock(mtx_);
players_[cid] = info;
}
SavePlayerToDB(info);
peer_manager.sendPeersAddSeller(cid, hid, name, enabled, invFlag);
}
void BrokerManager::LoadSeller(int32 cid, const std::string& name,
int32 hid, bool enabled, bool invFlag,
int64 coin_session, int64 coin_total)
{
SellerInfo info{cid, name, hid, enabled, invFlag, coin_session, coin_total};
{
std::unique_lock lock(mtx_);
players_[cid] = info;
}
}
int64 BrokerManager::ResetSellerSessionCoins(int32 cid) {
database.ClearSellerSession(cid);
int64 session_coin = 0;
{
std::unique_lock lock(mtx_);
auto it = players_.find(cid);
if (it == players_.end()) return 0;
session_coin = it->second.coin_session;
it->second.coin_total += it->second.coin_session;
it->second.coin_session = 0;
}
return session_coin;
}
void BrokerManager::AddSellerSessionCoins(int32 cid, uint64 session) {
database.AddToSellerSession(cid, session);
{
std::unique_lock lock(mtx_);
auto it = players_.find(cid);
if (it == players_.end()) return;
it->second.coin_session += session;
}
}
void BrokerManager::RemoveSeller(int32 cid, bool peerCacheOnly)
{
{
std::unique_lock lock(mtx_);
players_.erase(cid);
active_items_by_char_.erase(cid);
inactive_items_by_char_.erase(cid);
}
if(!peerCacheOnly)
peer_manager.sendPeersRemoveSeller(cid);
}
void BrokerManager::AddItem(const SaleItem& item, bool peerCacheOnly)
{
{
std::unique_lock lock(mtx_);
auto& a = active_items_by_char_[item.character_id];
auto& i = inactive_items_by_char_[item.character_id];
a.erase(item.unique_id);
i.erase(item.unique_id);
if (item.for_sale) a[item.unique_id] = item;
else i[item.unique_id] = item;
}
SaveItemToDB(item);
if(!peerCacheOnly)
peer_manager.sendPeersAddItemSale(item.character_id, item.house_id, item.item_id, item.unique_id, item.cost_copper, item.inv_slot_id,
item.slot_id, item.count, item.from_inventory, item.for_sale, item.creator);
}
void BrokerManager::LoadItem(const SaleItem& item)
{
std::unique_lock lock(mtx_);
if (item.for_sale)
active_items_by_char_[item.character_id][item.unique_id] = item;
else
inactive_items_by_char_[item.character_id][item.unique_id] = item;
}
void BrokerManager::SetSaleStatus(int32 cid, int64 uid, bool for_sale)
{
std::optional<SaleItem> toUpdate;
{
std::unique_lock lock(mtx_);
auto& a = active_items_by_char_[cid];
auto& i = inactive_items_by_char_[cid];
if (for_sale) {
LogWrite(PLAYER__ERROR, 5, "Broker",
"--SetSaleStatus: %u (%u), for_sale=%u",
cid, uid, for_sale
);
if (auto it = i.find(uid); it != i.end()) {
LogWrite(PLAYER__ERROR, 5, "Broker",
"--SetSaleStatusFuckOff: %u (%u), for_sale=%u",
cid, uid, for_sale
);
SaleItem copy = it->second;
copy.for_sale = true;
i.erase(it);
a[uid] = copy;
toUpdate = copy;
}
} else {
LogWrite(PLAYER__ERROR, 5, "Broker",
"--SetSaleStatusInactive: %u (%u), for_sale=%u",
cid, uid, for_sale
);
if (auto it = a.find(uid); it != a.end()) {
LogWrite(PLAYER__ERROR, 5, "Broker",
"--SetSaleStatusFuckyou: %u (%u), for_sale=%u",
cid, uid, for_sale
);
SaleItem copy = it->second;
copy.for_sale = false;
a.erase(it);
i[uid] = copy;
toUpdate = copy;
}
}
}
if (toUpdate) {
SaveItemToDB(*toUpdate);
peer_manager.sendPeersAddItemSale(toUpdate->character_id, toUpdate->house_id, toUpdate->item_id, toUpdate->unique_id, toUpdate->cost_copper, toUpdate->inv_slot_id,
toUpdate->slot_id, toUpdate->count, toUpdate->from_inventory, toUpdate->for_sale, toUpdate->creator);
}
}
bool BrokerManager::IsItemListed(int32 cid, int64 uid) {
std::shared_lock lock(mtx_);
auto& active_map = active_items_by_char_[cid];
auto& inactive_map = inactive_items_by_char_[cid];
auto it = inactive_map.find(uid);
if (it != inactive_map.end()) {
return true;
}
auto it2 = active_map.find(uid);
if (it2 != active_map.end()) {
return true;
}
return false;
}
void BrokerManager::SetSalePrice(int32 cid, int64 uid, int64 price)
{
std::optional<SaleItem> toUpdate;
{
std::unique_lock lock(mtx_);
if (auto ait = active_items_by_char_.find(cid); ait != active_items_by_char_.end()) {
if (auto it = ait->second.find(uid); it != ait->second.end()) {
LogWrite(PLAYER__ERROR, 5, "Broker",
"--SetSalePriceActive: %u (%u), cost=%u",
cid, uid, price
);
it->second.cost_copper = price;
toUpdate = it->second;
}
}
if (auto iit = inactive_items_by_char_.find(cid); iit != inactive_items_by_char_.end()) {
if (auto it = iit->second.find(uid); it != iit->second.end()) {
LogWrite(PLAYER__ERROR, 5, "Broker",
"--SetSalePriceInactive: %u (%u), cost=%u",
cid, uid, price
);
it->second.cost_copper = price;
toUpdate = it->second;
}
}
}
if (toUpdate) {
SaveItemToDB(*toUpdate);
peer_manager.sendPeersAddItemSale(toUpdate->character_id, toUpdate->house_id, toUpdate->item_id, toUpdate->unique_id, toUpdate->cost_copper, toUpdate->inv_slot_id,
toUpdate->slot_id, toUpdate->count, toUpdate->from_inventory, toUpdate->for_sale, toUpdate->creator);
}
}
void BrokerManager::RemoveItem(int32 cid, int64 uid, int16 qty, bool shouldDelete)
{
bool didDelete = false;
std::optional<SaleItem> snapshot;
{
std::unique_lock lock(mtx_);
if (auto ait = active_items_by_char_.find(cid); ait != active_items_by_char_.end()) {
auto& m = ait->second;
if (auto it = m.find(uid); it != m.end()) {
if(shouldDelete)
qty = it->second.count;
it->second.count -= qty;
if (it->second.count <= 0) {
didDelete = true;
snapshot = it->second;
m.erase(it);
} else {
snapshot = it->second;
}
if (m.empty())
active_items_by_char_.erase(ait);
}
}
}
if (didDelete || shouldDelete) {
DeleteItemFromDB(cid, uid);
peer_manager.sendPeersRemoveItemSale(cid, uid);
}
else if (snapshot) {
SaveItemToDB(*snapshot);
peer_manager.sendPeersAddItemSale(snapshot->character_id, snapshot->house_id, snapshot->item_id, snapshot->unique_id, snapshot->cost_copper, snapshot->inv_slot_id,
snapshot->slot_id, snapshot->count, snapshot->from_inventory, snapshot->for_sale, snapshot->creator);
}
}
bool BrokerManager::BuyItem(Client* buyer, int32 seller_cid, int64 uid, int32 quantity)
{
Client* seller = zone_list.GetClientByCharID(seller_cid); // establish if seller is online
if(buyer && buyer->GetCharacterID() == seller_cid) {
buyer->Message(CHANNEL_COLOR_RED, "You cannot buy from yourself!");
return false;
}
if (quantity <= 0 || !IsSaleEnabled(seller_cid) || !IsItemForSale(seller_cid, uid)) {
if(buyer)
buyer->Message(CHANNEL_COLOR_RED, "Quantity not provided (%u), sale is not enabled (sale enabled? %u) or item is not for sale (itemforsale? %u).", quantity, IsSaleEnabled(seller_cid), IsItemForSale(seller_cid, uid));
return false;
}
int64 price = GetSalePrice(seller_cid, uid) * quantity;
if(!buyer || !buyer->GetPlayer() || !buyer->GetPlayer()->RemoveCoins(price)) {
if(buyer)
buyer->Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase.");
return false;
}
if (!database.RemoveBrokerItem(seller_cid, uid, quantity)) {
buyer->GetPlayer()->AddCoins(price);
buyer->Message(CHANNEL_COLOR_RED, "Failed to remove broker item from database.");
return false;
}
Item* giveItem = nullptr;
int16 result_count = 0;
std::string creator;
bool deleteItem = false;
int16 quantity_left = 0;
// 2) Mirror in-memory
std::optional<SaleItem> toUpdate;
{
std::unique_lock lock(mtx_);
if (auto sit = active_items_by_char_.find(seller_cid); sit != active_items_by_char_.end()) {
auto& m = sit->second;
if (auto it = m.find(uid); it != m.end()) {
creator = it->second.creator;
giveItem = master_item_list.GetItem(it->second.item_id);
SaleItem copy = it->second;
toUpdate = copy;
if (it->second.count > quantity) {
it->second.count -= quantity;
quantity_left = it->second.count;
result_count = quantity;
if(seller && seller->GetPlayer()) {
seller->GetPlayer()->item_list.SetVaultItemUniqueIDCount(seller, uid, it->second.count);
}
}
else {
result_count = it->second.count;
if(seller && seller->GetPlayer()) {
seller->GetPlayer()->item_list.RemoveVaultItemFromUniqueID(seller, uid);
}
m.erase(it);
deleteItem = true;
}
if (m.empty())
active_items_by_char_.erase(sit);
}
}
}
if(!giveItem) {
buyer->GetPlayer()->AddCoins(price);
buyer->Message(CHANNEL_COLOR_RED, "Failed to find item on broker memory.");
if(toUpdate)
AddItem(*toUpdate);
return false;
}
Item* resultItem = new Item(giveItem);
resultItem->details.count = result_count;
resultItem->creator = creator;
if (buyer->GetPlayer()->item_list.HasFreeSlot() || buyer->GetPlayer()->item_list.CanStack(resultItem, false)) {
if(!buyer->AddItem(resultItem, nullptr, AddItemType::BUY_FROM_BROKER)) {
buyer->GetPlayer()->AddCoins(price);
safe_delete(resultItem);
if(toUpdate)
AddItem(*toUpdate);
return false;
}
}
else {
buyer->Message(CHANNEL_COLOR_RED, "You have no free slot available.");
buyer->GetPlayer()->AddCoins(price);
safe_delete(resultItem);
if(toUpdate)
AddItem(*toUpdate);
return false;
}
if(deleteItem) {
DeleteItemFromDB(seller_cid, uid);
DeleteCharacterItemFromDB(seller_cid, uid);
}
else if(quantity_left) {
UpdateCharacterItemDB(seller_cid, uid, quantity_left);
}
AddSellerSessionCoins(seller_cid, price);
LogSale(seller_cid, std::string(buyer->GetPlayer()->GetName()), std::string(resultItem->name), result_count, price);
if(seller) {
std::string logMsg = GetShopPurchaseMessage(buyer->GetPlayer()->GetName(), std::string(resultItem->name), result_count, price);
auto seller_info = GetSellerInfo(seller_cid);
seller->SendHouseSaleLog(logMsg, 0, seller_info ? seller_info->coin_total : 0, 0);
seller->OpenShopWindow(nullptr);
}
return true;
}
void BrokerManager::OnPeerRemoveItem(int32 cid, int64 uid)
{
std::unique_lock lock(mtx_);
if (auto ait = active_items_by_char_.find(cid); ait != active_items_by_char_.end())
ait->second.erase(uid);
if (auto iit = inactive_items_by_char_.find(cid); iit != inactive_items_by_char_.end())
iit->second.erase(uid);
}
bool BrokerManager::IsItemForSale(int32 cid, int64 uid) const
{
std::shared_lock lock(mtx_);
if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end())
return pit->second.find(uid) != pit->second.end();
return false;
}
int64 BrokerManager::GetSalePrice(int32 cid, int64 uid)
{
std::shared_lock lock(mtx_);
if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) {
if (auto it = pit->second.find(uid); it != pit->second.end())
return it->second.cost_copper;
}
if (auto pit2 = inactive_items_by_char_.find(cid); pit2 != inactive_items_by_char_.end()) {
if (auto it = pit2->second.find(uid); it != pit2->second.end())
return it->second.cost_copper;
}
return 0;
}
std::vector<SaleItem> BrokerManager::GetActiveForSaleItems(int32 cid) const
{
std::shared_lock lock(mtx_);
std::vector<SaleItem> out;
if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) {
for (auto const& kv : pit->second)
out.push_back(kv.second);
}
return out;
}
std::optional<SaleItem> BrokerManager::GetActiveItem(int32 cid, int64 uid) const
{
std::shared_lock lock(mtx_);
auto pit = active_items_by_char_.find(cid);
if (pit == active_items_by_char_.end())
return std::nullopt;
auto it = pit->second.find(uid);
if (it == pit->second.end())
return std::nullopt;
return it->second; // copy out the SaleItem
}
bool BrokerManager::IsSellingItems(int32 cid, bool vaultOnly) const
{
// Grab shared lock for threadsafe read
std::shared_lock lock(mtx_);
// Find this characters active sales
auto pit = active_items_by_char_.find(cid);
if (pit == active_items_by_char_.end())
return false; // no items => not selling from vault
// Scan through each SaleItem; if any has sell_from_inventory == false,
// that means its coming from the vault
for (auto const& kv : pit->second) {
const SaleItem& item = kv.second;
if ((item.for_sale && (!item.from_inventory || !vaultOnly)))
return true;
}
return false;
}
std::vector<SaleItem> BrokerManager::GetInactiveItems(int32 cid) const
{
std::shared_lock lock(mtx_);
std::vector<SaleItem> out;
if (auto pit = inactive_items_by_char_.find(cid); pit != inactive_items_by_char_.end()) {
for (auto const& kv : pit->second)
out.push_back(kv.second);
}
return out;
}
std::vector<std::pair<int64,int32>>
BrokerManager::GetUniqueIDsAndCost(int32 cid) const
{
std::shared_lock lock(mtx_);
std::vector<std::pair<int64,int32>> out;
if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) {
for (auto const& kv : pit->second)
out.emplace_back(kv.second.unique_id, kv.second.cost_copper);
}
return out;
}
vector<Item*>* BrokerManager::GetItems(
const std::string& name,
int64 itype,
int64 ltype,
int64 btype,
int64 minprice,
int64 maxprice,
int8 minskill,
int8 maxskill,
const std::string& seller,
const std::string& adornment,
int8 mintier,
int8 maxtier,
int16 minlevel,
int16 maxlevel,
int8 itemclass
) const {
vector<Item*>* ret = new vector<Item*>;
string lower_name = ::ToLower(string(name.c_str()));
std::shared_lock lock(mtx_);
std::vector<SaleItem> out;
for (auto const& char_pair : active_items_by_char_) {
int32 cid = char_pair.first;
auto pit_player = players_.find(cid);
if (pit_player == players_.end())
continue;
bool allowInv = pit_player->second.sell_from_inventory;
for (auto const& kv : char_pair.second) {
auto const& itm = kv.second;
LogWrite(PLAYER__DEBUG, 5, "Broker",
"--GetItems: %u (selling: %u), allowinv: %u",
itm.unique_id, itm.for_sale, allowInv
);
if (!itm.for_sale) continue;
if (!allowInv && itm.from_inventory) continue;
Item* def = master_item_list.GetItem(itm.item_id);
if (!def) continue;
LogWrite(PLAYER__DEBUG, 5, "Broker",
"--GetItems#1: %u (selling: %u), allowinv: %u",
itm.unique_id, itm.for_sale, allowInv
);
if (!name.empty() && def->lowername.find(lower_name) == std::string::npos) continue;
if (itype!=ITEM_BROKER_TYPE_ANY && itype !=ITEM_BROKER_TYPE_ANY64BIT && !master_item_list.ShouldAddItemBrokerType(def, itype)) continue;
if (ltype!=ITEM_BROKER_SLOT_ANY && !master_item_list.ShouldAddItemBrokerSlot(def, ltype)) continue;
if (btype!=0xFFFFFFFF && !master_item_list.ShouldAddItemBrokerStat(def, btype)) continue;
LogWrite(PLAYER__DEBUG, 5, "Broker",
"--GetItems#2: %u (cost_copper: %u), seller: %s",
itm.unique_id, itm.cost_copper, seller.c_str()
);
//if (itm.cost_copper < minprice || itm.cost_copper > maxprice) continue;
//if (!seller.empty() && pit_player->second.seller_name.find(seller)==std::string::npos) continue;
/*if(mintier > 1 && def->details.tier < mintier)
continue;
if(maxtier > 0 && def->details.tier > maxtier)
continue;*/
LogWrite(PLAYER__DEBUG, 5, "Broker",
"--GetItems#3: %u (selling: %u), allowinv: %u",
itm.unique_id, itm.for_sale, allowInv
);
if(def->generic_info.adventure_default_level == 0 && def->generic_info.tradeskill_default_level == 0 && minlevel > 0 && maxlevel > 0){
if(def->details.recommended_level < minlevel)
continue;
if(def->details.recommended_level > maxlevel)
continue;
}
else{
if(minlevel > 0 && ((def->generic_info.adventure_default_level == 0 && def->generic_info.tradeskill_default_level == 0) || (def->generic_info.adventure_default_level > 0 && def->generic_info.adventure_default_level < minlevel) || (def->generic_info.tradeskill_default_level > 0 && def->generic_info.tradeskill_default_level < minlevel)))
continue;
if(maxlevel > 0 && ((def->generic_info.adventure_default_level > 0 && def->generic_info.adventure_default_level > maxlevel) || (def->generic_info.tradeskill_default_level > 0 && def->generic_info.tradeskill_default_level > maxlevel)))
continue;
}
if (itemclass>0) {
int64 bit = ((int64)2 << (itemclass-1));
if (!(def->generic_info.adventure_classes & bit) &&
!(def->generic_info.tradeskill_classes & bit))
continue;
}
LogWrite(PLAYER__DEBUG, 5, "Broker",
"--GetItemsPass: %u (selling: %u), allowinv: %u",
itm.unique_id, itm.for_sale, allowInv
);
ret->push_back(new Item(def, itm.unique_id, itm.creator, pit_player->second.seller_name, cid, itm.cost_copper, itm.count, pit_player->second.house_id, itm.from_inventory));
}
}
return ret;
}
std::string BrokerManager::GetSellerName(int32 cid) const {
std::shared_lock lock(mtx_);
auto it = players_.find(cid);
return (it != players_.end()) ? it->second.seller_name : std::string();
}
bool BrokerManager::IsSaleEnabled(int32 cid) const {
std::shared_lock lock(mtx_);
auto it = players_.find(cid);
return (it != players_.end()) ? it->second.sale_enabled : false;
}
bool BrokerManager::CanSellFromInventory(int32 cid) const {
std::shared_lock lock(mtx_);
auto it = players_.find(cid);
return (it != players_.end()) ? it->second.sell_from_inventory : false;
}
int32 BrokerManager::GetHouseID(int32 cid) const {
std::shared_lock lock(mtx_);
auto it = players_.find(cid);
return (it != players_.end()) ? it->second.house_id : -1;
}
std::optional<SellerInfo> BrokerManager::GetSellerInfo(int32 cid) const {
std::shared_lock lock(mtx_);
auto it = players_.find(cid);
if (it == players_.end())
return std::nullopt;
return it->second;
}
void BrokerManager::SavePlayerToDB(const SellerInfo& p) {
Query q;
std::ostringstream oss;
oss << "INSERT INTO broker_sellers "
"(character_id,seller_name,house_id,sale_enabled,sell_from_inventory,coin_session,coin_total) "
"VALUES("
<< p.character_id << ",'"
<< EscapeSQLString(p.seller_name) << "',"
<< p.house_id << ","
<< (p.sale_enabled ? 1 : 0) << ","
<< (p.sell_from_inventory ? 1 : 0) << ","
<< p.coin_session << ","
<< p.coin_total
<< ") ON DUPLICATE KEY UPDATE "
"seller_name=VALUES(seller_name),"
"house_id=VALUES(house_id),"
"sale_enabled=VALUES(sale_enabled),"
"sell_from_inventory=VALUES(sell_from_inventory),"
"coin_session=VALUES(coin_session), "
"coin_total=VALUES(coin_total)";
std::string sql = oss.str();
q.AddQueryAsync(p.character_id, &database, Q_INSERT,
sql.c_str()
);
}
#include <sstream>
void BrokerManager::SaveItemToDB(const SaleItem& i) {
// 2) Build full SQL
std::ostringstream oss;
oss
<< "INSERT INTO broker_items "
"(unique_id,character_id,house_id,item_id,cost_copper,for_sale,"
"inv_slot_id,slot_id,`count`,from_inventory,creator) "
"VALUES("
<< i.unique_id << ","
<< i.character_id << ","
<< i.house_id << ","
<< i.item_id << ","
<< i.cost_copper << ","
<< (i.for_sale ? 1 : 0) << ","
<< i.inv_slot_id << ","
<< i.slot_id << ","
<< i.count << ","
<< (i.from_inventory ? 1 : 0) << ",'"
<< EscapeSQLString(i.creator) << "') "
"ON DUPLICATE KEY UPDATE "
"house_id=VALUES(house_id),"
"item_id=VALUES(item_id),"
"cost_copper=VALUES(cost_copper),"
"for_sale=VALUES(for_sale),"
"inv_slot_id=VALUES(inv_slot_id),"
"slot_id=VALUES(slot_id),"
"`count`=VALUES(`count`),"
"from_inventory=VALUES(from_inventory),"
"creator=VALUES(creator)";
std::string sql = oss.str();
Query q;
q.AddQueryAsync(i.character_id, &database, Q_INSERT, sql.c_str());
}
void BrokerManager::UpdateItemInDB(const SaleItem& i) {
Query q;
std::ostringstream oss;
oss << "UPDATE broker_items SET "
<< "house_id=" << i.house_id
<< ",item_id=" << i.item_id
<< ",cost_copper=" << i.cost_copper
<< ",for_sale=" << (i.for_sale ? 1 : 0)
<< ",inv_slot_id=" << i.inv_slot_id
<< ",slot_id=" << i.slot_id
<< ",count=" << i.count
<< ",from_inventory=" << (i.from_inventory ? 1 : 0)
<< ",creator='" << EscapeSQLString(i.creator) << "' "
<< "WHERE unique_id=" << i.unique_id
<< " AND character_id=" << i.character_id;
std::string sql = oss.str();
q.AddQueryAsync(i.character_id, &database, Q_UPDATE,
sql.c_str()
);
}
void BrokerManager::DeleteItemFromDB(int32 cid, int64 uid) {
Query q;
q.AddQueryAsync(cid, &database, Q_DELETE,
"DELETE FROM broker_items WHERE unique_id=%llu AND character_id=%u",
uid, cid
);
}
void BrokerManager::DeleteCharacterItemFromDB(int32 cid, int64 uid) {
Query q;
q.AddQueryAsync(cid, &database, Q_DELETE,
"DELETE FROM character_items WHERE id=%llu AND char_id=%u",
uid, cid
);
}
void BrokerManager::UpdateCharacterItemDB(int32 cid, int64 uid, int16 count) {
Query q;
q.AddQueryAsync(cid, &database, Q_UPDATE,
"UPDATE character_items set count=%u WHERE id=%llu AND char_id=%u",
count, uid, cid
);
}
void BrokerManager::DeletePlayerFromDB(int32 cid) {
Query q;
// delete from broker_sellers
q.AddQueryAsync(cid, &database, Q_DELETE,
"DELETE FROM broker_sellers WHERE character_id=%u", cid);
// delete all their items
q.AddQueryAsync(cid, &database, Q_DELETE,
"DELETE FROM broker_items WHERE character_id=%u", cid);
}
bool BrokerManager::IsItemFromInventory(int32 cid, int64 uid) const {
std::shared_lock lock(mtx_);
// 1) Check active items for that character
auto pit = active_items_by_char_.find(cid);
if (pit != active_items_by_char_.end()) {
auto it = pit->second.find(uid);
if (it != pit->second.end())
return it->second.from_inventory;
}
// 2) Otherwise check inactive items
auto iit = inactive_items_by_char_.find(cid);
if (iit != inactive_items_by_char_.end()) {
auto it = iit->second.find(uid);
if (it != iit->second.end())
return it->second.from_inventory;
}
// 3) Not found → false
return false;
}
void BrokerManager::LockActiveItemsForClient(Client* client) const {
int32 cid = client->GetCharacterID();
auto infoOpt = GetSellerInfo(cid);
if (!infoOpt)
return;
const SellerInfo& info = *infoOpt;
{
auto items = GetActiveForSaleItems(cid);
for (auto const& itm : items) {
if (!itm.for_sale) {
client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, itm.unique_id, false, true);
continue;
}
LogWrite(PLAYER__ERROR, 5, "Broker",
"--LockActiveItemsForClient: %u (selling: %u), allowinv: %u, sellfrominv: %u",
itm.unique_id, itm.for_sale, itm.from_inventory, info.sell_from_inventory
);
if (!info.sell_from_inventory && itm.from_inventory) {
client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, itm.unique_id, false, true);
continue;
}
client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, itm.unique_id, true, true);
}
}
}
void WorldDatabase::ClearSellerSession(int32 character_id) {
Query q;
std::ostringstream sql;
sql << "UPDATE broker_sellers SET coin_session = 0"
<< " WHERE character_id = " << character_id;
std::string full = sql.str();
q.AddQueryAsync(character_id, &database, Q_INSERT, full.c_str());
}
void WorldDatabase::AddToSellerSession(int32 character_id, int64 amount) {
Query q;
std::ostringstream sql;
sql << "UPDATE broker_sellers SET coin_session = coin_session + "
<< amount
<< " WHERE character_id = " << character_id;
std::string full = sql.str();
q.AddQueryAsync(character_id, &database, Q_INSERT, full.c_str());
}
int64 WorldDatabase::GetSellerSession(int32 character_id) {
Query query;
MYSQL_RES* result = query.RunQuery2(
Q_SELECT,
"SELECT "
"coin_session "
"FROM broker_sellers "
"WHERE character_id = %d "
"LIMIT 1",
character_id
);
if (result) {
MYSQL_ROW row;
if ((row = mysql_fetch_row(result))) {
return strtoull(row[0], NULL, 0);
}
}
return 0;
}
std::string BrokerManager::GetShopPurchaseMessage(const std::string& buyer_name, const std::string& item_desc, int16 quantity, int64 coin) {
int64 platinum = static_cast<int64>(coin / 1000000);
// 1) Break totalCopper into denominations
int64 rem = coin % 1000000;
int64 gold = static_cast<int64>(rem / 10000);
rem %= 10000;
int64 silver = static_cast<int64>(rem / 100);
int64 copper = static_cast<int64>(rem % 100);
// 1) Timestamp
auto now = std::time(nullptr);
std::tm tm;
localtime_r(&now, &tm);
char timebuf[64];
std::strftime(timebuf, sizeof(timebuf),
"%B %d, %Y, %I:%M:%S %p", &tm);
// 2) Build the sale line
std::ostringstream msg;
msg << timebuf
<< " " << buyer_name << " buys ";
if (quantity > 1)
msg << quantity << " ";
msg << item_desc
<< " for ";
// 3) Denominations
std::vector<std::string> parts;
if (platinum > 0) parts.push_back(std::to_string(platinum) + " Platinum");
if (gold > 0) parts.push_back(std::to_string(gold) + " Gold");
if (silver > 0) parts.push_back(std::to_string(silver) + " Silver");
if (copper > 0) parts.push_back(std::to_string(copper) + " Copper");
// If all are zero (unlikely), still print "0 Copper"
if (parts.empty())
parts.push_back("0 Copper");
// Join with ", "
for (size_t i = 0; i < parts.size(); ++i) {
if (i) msg << ", ";
msg << parts[i];
}
return msg.str();
}
void BrokerManager::LogSale(int32 cid,
const std::string& buyer_name,
const std::string& item_desc,
int16 quantity,
int64 coin)
{
std::string msg = GetShopPurchaseMessage(buyer_name, item_desc, quantity, coin);
std::string esc = EscapeSQLString(msg.c_str());
std::ostringstream sql;
sql << "INSERT INTO broker_seller_log "
"(character_id,log_time,message) VALUES ("
<< cid << ",NOW(),'"
<< esc << "')";
std::string full = sql.str();
Query q;
q.AddQueryAsync(cid, &database, Q_INSERT, full.c_str());
}
void BrokerManager::LogSaleMessage(int32 cid,
const std::string& log_message)
{
auto now = std::time(nullptr);
std::tm tm;
localtime_r(&now, &tm);
char timebuf[64];
std::strftime(timebuf, sizeof(timebuf),
"%B %d, %Y, %I:%M:%S %p", &tm);
std::ostringstream msg;
msg << timebuf
<< " " << log_message;
std::string esc = EscapeSQLString(msg.str());
std::ostringstream sql;
sql << "INSERT INTO broker_seller_log "
"(character_id,log_time,message) VALUES ("
<< cid << ",NOW(),'"
<< esc << "')";
std::string full = sql.str();
Query q;
q.AddQueryAsync(cid, &database, Q_INSERT, full.c_str());
}
std::vector<SellerLog> BrokerManager::GetSellerLog(int32 cid) const {
std::vector<SellerLog> out;
Query query;
MYSQL_RES* result = query.RunQuery2(
Q_SELECT,
"SELECT "
"log_id, "
"DATE_FORMAT(log_time, '%%M %%d, %%Y, %%r') as ts, "
"message "
"FROM broker_seller_log "
"WHERE character_id=%u "
"ORDER BY log_time ASC",
cid
);
if (result) {
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
SellerLog entry;
entry.log_id = row[0] ? atoll(row[0]) : 0;
entry.timestamp = row[1] ? row[1] : "";
entry.message = row[2] ? row[2] : "";
out.push_back(std::move(entry));
}
}
return out;
}

View File

@ -0,0 +1,182 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <string>
#include <optional>
#include <shared_mutex>
#include "../../common/types.h"
// Key = (character_id, unique_id)
using Key = std::pair<int32, int64>;
class Item;
class Client;
struct SaleItem {
int64 unique_id;
int32 character_id;
int32 house_id;
int64 item_id;
int64 cost_copper;
bool for_sale;
sint32 inv_slot_id;
int16 slot_id;
int16 count;
bool from_inventory;
string creator;
};
struct SellerInfo {
int32 character_id;
std::string seller_name;
int64 house_id;
bool sale_enabled;
bool sell_from_inventory;
int64 coin_session;
int64 coin_total;
};
struct SellerLog {
int64 log_id; // auto_increment PK
std::string timestamp; // e.g. "October 20, 2005, 05:29:33 AM"
std::string message; // the human-readable text
};
class BrokerManager {
public:
BrokerManager();
// Player management
void AddSeller(int32 cid,
const std::string& name,
int32 house_id,
bool sale_enabled,
bool sell_from_inventory);
void LoadSeller(int32 cid,
const std::string& name,
int32 house_id,
bool sale_enabled,
bool sell_from_inventory,
int64 coin_session,
int64 coin_total);
int64 ResetSellerSessionCoins(int32 cid);
void AddSellerSessionCoins(int32 cid, uint64 session);
void RemoveSeller(int32 character_id, bool peerCacheOnly = false);
// Item management
void AddItem(const SaleItem& item, bool peerCacheOnly = false);
void LoadItem(const SaleItem& item);
// Activate / deactivate sale flag
void SetSaleStatus(int32 cid, int64 uid, bool for_sale);
bool IsItemListed(int32 cid, int64 uid);
void SetSalePrice(int32 cid, int64 uid, int64 price);
int64 GetSalePrice(int32 cid, int64 uid);
// Remove quantity
void RemoveItem(int32 cid, int64 uid, int16 quantity = 1, bool shouldDelete = false);
// Attempt to buy (atomic DB + in-memory + broadcast)
bool BuyItem(Client* buyer, int32 seller_cid, int64 uid, int32 quantity);
bool IsItemForSale(int32 seller_cid, int64 uid) const;
// Called when a peer notifies that an item was sold/removed (in-memory only)
void OnPeerRemoveItem(int32 character_id, int64 unique_id);
// Queries
std::vector<SaleItem> GetActiveForSaleItems(int32 cid) const;
std::optional<SaleItem> GetActiveItem(int32 cid, int64 uid) const;
bool IsSellingItems(int32 cid, bool vaultOnly = false) const;
std::vector<SaleItem> GetInactiveItems(int32 cid) const;
// Global search API (only active_items_)
vector<Item*>* GetItems(
const std::string& name,
int64 itype,
int64 ltype,
int64 btype,
int64 minprice,
int64 maxprice,
int8 minskill,
int8 maxskill,
const std::string& seller,
const std::string& adornment,
int8 mintier,
int8 maxtier,
int16 minlevel,
int16 maxlevel,
int8 itemclass
) const;
// UI helper: get (unique_id, cost) for active items
std::vector<std::pair<int64,int32>> GetUniqueIDsAndCost(int32 cid) const;
std::optional<SellerInfo> GetSellerInfo(int32 character_id) const;
// Lookup seller name
std::string GetSellerName(int32 cid) const;
bool IsSaleEnabled(int32 cid) const;
bool CanSellFromInventory(int32 cid) const;
int32 GetHouseID(int32 cid) const;
bool IsItemFromInventory(int32 cid, int64 uid) const;
void LockActiveItemsForClient(Client* client) const;
std::string GetShopPurchaseMessage(const std::string& buyer_name, const std::string& item_desc, int16 quantity, int64 coin);
void LogSale(int32 character_id, const std::string& buyer_name,const std::string& item_desc, int16 quantity, int64 coin);
void LogSaleMessage(int32 character_id,const std::string& log_message);
std::vector<SellerLog> GetSellerLog(int32 character_id) const;
static std::string EscapeSQLString(const std::string& s) {
std::string out;
out.reserve(s.size() * 2);
for (char c : s) {
if (c == '\'') out += "''";
else out += c;
}
return out;
}
private:
mutable std::shared_mutex mtx_;
std::unordered_map<int32,SellerInfo> players_;
std::unordered_map<
int32,
std::unordered_map<int64_t, SaleItem>
> active_items_by_char_;
std::unordered_map<
int32,
std::unordered_map<int64_t, SaleItem>
> inactive_items_by_char_;
// DB sync (async writes)
void SavePlayerToDB(const SellerInfo& p);
void SaveItemToDB(const SaleItem& i);
void UpdateItemInDB(const SaleItem& i);
void DeleteItemFromDB(int32 character_id, int64 unique_id);
void DeleteCharacterItemFromDB(int32 cid, int64 uid);
void UpdateCharacterItemDB(int32 cid, int64 uid, int16 count);
void DeletePlayerFromDB(int32 cid);
};

View File

@ -20,7 +20,7 @@
#include "Chat.h" #include "Chat.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
#include "../../common/PacketStruct.h" #include "../../common/PacketStruct.h"
#include "../Rules/Rules.h" #include "../Rules/Rules.h"
extern RuleManager rule_manager; extern RuleManager rule_manager;

View File

@ -21,9 +21,9 @@
#define CHAT_CHAT_H_ #define CHAT_CHAT_H_
#include <vector> #include <vector>
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/packet/eq_packet.hpp" #include "../../common/EQPacket.h"
#include "../../common/Mutex.h"
#include "../client.h" #include "../client.h"
#include "ChatChannel.h" #include "ChatChannel.h"

View File

@ -1,6 +1,6 @@
#include <string.h> #include <string.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
#include "../../common/PacketStruct.h" #include "../../common/PacketStruct.h"
#include "../World.h" #include "../World.h"
#include "ChatChannel.h" #include "ChatChannel.h"

View File

@ -21,7 +21,7 @@
#ifndef CHAT_CHATCHANNEL_H_ #ifndef CHAT_CHATCHANNEL_H_
#define CHAT_CHATCHANNEL_H_ #define CHAT_CHATCHANNEL_H_
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../client.h" #include "../client.h"
#include <vector> #include <vector>

View File

@ -21,7 +21,7 @@
#include "../../common/Log.h" #include "../../common/Log.h"
#include "Chat.h" #include "Chat.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
extern Chat chat; extern Chat chat;

View File

@ -18,12 +18,12 @@
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "ClientPacketFunctions.h" #include "ClientPacketFunctions.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "Variables.h" #include "Variables.h"
#include "World.h" #include "World.h"
#include "classes.h" #include "classes.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "Traits/Traits.h" #include "Traits/Traits.h"
extern Classes classes; extern Classes classes;
@ -354,7 +354,7 @@ void ClientPacketFunctions::SendInstanceList(Client* client) {
} }
void ClientPacketFunctions::SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type){ void ClientPacketFunctions::SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type){
if (!client) if (!client || client->GetVersion() <= 561) // KoS and earlier clients don't support this packet, best to just return and not spam logs w/ errors
return; return;
PacketStruct* packet = configReader.getStruct("WS_UpdateMaintainedExamine", client->GetVersion()); PacketStruct* packet = configReader.getStruct("WS_UpdateMaintainedExamine", client->GetVersion());

View File

@ -1,7 +1,8 @@
#ifndef COLLECTIONS_H_ #ifndef COLLECTIONS_H_
#define COLLECTIONS_H_ #define COLLECTIONS_H_
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/Mutex.h"
#include "../Items/Items.h" #include "../Items/Items.h"
#include <map> #include <map>
#include <vector> #include <vector>

View File

@ -24,7 +24,7 @@
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "Collections.h" #include "Collections.h"
extern MasterCollectionList master_collection_list; extern MasterCollectionList master_collection_list;

View File

@ -19,10 +19,10 @@
*/ */
#include "Combat.h" #include "Combat.h"
#include "client.h" #include "client.h"
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "classes.h" #include "classes.h"
#include "../common/debug.hpp" #include "../common/debug.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "zoneserver.h" #include "zoneserver.h"
#include "Skills.h" #include "Skills.h"
#include "classes.h" #include "classes.h"
@ -141,6 +141,10 @@ bool Entity::AttackAllowed(Entity* target, float distance, bool range_attack) {
} }
} }
if(attacker->IsNPC() && target->IsNPC() && attacker->GetFactionID() > 10 && attacker->GetFactionID() == target->GetFactionID()) {
return false;
}
if (attacker->IsPlayer() && target->IsPlayer()) if (attacker->IsPlayer() && target->IsPlayer())
{ {
bool pvp_allowed = rule_manager.GetZoneRule(GetZoneID(), R_PVP, AllowPVP)->GetBool(); bool pvp_allowed = rule_manager.GetZoneRule(GetZoneID(), R_PVP, AllowPVP)->GetBool();
@ -275,6 +279,8 @@ void Entity::MeleeAttack(Spawn* victim, float distance, bool primary, bool multi
CheckEncounterState((Entity*)victim); CheckEncounterState((Entity*)victim);
} }
CheckProcs(PROC_TYPE_PHYSICAL_ATTEMPT, victim);
if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL){ if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL){
/*if(GetAdventureClass() == MONK){ /*if(GetAdventureClass() == MONK){
max_damage*=3; max_damage*=3;
@ -290,7 +296,7 @@ void Entity::MeleeAttack(Spawn* victim, float distance, bool primary, bool multi
DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG, damage_type, min_damage, max_damage, 0); DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG, damage_type, min_damage, max_damage, 0);
} }
else*/ else*/
DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, damage_type, min_damage, max_damage, 0); DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, damage_type, min_damage, max_damage, 0, 0, false, false, false, false, nullptr, false, true);
if (!multi_attack) { if (!multi_attack) {
CheckProcs(PROC_TYPE_OFFENSIVE, victim); CheckProcs(PROC_TYPE_OFFENSIVE, victim);
CheckProcs(PROC_TYPE_PHYSICAL_OFFENSIVE, victim); CheckProcs(PROC_TYPE_PHYSICAL_OFFENSIVE, victim);
@ -351,10 +357,13 @@ void Entity::MeleeAttack(Spawn* victim, float distance, bool primary, bool multi
} }
} }
void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack) { bool Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack) {
if(!victim) if(!victim)
return; return false;
CheckProcs(PROC_TYPE_PHYSICAL_ATTEMPT, victim);
bool item_deleted = false;
if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()) { if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()) {
if(weapon->ranged_info->range_low <= distance && (weapon->ranged_info->range_high + ammo->thrown_info->range) >= distance) { if(weapon->ranged_info->range_low <= distance && (weapon->ranged_info->range_high + ammo->thrown_info->range) >= distance) {
int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, ammo->thrown_info->hit_bonus, false); int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, ammo->thrown_info->hit_bonus, false);
@ -368,6 +377,9 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
else else
GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, hit_result, ammo->thrown_info->damage_type, 0, 0); GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, hit_result, ammo->thrown_info->damage_type, 0, 0);
bool inv_slot_incl = false;
if(ammo->details.inv_slot_id >= 6)
inv_slot_incl = true;
// If is a player subtract ammo // If is a player subtract ammo
if (IsPlayer()) { if (IsPlayer()) {
if (ammo->details.count > 1) { if (ammo->details.count > 1) {
@ -376,11 +388,15 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
} }
else { else {
if(ammo->details.inv_slot_id >= 6) { if(ammo->details.inv_slot_id >= 6) {
((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, false); ((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id);
((Player*)this)->item_list.DestroyItem(ammo->details.index); ((Player*)this)->item_list.DestroyItem(ammo->details.index);
ammo = nullptr; // item is gone
item_deleted = true;
} }
else { else {
((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, true); ((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, true);
ammo = nullptr;
item_deleted = true;
} }
} }
@ -390,7 +406,7 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
if(outapp) if(outapp)
client->QueuePacket(outapp); client->QueuePacket(outapp);
if(ammo->details.inv_slot_id > 6) { if(inv_slot_incl) {
EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
if (outapp) if (outapp)
client->QueuePacket(outapp); client->QueuePacket(outapp);
@ -433,26 +449,28 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
} }
} }
//Multi Attack roll //Multi Attack roll
if(!multi_attack){ if(!multi_attack && !item_deleted){
float multi_attack = info_struct.get_multi_attack(); float multi_attack = info_struct.get_multi_attack();
if(multi_attack > 0){ if(multi_attack > 0){
float chance = multi_attack; float chance = multi_attack;
if (multi_attack > 100){ if (multi_attack > 100){
int8 automatic_multi = (int8)floor((float)(multi_attack / 100)); int8 automatic_multi = (int8)floor((float)(multi_attack / 100));
chance = (multi_attack - (floor((float)(multi_attack / 100) * 100))); chance = (multi_attack - (floor((float)(multi_attack / 100) * 100)));
while(automatic_multi > 0){ while(!item_deleted && automatic_multi > 0){
RangeAttack(victim, 100, weapon, ammo, true); item_deleted = RangeAttack(victim, 100, weapon, ammo, true);
automatic_multi--; automatic_multi--;
} }
} }
if (MakeRandomFloat(0, 100) <= chance) if (!item_deleted && MakeRandomFloat(0, 100) <= chance)
RangeAttack(victim, 100, weapon, ammo, true); item_deleted = RangeAttack(victim, 100, weapon, ammo, true);
} }
} }
//Apply attack speed mods //Apply attack speed mods
if(!multi_attack) if(!multi_attack)
SetAttackDelay(false, true); SetAttackDelay(false, true);
return item_deleted;
} }
bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs, int8 override_packet_type, bool take_power){ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs, int8 override_packet_type, bool take_power){
@ -481,6 +499,9 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
if(victim->IsEntity()) { if(victim->IsEntity()) {
CheckEncounterState((Entity*)victim); CheckEncounterState((Entity*)victim);
} }
CheckProcs(PROC_TYPE_MAGICAL_ATTEMPT, victim);
bool successful_hit = true; bool successful_hit = true;
if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) {
luaspell->last_spellattack_hit = true; luaspell->last_spellattack_hit = true;
@ -495,26 +516,6 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
CheckProcs(PROC_TYPE_OFFENSIVE, victim); CheckProcs(PROC_TYPE_OFFENSIVE, victim);
CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim); CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim);
if(spell->GetSpellData()->success_message.length() > 0){
Client* client = nullptr;
if(IsPlayer())
client = ((Player*)this)->GetClient();
if(client){
string success_message = spell->GetSpellData()->success_message;
if(success_message.find("%t") < 0xFFFFFFFF)
success_message.replace(success_message.find("%t"), 2, victim->GetName());
client->Message(CHANNEL_YOU_CAST, success_message.c_str());
//commented out the following line as it was causing a duplicate message EmemJR 5/4/2019
//GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, spell->GetName());
}
}
if(spell->GetSpellData()->effect_message.length() > 0){
string effect_message = spell->GetSpellData()->effect_message;
if(effect_message.find("%t") < 0xFFFFFFFF)
effect_message.replace(effect_message.find("%t"), 2, victim->GetName());
GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_message.c_str(), victim, 50);
}
} }
else { else {
successful_hit = false; successful_hit = false;
@ -603,15 +604,15 @@ bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32
if(IsPlayer()) if(IsPlayer())
client = ((Player*)this)->GetClient(); client = ((Player*)this)->GetClient();
if(client) { if(client) {
if(success_msg.find("%t") < 0xFFFFFFFF) std::string castMsg = std::string(success_msg);
success_msg.replace(success_msg.find("%t"), 2, victim->GetName()); SpellProcess::ReplaceEffectTokens(castMsg, (Spawn*)this, victim);
client->Message(CHANNEL_YOU_CAST, success_msg.c_str()); client->Message(CHANNEL_YOU_CAST, castMsg.c_str());
} }
} }
if (effect_msg.length() > 0) { if (effect_msg.length() > 0) {
if(effect_msg.find("%t") < 0xFFFFFFFF) std::string effectMsg = std::string(effect_msg);
effect_msg.replace(effect_msg.find("%t"), 2, victim->GetName()); SpellProcess::ReplaceEffectTokens(effectMsg, (Spawn*)this, victim);
GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_msg.c_str(), victim, 50); GetZone()->SimpleMessage(CHANNEL_SPELLS, effectMsg.c_str(), victim, 50);
} }
} }
else { else {
@ -1063,7 +1064,7 @@ Skill* Entity::GetSkillByWeaponType(int8 type, int8 damage_type, bool update) {
return 0; return 0;
} }
bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, bool take_power, LuaSpell* spell) { bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, bool take_power, LuaSpell* spell, bool skip_check_wards, bool is_melee_spawn) {
if(spell) { if(spell) {
spell->is_damage_spell = true; spell->is_damage_spell = true;
} }
@ -1178,7 +1179,8 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
if(damage) { if(damage) {
int32 prevDmg = damage; int32 prevDmg = damage;
damage = victim->CheckWards(this, damage, damage_type); if(!skip_check_wards)
damage = victim->CheckWards(this, damage, damage_type);
if (damage < (sint64)prevDmg) if (damage < (sint64)prevDmg)
useWards = true; useWards = true;
@ -1241,7 +1243,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
} }
if (victim->GetHP() <= 0) if (victim->GetHP() <= 0)
KillSpawn(victim, type, damage_type, blow_type); KillSpawn(victim, type, damage_type, blow_type, is_melee_spawn);
else { else {
victim->CheckProcs(PROC_TYPE_DEFENSIVE, this); victim->CheckProcs(PROC_TYPE_DEFENSIVE, this);
if (spell_name) if (spell_name)
@ -1444,7 +1446,7 @@ bool Entity::CheckInterruptSpell(Entity* attacker) {
return false; return false;
} }
void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow_type) { void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow_type, bool is_melee_spawn) {
if(!dead) if(!dead)
return; return;
@ -1484,7 +1486,7 @@ void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow
dead->ClearRunningLocations(); dead->ClearRunningLocations();
dead->CalculateRunningLocation(true); dead->CalculateRunningLocation(true);
GetZone()->KillSpawn(true, dead, this, true, type, damage_type, kill_blow_type); GetZone()->KillSpawn(is_melee_spawn, dead, this, true, type, damage_type, kill_blow_type);
} }
void Entity::HandleDeathExperienceDebt(Spawn* killer) void Entity::HandleDeathExperienceDebt(Spawn* killer)
@ -1553,11 +1555,11 @@ void NPC::ProcessCombat() {
void Player::ProcessCombat() { void Player::ProcessCombat() {
// if not in combat OR casting a spell OR dazed OR feared return out // if not in combat OR casting a spell OR dazed OR feared return out
if (!EngagedInCombat() || IsCasting() || IsDazed() || IsFeared()) if (!GetZone() || !EngagedInCombat() || IsCasting() || IsDazed() || IsFeared())
return; return;
//If no target delete combat_target and return out //If no target delete combat_target and return out
Spawn* Target = GetZone()->GetSpawnByID(target); Spawn* Target = GetZone()->GetSpawnByID(target, true);
if (!Target) { if (!Target) {
combat_target = 0; combat_target = 0;
if (target > 0) { if (target > 0) {
@ -1932,6 +1934,9 @@ sint32 Entity::CalculateHateAmount(Spawn* target, sint32 amt) {
amt = CalculateFormulaByStat(amt, ITEM_STAT_ABILITY_MODIFIER); amt = CalculateFormulaByStat(amt, ITEM_STAT_ABILITY_MODIFIER);
float multiplier = 1.0f + (GetInfoStruct()->get_hate_mod() / 100.0f);
amt = static_cast<sint32>(amt * multiplier);
return amt; return amt;
} }

View File

@ -20,7 +20,7 @@
#ifndef __EQ2_COMBAT_H__ #ifndef __EQ2_COMBAT_H__
#define __EQ2_COMBAT_H__ #define __EQ2_COMBAT_H__
#include "Player.h" #include "Player.h"
#include "../common/timer.hpp" #include "../common/timer.h"
#include "NPC_AI.h" #include "NPC_AI.h"
#include "MutexList.h" #include "MutexList.h"

View File

@ -1,22 +1,23 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <sys/types.h> #include <sys/types.h>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -26,11 +27,11 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "../../common/version.h" #include "../../common/version.h"
#include "../../common/seperator.h" #include "../../common/seperator.h"
#include "../../common/servertalk.h" #include "../../common/servertalk.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../World.h" #include "../World.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
#include "../VisualStates.h" #include "../VisualStates.h"
#include "../../common/debug.hpp" #include "../../common/debug.h"
#include "../LuaInterface.h" #include "../LuaInterface.h"
#include "../Quests.h" #include "../Quests.h"
#include "../client.h" #include "../client.h"
@ -39,7 +40,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "../SpellProcess.h" #include "../SpellProcess.h"
#include "../Tradeskills/Tradeskills.h" #include "../Tradeskills/Tradeskills.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/misc_functions.hpp" #include "../../common/MiscFunctions.h"
#include "../Languages.h" #include "../Languages.h"
#include "../Traits/Traits.h" #include "../Traits/Traits.h"
#include "../Chat/Chat.h" #include "../Chat/Chat.h"
@ -51,6 +52,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "../Bots/Bot.h" #include "../Bots/Bot.h"
#include "../Web/PeerManager.h" #include "../Web/PeerManager.h"
#include "../../common/GlobalHeaders.h" #include "../../common/GlobalHeaders.h"
#include "../Broker/BrokerManager.h"
extern WorldDatabase database; extern WorldDatabase database;
extern MasterSpellList master_spell_list; extern MasterSpellList master_spell_list;
@ -75,6 +77,7 @@ extern MasterAAList master_aa_list;
extern MasterRaceTypeList race_types_list; extern MasterRaceTypeList race_types_list;
extern Classes classes; extern Classes classes;
extern PeerManager peer_manager; extern PeerManager peer_manager;
extern BrokerManager broker;
//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp //devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp
#if defined(__GNUC__) #if defined(__GNUC__)
@ -1987,6 +1990,18 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
break; break;
} }
case COMMAND_RELOAD_PLAYERSCRIPTS: {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Player Scripts...");
world.SetReloadingSubsystem("PlayerScripts");
world.ResetPlayerScripts();
world.LoadPlayerScripts();
if (lua_interface)
lua_interface->DestroyPlayerScripts();
world.RemoveReloadingSubSystem("PlayerScripts");
peer_manager.sendPeersMessage("/reloadcommand", command->handler);
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
break;
}
case COMMAND_RELOAD_ENTITYCOMMANDS: { case COMMAND_RELOAD_ENTITYCOMMANDS: {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands..."); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands...");
world.SetReloadingSubsystem("EntityCommands"); world.SetReloadingSubsystem("EntityCommands");
@ -2180,14 +2195,35 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
LogWrite(COMMAND__ERROR, 0, "Command", "/info appearance: Unknown Index: %u", item_index); LogWrite(COMMAND__ERROR, 0, "Command", "/info appearance: Unknown Index: %u", item_index);
} }
else if(strcmp(sep->arg[0], "item") == 0 || strcmp(sep->arg[0], "merchant") == 0 || strcmp(sep->arg[0], "store") == 0 || strcmp(sep->arg[0], "buyback") == 0 || strcmp(sep->arg[0], "consignment") == 0){ else if(strcmp(sep->arg[0], "item") == 0 || strcmp(sep->arg[0], "merchant") == 0 || strcmp(sep->arg[0], "store") == 0 || strcmp(sep->arg[0], "buyback") == 0 || strcmp(sep->arg[0], "consignment") == 0){
int32 item_id = atoul(sep->arg[1]); int64 item_id = strtoull(sep->arg[1], NULL, 0);
Item* item = master_item_list.GetItem(item_id); Item* item = nullptr;
if(item){
if (strcmp(sep->arg[0], "store") == 0)
item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(item_id, true);
else
item = master_item_list.GetItem(item_id);
if(!item && client->GetMerchantTransactionID() && strcmp(sep->arg[0], "merchant") == 0) {
Spawn* merchant = client->GetPlayer()->GetZone()->GetSpawnByID(client->GetMerchantTransactionID());
if(merchant && merchant->GetHouseCharacterID() && merchant->GetPickupUniqueItemID()) {
if(auto itemInfo = broker.GetActiveItem(merchant->GetHouseCharacterID(), item_id)) {
item = master_item_list.GetItem(itemInfo->item_id);
if(item) {
EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
client->QueuePacket(app);
}
}
}
}
else if(!item && strcmp(sep->arg[0], "consignment") == 0) {
client->SendSellerItemByItemUniqueId(item_id);
}
else if(item){
EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
client->QueuePacket(app); client->QueuePacket(app);
} }
else else
LogWrite(COMMAND__ERROR, 0, "Command", "/info item|merchant|store|buyback|consignment: Unknown Item ID: %u", item_id); LogWrite(COMMAND__ERROR, 0, "Command", "/info item|merchant|store|buyback|consignment: Unknown Item ID: %u (full arguments %s)", item_id, sep->argplus[0]);
} }
else if (strcmp(sep->arg[0], "spell") == 0) { else if (strcmp(sep->arg[0], "spell") == 0) {
sint32 spell_id = atol(sep->arg[1]); sint32 spell_id = atol(sep->arg[1]);
@ -2679,7 +2715,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
break; break;
} }
case COMMAND_BANK_DEPOSIT:{ case COMMAND_BANK_DEPOSIT:{
if(client->GetBanker() && sep && sep->arg[0]){ Spawn* banker = client->GetCurrentZone()->GetSpawnByID(client->GetBanker());
if(banker && sep && sep->arg[0]){
int64 amount = 0; int64 amount = 0;
string deposit = string(sep->arg[0]); string deposit = string(sep->arg[0]);
amount = atoi64(deposit.c_str()); amount = atoi64(deposit.c_str());
@ -2688,7 +2725,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
break; break;
} }
case COMMAND_BANK_WITHDRAWAL:{ case COMMAND_BANK_WITHDRAWAL:{
if(client->GetBanker() && sep && sep->arg[0] && sep->IsNumber(0)){ Spawn* banker = client->GetCurrentZone()->GetSpawnByID(client->GetBanker());
if(banker && sep && sep->arg[0] && sep->IsNumber(0)){
int64 amount = 0; int64 amount = 0;
string deposit = string(sep->arg[0]); string deposit = string(sep->arg[0]);
amount = atoi64(deposit.c_str()); amount = atoi64(deposit.c_str());
@ -2891,7 +2929,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
} }
case COMMAND_CLASS:{ case COMMAND_CLASS:{
if(sep && sep->arg[ndx][0]){ if(sep && sep->arg[ndx][0]){
client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx])); client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx]), true);
}else }else
client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /class {class_id}"); client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /class {class_id}");
break; break;
@ -3138,12 +3176,16 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
world.ResetZoneScripts(); world.ResetZoneScripts();
database.LoadZoneScriptData(); database.LoadZoneScriptData();
world.ResetPlayerScripts();
world.LoadPlayerScripts();
if(lua_interface) { if(lua_interface) {
lua_interface->DestroySpawnScripts(); lua_interface->DestroySpawnScripts();
lua_interface->DestroyRegionScripts(); lua_interface->DestroyRegionScripts();
lua_interface->DestroyQuests(); lua_interface->DestroyQuests();
lua_interface->DestroyItemScripts(); lua_interface->DestroyItemScripts();
lua_interface->DestroyZoneScripts(); lua_interface->DestroyZoneScripts();
lua_interface->DestroyPlayerScripts();
} }
int32 quest_count = database.LoadQuests(); int32 quest_count = database.LoadQuests();
@ -3173,21 +3215,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
int32 quest_id = 0; int32 quest_id = 0;
if(sep && sep->arg[0] && sep->IsNumber(0)) if(sep && sep->arg[0] && sep->IsNumber(0))
quest_id = atoul(sep->arg[0]); quest_id = atoul(sep->arg[0]);
if(quest_id > 0){ client->DeleteQuest(quest_id);
client->GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__);
if(lua_interface && client->GetPlayer()->player_quests.count(quest_id) > 0) {
Quest* quest = client->GetPlayer()->player_quests[quest_id];
client->GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
if (quest && quest->CanDeleteQuest()) {
lua_interface->CallQuestFunction(quest, "Deleted", client->GetPlayer());
client->RemovePlayerQuest(quest_id);
client->GetCurrentZone()->SendQuestUpdates(client);
}
}
else {
client->GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
}
}
break; break;
} }
case COMMAND_ACCEPT_QUEST:{ case COMMAND_ACCEPT_QUEST:{
@ -3895,8 +3923,12 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID()) if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID())
break; break;
Item* tmpItem = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(spawn->GetPickupUniqueItemID());
if(client->AddItem(spawn->GetPickupItemID(), 1)) { if((tmpItem && tmpItem->generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER) || client->AddItem(spawn->GetPickupItemID(), 1)) {
if ( tmpItem && tmpItem->generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER ) {
tmpItem->TryUnlockItem(LockReason::LockReason_House);
client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion()));
}
Query query; Query query;
query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID()); query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID());
@ -4025,12 +4057,16 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
case COMMAND_PLACE_HOUSE_ITEM: { case COMMAND_PLACE_HOUSE_ITEM: {
if (sep && sep->IsNumber(0)) if (sep && sep->IsNumber(0))
{ {
int32 uniqueid = atoi(sep->arg[0]); int64 uniqueid = strtoull(sep->arg[0], NULL, 0);
Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(uniqueid); Item* item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(uniqueid);
//Item* item = player->GetEquipmentList()->GetItem(slot);
if (item && item->IsHouseItem()) if (item && (item->IsHouseItem() || item->IsHouseContainer()))
{ {
if(item->IsHouseContainer() && item->details.inv_slot_id != InventorySlotType::HOUSE_VAULT) { // must be in base slot of vault in house for house containers
client->SimpleMessage(CHANNEL_COLOR_RED, "Must be in vault to place this item.");
break;
}
if (!client->HasOwnerOrEditAccess()) if (!client->HasOwnerOrEditAccess())
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!");
@ -4057,7 +4093,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
Object* obj = new Object(); Object* obj = new Object();
Spawn* spawn = (Spawn*)obj; Spawn* spawn = (Spawn*)obj;
memset(&spawn->appearance, 0, sizeof(spawn->appearance)); memset(&spawn->appearance, 0, sizeof(spawn->appearance));
strcpy(spawn->appearance.name, "temp"); strcpy(spawn->appearance.name, item->name.c_str());
spawn->SetX(client->GetPlayer()->GetX()); spawn->SetX(client->GetPlayer()->GetX());
spawn->SetY(client->GetPlayer()->GetY()); spawn->SetY(client->GetPlayer()->GetY());
spawn->SetZ(client->GetPlayer()->GetZ()); spawn->SetZ(client->GetPlayer()->GetZ());
@ -4398,20 +4434,49 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
} }
case COMMAND_BUY_FROM_BROKER:{ case COMMAND_BUY_FROM_BROKER:{
if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){
int32 item_id = atoul(sep->arg[0]); int64 item_id = strtoull(sep->arg[0], NULL, 0);
int16 quantity = atoul(sep->arg[1]); int16 quantity = atoul(sep->arg[1]);
Item* item = master_item_list.GetItem(item_id); if(client->IsGMStoreSearch()) {
if(item && item->generic_info.max_charges > 1) Item* item = master_item_list.GetItem(item_id);
quantity = item->generic_info.max_charges; if(item && item->generic_info.max_charges > 1)
client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER); quantity = item->generic_info.max_charges;
client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER);
}
else {
client->BuySellerItemByItemUniqueId(item_id, quantity);
LogWrite(COMMAND__ERROR, 0, "Command", "BUY_FROM_BROKER. Item ID %u, Quantity %u, full args %s.", item_id, quantity, sep->argplus[0]);
}
} }
break; break;
} }
case COMMAND_SEARCH_STORES_PAGE:{ case COMMAND_SEARCH_STORES_PAGE:{
LogWrite(COMMAND__ERROR, 0, "Command", "SearchStores: %s", sep && sep->arg[0] ? sep->argplus[0] : "");
if(sep && sep->arg[0][0] && sep->IsNumber(0)){ if(sep && sep->arg[0][0] && sep->IsNumber(0)){
int32 page = atoul(sep->arg[0]); int32 page = atoul(sep->arg[0]);
client->SearchStore(page); client->SearchStore(page);
} }
else {
client->SetGMStoreSearch(false);
PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion());
if (packet) {
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
//packet->setDataByName("unknown", 1);
packet->setDataByName("unknown2", 5, 0);
packet->setDataByName("unknown2", 20, 1);
packet->setDataByName("unknown2", 58, 3);
packet->setDataByName("unknown2", 40, 4);
client->QueuePacket(packet->serialize());
if(client->GetVersion() > 561) {
PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion());
if (packet2) {
packet2->setDataByName("char_id", client->GetCharacterID());
client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data
safe_delete(packet2);
}
safe_delete(packet);
}
}
}
break; break;
} }
case COMMAND_SEARCH_STORES:{ case COMMAND_SEARCH_STORES:{
@ -4419,11 +4484,11 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
const char* values = sep->argplus[0]; const char* values = sep->argplus[0];
if(values){ if(values){
LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values); LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values);
map<string, string> str_values = TranslateBrokerRequest(values); map<string, string> str_values = TranslateBrokerRequest(values);
vector<Item*>* items = master_item_list.GetItems(str_values, client); vector<Item*>* items = master_item_list.GetItems(str_values, client);
if(items){ if(items){
client->SetItemSearch(items); client->SetItemSearch(items, str_values);
client->SetSearchPage(0);
client->SearchStore(0); client->SearchStore(0);
} }
} }
@ -4820,6 +4885,17 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->Message(CHANNEL_COLOR_YELLOW, "Angle %f between player %s and target %s", spawnAngle, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget()->GetName() : client->GetPlayer()->GetName(), client->GetPlayer()->GetName()); client->Message(CHANNEL_COLOR_YELLOW, "Angle %f between player %s and target %s", spawnAngle, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget()->GetName() : client->GetPlayer()->GetName(), client->GetPlayer()->GetName());
break; break;
} }
else if (ToLower(string(sep->arg[0])) == "hated")
{
if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) {
Entity* target = (Entity*)client->GetPlayer()->GetTarget();
target->SendHatedByList(client);
}
else {
client->Message(CHANNEL_COLOR_YELLOW, "No target or target is not entity to display hated by list.");
}
break;
}
} }
if (sep->IsNumber(0)) if (sep->IsNumber(0))
{ {
@ -4953,6 +5029,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
details3 += "STA / STABase: " + to_string(ent->GetInfoStruct()->get_sta()) + " / " + to_string(ent->GetInfoStruct()->get_sta_base()) + "\n"; details3 += "STA / STABase: " + to_string(ent->GetInfoStruct()->get_sta()) + " / " + to_string(ent->GetInfoStruct()->get_sta_base()) + "\n";
details3 += "INT / INTBase: " + to_string(ent->GetInfoStruct()->get_intel()) + " / " + to_string(ent->GetInfoStruct()->get_intel_base()) + "\n"; details3 += "INT / INTBase: " + to_string(ent->GetInfoStruct()->get_intel()) + " / " + to_string(ent->GetInfoStruct()->get_intel_base()) + "\n";
details3 += "WIS / WISBase: " + to_string(ent->GetInfoStruct()->get_wis()) + " / " + to_string(ent->GetInfoStruct()->get_wis_base()) + "\n"; details3 += "WIS / WISBase: " + to_string(ent->GetInfoStruct()->get_wis()) + " / " + to_string(ent->GetInfoStruct()->get_wis_base()) + "\n";
details3 += "HPRegen: " + to_string(ent->GetInfoStruct()->get_hp_regen()) + "\n";
details3 += "PowerRegen: " + to_string(ent->GetInfoStruct()->get_power_regen()) + "\n";
} }
string details4; string details4;
@ -5372,14 +5450,14 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
} }
case COMMAND_BROADCAST: { case COMMAND_BROADCAST: {
if (sep && sep->arg[0]) if (sep && sep->arg[0])
zone_list.HandleGlobalBroadcast(sep->argplus[0]); zone_list.TransmitBroadcast(sep->argplus[0]);
else else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /broadcast {message}"); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /broadcast {message}");
break; break;
} }
case COMMAND_ANNOUNCE: { case COMMAND_ANNOUNCE: {
if (sep && sep->arg[0]) if (sep && sep->arg[0])
zone_list.HandleGlobalAnnouncement(sep->argplus[0]); zone_list.TransmitGlobalAnnouncement(sep->argplus[0]);
else else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /announce {message}"); client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /announce {message}");
break; break;
@ -5401,6 +5479,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
database.LoadMerchantInformation(); // we skip if there is only a reload of single item not all items database.LoadMerchantInformation(); // we skip if there is only a reload of single item not all items
} }
peer_manager.sendPeersMessage("/reloadcommand", command->handler, item_id);
if(item_id > 0) { if(item_id > 0) {
client->Message(CHANNEL_COLOR_YELLOW, "Reloaded item %u.", item_id); client->Message(CHANNEL_COLOR_YELLOW, "Reloaded item %u.", item_id);
} }
@ -5417,6 +5497,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
} }
case COMMAND_ITEMSEARCH: case COMMAND_ITEMSEARCH:
case COMMAND_FROMBROKER:{ case COMMAND_FROMBROKER:{
if(command->handler == COMMAND_ITEMSEARCH) {
client->SetGMStoreSearch(true);
}
else {
client->SetGMStoreSearch(false);
}
PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion()); PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion());
if (packet) { if (packet) {
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer())); packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
@ -5426,13 +5513,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
packet->setDataByName("unknown2", 58, 3); packet->setDataByName("unknown2", 58, 3);
packet->setDataByName("unknown2", 40, 4); packet->setDataByName("unknown2", 40, 4);
client->QueuePacket(packet->serialize()); client->QueuePacket(packet->serialize());
PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion()); if(client->GetVersion() > 561) {
if (packet2) { PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion());
packet2->setDataByName("char_id", client->GetCharacterID()); if (packet2) {
client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data packet2->setDataByName("char_id", client->GetCharacterID());
safe_delete(packet2); client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data
safe_delete(packet2);
}
safe_delete(packet);
} }
safe_delete(packet);
} }
break; break;
} }
@ -5831,6 +5920,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
case COMMAND_SPLIT: { Command_Split(client, sep); break; } case COMMAND_SPLIT: { Command_Split(client, sep); break; }
case COMMAND_RAIDSAY: { Command_RaidSay(client, sep); break; } case COMMAND_RAIDSAY: { Command_RaidSay(client, sep); break; }
case COMMAND_RELOAD_ZONEINFO: { Command_ReloadZoneInfo(client, sep); break; } case COMMAND_RELOAD_ZONEINFO: { Command_ReloadZoneInfo(client, sep); break; }
case COMMAND_SLE: { Command_SetLocationEntry(client, sep); break; }
case COMMAND_STORE_LIST_ITEM: { Command_StoreListItem(client, sep); break; }
case COMMAND_STORE_SET_PRICE: { Command_StoreSetPrice(client, sep); break; }
case COMMAND_STORE_SET_PRICE_LOCAL: { Command_StoreSetPriceLocal(client, sep); break; }
case COMMAND_STORE_START_SELLING: { Command_StoreStartSelling(client, sep); break; }
case COMMAND_STORE_STOP_SELLING: { Command_StoreStopSelling(client, sep); break; }
case COMMAND_STORE_UNLIST_ITEM: { Command_StoreUnlistItem(client, sep); break; }
case COMMAND_CLOSE_STORE_KEEP_SELLING: { Command_CloseStoreKeepSelling(client, sep); break; }
case COMMAND_CANCEL_STORE: { Command_CancelStore(client, sep); break; }
default: default:
{ {
LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str()); LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@ -6184,11 +6282,6 @@ void Commands::Command_DuelSurrender(Client* client, Seperator* sep)
PrintSep(sep, "COMMAND_DUEL_SURRENDER"); PrintSep(sep, "COMMAND_DUEL_SURRENDER");
LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Surrender Duel Command"); LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Surrender Duel Command");
client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)");
// JA, just messin around ;)
char surrender[64];
sprintf(surrender, "%s surrendered like a cowardly dog!", client->GetPlayer()->GetName());
zone_list.HandleGlobalAnnouncement(surrender);
} }
/* /*
@ -7022,7 +7115,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
if(item) if(item)
{ {
if(item->details.item_locked) { if(item->IsItemLocked()) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use.");
return; return;
} }
@ -7034,11 +7127,15 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->SimpleMessage(CHANNEL_COLOR_RED, "You can't destroy this item."); client->SimpleMessage(CHANNEL_COLOR_RED, "You can't destroy this item.");
return; return;
} }
if(client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT) || client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::BASE_INVENTORY)) {
broker.RemoveItem(client->GetPlayer()->GetCharacterID(), item->details.unique_id, item->details.count, true);
}
if(item->GetItemScript() && lua_interface) if(item->GetItemScript() && lua_interface)
lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer()); lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer());
//reobtain item make sure it wasn't removed //reobtain item make sure it wasn't removed
item = player->item_list.GetItemFromIndex(index); item = player->item_list.GetItemFromIndex(index);
int32 bag_id = 0; int32 bag_id = 0;
if(item){ if(item){
bag_id = item->details.inv_slot_id; bag_id = item->details.inv_slot_id;
@ -7047,6 +7144,8 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->GetPlayer()->item_list.DestroyItem(index); client->GetPlayer()->item_list.DestroyItem(index);
client->GetPlayer()->UpdateInventory(bag_id); client->GetPlayer()->UpdateInventory(bag_id);
client->GetPlayer()->CalculateApplyWeight(); client->GetPlayer()->CalculateApplyWeight();
client->OpenShopWindow(nullptr); // update the window if it is open
} }
} }
else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4))
@ -7056,33 +7155,34 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
sint32 bag_id = atol(sep->arg[3]); sint32 bag_id = atol(sep->arg[3]);
int8 charges = atoi(sep->arg[4]); int8 charges = atoi(sep->arg[4]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index); Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index);
int64 unique_id = 0;
int16 count = 0;
if(!item) { if(!item) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item."); client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item.");
return; return;
} }
unique_id = item->details.unique_id;
if(to_slot == item->details.slot_id && (bag_id < 0 || bag_id == item->details.inv_slot_id)) { count = item->details.count;
if(to_slot == item->details.slot_id && (bag_id == item->details.inv_slot_id)) {
return; return;
} }
if(item->details.item_locked) if(item->IsItemLocked())
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use.");
return; return;
} }
if(bag_id == -4 && !client->GetPlayer()->item_list.SharedBankAddAllowed(item)) if(bag_id == InventorySlotType::SHARED_BANK && !client->GetPlayer()->item_list.SharedBankAddAllowed(item))
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared."); client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared.");
return; return;
} }
sint32 old_inventory_id = 0; sint32 old_inventory_id = 0;
if(item) if(item)
old_inventory_id = item->details.inv_slot_id; old_inventory_id = item->details.inv_slot_id;
//autobank //autobank
if (bag_id == -3 && to_slot == -1) if (bag_id == InventorySlotType::BANK && to_slot == -1)
{ {
if (player->HasFreeBankSlot()) if (player->HasFreeBankSlot())
to_slot = player->FindFreeBankSlot(); to_slot = player->FindFreeBankSlot();
@ -7127,6 +7227,16 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
} }
client->GetPlayer()->CalculateApplyWeight(); client->GetPlayer()->CalculateApplyWeight();
if(item) {
if(!client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT) &&
!client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::BASE_INVENTORY)) {
broker.RemoveItem(client->GetPlayer()->GetCharacterID(), unique_id, charges);
}
}
else {
broker.RemoveItem(client->GetPlayer()->GetCharacterID(), unique_id, count);
}
client->OpenShopWindow(nullptr); // update the window if it is open
} }
else if(sep->arg[1][0] && strncasecmp("equip", sep->arg[0], 5) == 0 && sep->IsNumber(1)) else if(sep->arg[1][0] && strncasecmp("equip", sep->arg[0], 5) == 0 && sep->IsNumber(1))
{ {
@ -7157,6 +7267,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->QueuePacket(characterSheetPackets); client->QueuePacket(characterSheetPackets);
client->GetPlayer()->CalculateBonuses(); client->GetPlayer()->CalculateBonuses();
client->OpenShopWindow(nullptr); // update the window if it is open
} }
else if (sep->arg[1][0] && strncasecmp("unpack", sep->arg[0], 6) == 0 && sep->IsNumber(1)) else if (sep->arg[1][0] && strncasecmp("unpack", sep->arg[0], 6) == 0 && sep->IsNumber(1))
{ {
@ -7166,7 +7277,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
int16 index = atoi(sep->arg[1]); int16 index = atoi(sep->arg[1]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index); Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index);
if (item) { if (item) {
if(item->details.item_locked) if(item->IsItemLocked())
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use.");
return; return;
@ -7184,6 +7295,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
} }
client->RemoveItem(item, 1); client->RemoveItem(item, 1);
client->OpenShopWindow(nullptr); // update the window if it is open
} }
} }
@ -7222,6 +7334,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->UnequipItem(index, bag_id, to_slot, appearance_equip); client->UnequipItem(index, bag_id, to_slot, appearance_equip);
client->GetPlayer()->CalculateBonuses(); client->GetPlayer()->CalculateBonuses();
client->OpenShopWindow(nullptr); // update the window if it is open
} }
else if(sep->arg[2][0] && strncasecmp("swap_equip", sep->arg[0], 10) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) else if(sep->arg[2][0] && strncasecmp("swap_equip", sep->arg[0], 10) == 0 && sep->IsNumber(1) && sep->IsNumber(2))
{ {
@ -7274,9 +7387,10 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
// Send the inventory update packet // Send the inventory update packet
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return; return;
} }
else if (bag_id == -3 && to_slot == -1) { else if (bag_id == InventorySlotType::BANK && to_slot == -1) {
// Auto Bank // Auto Bank
if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) { if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) {
client->SimpleMessage(CHANNEL_STATUS, "You do not have any free bank slots."); client->SimpleMessage(CHANNEL_STATUS, "You do not have any free bank slots.");
@ -7289,14 +7403,15 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item); player->item_list.RemoveOverflowItem(item);
} }
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
} }
else if (bag_id == -4) { else if (bag_id == InventorySlotType::SHARED_BANK) {
// Shared Bank // Shared Bank
if (!player->item_list.SharedBankAddAllowed(item)) { if (!player->item_list.SharedBankAddAllowed(item)) {
client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared."); client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared.");
return; return;
} }
Item* tmp_item = player->item_list.GetItem(-4, to_slot); Item* tmp_item = player->item_list.GetItem(bag_id, to_slot);
if (tmp_item) { if (tmp_item) {
client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot"); client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot");
return; return;
@ -7309,6 +7424,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item); player->item_list.RemoveOverflowItem(item);
} }
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return; return;
} }
} }
@ -7328,6 +7444,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item); player->item_list.RemoveOverflowItem(item);
} }
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return; return;
} }
} }
@ -9974,14 +10091,18 @@ void Commands::Command_TradeAddItem(Client* client, Seperator* sep)
int32 index = atoi(sep->arg[0]); int32 index = atoi(sep->arg[0]);
item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index); item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index);
if (item) { if (item) {
if(item->details.item_locked || item->details.equip_slot_id) { if(item->IsItemLocked() || item->details.equip_slot_id) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item currently in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item currently in use.");
return; return;
} }
else if(item->details.inv_slot_id == -3 || item->details.inv_slot_id == -4) { else if(item->details.inv_slot_id == InventorySlotType::BANK || item->details.inv_slot_id == InventorySlotType::SHARED_BANK) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item in the bank."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item in the bank.");
return; return;
} }
else if(client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT)) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item in the house vault.");
return;
}
int8 result = client->GetPlayer()->trade->AddItemToTrade(client->GetPlayer(), item, atoi(sep->arg[2]), atoi(sep->arg[1])); int8 result = client->GetPlayer()->trade->AddItemToTrade(client->GetPlayer(), item, atoi(sep->arg[2]), atoi(sep->arg[1]));
if (result == 1) if (result == 1)
@ -11039,6 +11160,23 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
else if(atoi(sep->arg[0]) == 38) { else if(atoi(sep->arg[0]) == 38) {
client->GetPlayer()->GetZone()->SendFlightPathsPackets(client); client->GetPlayer()->GetZone()->SendFlightPathsPackets(client);
} }
else if(atoi(sep->arg[0]) == 39) {
client->OpenShopWindow(nullptr, true);
}
else if(atoi(sep->arg[0]) == 40) {
PacketStruct* packet2 = configReader.getStruct("WS_HouseStoreLog", client->GetVersion());
if (packet2) {
packet2->setDataByName("data", sep->arg[1]);
packet2->setDataByName("coin_gain_session", atoul(sep->arg[2]));
packet2->setDataByName("coin_gain_alltime", atoul(sep->arg[3]));
packet2->setDataByName("sales_log_open", atoi(sep->arg[4]));
EQ2Packet* outapp = packet2->serialize();
DumpPacket(outapp);
client->QueuePacket(outapp);
safe_delete(packet2);
}
}
} }
else { else {
PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion()); PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion());
@ -11501,37 +11639,8 @@ void Commands::Command_Wind(Client* client, Seperator* sep) {
void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) { void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) {
Spawn* spawn = client->GetPlayer()->GetTarget(); Spawn* spawn = client->GetPlayer()->GetTarget();
if(client->GetVersion() < 561) { if(spawn)
sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window client->SendMerchantWindow(spawn, sell);
}
if(spawn) {
client->SetMerchantTransaction(spawn);
if (spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(client)){
client->SendHailCommand(spawn);
//MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(spawn->GetMerchantID());
//if(!multiplier || (multiplier && client->GetPlayer()->GetFactions()->GetFactionValue(multiplier->faction_id) >= multiplier->faction_min)){
client->SendBuyMerchantList(sell);
if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY))
client->SendSellMerchantList(sell);
if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK))
client->SendBuyBackList(sell);
if(client->GetVersion() > 561) {
PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", client->GetVersion());
if (packet) {
packet->setDataByName("spawn_id", 0xFFFFFFFF);
packet->setDataByName("type", 16);
EQ2Packet* outapp = packet->serialize();
if (outapp)
client->QueuePacket(outapp);
safe_delete(packet);
}
}
}
if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)
client->SendRepairList();
}
// client->SimpleMessage(CHANNEL_COLOR_RED, "Your faction is too low to use this merchant.");
} }
@ -12945,3 +13054,300 @@ void Commands::Command_RaidSay(Client* client, Seperator* sep) {
void Commands::Command_ReloadZoneInfo(Client* client, Seperator* sep) { void Commands::Command_ReloadZoneInfo(Client* client, Seperator* sep) {
world.ClearZoneInfoCache(); world.ClearZoneInfoCache();
} }
// somewhere in your commandhandler:
void Commands::Command_SetLocationEntry(Client* client, Seperator* sep) {
if(client->GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE) {
client->Message(CHANNEL_COLOR_YELLOW, "Use in a non house zone.");
return;
}
if (!sep->IsSet(1) || (sep->IsNumber(0))) {
client->Message(CHANNEL_COLOR_YELLOW, "Usage: /sle <column_name> <new_value>");
return;
}
Spawn* target = client->GetPlayer()->GetTarget();
if(!target) {
client->Message(CHANNEL_COLOR_RED, "Target missing for set location entry, /sle <column_name> <new_value>");
return;
}
// GetSpawnEntryID for spawn_location_entry to set the spawnpercentage
int32 spawnEntryID = target->GetSpawnEntryID();
int32 spawnPlacementID = target->GetSpawnLocationPlacementID();
int32 spawnLocationID = target->GetSpawnLocationID();
int32 dbID = target->GetDatabaseID();
if (spawnPlacementID == 0 || spawnLocationID == 0 || spawnEntryID == 0 || dbID == 0) {
client->Message(CHANNEL_COLOR_RED, "Error: no valid spawn entry selected.");
return;
}
// 2) Whitelist the allowed columns
static const std::unordered_set<std::string> allowed = {
"x",
"y",
"z",
"x_offset",
"y_offset",
"z_offset",
"heading",
"pitch",
"roll",
"respawn",
"respawn_offset_low",
"respawn_offset_high",
"duplicated_spawn",
"expire_timer",
"expire_offset",
"grid_id",
"processed",
"instance_id",
"lvl_override",
"hp_override",
"mp_override",
"str_override",
"sta_override",
"wis_override",
"int_override",
"agi_override",
"heat_override",
"cold_override",
"magic_override",
"mental_override",
"divine_override",
"disease_override",
"poison_override",
"difficulty_override",
"spawnpercentage",
"condition"
};
const std::string& field = std::string(sep->arg[0]);
if (!allowed.count(field)) {
client->Message(CHANNEL_COLOR_RED, "Error: column '%s' is not modifiable.", field.c_str());
return;
}
const std::string& val = std::string(sep->arg[1]);
Query query;
if(field == "spawnpercentage" || field == "condition") {
query.AddQueryAsync(0,
&database,
Q_UPDATE,
// we embed the whitelisted field name directly in the format
"UPDATE spawn_location_entry "
"SET %s=%s "
"WHERE id=%u and spawn_location_id=%u and spawn_id=%u ",
field.c_str(),
val.c_str(),
spawnEntryID,
spawnLocationID,
dbID
);
}
else {
query.AddQueryAsync(0,
&database,
Q_UPDATE,
// we embed the whitelisted field name directly in the format
"UPDATE spawn_location_placement "
"SET %s=%s "
"WHERE id=%u and spawn_location_id=%u ",
field.c_str(),
val.c_str(),
spawnPlacementID,
spawnLocationID
);
}
client->Message(CHANNEL_COLOR_YELLOW, "Modified %s to %s for row entry id %u, spawn placement id %u, related to location id %u and spawn database id %u.",
field.c_str(), val.c_str(), spawnEntryID, spawnPlacementID, spawnLocationID, dbID);
}
void Commands::Command_StoreListItem(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
if(sep && sep->arg[0]) {
auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
if(!info) {
client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
}
else if(sep->IsNumber(0)) {
int64 unique_id = atoll(sep->arg[0]);
if(client->GetPlayer()->item_list.CanStoreSellItem(unique_id, true)) {
Item* item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(unique_id, true);
if(item) {
bool isInv = !client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT);
int64 cost = broker.GetSalePrice(client->GetPlayer()->GetCharacterID(), item->details.unique_id);
client->AddItemSale(item->details.unique_id, item->details.item_id, cost, item->details.inv_slot_id, item->details.slot_id, item->details.count, isInv, true, item->creator);
}
else
client->Message(CHANNEL_COLOR_RED, "Broker issue, cannot find item %u.", unique_id);
client->SetItemSaleStatus(unique_id, true);
client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, unique_id, true, false);
client->SetSellerStatus();
}
}
else {
client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_list_item unique_id.");
}
}
}
void Commands::Command_StoreSetPrice(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
if(sep && sep->arg[0]) {
auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
int64 unique_id = atoll(sep->arg[0]);
if(!info) {
client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
}
else if(info->sell_from_inventory && broker.IsItemFromInventory(client->GetPlayer()->GetCharacterID(), unique_id)) {
client->Message(CHANNEL_COLOR_RED, "You cannot change the price while selling.");
}
else if(info->sell_from_inventory && !broker.IsItemFromInventory(client->GetPlayer()->GetCharacterID(), unique_id) && broker.IsItemForSale(client->GetPlayer()->GetCharacterID(), unique_id)) {
client->Message(CHANNEL_COLOR_RED, "You cannot change the price while selling.");
}
else if(sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) {
int32 plat = atoul(sep->arg[1]);
int32 gold = atoul(sep->arg[2]);
int32 silver = atoul(sep->arg[3]);
int32 copper = atoul(sep->arg[4]);
int64 price = plat * 1000000 + gold * 10000 + silver * 100 + copper;
LogWrite(PLAYER__INFO, 5, "Broker",
"--StoreSetPrice: %u (%u), cost=%u",
client->GetPlayer()->GetCharacterID(), unique_id, price
);
client->SetItemSaleCost(unique_id, plat, gold, silver, copper);
}
else {
client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_set_price unique_id platinum gold silver copper.");
}
}
}
void Commands::Command_StoreSetPriceLocal(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
if(sep && sep->arg[0]) {
auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
if(!info) {
client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
}
else if(info->sell_from_inventory) {
client->Message(CHANNEL_COLOR_RED, "You cannot change the price while selling.");
}
else if(sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) {
int64 unique_id = atoll(sep->arg[0]);
int32 plat = atoul(sep->arg[1]);
int32 gold = atoul(sep->arg[2]);
int32 silver = atoul(sep->arg[3]);
int32 copper = atoul(sep->arg[4]);
int64 price = plat * 1000000 + gold * 10000 + silver * 100 + copper;
LogWrite(PLAYER__INFO, 5, "Broker",
"--StoreSetLocalPrice: %u (%u), cost=%u",
client->GetPlayer()->GetCharacterID(), unique_id, price
);
client->SetItemSaleCost(unique_id, plat, gold, silver, copper);
}
else {
client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_set_price_local unique_id platinum gold silver copper.");
}
}
}
void Commands::Command_StoreStartSelling(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, true);
client->OpenShopWindow(nullptr);
broker.LockActiveItemsForClient(client);
}
void Commands::Command_StoreStopSelling(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, false);
client->OpenShopWindow(nullptr);
broker.LockActiveItemsForClient(client);
}
void Commands::Command_StoreUnlistItem(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
if(sep && sep->arg[0]) {
auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
if(!info) {
client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
}
else if(sep->IsNumber(0)) {
int64 unique_id = atoll(sep->arg[0]);
client->SetItemSaleStatus(unique_id, false);
client->SetSellerStatus();
client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, unique_id, false, false);
}
else {
client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_unlist_item unique_id.");
}
}
}
void Commands::Command_CloseStoreKeepSelling(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
client->SetShopWindowStatus(false);
if(!info) {
client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
}
else {
bool itemsSelling = broker.IsSellingItems(client->GetPlayer()->GetCharacterID());
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), itemsSelling, true);
}
broker.LockActiveItemsForClient(client);
}
void Commands::Command_CancelStore(Client* client, Seperator* sep) {
if(!client->GetShopWindowStatus()) {
client->Message(CHANNEL_COLOR_RED, "Shop not available.");
return;
}
auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
client->SetShopWindowStatus(false);
if(!info) {
client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
}
else {
bool itemsSelling = broker.IsSellingItems(client->GetPlayer()->GetCharacterID(), true);
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), itemsSelling, false);
}
broker.LockActiveItemsForClient(client);
}

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,16 +17,17 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_COMMANDS__ #ifndef __EQ2_COMMANDS__
#define __EQ2_COMMANDS__ #define __EQ2_COMMANDS__
#include "../../common/data_buffer.hpp" #include "../../common/DataBuffer.h"
#include "../../common/misc_functions.hpp" #include "../../common/MiscFunctions.h"
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/opcodes/opcode_manager.hpp" #include "../../common/opcodemgr.h"
#include <vector> #include <vector>
#include <string> #include <string>
#include <map> #include <map>
#include "../../common/debug.hpp" #include "../../common/debug.h"
using namespace std; using namespace std;
class Client; class Client;
@ -466,6 +467,15 @@ public:
void Command_RaidSay(Client* client, Seperator* sep); void Command_RaidSay(Client* client, Seperator* sep);
void Command_ReloadZoneInfo(Client* client, Seperator* sep); void Command_ReloadZoneInfo(Client* client, Seperator* sep);
void Command_SetLocationEntry(Client* client, Seperator* sep);
void Command_StoreListItem(Client* client, Seperator* sep);
void Command_StoreSetPrice(Client* client, Seperator* sep);
void Command_StoreSetPriceLocal(Client* client, Seperator* sep);
void Command_StoreStartSelling(Client* client, Seperator* sep);
void Command_StoreStopSelling(Client* client, Seperator* sep);
void Command_StoreUnlistItem(Client* client, Seperator* sep);
void Command_CloseStoreKeepSelling(Client* client, Seperator* sep);
void Command_CancelStore(Client* client, Seperator* sep);
// AA Commands // AA Commands
void Get_AA_Xml(Client* client, Seperator* sep); void Get_AA_Xml(Client* client, Seperator* sep);
@ -981,8 +991,17 @@ private:
#define CANCEL_AA_PROFILE 757 #define CANCEL_AA_PROFILE 757
#define SAVE_AA_PROFILE 758 #define SAVE_AA_PROFILE 758
#define COMMAND_MOOD 800 #define COMMAND_MOOD 800
#define COMMAND_RELOAD_PLAYERSCRIPTS 801
#define COMMAND_SLE 802
#define COMMAND_STORE_LIST_ITEM 803
#define COMMAND_STORE_SET_PRICE 804
#define COMMAND_STORE_SET_PRICE_LOCAL 805
#define COMMAND_STORE_START_SELLING 806
#define COMMAND_STORE_STOP_SELLING 807
#define COMMAND_STORE_UNLIST_ITEM 808
#define COMMAND_CLOSE_STORE_KEEP_SELLING 809
#define COMMAND_CANCEL_STORE 810
#define COMMAND_MODIFY 1000 // INSERT INTO `commands`(`id`,`type`,`command`,`subcommand`,`handler`,`required_status`) VALUES ( NULL,'1','modify','','1000','200'); #define COMMAND_MODIFY 1000 // INSERT INTO `commands`(`id`,`type`,`command`,`subcommand`,`handler`,`required_status`) VALUES ( NULL,'1','modify','','1000','200');
#define COMMAND_MODIFY_CHARACTER 1001 #define COMMAND_MODIFY_CHARACTER 1001

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifdef WIN32 #ifdef WIN32
#include <WinSock2.h> #include <WinSock2.h>
#include <windows.h> #include <windows.h>
@ -24,7 +25,7 @@
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "Commands.h" #include "Commands.h"
#include "ConsoleCommands.h" #include "ConsoleCommands.h"
@ -118,6 +119,9 @@ bool WorldDatabase::RemoveSpawnTemplate(int32 template_id)
int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id) int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id)
{ {
if(client && client->GetCurrentZone() && client->GetCurrentZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE) {
return 0;
}
Query query, query2, query3, query4, query5, query6; Query query, query2, query3, query4, query5, query6;
MYSQL_ROW row; MYSQL_ROW row;
int32 spawn_location_id = 0; int32 spawn_location_id = 0;

View File

@ -24,13 +24,13 @@ using namespace std;
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "../../common/debug.hpp" #include "../../common/debug.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/seperator.h" #include "../../common/seperator.h"
#include "ConsoleCommands.h" #include "ConsoleCommands.h"
#include "../World.h" #include "../World.h"
#include "../Rules/Rules.h" #include "../Rules/Rules.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
extern volatile bool RunLoops; extern volatile bool RunLoops;
bool ContinueLoops = false; bool ContinueLoops = false;
@ -153,7 +153,7 @@ bool ConsoleBroadcastCommand(Seperator *sep)
char message[4096]; char message[4096];
snprintf(message, sizeof(message), "%s %s", "BROADCAST:", sep->argplus[1]); snprintf(message, sizeof(message), "%s %s", "BROADCAST:", sep->argplus[1]);
zone_list.HandleGlobalBroadcast(message); zone_list.TransmitBroadcast(message);
return true; return true;
} }
@ -366,7 +366,7 @@ bool ConsoleZoneCommand(Seperator *sep)
printf("============================================================================================\n"); printf("============================================================================================\n");
printf("| %30s | %10s | %42s |\n", "Zone", "Param", "Value"); printf("| %30s | %10s | %42s |\n", "Zone", "Param", "Value");
printf("============================================================================================\n"); printf("============================================================================================\n");
printf("| %30s | %10s | %42s |\n", zone_details.zoneName, "locked", zone_details.lockState ? "true" : "false"); printf("| %30s | %10s | %42s |\n", zone_details.zoneName.c_str(), "locked", zone_details.lockState ? "true" : "false");
} }
else else
{ {
@ -487,7 +487,7 @@ bool ConsoleShutdownCommand(Seperator *sep)
// shutting down gracefully, warn players. // shutting down gracefully, warn players.
char message[4096]; char message[4096];
snprintf(message, sizeof(message), "BROADCAST: Server is shutting down in %s second(s)", sep->arg[1]); snprintf(message, sizeof(message), "BROADCAST: Server is shutting down in %s second(s)", sep->arg[1]);
zone_list.HandleGlobalBroadcast(message); zone_list.TransmitBroadcast(message);
Sleep(shutdown_delay * 1000); Sleep(shutdown_delay * 1000);
} }
else { else {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -20,6 +20,7 @@
#ifndef __EQ2_ENTITY__ #ifndef __EQ2_ENTITY__
#define __EQ2_ENTITY__ #define __EQ2_ENTITY__
#include "Spawn.h" #include "Spawn.h"
#include "../common/Mutex.h"
#include "Skills.h" #include "Skills.h"
#include "MutexList.h" #include "MutexList.h"
#include "MutexVector.h" #include "MutexVector.h"
@ -27,6 +28,8 @@
#include <set> #include <set>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <unordered_map>
#include <optional>
#include <boost/function.hpp> #include <boost/function.hpp>
#include <boost/lambda/bind.hpp> #include <boost/lambda/bind.hpp>
@ -54,6 +57,7 @@ struct MaintainedEffects{
int32 target; int32 target;
int8 target_type; int8 target_type;
int32 spell_id; int32 spell_id;
int32 inherited_spell_id;
int32 slot_pos; int32 slot_pos;
int16 icon; int16 icon;
int16 icon_backdrop; int16 icon_backdrop;
@ -66,6 +70,7 @@ struct MaintainedEffects{
struct SpellEffects{ struct SpellEffects{
int32 spell_id; int32 spell_id;
int32 inherited_spell_id;
Entity* caster; Entity* caster;
float total_time; float total_time;
int32 expire_timestamp; int32 expire_timestamp;
@ -77,6 +82,7 @@ struct SpellEffects{
struct DetrimentalEffects { struct DetrimentalEffects {
int32 spell_id; int32 spell_id;
int32 inherited_spell_id;
Entity* caster; Entity* caster;
int32 expire_timestamp; int32 expire_timestamp;
int16 icon; int16 icon;
@ -209,6 +215,8 @@ struct InfoStruct{
recovery_speed_ = 0; recovery_speed_ = 0;
spell_reuse_speed_ = 0; spell_reuse_speed_ = 0;
spell_multi_attack_ = 0; spell_multi_attack_ = 0;
size_mod_ = 0.0f;
ignore_size_mod_calc_ = 0;
dps_ = 0; dps_ = 0;
dps_multiplier_ = 0; dps_multiplier_ = 0;
attackspeed_ = 0; attackspeed_ = 0;
@ -229,6 +237,11 @@ struct InfoStruct{
pet_movement_ = 0; pet_movement_ = 0;
pet_behavior_ = 0; pet_behavior_ = 0;
vision_ = 0; vision_ = 0;
redlight_ = 0;
greenlight_ = 0;
bluelight_ = 0;
breathe_underwater_ = 0; breathe_underwater_ = 0;
biography_ = std::string(""); biography_ = std::string("");
drunk_ = 0; drunk_ = 0;
@ -419,6 +432,10 @@ struct InfoStruct{
spell_reuse_speed_ = oldStruct->get_spell_reuse_speed(); spell_reuse_speed_ = oldStruct->get_spell_reuse_speed();
spell_multi_attack_ = oldStruct->get_spell_multi_attack(); spell_multi_attack_ = oldStruct->get_spell_multi_attack();
dps_ = oldStruct->get_dps(); dps_ = oldStruct->get_dps();
size_mod_ = oldStruct->get_size_mod();
ignore_size_mod_calc_ = oldStruct->get_ignore_size_mod_calc();
dps_multiplier_ = oldStruct->get_dps_multiplier(); dps_multiplier_ = oldStruct->get_dps_multiplier();
attackspeed_ = oldStruct->get_attackspeed(); attackspeed_ = oldStruct->get_attackspeed();
haste_ = oldStruct->get_haste(); haste_ = oldStruct->get_haste();
@ -438,6 +455,11 @@ struct InfoStruct{
pet_movement_ = oldStruct->get_pet_movement(); pet_movement_ = oldStruct->get_pet_movement();
pet_behavior_ = oldStruct->get_pet_behavior(); pet_behavior_ = oldStruct->get_pet_behavior();
vision_ = oldStruct->get_vision(); vision_ = oldStruct->get_vision();
redlight_ = oldStruct->get_redlight();
greenlight_ = oldStruct->get_greenlight();
bluelight_ = oldStruct->get_bluelight();
breathe_underwater_ = oldStruct->get_breathe_underwater(); breathe_underwater_ = oldStruct->get_breathe_underwater();
biography_ = std::string(oldStruct->get_biography()); biography_ = std::string(oldStruct->get_biography());
drunk_ = oldStruct->get_drunk(); drunk_ = oldStruct->get_drunk();
@ -636,6 +658,7 @@ struct InfoStruct{
float get_spell_reuse_speed() { std::lock_guard<std::mutex> lk(classMutex); return spell_reuse_speed_; } float get_spell_reuse_speed() { std::lock_guard<std::mutex> lk(classMutex); return spell_reuse_speed_; }
float get_spell_multi_attack() { std::lock_guard<std::mutex> lk(classMutex); return spell_multi_attack_; } float get_spell_multi_attack() { std::lock_guard<std::mutex> lk(classMutex); return spell_multi_attack_; }
float get_size_mod() { std::lock_guard<std::mutex> lk(classMutex); return size_mod_; } float get_size_mod() { std::lock_guard<std::mutex> lk(classMutex); return size_mod_; }
int8 get_ignore_size_mod_calc() { std::lock_guard<std::mutex> lk(classMutex); return ignore_size_mod_calc_; }
float get_dps() { std::lock_guard<std::mutex> lk(classMutex); return dps_; } float get_dps() { std::lock_guard<std::mutex> lk(classMutex); return dps_; }
float get_dps_multiplier() { std::lock_guard<std::mutex> lk(classMutex); return dps_multiplier_; } float get_dps_multiplier() { std::lock_guard<std::mutex> lk(classMutex); return dps_multiplier_; }
float get_attackspeed() { std::lock_guard<std::mutex> lk(classMutex); return attackspeed_; } float get_attackspeed() { std::lock_guard<std::mutex> lk(classMutex); return attackspeed_; }
@ -657,6 +680,11 @@ struct InfoStruct{
int8 get_pet_movement() { std::lock_guard<std::mutex> lk(classMutex); return pet_movement_; } int8 get_pet_movement() { std::lock_guard<std::mutex> lk(classMutex); return pet_movement_; }
int8 get_pet_behavior() { std::lock_guard<std::mutex> lk(classMutex); return pet_behavior_; } int8 get_pet_behavior() { std::lock_guard<std::mutex> lk(classMutex); return pet_behavior_; }
int32 get_vision() { std::lock_guard<std::mutex> lk(classMutex); return vision_; } int32 get_vision() { std::lock_guard<std::mutex> lk(classMutex); return vision_; }
int32 get_redlight() { std::lock_guard<std::mutex> lk(classMutex); return redlight_; }
int32 get_greenlight() { std::lock_guard<std::mutex> lk(classMutex); return greenlight_; }
int32 get_bluelight() { std::lock_guard<std::mutex> lk(classMutex); return bluelight_; }
int8 get_breathe_underwater() { std::lock_guard<std::mutex> lk(classMutex); return breathe_underwater_; } int8 get_breathe_underwater() { std::lock_guard<std::mutex> lk(classMutex); return breathe_underwater_; }
std::string get_biography() { std::lock_guard<std::mutex> lk(classMutex); return biography_; } std::string get_biography() { std::lock_guard<std::mutex> lk(classMutex); return biography_; }
float get_drunk() { std::lock_guard<std::mutex> lk(classMutex); return drunk_; } float get_drunk() { std::lock_guard<std::mutex> lk(classMutex); return drunk_; }
@ -916,6 +944,7 @@ struct InfoStruct{
void set_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_reuse_speed_ = value; } void set_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_reuse_speed_ = value; }
void set_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_multi_attack_ = value; } void set_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_multi_attack_ = value; }
void set_size_mod(float value) { std::lock_guard<std::mutex> lk(classMutex); size_mod_ = value; } void set_size_mod(float value) { std::lock_guard<std::mutex> lk(classMutex); size_mod_ = value; }
void set_ignore_size_mod_calc(int8 value) { std::lock_guard<std::mutex> lk(classMutex); ignore_size_mod_calc_ = value; }
void set_dps(float value) { std::lock_guard<std::mutex> lk(classMutex); dps_ = value; } void set_dps(float value) { std::lock_guard<std::mutex> lk(classMutex); dps_ = value; }
void set_dps_multiplier(float value) { std::lock_guard<std::mutex> lk(classMutex); dps_multiplier_ = value; } void set_dps_multiplier(float value) { std::lock_guard<std::mutex> lk(classMutex); dps_multiplier_ = value; }
void set_attackspeed(float value) { std::lock_guard<std::mutex> lk(classMutex); attackspeed_ = value; } void set_attackspeed(float value) { std::lock_guard<std::mutex> lk(classMutex); attackspeed_ = value; }
@ -975,6 +1004,11 @@ struct InfoStruct{
void set_max_weight(int32 value) { std::lock_guard<std::mutex> lk(classMutex); max_weight_ = value; } void set_max_weight(int32 value) { std::lock_guard<std::mutex> lk(classMutex); max_weight_ = value; }
void set_vision(int32 value) { std::lock_guard<std::mutex> lk(classMutex); vision_ = value; } void set_vision(int32 value) { std::lock_guard<std::mutex> lk(classMutex); vision_ = value; }
void set_redlight(int32 value) { std::lock_guard<std::mutex> lk(classMutex); redlight_ = value; }
void set_greenlight(int32 value) { std::lock_guard<std::mutex> lk(classMutex); greenlight_ = value; }
void set_bluelight(int32 value) { std::lock_guard<std::mutex> lk(classMutex); bluelight_ = value; }
void set_breathe_underwater(int8 value) { std::lock_guard<std::mutex> lk(classMutex); breathe_underwater_ = value; } void set_breathe_underwater(int8 value) { std::lock_guard<std::mutex> lk(classMutex); breathe_underwater_ = value; }
void set_drunk(float value) { std::lock_guard<std::mutex> lk(classMutex); drunk_ = value; } void set_drunk(float value) { std::lock_guard<std::mutex> lk(classMutex); drunk_ = value; }
@ -1058,12 +1092,19 @@ struct InfoStruct{
for(int i=0;i<45;i++){ for(int i=0;i<45;i++){
if(i<30){ if(i<30){
maintained_effects[i].spell_id = 0xFFFFFFFF; maintained_effects[i].spell_id = 0xFFFFFFFF;
maintained_effects[i].inherited_spell_id = 0;
if (spawn->IsPlayer()) if (spawn->IsPlayer())
maintained_effects[i].icon = 0xFFFF; maintained_effects[i].icon = 0xFFFF;
maintained_effects[i].spell = nullptr; maintained_effects[i].spell = nullptr;
} }
spell_effects[i].icon = 0;
spell_effects[i].spell_id = 0xFFFFFFFF; spell_effects[i].spell_id = 0xFFFFFFFF;
spell_effects[i].inherited_spell_id = 0;
spell_effects[i].icon_backdrop = 0;
spell_effects[i].tier = 0;
spell_effects[i].total_time = 0.0f;
spell_effects[i].expire_timestamp = 0;
spell_effects[i].spell = nullptr; spell_effects[i].spell = nullptr;
} }
} }
@ -1071,6 +1112,9 @@ struct InfoStruct{
// maintained via their own mutex // maintained via their own mutex
SpellEffects spell_effects[45]; SpellEffects spell_effects[45];
MaintainedEffects maintained_effects[30]; MaintainedEffects maintained_effects[30];
// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
std::mutex classMutex;
std::unordered_map<std::string, std::string> props;
private: private:
std::string name_; std::string name_;
int8 class1_; int8 class1_;
@ -1190,6 +1234,7 @@ private:
float spell_reuse_speed_; float spell_reuse_speed_;
float spell_multi_attack_; float spell_multi_attack_;
float size_mod_; float size_mod_;
int8 ignore_size_mod_calc_;
float dps_; float dps_;
float dps_multiplier_; float dps_multiplier_;
float attackspeed_; float attackspeed_;
@ -1212,6 +1257,11 @@ private:
int8 pet_behavior_; int8 pet_behavior_;
int32 vision_; int32 vision_;
int32 redlight_;
int32 greenlight_;
int32 bluelight_;
int8 breathe_underwater_; int8 breathe_underwater_;
std::string biography_; std::string biography_;
float drunk_; float drunk_;
@ -1283,8 +1333,6 @@ private:
int8 max_spell_reduction_override_; int8 max_spell_reduction_override_;
float max_chase_distance_; float max_chase_distance_;
// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
std::mutex classMutex;
}; };
struct WardInfo { struct WardInfo {
@ -1347,6 +1395,8 @@ struct Proc {
#define PROC_TYPE_DAMAGED_MAGIC 17 #define PROC_TYPE_DAMAGED_MAGIC 17
#define PROC_TYPE_RANGED_ATTACK 18 #define PROC_TYPE_RANGED_ATTACK 18
#define PROC_TYPE_RANGED_DEFENSE 19 #define PROC_TYPE_RANGED_DEFENSE 19
#define PROC_TYPE_PHYSICAL_ATTEMPT 20
#define PROC_TYPE_MAGICAL_ATTEMPT 21
struct ThreatTransfer { struct ThreatTransfer {
int32 Target; int32 Target;
@ -1402,6 +1452,9 @@ public:
void DeleteSpellEffects(bool removeClient = false); void DeleteSpellEffects(bool removeClient = false);
void RemoveSpells(bool unfriendlyOnly = false); void RemoveSpells(bool unfriendlyOnly = false);
void MapInfoStruct(); void MapInfoStruct();
void RegisterProperty(const std::string& name);
void SetProperty(const std::string& name, const std::string& value);
std::optional<std::string> GetProperty(const std::string& name) const;
virtual float GetDodgeChance(); virtual float GetDodgeChance();
virtual void AddMaintainedSpell(LuaSpell* spell); virtual void AddMaintainedSpell(LuaSpell* spell);
virtual void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0); virtual void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0);
@ -1410,7 +1463,7 @@ public:
virtual void AddSkillBonus(int32 spell_id, int32 skill_id, float value); virtual void AddSkillBonus(int32 spell_id, int32 skill_id, float value);
void AddDetrimentalSpell(LuaSpell* spell, int32 override_expire_timestamp = 0); void AddDetrimentalSpell(LuaSpell* spell, int32 override_expire_timestamp = 0);
DetrimentalEffects* GetDetrimentalEffect(int32 spell_id, Entity* caster); DetrimentalEffects* GetDetrimentalEffect(int32 spell_id, Entity* caster);
virtual MaintainedEffects* GetMaintainedSpell(int32 spell_id); virtual MaintainedEffects* GetMaintainedSpell(int32 spell_id, bool on_char_load = false);
void RemoveDetrimentalSpell(LuaSpell* spell); void RemoveDetrimentalSpell(LuaSpell* spell);
void SetDeity(int8 new_deity){ void SetDeity(int8 new_deity){
deity = new_deity; deity = new_deity;
@ -1474,10 +1527,10 @@ public:
void DoRegenUpdate(); void DoRegenUpdate();
MaintainedEffects* GetFreeMaintainedSpellSlot(); MaintainedEffects* GetFreeMaintainedSpellSlot();
SpellEffects* GetFreeSpellEffectSlot(); SpellEffects* GetFreeSpellEffectSlot();
SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0); SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0, bool on_char_load = false);
SpellEffects* GetSpellEffectBySpellType(int8 spell_type); SpellEffects* GetSpellEffectBySpellType(int8 spell_type);
SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0); SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0, bool notCaster = false);
LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true); LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true, bool checkNotCaster = false);
//flags //flags
int32 GetFlags() { return info_struct.get_flags(); } int32 GetFlags() { return info_struct.get_flags(); }
@ -1536,19 +1589,19 @@ public:
bool SecondaryWeaponReady(); bool SecondaryWeaponReady();
bool RangeWeaponReady(); bool RangeWeaponReady();
void MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack = false); void MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack = false);
void RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false); bool RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false);
bool SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false, int8 override_packet_type = 0, bool take_power = false); bool SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false, int8 override_packet_type = 0, bool take_power = false);
bool ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg); bool ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg);
bool SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name=""); bool SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name="");
int8 DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr); int8 DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr);
float GetDamageTypeResistPercentage(int8 damage_type); float GetDamageTypeResistPercentage(int8 damage_type);
Skill* GetSkillByWeaponType(int8 type, int8 damage_type, bool update); Skill* GetSkillByWeaponType(int8 type, int8 damage_type, bool update);
bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, bool take_power = false, LuaSpell* spell = 0); bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, bool take_power = false, LuaSpell* spell = 0, bool skip_check_wards = false, bool is_melee_spawn = false);
float CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false); float CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false);
void AddHate(Entity* attacker, sint32 hate, bool ignore_pet_behavior = false); void AddHate(Entity* attacker, sint32 hate, bool ignore_pet_behavior = false);
bool CheckInterruptSpell(Entity* attacker); bool CheckInterruptSpell(Entity* attacker);
bool CheckFizzleSpell(LuaSpell* spell); bool CheckFizzleSpell(LuaSpell* spell);
void KillSpawn(Spawn* dead, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); void KillSpawn(Spawn* dead, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0, bool is_melee_spawn = false);
void HandleDeathExperienceDebt(Spawn* killer); void HandleDeathExperienceDebt(Spawn* killer);
void SetAttackDelay(bool primary = false, bool ranged = false); void SetAttackDelay(bool primary = false, bool ranged = false);
float CalculateAttackSpeedMod(); float CalculateAttackSpeedMod();
@ -1835,6 +1888,7 @@ public:
/// <summary>Removes the ward with the given spell id</summary> /// <summary>Removes the ward with the given spell id</summary>
/// <param name='spellID'>The spell id of the ward to remove</param> /// <param name='spellID'>The spell id of the ward to remove</param>
void RemoveWard(int32 spellID); void RemoveWard(int32 spellID);
void RemoveWard(LuaSpell* spell);
/// <summary>Subtracts the given damage from the wards</summary> /// <summary>Subtracts the given damage from the wards</summary>
/// <param name='damage'>The damage to subtract from the wards</param> /// <param name='damage'>The damage to subtract from the wards</param>
@ -1944,15 +1998,8 @@ public:
set<int32> HatedBy; set<int32> HatedBy;
std::mutex MHatedBy; std::mutex MHatedBy;
bool IsAggroed() { bool IsAggroed();
int32 size = 0; void SendHatedByList(Client* client);
MHatedBy.lock();
size = HatedBy.size();
MHatedBy.unlock();
return size > 0;
}
Mutex MCommandMutex; Mutex MCommandMutex;
@ -2164,6 +2211,8 @@ private:
map<string, boost::function<void(sint8)> > set_sint8_funcs; map<string, boost::function<void(sint8)> > set_sint8_funcs;
map<string, boost::function<void(std::string)> > set_string_funcs; map<string, boost::function<void(std::string)> > set_string_funcs;
mutable std::shared_mutex propertiesMutex;
}; };
#endif #endif

View File

@ -20,7 +20,8 @@
#ifndef EQ2_FACTIONS #ifndef EQ2_FACTIONS
#define EQ2_FACTIONS #define EQ2_FACTIONS
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "../common/Mutex.h"
struct Faction { struct Faction {
int32 id; int32 id;

View File

@ -21,8 +21,8 @@
#include "World.h" #include "World.h"
#include "Spells.h" #include "Spells.h"
#include "Rules/Rules.h" #include "Rules/Rules.h"
#include "../common/misc_functions.hpp" #include "../common/MiscFunctions.h"
#include "../common/log.hpp" #include "../common/Log.h"
extern ConfigReader configReader; extern ConfigReader configReader;
extern MasterSpellList master_spell_list; extern MasterSpellList master_spell_list;

View File

@ -22,7 +22,7 @@
#include "Spawn.h" #include "Spawn.h"
#include "client.h" #include "client.h"
#include "../common/Mutex.h"
class GroundSpawn : public Spawn { class GroundSpawn : public Spawn {
public: public:

View File

@ -26,7 +26,7 @@
#include "../client.h" #include "../client.h"
#include "../World.h" #include "../World.h"
#include "../zoneserver.h" #include "../zoneserver.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Rules/Rules.h" #include "../Rules/Rules.h"
#include "../Web/PeerManager.h" #include "../Web/PeerManager.h"
@ -187,7 +187,7 @@ void Guild::AddEXPCurrent(sint64 exp, bool send_packet) {
else else
strncpy(adjective, "too uber for cheerios", sizeof(adjective) - 1); strncpy(adjective, "too uber for cheerios", sizeof(adjective) - 1);
sprintf(message, "The %s guild <%s> has attained level %u", adjective, name, level); sprintf(message, "The %s guild <%s> has attained level %u", adjective, name, level);
zone_list.HandleGlobalAnnouncement(message); zone_list.TransmitGlobalAnnouncement(message);
} }
} }
save_needed = true; save_needed = true;

View File

@ -24,6 +24,7 @@
#include <vector> #include <vector>
#include <deque> #include <deque>
#include <map> #include <map>
#include "../../common/Mutex.h"
#include "../MutexMap.h" #include "../MutexMap.h"
using namespace std; using namespace std;

View File

@ -29,7 +29,7 @@
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "Guild.h" #include "Guild.h"
extern GuildList guild_list; extern GuildList guild_list;

View File

@ -24,7 +24,7 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include "../../common/types.hpp" #include "../../common/types.h"
using namespace std; using namespace std;

View File

@ -18,7 +18,7 @@
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "HeroicOp.h" #include "HeroicOp.h"

View File

@ -1,4 +1,4 @@
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../World.h" #include "../World.h"
extern World world; extern World world;

View File

@ -1,7 +1,7 @@
#include "../ClientPacketFunctions.h" #include "../ClientPacketFunctions.h"
#include "../World.h" #include "../World.h"
#include "../client.h" #include "../client.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../Rules/Rules.h" #include "../Rules/Rules.h"
extern ConfigReader configReader; extern ConfigReader configReader;

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,15 +17,17 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_ITEMS__ #ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__ #define __EQ2_ITEMS__
#include <map> #include <map>
#include <vector> #include <vector>
#include <ctime> #include <ctime>
#include "../../common/types.hpp" #include <shared_mutex>
#include "../../common/data_buffer.hpp" #include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../Commands/Commands.h" #include "../Commands/Commands.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
using namespace std; using namespace std;
class MasterItemList; class MasterItemList;
@ -647,6 +649,47 @@ enum ItemEffectType {
EFFECT_CURE_TYPE_MAGIC=6, EFFECT_CURE_TYPE_MAGIC=6,
EFFECT_CURE_TYPE_ALL=7 EFFECT_CURE_TYPE_ALL=7
}; };
enum InventorySlotType {
HOUSE_VAULT=-5,
SHARED_BANK=-4,
BANK=-3,
OVERFLOW=-2,
UNKNOWN_INV_SLOT_TYPE=-1,
BASE_INVENTORY=0
};
enum class LockReason : int32 {
LockReason_None = 0,
LockReason_House = 1u << 0,
LockReason_Crafting = 1u << 1,
LockReason_Shop = 1u << 2,
};
inline LockReason operator|(LockReason a, LockReason b) {
return static_cast<LockReason>(
static_cast<uint32_t>(a) | static_cast<uint32_t>(b)
);
}
inline LockReason operator&(LockReason a, LockReason b) {
return static_cast<LockReason>(
static_cast<uint32_t>(a) & static_cast<uint32_t>(b)
);
}
inline LockReason operator~(LockReason a) {
return static_cast<LockReason>(~static_cast<uint32_t>(a));
}
enum HouseStoreItemFlags {
HOUSE_STORE_ITEM_TEXT_RED=1,
HOUSE_STORE_UNKNOWN_BIT2=2,
HOUSE_STORE_UNKNOWN_BIT4=4,
HOUSE_STORE_FOR_SALE=8,
HOUSE_STORE_UNKNOWN_BIT16=16,
HOUSE_STORE_VAULT_TAB=32
// rest are also unknown
};
#pragma pack(1) #pragma pack(1)
struct ItemStatsValues{ struct ItemStatsValues{
sint16 str; sint16 str;
@ -710,10 +753,11 @@ struct ItemCore{
int16 count; int16 count;
int8 tier; int8 tier;
int8 num_slots; int8 num_slots;
int32 unique_id; int64 unique_id;
int8 num_free_slots; int8 num_free_slots;
int16 recommended_level; int16 recommended_level;
bool item_locked; bool item_locked;
int32 lock_flags;
bool new_item; bool new_item;
int16 new_index; int16 new_index;
}; };
@ -934,6 +978,8 @@ public:
#pragma pack() #pragma pack()
Item(); Item();
Item(Item* in_item); Item(Item* in_item);
Item(Item* in_item, int64 unique_id, std::string in_creator, std::string in_seller_name, int32 in_seller_char_id, int64 in_broker_price, int16 count, int64 in_seller_house_id, bool search_in_inventory);
~Item(); ~Item();
string lowername; string lowername;
string name; string name;
@ -942,10 +988,16 @@ public:
int32 sell_price; int32 sell_price;
int32 sell_status; int32 sell_status;
int32 max_sell_value; int32 max_sell_value;
int64 broker_price;
bool is_search_store_item;
bool is_search_in_inventory;
bool save_needed; bool save_needed;
int8 weapon_type; int8 weapon_type;
string adornment; string adornment;
string creator; string creator;
string seller_name;
int32 seller_char_id;
int64 seller_house_id;
int32 adorn0; int32 adorn0;
int32 adorn1; int32 adorn1;
int32 adorn2; int32 adorn2;
@ -986,6 +1038,7 @@ public:
bool crafted; bool crafted;
bool tinkered; bool tinkered;
int8 book_language; int8 book_language;
mutable std::shared_mutex item_lock_mtx_;
void AddEffect(string effect, int8 percentage, int8 subbulletflag); void AddEffect(string effect, int8 percentage, int8 subbulletflag);
void AddBookPage(int8 page, string page_text,int8 valign, int8 halign); void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);
@ -1067,6 +1120,10 @@ public:
void AddSlot(int8 slot_id); void AddSlot(int8 slot_id);
void SetSlots(int32 slots); void SetSlots(int32 slots);
int16 GetIcon(int16 version); int16 GetIcon(int16 version);
bool TryLockItem(LockReason reason);
bool TryUnlockItem(LockReason reason);
bool IsItemLocked();
bool IsItemLockedFor(LockReason reason);
}; };
class MasterItemList{ class MasterItemList{
public: public:
@ -1079,14 +1136,16 @@ public:
Item* GetAllItemsByClassification(const char* name); Item* GetAllItemsByClassification(const char* name);
ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0); ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0);
ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0); ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0);
bool ShouldAddItemBrokerType(Item* item, int64 itype);
bool ShouldAddItemBrokerSlot(Item* item, int64 ltype);
bool ShouldAddItemBrokerStat(Item* item, int64 btype);
vector<Item*>* GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); vector<Item*>* GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass);
vector<Item*>* GetItems(map<string, string> criteria, Client* client_to_map); vector<Item*>* GetItems(map<string, string> criteria, Client* client_to_map);
void AddItem(Item* item); void AddItem(Item* item);
bool IsBag(int32 item_id); bool IsBag(int32 item_id);
void RemoveAll(); void RemoveAll();
static int32 NextUniqueID(); static int64 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
int32 GetItemStatIDByName(std::string name); int32 GetItemStatIDByName(std::string name);
std::string GetItemStatNameByID(int32 id); std::string GetItemStatNameByID(int32 id);
void AddMappedItemStat(int32 id, std::string lower_case_name); void AddMappedItemStat(int32 id, std::string lower_case_name);
@ -1120,10 +1179,18 @@ public:
void MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old); // erase old was true void MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old); // erase old was true
bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges); bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges);
void EraseItem(Item* item); void EraseItem(Item* item);
Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true);
void SetVaultItemLockUniqueID(Client* client, int64 id, bool state, bool lock);
bool CanStoreSellItem(int64 unique_id, bool lock);
bool IsItemInSlotType(Item* item, InventorySlotType type, bool lockItems=true);
void SetVaultItemUniqueIDCount(Client* client, int64 unique_id, int16 count, bool lock = true);
void RemoveVaultItemFromUniqueID(Client* client, int64 item_id, bool lock = true);
Item* GetVaultItemFromUniqueID(int64 item_id, bool lock = true);
Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
sint32 GetAllStackCountItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); sint32 GetAllStackCountItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
bool AssignItemToFreeSlot(Item* item); bool AssignItemToFreeSlot(Item* item, bool inventory_only = true);
int16 GetNumberOfFreeSlots(); int16 GetNumberOfFreeSlots();
int16 GetNumberOfItems(); int16 GetNumberOfItems();
int32 GetWeight(); int32 GetWeight();
@ -1132,7 +1199,7 @@ public:
void DestroyItem(int16 index); void DestroyItem(int16 index);
Item* CanStack(Item* item, bool include_bank = false); Item* CanStack(Item* item, bool include_bank = false);
vector<Item*> GetAllItemsFromID(int32 item, bool include_bank = false, bool lock = false); vector<Item*> GetAllItemsFromID(int32 item, bool include_bank = false, bool lock = false);
void RemoveItem(Item* item, bool delete_item = false); void RemoveItem(Item* item, bool delete_item = false, bool lock = true);
bool AddItem(Item* item); bool AddItem(Item* item);
Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0); Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0);
@ -1144,6 +1211,9 @@ public:
bool HasFreeBankSlot(); bool HasFreeBankSlot();
int8 FindFreeBankSlot(); int8 FindFreeBankSlot();
void GetVaultItems(Client* client, int32 spawn_id, int8 maxSlots, bool isSelling = false);
void PopulateHouseStoragePacket(Client* client, PacketStruct* packet, Item* item, int16 itemIdx, int8 storage_flags);
///<summary>Get the first free slot and store them in the provided variables</summary> ///<summary>Get the first free slot and store them in the provided variables</summary>
///<param name='bag_id'>Will contain the bag id of the first free spot</param> ///<param name='bag_id'>Will contain the bag id of the first free spot</param>
///<param name='slot'>Will contain the slot id of the first free slot</param> ///<param name='slot'>Will contain the slot id of the first free slot</param>

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifdef WIN32 #ifdef WIN32
#include <WinSock2.h> #include <WinSock2.h>
#include <windows.h> #include <windows.h>
@ -24,7 +25,7 @@
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "Items.h" #include "Items.h"
#include "../World.h" #include "../World.h"
#include "../Rules/Rules.h" #include "../Rules/Rules.h"
@ -172,6 +173,7 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
item->generic_info.part_of_quest_id = result->GetInt32Str("part_of_quest_id"); item->generic_info.part_of_quest_id = result->GetInt32Str("part_of_quest_id");
item->details.recommended_level = result->GetInt16Str("recommended_level"); item->details.recommended_level = result->GetInt16Str("recommended_level");
item->details.item_locked = false; item->details.item_locked = false;
item->details.lock_flags = 0;
item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level"); item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level");
item->generic_info.max_charges = result->GetInt16Str("max_charges"); item->generic_info.max_charges = result->GetInt16Str("max_charges");
item->generic_info.display_charges = result->GetInt8Str("display_charges"); item->generic_info.display_charges = result->GetInt8Str("display_charges");
@ -473,7 +475,7 @@ int32 WorldDatabase::LoadHouseItem(int32 item_id)
{ {
LogWrite(ITEM__DEBUG, 5, "Items", "\tItem HouseItem for item_id %u", id); LogWrite(ITEM__DEBUG, 5, "Items", "\tItem HouseItem for item_id %u", id);
LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %u, %.2f, %u", ITEM_TYPE_HOUSE, atoul(row[1]), atoi(row[2]), atof(row[3]), atoul(row[4])); LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %u, %.2f, %u", ITEM_TYPE_HOUSE, atoul(row[1]), atoi(row[2]), atof(row[3]), atoul(row[4]));
item->SetItemType(ITEM_TYPE_HOUSE); item->SetItemType(ITEM_TYPE_HOUSE); // container will be overwritten by LoadHouseContainers which is ran after
item->houseitem_info->status_rent_reduction = atoi(row[2]); item->houseitem_info->status_rent_reduction = atoi(row[2]);
item->houseitem_info->coin_rent_reduction = atof(row[3]); item->houseitem_info->coin_rent_reduction = atof(row[3]);
item->houseitem_info->house_only = atoi(row[4]); item->houseitem_info->house_only = atoi(row[4]);
@ -541,7 +543,7 @@ int32 WorldDatabase::LoadHouseContainers(int32 item_id){
if (item) if (item)
{ {
LogWrite(ITEM__DEBUG, 5, "Items", "\tHouse Container for item_id %u", id); LogWrite(ITEM__DEBUG, 5, "Items", "\tHouse Container for item_id %u", id);
LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%i', '%u', '%i', '%i'", ITEM_TYPE_RECIPE, result.GetInt8Str("num_slots"), result.GetInt64Str("allowed_types"), result.GetInt8Str("broker_commission"), result.GetInt8Str("fence_commission")); LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%i', '%u', '%i', '%i'", ITEM_TYPE_HOUSE_CONTAINER, result.GetInt8Str("num_slots"), result.GetInt64Str("allowed_types"), result.GetInt8Str("broker_commission"), result.GetInt8Str("fence_commission"));
item->SetItemType(ITEM_TYPE_HOUSE_CONTAINER); item->SetItemType(ITEM_TYPE_HOUSE_CONTAINER);
item->housecontainer_info->num_slots = result.GetInt8Str("num_slots"); item->housecontainer_info->num_slots = result.GetInt8Str("num_slots");
@ -549,6 +551,11 @@ int32 WorldDatabase::LoadHouseContainers(int32 item_id){
item->housecontainer_info->broker_commission = result.GetInt8Str("broker_commission"); item->housecontainer_info->broker_commission = result.GetInt8Str("broker_commission");
item->housecontainer_info->fence_commission = result.GetInt8Str("fence_commission"); item->housecontainer_info->fence_commission = result.GetInt8Str("fence_commission");
item->details.num_slots = item->housecontainer_info->num_slots;
item->details.num_free_slots = item->housecontainer_info->num_slots;
item->bag_info->num_slots = item->housecontainer_info->num_slots;
item->bag_info->weight_reduction = 0;
total++; total++;
} }
else else
@ -1069,7 +1076,7 @@ void WorldDatabase::LoadItemList(int32 item_id)
LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Skill Items", LoadSkillItems(item_id)); LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Skill Items", LoadSkillItems(item_id));
LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Adornment Items", LoadAdornments(item_id)); LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Adornment Items", LoadAdornments(item_id));
LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Recipe Book Items", LoadRecipeBookItems(item_id)); LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Recipe Book Items", LoadRecipeBookItems(item_id));
LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u House Containers", LoadHouseContainers(item_id)); LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u House Containers", LoadHouseContainers(item_id)); // must be called after LoadHouseItem
LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Appearances..."); LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Appearances...");
LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Appearances", LoadItemAppearances(item_id)); LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Appearances", LoadItemAppearances(item_id));
@ -1097,18 +1104,19 @@ void WorldDatabase::LoadItemList(int32 item_id)
LogWrite(ITEM__INFO, 0, "Items", "Loaded %u Total Item%s (took %u seconds)", total, ( total == 1 ) ? "" : "s", Timer::GetUnixTimeStamp() - t_now); LogWrite(ITEM__INFO, 0, "Items", "Loaded %u Total Item%s (took %u seconds)", total, ( total == 1 ) ? "" : "s", Timer::GetUnixTimeStamp() - t_now);
} }
int32 WorldDatabase::LoadNextUniqueItemID() int64 WorldDatabase::LoadNextUniqueItemID()
{ {
Query query; Query query;
MYSQL_ROW row; MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM character_items"); MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT NEXT VALUE FOR seq_character_items AS next_id");
if(result && (row = mysql_fetch_row(result))) if(result && (row = mysql_fetch_row(result)))
{ {
if(row[0]) if(row[0])
{ {
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, atoul(row[0])); int64 max_ = strtoull(row[0], NULL, 0);
return strtoul(row[0], NULL, 0); LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, max_);
return max_;
} }
else else
return 0; return 0;
@ -1119,6 +1127,31 @@ int32 WorldDatabase::LoadNextUniqueItemID()
return 0; return 0;
} }
void WorldDatabase::ResetNextUniqueItemID()
{
Query query;
Query query2;
MYSQL_ROW row;
MYSQL_ROW row2;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT next_not_cached_value FROM seq_character_items");
MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT COALESCE(MAX(id),0) + 1 FROM character_items");
if(result && (row = mysql_fetch_row(result)) && result2 && (row2 = mysql_fetch_row(result2)))
{
if(row[0] && row2[0])
{
int64 max_cur = strtoull(row[0], NULL, 0);
int64 max_expected = strtoull(row2[0], NULL, 0);
string update_item = string("ALTER SEQUENCE seq_character_items RESTART WITH %llu");
if(max_cur < max_expected)
query.AddQueryAsync(0, this, Q_UPDATE, update_item.c_str(), max_expected);
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(current): %u max(expected): %u", __FUNCTION__, max_cur, max_expected);
}
}
else if(!result)
LogWrite(ITEM__ERROR, 0, "Items", "%s: Unable to reset next unique item ID.", __FUNCTION__);
}
void WorldDatabase::SaveItems(Client* client) void WorldDatabase::SaveItems(Client* client)
{ {
LogWrite(ITEM__DEBUG, 3, "Items", "Save Items for Player %i", client->GetCharacterID()); LogWrite(ITEM__DEBUG, 3, "Items", "Save Items for Player %i", client->GetCharacterID());
@ -1382,7 +1415,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
int8 remainder = item->details.count % 255; int8 remainder = item->details.count % 255;
item->details.count = remainder; item->details.count = remainder;
if (item->details.inv_slot_id == -2) if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item); player->item_list.AddOverflowItem(item);
else { else {
if(!player->item_list.AddItem(item)) if(!player->item_list.AddItem(item))
@ -1398,7 +1431,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
} }
} }
else { else {
if (item->details.inv_slot_id == -2) if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item); player->item_list.AddOverflowItem(item);
else else
player->item_list.AddItem(item); player->item_list.AddItem(item);

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,15 +17,16 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_ITEMS__ #ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__ #define __EQ2_ITEMS__
#include <map> #include <map>
#include <vector> #include <vector>
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/data_buffer.hpp" #include "../../common/DataBuffer.h"
#include "../../common/misc_functions.hpp" #include "../../common/MiscFunctions.h"
#include "../Commands/Commands.h" #include "../Commands/Commands.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
using namespace std; using namespace std;
class MasterItemList; class MasterItemList;
@ -730,9 +731,7 @@ public:
void AddItem(Item* item); void AddItem(Item* item);
bool IsBag(int32 item_id); bool IsBag(int32 item_id);
void RemoveAll(); void RemoveAll();
static int32 NextUniqueID(); static int64 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
}; };
class PlayerItemList { class PlayerItemList {
public: public:

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,15 +17,16 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_ITEMS__ #ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__ #define __EQ2_ITEMS__
#include <map> #include <map>
#include <vector> #include <vector>
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/data_buffer.hpp" #include "../../common/DataBuffer.h"
#include "../../common/misc_functions.hpp" #include "../../common/MiscFunctions.h"
#include "../Commands/Commands.h" #include "../Commands/Commands.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
using namespace std; using namespace std;
class MasterItemList; class MasterItemList;
@ -824,9 +825,7 @@ public:
void AddItem(Item* item); void AddItem(Item* item);
bool IsBag(int32 item_id); bool IsBag(int32 item_id);
void RemoveAll(); void RemoveAll();
static int32 NextUniqueID(); static int64 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
}; };
class PlayerItemList { class PlayerItemList {
public: public:

View File

@ -19,9 +19,9 @@
*/ */
#include "Loot.h" #include "Loot.h"
#include "../client.h" #include "../client.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
#include "../classes.h" #include "../classes.h"
#include "../../common/debug.hpp" #include "../../common/debug.h"
#include "../zoneserver.h" #include "../zoneserver.h"
#include "../Skills.h" #include "../Skills.h"
#include "../classes.h" #include "../classes.h"

View File

@ -19,7 +19,7 @@
*/ */
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../World.h" #include "../World.h"
extern World world; extern World world;

View File

@ -23,7 +23,7 @@
#include <string> #include <string>
#include <list> #include <list>
#include "../common/types.hpp" #include "../common/types.h"
using namespace std; using namespace std;

View File

@ -17,8 +17,8 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "../common/debug.hpp" #include "../common/debug.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include <iostream> #include <iostream>
using namespace std; using namespace std;
#include <string.h> #include <string.h>
@ -58,13 +58,13 @@ extern int errno;
#include "../common/servertalk.h" #include "../common/servertalk.h"
#include "LoginServer.h" #include "LoginServer.h"
#include "../common/packet/packet_dump.hpp" #include "../common/packet_dump.h"
#include "net.h" #include "net.h"
#include "zoneserver.h" #include "zoneserver.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
#include "Variables.h" #include "Variables.h"
#include "World.h" #include "World.h"
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "Rules/Rules.h" #include "Rules/Rules.h"
#include "Web/PeerManager.h" #include "Web/PeerManager.h"
#include "Web/HTTPSClientPool.h" #include "Web/HTTPSClientPool.h"
@ -583,7 +583,7 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u
else else
status = database.GetCharacterAdminStatus ( utwr->lsaccountid , utwr->char_id ); status = database.GetCharacterAdminStatus ( utwr->lsaccountid , utwr->char_id );
if(status < 100 && zone_list.ClientConnected(utwr->lsaccountid)) if(status < 100 && zone_list.ClientConnected(utwr->lsaccountid, utwr->char_id))
status = -9; status = -9;
if(status < 0){ if(status < 0){
LogWrite(WORLD__ERROR, 0, "World", "Login Rejected based on PLAY_ERROR (UserStatus) (MinStatus: %i), UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id ); LogWrite(WORLD__ERROR, 0, "World", "Login Rejected based on PLAY_ERROR (UserStatus) (MinStatus: %i), UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id );
@ -592,7 +592,7 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u
response = PLAY_ERROR_CHAR_NOT_LOADED; response = PLAY_ERROR_CHAR_NOT_LOADED;
break; break;
case -9: case -9:
response = 0;//PLAY_ERROR_ACCOUNT_IN_USE; response = PLAY_ERROR_ACCOUNT_IN_USE;
break; break;
case -8: case -8:
response = PLAY_ERROR_LOADING_ERROR; response = PLAY_ERROR_LOADING_ERROR;

View File

@ -24,7 +24,7 @@
#include "../common/linked_list.h" #include "../common/linked_list.h"
#include "../common/timer.h" #include "../common/timer.h"
#include "../common/queue.h" #include "../common/queue.h"
#include "../common/Mutex.h"
#include "../common/TCPConnection.h" #include "../common/TCPConnection.h"
#include <deque> #include <deque>
#include "MutexMap.h" #include "MutexMap.h"

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef LUA_FUNCTIONS_H #ifndef LUA_FUNCTIONS_H
#define LUA_FUNCTIONS_H #define LUA_FUNCTIONS_H
@ -260,6 +261,8 @@ int EQ2Emu_lua_QuestStepIsComplete(lua_State* state);
int EQ2Emu_lua_GetQuestStep(lua_State* state); int EQ2Emu_lua_GetQuestStep(lua_State* state);
int EQ2Emu_lua_RegisterQuest(lua_State* state); int EQ2Emu_lua_RegisterQuest(lua_State* state);
int EQ2Emu_lua_OfferQuest(lua_State* state); int EQ2Emu_lua_OfferQuest(lua_State* state);
int EQ2Emu_lua_DeleteQuest(lua_State* state);
int EQ2Emu_lua_DeleteAllQuests(lua_State* state);
int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state); int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state); int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state); int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state);
@ -680,4 +683,20 @@ int EQ2Emu_lua_GetZonePlayerAvgLevel(lua_State* state);
int EQ2Emu_lua_GetZonePlayerFirstLevel(lua_State* state); int EQ2Emu_lua_GetZonePlayerFirstLevel(lua_State* state);
int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state); int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state);
int EQ2Emu_lua_GetExpRequiredByLevel(lua_State* state);
int EQ2Emu_lua_ShowShopWindow(lua_State* state);
int EQ2Emu_lua_SetSpawnHouseScript(lua_State* state);
int EQ2Emu_lua_SendBook(lua_State* state);
int EQ2Emu_lua_GetPickupItemID(lua_State* state);
int EQ2Emu_lua_SetHouseCharacterID(lua_State* state);
int EQ2Emu_lua_GetHouseCharacterID(lua_State* state);
int EQ2Emu_lua_ShowHouseShopMerchant(lua_State* state);
int EQ2Emu_lua_AttackAllowed(lua_State* state);
int EQ2Emu_lua_IsInRaid(lua_State* state);
int EQ2Emu_lua_InSameRaid(lua_State* state);
int EQ2Emu_lua_GetRaid(lua_State* state);
int EQ2Emu_lua_AdjustHatePosition(lua_State* state);
int EQ2Emu_lua_RemoveCharacterProperty(lua_State* state);
#endif #endif

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,11 +17,12 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "LuaInterface.h" #include "LuaInterface.h"
#include "LuaFunctions.h" #include "LuaFunctions.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
#include "SpellProcess.h" #include "SpellProcess.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "World.h" #include "World.h"
#ifndef WIN32 #ifndef WIN32
@ -38,6 +39,149 @@ extern WorldDatabase database;
extern ZoneList zone_list; extern ZoneList zone_list;
const std::unordered_map<std::string, std::pair<SpellFieldType, SpellFieldGetter>> SpellDataFieldAccessors = {
{"spell_book_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_book_type); }}},
{"icon", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->icon); }}},
{"icon_heroic_op", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->icon_heroic_op); }}},
{"icon_backdrop", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->icon_backdrop); }}},
{"type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->type); }}},
{"class_skill", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->class_skill); }}},
{"min_class_skill_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->min_class_skill_req); }}},
{"mastery_skill", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->mastery_skill); }}},
{"ts_loc_index", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->ts_loc_index); }}},
{"num_levels", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->num_levels); }}},
{"tier", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->tier); }}},
{"hp_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->hp_req); }}},
{"hp_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->hp_upkeep); }}},
{"power_req", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->power_req); }}},
{"power_by_level", {SpellFieldType::Boolean, [](SpellData* d) { return d->power_by_level ? "1" : "0"; }}},
{"power_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->power_upkeep); }}},
{"savagery_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savagery_req); }}},
{"savagery_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savagery_upkeep); }}},
{"dissonance_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->dissonance_req); }}},
{"dissonance_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->dissonance_upkeep); }}},
{"target_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->target_type); }}},
{"cast_time", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->cast_time); }}},
{"recovery", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->recovery); }}},
{"recast", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->recast); }}},
{"linked_timer", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->linked_timer); }}},
{"radius", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->radius); }}},
{"max_aoe_targets", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->max_aoe_targets); }}},
{"friendly_spell", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->friendly_spell); }}},
{"req_concentration", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->req_concentration); }}},
{"range", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->range); }}},
{"duration1", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->duration1); }}},
{"duration2", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->duration2); }}},
{"resistibility", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->resistibility); }}},
{"duration_until_cancel", {SpellFieldType::Boolean, [](SpellData* d) { return d->duration_until_cancel ? "1" : "0"; }}},
{"power_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->power_req_percent); }}},
{"hp_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->hp_req_percent); }}},
{"savagery_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savagery_req_percent); }}},
{"dissonance_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->dissonance_req_percent); }}},
{"name", {SpellFieldType::String, [](SpellData* d) { return d->name.data; }}},
{"description", {SpellFieldType::String, [](SpellData* d) { return d->description.data; }}},
{"success_message", {SpellFieldType::String, [](SpellData* d) { return d->success_message; }}},
{"fade_message", {SpellFieldType::String, [](SpellData* d) { return d->fade_message; }}},
{"fade_message_others", {SpellFieldType::String, [](SpellData* d) { return d->fade_message_others; }}},
{"cast_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->cast_type).c_str(); }}},
{"lua_script", {SpellFieldType::String, [](SpellData* d) { return d->lua_script; }}},
{"interruptable", {SpellFieldType::Boolean, [](SpellData* d) { return d->interruptable ? "1" : "0"; }}},
{"spell_visual", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_visual); }}},
{"effect_message", {SpellFieldType::String, [](SpellData* d) { return d->effect_message; }}},
{"min_range", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->min_range); }}},
{"can_effect_raid", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->can_effect_raid); }}},
{"affect_only_group_members", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->affect_only_group_members); }}},
{"group_spell", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->group_spell); }}},
{"hit_bonus", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->hit_bonus); }}},
{"display_spell_tier", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->display_spell_tier); }}},
{"is_active", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->is_active); }}},
{"det_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->det_type); }}},
{"incurable", {SpellFieldType::Boolean, [](SpellData* d) { return d->incurable ? "1" : "0"; }}},
{"control_effect_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->control_effect_type); }}},
{"casting_flags", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->casting_flags); }}},
{"cast_while_moving", {SpellFieldType::Boolean, [](SpellData* d) { return d->cast_while_moving ? "1" : "0"; }}},
{"persist_through_death", {SpellFieldType::Boolean, [](SpellData* d) { return d->persist_through_death ? "1" : "0"; }}},
{"not_maintained", {SpellFieldType::Boolean, [](SpellData* d) { return d->not_maintained ? "1" : "0"; }}},
{"is_aa", {SpellFieldType::Boolean, [](SpellData* d) { return d->is_aa ? "1" : "0"; }}},
{"savage_bar", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savage_bar); }}},
{"savage_bar_slot", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savage_bar_slot); }}},
{"soe_spell_crc", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->soe_spell_crc); }}},
{"spell_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_type); }}},
{"spell_name_crc", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_name_crc); }}},
{"type_group_spell_id", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->type_group_spell_id); }}},
{"can_fizzle", {SpellFieldType::Boolean, [](SpellData* d) { return d->can_fizzle ? "1" : "0"; }}}
};
const std::unordered_map<std::string, std::function<void(Spell*, const std::string&)>> SpellFieldGenericSetters = {
{ "spell_book_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->spell_book_type = static_cast<int32>(std::stoi(val)); } },
{ "icon", [](Spell* spell, const std::string& val) { spell->GetSpellData()->icon = static_cast<sint16>(std::stoi(val)); } },
{ "icon_heroic_op", [](Spell* spell, const std::string& val) { spell->GetSpellData()->icon_heroic_op = static_cast<int16>(std::stoi(val)); } },
{ "icon_backdrop", [](Spell* spell, const std::string& val) { spell->GetSpellData()->icon_backdrop = static_cast<int16>(std::stoi(val)); } },
{ "type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->type = static_cast<int16>(std::stoi(val)); } },
{ "class_skill", [](Spell* spell, const std::string& val) { spell->GetSpellData()->class_skill = static_cast<int32>(std::stoi(val)); } },
{ "min_class_skill_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->min_class_skill_req = static_cast<int16>(std::stoi(val)); } },
{ "mastery_skill", [](Spell* spell, const std::string& val) { spell->GetSpellData()->mastery_skill = static_cast<int32>(std::stoi(val)); } },
{ "ts_loc_index", [](Spell* spell, const std::string& val) { spell->GetSpellData()->ts_loc_index = static_cast<int8>(std::stoi(val)); } },
{ "num_levels", [](Spell* spell, const std::string& val) { spell->GetSpellData()->num_levels = static_cast<int8>(std::stoi(val)); } },
{ "tier", [](Spell* spell, const std::string& val) { spell->GetSpellData()->tier = static_cast<int8>(std::stoi(val)); } },
{ "hp_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hp_req = static_cast<int16>(std::stoi(val)); } },
{ "hp_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hp_upkeep = static_cast<int16>(std::stoi(val)); } },
{ "power_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_req = std::stof(val); } },
{ "power_by_level", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_by_level = (val == "1" || val == "true"); } },
{ "power_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_upkeep = static_cast<int16>(std::stoi(val)); } },
{ "savagery_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savagery_req = static_cast<int16>(std::stoi(val)); } },
{ "savagery_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savagery_upkeep = static_cast<int16>(std::stoi(val)); } },
{ "dissonance_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->dissonance_req = static_cast<int16>(std::stoi(val)); } },
{ "dissonance_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->dissonance_upkeep = static_cast<int16>(std::stoi(val)); } },
{ "target_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->target_type = static_cast<int16>(std::stoi(val)); } },
{ "cast_time", [](Spell* spell, const std::string& val) { spell->GetSpellData()->cast_time = static_cast<int16>(std::stoi(val)); } },
{ "recovery", [](Spell* spell, const std::string& val) { spell->GetSpellData()->recovery = std::stof(val); } },
{ "recast", [](Spell* spell, const std::string& val) { spell->GetSpellData()->recast = std::stof(val); } },
{ "linked_timer", [](Spell* spell, const std::string& val) { spell->GetSpellData()->linked_timer = static_cast<int32>(std::stoi(val)); } },
{ "radius", [](Spell* spell, const std::string& val) { spell->GetSpellData()->radius = std::stof(val); } },
{ "max_aoe_targets", [](Spell* spell, const std::string& val) { spell->GetSpellData()->max_aoe_targets = static_cast<int16>(std::stoi(val)); } },
{ "friendly_spell", [](Spell* spell, const std::string& val) { spell->GetSpellData()->friendly_spell = static_cast<int8>(std::stoi(val)); } },
{ "req_concentration", [](Spell* spell, const std::string& val) { spell->GetSpellData()->req_concentration = static_cast<int16>(std::stoi(val)); } },
{ "range", [](Spell* spell, const std::string& val) { spell->GetSpellData()->range = std::stof(val); } },
{ "duration1", [](Spell* spell, const std::string& val) { spell->GetSpellData()->duration1 = static_cast<sint32>(std::stoi(val)); } },
{ "duration2", [](Spell* spell, const std::string& val) { spell->GetSpellData()->duration2 = static_cast<sint32>(std::stoi(val)); } },
{ "resistibility", [](Spell* spell, const std::string& val) { spell->GetSpellData()->resistibility = std::stof(val); } },
{ "duration_until_cancel", [](Spell* spell, const std::string& val) { spell->GetSpellData()->duration_until_cancel = (val == "1" || val == "true"); } },
{ "power_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_req_percent = static_cast<int8>(std::stoi(val)); } },
{ "hp_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hp_req_percent = static_cast<int8>(std::stoi(val)); } },
{ "savagery_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savagery_req_percent = static_cast<int8>(std::stoi(val)); } },
{ "dissonance_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->dissonance_req_percent = static_cast<int8>(std::stoi(val)); } },
{ "name", [](Spell* spell, const std::string& val) { spell->GetSpellData()->name.data = val; } },
{ "description", [](Spell* spell, const std::string& val) { spell->GetSpellData()->description.data = val; } },
{ "success_message", [](Spell* spell, const std::string& val) { spell->GetSpellData()->success_message = val; } },
{ "fade_message", [](Spell* spell, const std::string& val) { spell->GetSpellData()->fade_message = val; } },
{ "fade_message_others", [](Spell* spell, const std::string& val) { spell->GetSpellData()->fade_message_others = val; } },
{ "cast_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->cast_type = static_cast<int8>(std::stoi(val)); } },
{ "call_frequency", [](Spell* spell, const std::string& val) { spell->GetSpellData()->call_frequency = static_cast<int32>(std::stoi(val)); } },
{ "interruptable", [](Spell* spell, const std::string& val) { spell->GetSpellData()->interruptable = (val == "1" || val == "true"); } },
{ "spell_visual", [](Spell* spell, const std::string& val) { spell->GetSpellData()->spell_visual = static_cast<int32>(std::stoi(val)); } },
{ "effect_message", [](Spell* spell, const std::string& val) { spell->GetSpellData()->effect_message = val; } },
{ "min_range", [](Spell* spell, const std::string& val) { spell->GetSpellData()->min_range = std::stof(val); } },
{ "can_effect_raid", [](Spell* spell, const std::string& val) { spell->GetSpellData()->can_effect_raid = static_cast<int8>(std::stoi(val)); } },
{ "affect_only_group_members", [](Spell* spell, const std::string& val) { spell->GetSpellData()->affect_only_group_members = static_cast<int8>(std::stoi(val)); } },
{ "group_spell", [](Spell* spell, const std::string& val) { spell->GetSpellData()->group_spell = static_cast<int8>(std::stoi(val)); } },
{ "hit_bonus", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hit_bonus = std::stof(val); } },
{ "display_spell_tier", [](Spell* spell, const std::string& val) { spell->GetSpellData()->display_spell_tier = static_cast<int8>(std::stoi(val)); } },
{ "is_active", [](Spell* spell, const std::string& val) { spell->GetSpellData()->is_active = static_cast<int8>(std::stoi(val)); } },
{ "det_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->det_type = static_cast<int8>(std::stoi(val)); } },
{ "incurable", [](Spell* spell, const std::string& val) { spell->GetSpellData()->incurable = (val == "1" || val == "true"); } },
{ "control_effect_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->control_effect_type = static_cast<int8>(std::stoi(val)); } },
{ "casting_flags", [](Spell* spell, const std::string& val) { spell->GetSpellData()->casting_flags = static_cast<int32>(std::stoi(val)); } },
{ "cast_while_moving", [](Spell* spell, const std::string& val) { spell->GetSpellData()->cast_while_moving = (val == "1" || val == "true"); } },
{ "persist_through_death", [](Spell* spell, const std::string& val) { spell->GetSpellData()->persist_through_death = (val == "1" || val == "true"); } },
{ "not_maintained", [](Spell* spell, const std::string& val) { spell->GetSpellData()->not_maintained = (val == "1" || val == "true"); } },
{ "is_aa", [](Spell* spell, const std::string& val) { spell->GetSpellData()->is_aa = (val == "1" || val == "true"); } },
{ "savage_bar", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savage_bar = static_cast<int8>(std::stoi(val)); } },
{ "spell_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->spell_type = static_cast<int8>(std::stoi(val)); } },
{ "type_group_spell_id", [](Spell* spell, const std::string& val) { spell->GetSpellData()->type_group_spell_id = static_cast<sint32>(std::stoi(val)); } },
{ "can_fizzle", [](Spell* spell, const std::string& val) { spell->GetSpellData()->can_fizzle = (val == "1" || val == "true"); } },
};
LuaInterface::LuaInterface() { LuaInterface::LuaInterface() {
shutting_down = false; shutting_down = false;
lua_system_reloading = false; lua_system_reloading = false;
@ -45,6 +189,7 @@ LuaInterface::LuaInterface() {
MSpells.SetName("LuaInterface::MSpells"); MSpells.SetName("LuaInterface::MSpells");
MSpawnScripts.SetName("LuaInterface::MSpawnScripts"); MSpawnScripts.SetName("LuaInterface::MSpawnScripts");
MZoneScripts.SetName("LuaInterface::MZoneScripts"); MZoneScripts.SetName("LuaInterface::MZoneScripts");
MPlayerScripts.SetName("LuaInterface::MPlayerScripts");
MQuests.SetName("LuaInterface::MQuests"); MQuests.SetName("LuaInterface::MQuests");
MLUAMain.SetName("LuaInterface::MLUAMain"); MLUAMain.SetName("LuaInterface::MLUAMain");
MItemScripts.SetName("LuaInterface::MItemScripts"); MItemScripts.SetName("LuaInterface::MItemScripts");
@ -119,6 +264,7 @@ LuaInterface::~LuaInterface() {
DestroyQuests(); DestroyQuests();
DestroyItemScripts(); DestroyItemScripts();
DestroyZoneScripts(); DestroyZoneScripts();
DestroyPlayerScripts();
DestroyRegionScripts(); DestroyRegionScripts();
DeleteUserDataPtrs(true); DeleteUserDataPtrs(true);
DeletePendingSpells(true); DeletePendingSpells(true);
@ -241,6 +387,25 @@ void LuaInterface::DestroyZoneScripts() {
MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); MZoneScripts.releasewritelock(__FUNCTION__, __LINE__);
} }
void LuaInterface::DestroyPlayerScripts() {
map<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::iterator state_itr;
Mutex* mutex = 0;
MPlayerScripts.writelock(__FUNCTION__, __LINE__);
for (itr = player_scripts.begin(); itr != player_scripts.end(); itr++){
mutex = GetPlayerScriptMutex(itr->first.c_str());
mutex->writelock(__FUNCTION__, __LINE__);
for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++)
lua_close(state_itr->first);
mutex->releasewritelock(__FUNCTION__, __LINE__);
safe_delete(mutex);
}
player_scripts.clear();
player_inverse_scripts.clear();
player_scripts_mutex.clear();
MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__);
}
void LuaInterface::DestroyRegionScripts() { void LuaInterface::DestroyRegionScripts() {
map<string, map<lua_State*, bool> >::iterator itr; map<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::iterator state_itr; map<lua_State*, bool>::iterator state_itr;
@ -306,6 +471,20 @@ bool LuaInterface::LoadZoneScript(const char* name) {
return ret; return ret;
} }
bool LuaInterface::LoadPlayerScript(const char* name) {
bool ret = false;
if (name) {
lua_State* state = LoadLuaFile(name);
if (state) {
MPlayerScripts.writelock(__FUNCTION__, __LINE__);
player_scripts[name][state] = false;
MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__);
ret = true;
}
}
return ret;
}
bool LuaInterface::LoadRegionScript(const char* name) { bool LuaInterface::LoadRegionScript(const char* name) {
bool ret = false; bool ret = false;
if (name) { if (name) {
@ -469,6 +648,16 @@ const char* LuaInterface::GetScriptName(lua_State* state)
} }
MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); MZoneScripts.releasewritelock(__FUNCTION__, __LINE__);
MPlayerScripts.writelock(__FUNCTION__, __LINE__);
itr = player_inverse_scripts.find(state);
if (itr != player_inverse_scripts.end())
{
const char* scriptName = itr->second.c_str();
MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__);
return scriptName;
}
MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__);
MRegionScripts.writelock(__FUNCTION__, __LINE__); MRegionScripts.writelock(__FUNCTION__, __LINE__);
itr = region_inverse_scripts.find(state); itr = region_inverse_scripts.find(state);
if (itr != region_inverse_scripts.end()) if (itr != region_inverse_scripts.end())
@ -500,6 +689,10 @@ bool LuaInterface::LoadZoneScript(string name) {
return LoadZoneScript(name.c_str()); return LoadZoneScript(name.c_str());
} }
bool LuaInterface::LoadPlayerScript(string name) {
return LoadPlayerScript(name.c_str());
}
bool LuaInterface::LoadRegionScript(string name) { bool LuaInterface::LoadRegionScript(string name) {
return LoadRegionScript(name.c_str()); return LoadRegionScript(name.c_str());
} }
@ -822,6 +1015,20 @@ lua_State* LuaInterface::LoadLuaFile(const char* name) {
return 0; return 0;
} }
void LuaInterface::RemoveSpawnFromSpell(LuaSpell* spell, Spawn* spawn) {
if(spawn->IsEntity()) {
((Entity*)spawn)->RemoveProc(0, spell);
((Entity*)spawn)->RemoveSpellEffect(spell);
((Entity*)spawn)->RemoveSpellBonus(spell);
((Entity*)spawn)->RemoveEffectsFromLuaSpell(spell);
((Entity*)spawn)->RemoveWard(spell);
((Entity*)spawn)->RemoveMaintainedSpell(spell);
if(spell->spell && spell->spell->GetSpellData() && spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel))
((Entity*)spawn)->RemoveDetrimentalSpell(spell);
}
}
void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool can_delete, string reason, bool removing_all_spells, bool return_after_call_remove, Spawn* overrideTarget) { void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool can_delete, string reason, bool removing_all_spells, bool return_after_call_remove, Spawn* overrideTarget) {
if(call_remove_function){ if(call_remove_function){
lua_getglobal(spell->state, "remove"); lua_getglobal(spell->state, "remove");
@ -894,47 +1101,33 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
if(return_after_call_remove) { if(return_after_call_remove) {
if(overrideTarget && overrideTarget->IsEntity()) { if(overrideTarget && overrideTarget->IsEntity()) {
((Entity*)overrideTarget)->RemoveProc(0, spell); RemoveSpawnFromSpell(spell, overrideTarget);
((Entity*)overrideTarget)->RemoveSpellEffect(spell);
((Entity*)overrideTarget)->RemoveSpellBonus(spell);
((Entity*)overrideTarget)->RemoveEffectsFromLuaSpell(spell);
} }
return; return;
} }
spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); for (int32 id : spell->GetTargets()) {
for (int8 i = 0; i < spell->targets.size(); i++) {
if(!spell->zone) if(!spell->zone)
break; break;
Spawn* target = spell->zone->GetSpawnByID(spell->targets.at(i)); Spawn* target = spell->zone->GetSpawnByID(id);
if (!target || !target->IsEntity()) if (!target || !target->IsEntity())
continue; continue;
((Entity*)target)->RemoveProc(0, spell); RemoveSpawnFromSpell(spell, target);
((Entity*)target)->RemoveSpellEffect(spell);
((Entity*)target)->RemoveSpellBonus(spell);
((Entity*)target)->RemoveEffectsFromLuaSpell(spell);
} }
multimap<int32,int8>::iterator entries; for (const auto& [char_id, pet_type] : spell->GetCharIDTargets()) {
for(entries = spell->char_id_targets.begin(); entries != spell->char_id_targets.end(); entries++) Client* tmpClient = zone_list.GetClientByCharID(char_id);
{
Client* tmpClient = zone_list.GetClientByCharID(entries->first);
if(tmpClient && tmpClient->GetPlayer()) if(tmpClient && tmpClient->GetPlayer())
{ {
tmpClient->GetPlayer()->RemoveProc(0, spell); RemoveSpawnFromSpell(spell, tmpClient->GetPlayer());
tmpClient->GetPlayer()->RemoveSpellEffect(spell);
tmpClient->GetPlayer()->RemoveSpellBonus(spell);
tmpClient->GetPlayer()->RemoveEffectsFromLuaSpell(spell);
} }
} }
spell->char_id_targets.clear(); // TODO: reach out to those clients in different spell->ClearCharTargets(); // TODO: reach out to those clients in different
spell->timer.Disable(); spell->timer.Disable();
spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
if(removing_all_spells) { if(removing_all_spells) {
if(spell->zone && spell->zone->GetSpellProcess()) { if(spell->zone && spell->zone->GetSpellProcess()) {
spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell); spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell);
@ -947,10 +1140,7 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
if(spell->zone && spell->zone->GetSpellProcess()) { if(spell->zone && spell->zone->GetSpellProcess()) {
spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell, false); spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell, false);
} }
spell->caster->RemoveProc(0, spell); RemoveSpawnFromSpell(spell, spell->caster);
spell->caster->RemoveSpellEffect(spell);
spell->caster->RemoveMaintainedSpell(spell);
spell->caster->RemoveEffectsFromLuaSpell(spell);
if(spell->spell && spell->spell->GetSpellData() && spell->caster->IsPlayer() && !removing_all_spells) if(spell->spell && spell->spell->GetSpellData() && spell->caster->IsPlayer() && !removing_all_spells)
{ {
@ -1262,6 +1452,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
lua_register(state, "UpdateQuestZone", EQ2Emu_lua_UpdateQuestZone); lua_register(state, "UpdateQuestZone", EQ2Emu_lua_UpdateQuestZone);
lua_register(state, "SetCompletedDescription", EQ2Emu_lua_SetCompletedDescription); lua_register(state, "SetCompletedDescription", EQ2Emu_lua_SetCompletedDescription);
lua_register(state, "OfferQuest", EQ2Emu_lua_OfferQuest); lua_register(state, "OfferQuest", EQ2Emu_lua_OfferQuest);
lua_register(state, "DeleteQuest", EQ2Emu_lua_DeleteQuest);
lua_register(state, "DeleteAllQuests", EQ2Emu_lua_DeleteAllQuests);
lua_register(state, "ProvidesQuest", EQ2Emu_lua_ProvidesQuest); lua_register(state, "ProvidesQuest", EQ2Emu_lua_ProvidesQuest);
lua_register(state, "HasQuest", EQ2Emu_lua_HasQuest); lua_register(state, "HasQuest", EQ2Emu_lua_HasQuest);
lua_register(state, "HasPendingQuest", EQ2Emu_lua_HasPendingQuest); lua_register(state, "HasPendingQuest", EQ2Emu_lua_HasPendingQuest);
@ -1632,6 +1824,21 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
lua_register(state,"GetZonePlayerFirstLevel", EQ2Emu_lua_GetZonePlayerFirstLevel); lua_register(state,"GetZonePlayerFirstLevel", EQ2Emu_lua_GetZonePlayerFirstLevel);
lua_register(state,"GetSpellRequiredLevel", EQ2Emu_lua_GetSpellRequiredLevel); lua_register(state,"GetSpellRequiredLevel", EQ2Emu_lua_GetSpellRequiredLevel);
lua_register(state,"GetExpRequiredByLevel", EQ2Emu_lua_GetExpRequiredByLevel);
lua_register(state,"ShowShopWindow", EQ2Emu_lua_ShowShopWindow);
lua_register(state,"SetSpawnHouseScript", EQ2Emu_lua_SetSpawnHouseScript);
lua_register(state,"SendBook", EQ2Emu_lua_SendBook);
lua_register(state,"GetPickupItemID", EQ2Emu_lua_GetPickupItemID);
lua_register(state,"SetHouseCharacterID", EQ2Emu_lua_SetHouseCharacterID);
lua_register(state,"GetHouseCharacterID", EQ2Emu_lua_GetHouseCharacterID);
lua_register(state,"ShowHouseShopMerchant", EQ2Emu_lua_ShowHouseShopMerchant);
lua_register(state,"AttackAllowed", EQ2Emu_lua_AttackAllowed);
lua_register(state,"IsInRaid", EQ2Emu_lua_IsInRaid);
lua_register(state,"InSameRaid", EQ2Emu_lua_InSameRaid);
lua_register(state,"GetRaid", EQ2Emu_lua_GetRaid);
lua_register(state,"AdjustHatePosition", EQ2Emu_lua_AdjustHatePosition);
lua_register(state,"RemoveCharacterProperty", EQ2Emu_lua_RemoveCharacterProperty);
} }
void LuaInterface::LogError(const char* error, ...) { void LuaInterface::LogError(const char* error, ...) {
@ -1661,7 +1868,7 @@ void LuaInterface::AddUserDataPtr(LUAUserData* data, void* data_ptr) {
user_data[data] = Timer::GetCurrentTime2() + 300000; //allow a function to use this pointer for 5 minutes user_data[data] = Timer::GetCurrentTime2() + 300000; //allow a function to use this pointer for 5 minutes
} }
void LuaInterface::DeletePendingSpells(bool all) { void LuaInterface::DeletePendingSpells(bool all, ZoneServer* zone) {
MSpells.lock(); MSpells.lock();
MSpellDelete.lock(); MSpellDelete.lock();
if (spells_pending_delete.size() > 0) { if (spells_pending_delete.size() > 0) {
@ -1670,7 +1877,7 @@ void LuaInterface::DeletePendingSpells(bool all) {
vector<LuaSpell*> tmp_deletes; vector<LuaSpell*> tmp_deletes;
vector<LuaSpell*>::iterator del_itr; vector<LuaSpell*>::iterator del_itr;
for (itr = spells_pending_delete.begin(); itr != spells_pending_delete.end(); itr++) { for (itr = spells_pending_delete.begin(); itr != spells_pending_delete.end(); itr++) {
if (all || time >= itr->second) if ((!zone && (all || time >= itr->second)) || (zone && itr->first->zone == zone))
tmp_deletes.push_back(itr->first); tmp_deletes.push_back(itr->first);
} }
LuaSpell* spell = 0; LuaSpell* spell = 0;
@ -1684,23 +1891,17 @@ void LuaInterface::DeletePendingSpells(bool all) {
if(!all) { if(!all) {
// rely on targets the spell->caster could be corrupt // rely on targets the spell->caster could be corrupt
bool spellDeleted = false; for (int32 id : spell->GetTargets()) {
if(spell->targets.size() > 0) { Spawn* target = spell->zone->GetSpawnByID(id);
spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
for (int8 i = 0; i < spell->targets.size(); i++) {
Spawn* target = spell->zone->GetSpawnByID(spell->targets.at(i));
if (!target || !target->IsEntity()) if (!target || !target->IsEntity())
continue; continue;
if(!spellDeleted && spell->zone && spell->zone->GetSpellProcess()) if(target->IsEntity()) {
spell->zone->GetSpellProcess()->DeleteActiveSpell(spell, true); RemoveSpawnFromSpell(spell, target);
}
spellDeleted = true;
} }
spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
}
if(!spellDeleted && spell->zone && spell->zone->GetSpellProcess()) { if(spell->zone && spell->zone->GetSpellProcess()) {
spell->zone->GetSpellProcess()->DeleteActiveSpell(spell, true); spell->zone->GetSpellProcess()->DeleteActiveSpell(spell, true);
} }
} }
@ -2111,7 +2312,6 @@ LuaSpell* LuaInterface::LoadSpellScript(const char* name) {
spell->interrupted = false; spell->interrupted = false;
spell->last_spellattack_hit = false; spell->last_spellattack_hit = false;
spell->crit = false; spell->crit = false;
spell->MSpellTargets.SetName("LuaSpell.MSpellTargets");
spell->cancel_after_all_triggers = false; spell->cancel_after_all_triggers = false;
spell->num_triggers = 0; spell->num_triggers = 0;
spell->num_calls = 0; spell->num_calls = 0;
@ -2127,6 +2327,7 @@ LuaSpell* LuaInterface::LoadSpellScript(const char* name) {
spell->initial_target_char_id = 0; spell->initial_target_char_id = 0;
spell->zone = nullptr; spell->zone = nullptr;
spell->initial_caster_level = 0; spell->initial_caster_level = 0;
spell->is_loaded_recast = false;
MSpells.lock(); MSpells.lock();
current_spells[spell->state] = spell; current_spells[spell->state] = spell;
@ -2172,6 +2373,17 @@ Mutex* LuaInterface::GetZoneScriptMutex(const char* name) {
return mutex; return mutex;
} }
Mutex* LuaInterface::GetPlayerScriptMutex(const char* name) {
Mutex* mutex = 0;
if(player_scripts_mutex.count(name) > 0)
mutex = player_scripts_mutex[name];
if(!mutex){
mutex = new Mutex();
player_scripts_mutex[name] = mutex;
}
return mutex;
}
Mutex* LuaInterface::GetRegionScriptMutex(const char* name) { Mutex* LuaInterface::GetRegionScriptMutex(const char* name) {
Mutex* mutex = 0; Mutex* mutex = 0;
if(region_scripts_mutex.count(name) > 0) if(region_scripts_mutex.count(name) > 0)
@ -2216,6 +2428,14 @@ void LuaInterface::UseZoneScript(const char* name, lua_State* state, bool val) {
MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); MZoneScripts.releasewritelock(__FUNCTION__, __LINE__);
} }
void LuaInterface::UsePlayerScript(const char* name, lua_State* state, bool val) {
MPlayerScripts.writelock(__FUNCTION__, __LINE__);
player_scripts[name][state] = val;
player_inverse_scripts[state] = name;
MPlayerScripts.releasewritelock(__FUNCTION__, __LINE__);
}
void LuaInterface::UseRegionScript(const char* name, lua_State* state, bool val) { void LuaInterface::UseRegionScript(const char* name, lua_State* state, bool val) {
MRegionScripts.writelock(__FUNCTION__, __LINE__); MRegionScripts.writelock(__FUNCTION__, __LINE__);
@ -2328,6 +2548,40 @@ lua_State* LuaInterface::GetZoneScript(const char* name, bool create_new, bool u
return ret; return ret;
} }
lua_State* LuaInterface::GetPlayerScript(const char* name, bool create_new, bool use) {
map<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::iterator zone_script_itr;
lua_State* ret = 0;
Mutex* mutex = 0;
itr = player_scripts.find(name);
if(itr != player_scripts.end()) {
mutex = GetPlayerScriptMutex(name);
mutex->readlock(__FUNCTION__, __LINE__);
for(zone_script_itr = itr->second.begin(); zone_script_itr != itr->second.end(); zone_script_itr++){
if(!zone_script_itr->second){ //not in use
ret = zone_script_itr->first;
if (use)
{
zone_script_itr->second = true;
break; // don't keep iterating, we already have our result
}
}
}
mutex->releasereadlock(__FUNCTION__, __LINE__);
}
if(!ret && create_new){
if(name && LoadPlayerScript(name))
ret = GetPlayerScript(name, create_new, use);
else{
LogError("Error LUA Zone Script '%s'", name);
return 0;
}
}
return ret;
}
lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool use) { lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool use) {
map<string, map<lua_State*, bool> >::iterator itr; map<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::iterator region_script_itr; map<lua_State*, bool>::iterator region_script_itr;
@ -2408,7 +2662,6 @@ LuaSpell* LuaInterface::CreateSpellScript(const char* name, lua_State* existStat
new_spell->interrupted = false; new_spell->interrupted = false;
new_spell->crit = false; new_spell->crit = false;
new_spell->last_spellattack_hit = false; new_spell->last_spellattack_hit = false;
new_spell->MSpellTargets.SetName("LuaSpell.MSpellTargets");
new_spell->cancel_after_all_triggers = false; new_spell->cancel_after_all_triggers = false;
new_spell->num_triggers = 0; new_spell->num_triggers = 0;
new_spell->num_calls = 0; new_spell->num_calls = 0;
@ -2427,6 +2680,7 @@ LuaSpell* LuaInterface::CreateSpellScript(const char* name, lua_State* existStat
new_spell->initial_target_char_id = 0; new_spell->initial_target_char_id = 0;
new_spell->zone = nullptr; new_spell->zone = nullptr;
new_spell->initial_caster_level = 0; new_spell->initial_caster_level = 0;
new_spell->is_loaded_recast = false;
current_spells[new_spell->state] = new_spell; current_spells[new_spell->state] = new_spell;
return new_spell; return new_spell;
@ -2699,6 +2953,57 @@ bool LuaInterface::RunZoneScriptWithReturn(string script_name, const char* funct
return false; return false;
} }
bool LuaInterface::RunPlayerScriptWithReturn(const string script_name, const char* function_name, const std::vector<LuaArg>& args, sint32* returnValue) {
lua_State* state = GetPlayerScript(script_name.c_str(), true, true);
if (state) {
Mutex* mutex = GetPlayerScriptMutex(script_name.c_str());
if (mutex)
mutex->readlock(__FUNCTION__, __LINE__);
else {
LogError("Error getting lock for '%s'", script_name.c_str());
UsePlayerScript(script_name.c_str(), state, false);
return false;
}
lua_getglobal(state, function_name);
if (!lua_isfunction(state, lua_gettop(state))) {
lua_pop(state, 1);
mutex->releasereadlock(__FUNCTION__);
UsePlayerScript(script_name.c_str(), state, false);
return false;
}
int8 num_params = 0;
for (const LuaArg& arg : args) {
switch (arg.type) {
case LuaArgType::SINT64: SetSInt64Value(state, arg.si); num_params++; break;
case LuaArgType::SINT: SetSInt32Value(state, arg.low_si); num_params++; break;
case LuaArgType::INT64: SetInt64Value(state, arg.i); num_params++; break;
case LuaArgType::INT: SetInt32Value(state, arg.low_i); num_params++; break;
case LuaArgType::FLOAT: SetFloatValue(state, arg.f); num_params++; break;
case LuaArgType::STRING: SetStringValue(state, arg.s.c_str()); num_params++; break;
case LuaArgType::BOOL: SetBooleanValue(state, arg.b); num_params++; break;
case LuaArgType::SPAWN: SetSpawnValue(state, arg.spawn); num_params++; break;
case LuaArgType::ZONE: SetZoneValue(state, arg.zone); num_params++; break;
case LuaArgType::SKILL: SetSkillValue(state, arg.skill); num_params++; break;
case LuaArgType::ITEM: SetItemValue(state, arg.item); num_params++; break;
case LuaArgType::QUEST: SetQuestValue(state, arg.quest); num_params++; break;
case LuaArgType::SPELL: SetSpellValue(state, arg.spell); num_params++; break;
}
}
if (!CallScriptSInt32(state, num_params, returnValue)) {
if (mutex)
mutex->releasereadlock(__FUNCTION__, __LINE__);
UsePlayerScript(script_name.c_str(), state, false);
return false;
}
if (mutex)
mutex->releasereadlock(__FUNCTION__, __LINE__);
UsePlayerScript(script_name.c_str(), state, false);
return true;
}
else
return false;
}
bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) { bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) {
if (!zone) if (!zone)
@ -2803,6 +3108,15 @@ void LuaInterface::SetLuaUserDataStale(void* ptr) {
} }
} }
bool LuaInterface::IsLuaUserDataValid(void* ptr) {
std::shared_lock lock(MLUAUserData);
std::map<void*, LUAUserData*>::iterator itr = user_data_ptr.find(ptr);
if(itr != user_data_ptr.end()) {
return itr->second->correctly_initialized;
}
return false;
}
LUAUserData::LUAUserData(){ LUAUserData::LUAUserData(){
correctly_initialized = false; correctly_initialized = false;
quest = 0; quest = 0;
@ -2913,3 +3227,40 @@ LUASpellWrapper::LUASpellWrapper() {
bool LUASpellWrapper::IsSpell() { bool LUASpellWrapper::IsSpell() {
return true; return true;
} }
bool LuaSpell::SetSpellDataIndex(int idx, const std::string& value, const std::string& value2) {
if (!spell || spell->lua_data.size() <= idx)
return false;
LUAData* data = spell->lua_data[idx];
if (!data)
return false;
bool setVal = true;
switch (data->type) {
case 0: // int + int
data->int_value = std::stoi(value);
data->int_value2 = std::stoi(value2);
break;
case 1: // float + float
data->float_value = std::stof(value);
data->float_value2 = std::stof(value2);
break;
case 2: // bool
data->bool_value = (value == "1" || value == "true");
break;
case 3: // string + string
data->string_value = value;
data->string_value2 = value2;
break;
default:
setVal = false;
break;
}
if (setVal)
data->needs_db_save = true;
return setVal;
}

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,15 +17,17 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef LUA_INTERFACE_H #ifndef LUA_INTERFACE_H
#define LUA_INTERFACE_H #define LUA_INTERFACE_H
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include <unordered_set>
#include "Spawn.h" #include "Spawn.h"
#include "Spells.h" #include "Spells.h"
#include "../common/Mutex.h"
#include "Quests.h" #include "Quests.h"
#include "zoneserver.h" #include "zoneserver.h"
#include "client.h" #include "client.h"
@ -73,13 +75,27 @@ struct OptionWindowOption {
#define EFFECT_FLAG_FEAR_IMMUNE 2097152 #define EFFECT_FLAG_FEAR_IMMUNE 2097152
#define EFFECT_FLAG_SAFEFALL 4194304 #define EFFECT_FLAG_SAFEFALL 4194304
enum class SpellFieldType {
Integer,
Float,
Boolean,
String
};
using SpellFieldGetter = std::function<std::string(SpellData*)>;
extern const std::unordered_map<std::string, std::function<void(Spell*, const std::string&)>> SpellFieldGenericSetters;
extern const std::unordered_map<std::string, std::pair<SpellFieldType, SpellFieldGetter>> SpellDataFieldAccessors;
extern std::unordered_map<std::string, std::function<void(Spell*, const std::string&)>> SpellDataFieldSetters;
struct LuaSpell{ struct LuaSpell{
Entity* caster; Entity* caster;
int32 initial_caster_char_id; int32 initial_caster_char_id;
int32 initial_target; int32 initial_target;
int32 initial_target_char_id; int32 initial_target_char_id;
mutable std::shared_mutex targets_mutex;
vector<int32> targets; vector<int32> targets;
vector<int32> removed_targets; // previously cancelled, expired, used, so on vector<int32> removed_targets; // previously cancelled, expired, used, so on
mutable std::shared_mutex char_id_targets_mutex;
multimap<int32, int8> char_id_targets; multimap<int32, int8> char_id_targets;
Spell* spell; Spell* spell;
lua_State* state; lua_State* state;
@ -99,13 +115,239 @@ struct LuaSpell{
bool cancel_after_all_triggers; bool cancel_after_all_triggers;
bool had_triggers; bool had_triggers;
bool had_dmg_remaining; bool had_dmg_remaining;
Mutex MSpellTargets;
Mutex MScriptMutex; Mutex MScriptMutex;
int32 effect_bitmask; int32 effect_bitmask;
bool restored; // restored spell cross zone bool restored; // restored spell cross zone
std::atomic<bool> has_proc; std::atomic<bool> has_proc;
ZoneServer* zone; ZoneServer* zone;
int16 initial_caster_level; int16 initial_caster_level;
bool is_loaded_recast;
std::unordered_set<std::string> modified_fields;
mutable std::shared_mutex spell_modify_mutex;
void AddTarget(int32 target_id) {
std::unique_lock lock(targets_mutex);
bool hasTarget = std::find(targets.begin(), targets.end(), target_id) != targets.end();
if(!hasTarget)
targets.push_back(target_id);
}
int32 GetPrimaryTargetID() const {
std::shared_lock lock(targets_mutex);
return targets.empty() ? -1 : targets[0];
}
std::optional<int32_t> GetPrimaryTarget() const {
std::shared_lock lock(targets_mutex);
if (!targets.empty())
return targets[0];
return std::nullopt;
}
std::vector<int32> GetTargets() {
std::shared_lock lock(targets_mutex);
return targets;
}
bool HasNoTargets() const {
std::shared_lock lock(targets_mutex);
return targets.empty();
}
int32 GetTargetCount() const {
std::shared_lock lock(targets_mutex);
return static_cast<int32>(targets.size());
}
bool HasTarget(int32 id) const {
std::shared_lock lock(targets_mutex);
return std::find(targets.begin(), targets.end(), id) != targets.end();
}
bool HasAnyTarget(const std::vector<int32>& ids) const {
std::shared_lock lock(targets_mutex);
return std::any_of(
ids.begin(), ids.end(),
[this](int32 id){
return std::find(targets.begin(), targets.end(), id) != targets.end();
}
);
}
std::vector<int32> GetRemovedTargets() const {
std::shared_lock lock(targets_mutex);
return removed_targets;
}
void RemoveTarget(int32 target_id) {
std::unique_lock lock(targets_mutex);
auto& v = targets;
v.erase(std::remove(v.begin(), v.end(), target_id), v.end());
removed_targets.push_back(target_id);
}
void AddRemoveTarget(int32 target_id) {
std::unique_lock lock(targets_mutex);
removed_targets.push_back(target_id);
}
void SwapTargets(std::vector<int32>& new_targets) {
std::unique_lock lock(targets_mutex);
targets.swap(new_targets);
}
void ClearTargets() {
std::unique_lock lock(targets_mutex);
targets.clear();
removed_targets.clear();
}
std::multimap<int32, int8> GetCharIDTargets() const {
std::shared_lock lock(char_id_targets_mutex);
return char_id_targets;
}
void AddCharIDTarget(int32 char_id, int8 value) {
std::unique_lock lock(char_id_targets_mutex);
bool exists = false;
auto range = char_id_targets.equal_range(char_id);
for (auto it = range.first; it != range.second; ++it) {
if (it->second == value) {
exists = true;
break;
}
}
if(!exists)
char_id_targets.insert({char_id, value});
}
bool HasNoCharIDTargets() const {
std::shared_lock lock(char_id_targets_mutex);
return char_id_targets.empty();
}
void RemoveCharIDTarget(int32 char_id) {
std::unique_lock lock(char_id_targets_mutex);
char_id_targets.erase(char_id); // removes all entries with that key
}
void RemoveCharIDTargetAndType(int32 char_id, int8 type) {
std::unique_lock lock(char_id_targets_mutex);
auto range = char_id_targets.equal_range(char_id);
for (auto it = range.first; it != range.second; ++it) {
if (it->second == type) {
char_id_targets.erase(it);
break; // remove only one matching pair
}
}
}
void ClearCharTargets() {
std::unique_lock lock(char_id_targets_mutex);
char_id_targets.clear();
}
void MarkFieldModified(const std::string& field) {
std::unique_lock lock(spell_modify_mutex);
modified_fields.insert(field);
}
bool IsFieldModified(const std::string& field) const {
std::shared_lock lock(spell_modify_mutex);
return modified_fields.find(field) != modified_fields.end();
}
void ClearFieldModifications() {
std::unique_lock lock(spell_modify_mutex);
modified_fields.clear();
}
std::unordered_set<std::string> GetModifiedFieldsCopy() const {
std::shared_lock lock(spell_modify_mutex);
return modified_fields; // safe shallow copy
}
bool SetSpellDataGeneric(const std::string& field, int value) {
return SetSpellDataGeneric(field, std::to_string(value));
}
bool SetSpellDataGeneric(const std::string& field, float value) {
return SetSpellDataGeneric(field, std::to_string(value));
}
bool SetSpellDataGeneric(const std::string& field, bool value) {
return SetSpellDataGeneric(field, value ? "1" : "0");
}
bool SetSpellDataGeneric(const std::string& field, const std::string& value) {
auto it = SpellFieldGenericSetters.find(field);
if (it == SpellFieldGenericSetters.end())
return false;
if (!spell)
return false;
it->second(spell, value);
return true;
}
bool SetSpellDataIndex(int idx, const std::string& value, const std::string& value2 = "");
bool SetSpellDataIndex(int idx, int value, int value2) {
return SetSpellDataIndex(idx, std::to_string(value), std::to_string(value2));
}
bool SetSpellDataIndex(int idx, float value, float value2) {
return SetSpellDataIndex(idx, std::to_string(value), std::to_string(value2));
}
bool SetSpellDataIndex(int idx, bool value) {
return SetSpellDataIndex(idx, value ? "1" : "0");
}
};
enum class LuaArgType { SINT64, INT64, SINT, INT, FLOAT, STRING, BOOL, SPAWN, ZONE, SKILL, ITEM, QUEST, SPELL /* etc */ };
struct LuaArg {
LuaArgType type;
union {
sint64 si;
sint64 i;
int32 low_i;
sint32 low_si;
float f;
bool b;
};
std::string s;
Spawn* spawn = nullptr;
ZoneServer* zone = nullptr;
Skill* skill = nullptr;
Item* item = nullptr;
Quest* quest = nullptr;
LuaSpell* spell = nullptr;
LuaArg(sint64 val) : type(LuaArgType::SINT64), si(val) {}
LuaArg(sint32 val) : type(LuaArgType::SINT), low_si(val) {}
LuaArg(sint16 val) : type(LuaArgType::SINT), low_si(val) {}
LuaArg(sint8 val) : type(LuaArgType::SINT), low_si(val) {}
LuaArg(int64 val) : type(LuaArgType::INT64), i(val) {}
LuaArg(int32 val) : type(LuaArgType::INT), low_i(val) {}
LuaArg(int16 val) : type(LuaArgType::INT), low_i(val) {}
LuaArg(int8 val) : type(LuaArgType::INT), low_i(val) {}
LuaArg(float val) : type(LuaArgType::FLOAT), f(val) {}
LuaArg(bool val) : type(LuaArgType::BOOL), b(val) {}
LuaArg(const std::string& val) : type(LuaArgType::STRING), s(val) {}
LuaArg(Spawn* val) : type(LuaArgType::SPAWN), spawn(val) {}
LuaArg(ZoneServer* val) : type(LuaArgType::ZONE), zone(val) {}
LuaArg(Skill* val) : type(LuaArgType::SKILL), skill(val) {}
LuaArg(Item* val) : type(LuaArgType::ITEM), item(val) {}
LuaArg(Quest* val) : type(LuaArgType::QUEST), quest(val) {}
LuaArg(LuaSpell* val) : type(LuaArgType::SPELL), spell(val) {}
}; };
class LUAUserData{ class LUAUserData{
@ -192,10 +434,13 @@ public:
bool LoadSpawnScript(const char* name); bool LoadSpawnScript(const char* name);
bool LoadZoneScript(string name); bool LoadZoneScript(string name);
bool LoadZoneScript(const char* name); bool LoadZoneScript(const char* name);
bool LoadPlayerScript(string name);
bool LoadPlayerScript(const char* name);
bool LoadRegionScript(string name); bool LoadRegionScript(string name);
bool LoadRegionScript(const char* name); bool LoadRegionScript(const char* name);
LuaSpell* LoadSpellScript(string name); LuaSpell* LoadSpellScript(string name);
LuaSpell* LoadSpellScript(const char* name); LuaSpell* LoadSpellScript(const char* name);
void RemoveSpawnFromSpell(LuaSpell* spell, Spawn* spawn);
void RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "", bool removing_all_spells = false, bool return_after_call_remove = false, Spawn* overrideTarget = nullptr); void RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "", bool removing_all_spells = false, bool return_after_call_remove = false, Spawn* overrideTarget = nullptr);
Spawn* GetSpawn(lua_State* state, int8 arg_num = 1); Spawn* GetSpawn(lua_State* state, int8 arg_num = 1);
Item* GetItem(lua_State* state, int8 arg_num = 1); Item* GetItem(lua_State* state, int8 arg_num = 1);
@ -242,10 +487,12 @@ public:
void UseItemScript(const char* name, lua_State* state, bool val); void UseItemScript(const char* name, lua_State* state, bool val);
void UseSpawnScript(const char* name, lua_State* state, bool val); void UseSpawnScript(const char* name, lua_State* state, bool val);
void UseZoneScript(const char* name, lua_State* state, bool val); void UseZoneScript(const char* name, lua_State* state, bool val);
void UsePlayerScript(const char* name, lua_State* state, bool val);
void UseRegionScript(const char* name, lua_State* state, bool val); void UseRegionScript(const char* name, lua_State* state, bool val);
lua_State* GetItemScript(const char* name, bool create_new = true, bool use = false); lua_State* GetItemScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetSpawnScript(const char* name, bool create_new = true, bool use = false); lua_State* GetSpawnScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetZoneScript(const char* name, bool create_new = true, bool use = false); lua_State* GetZoneScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetPlayerScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetRegionScript(const char* name, bool create_new = true, bool use = false); lua_State* GetRegionScript(const char* name, bool create_new = true, bool use = false);
LuaSpell* GetSpellScript(const char* name, bool create_new = true, bool use = true); LuaSpell* GetSpellScript(const char* name, bool create_new = true, bool use = true);
LuaSpell* CreateSpellScript(const char* name, lua_State* existState); LuaSpell* CreateSpellScript(const char* name, lua_State* existState);
@ -263,6 +510,7 @@ public:
bool CallSpawnScript(lua_State* state, int8 num_parameters); bool CallSpawnScript(lua_State* state, int8 num_parameters);
bool RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0); bool RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0);
bool RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0); bool RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0);
bool RunPlayerScriptWithReturn(const string script_name, const char* function_name, const std::vector<LuaArg>& args, sint32* returnValue = 0);
bool CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue = 0); bool CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue = 0);
bool CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue = 0); bool CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue = 0);
bool RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0); bool RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0);
@ -273,6 +521,7 @@ public:
void DestroyItemScripts(); void DestroyItemScripts();
void DestroyQuests(bool reload = false); void DestroyQuests(bool reload = false);
void DestroyZoneScripts(); void DestroyZoneScripts();
void DestroyPlayerScripts();
void DestroyRegionScripts(); void DestroyRegionScripts();
void SimpleLogError(const char* error); void SimpleLogError(const char* error);
void LogError(const char* error, ...); void LogError(const char* error, ...);
@ -285,11 +534,12 @@ public:
map<Client*, int32> GetDebugClients(){ return debug_clients; } map<Client*, int32> GetDebugClients(){ return debug_clients; }
void AddUserDataPtr(LUAUserData* data, void* data_ptr = 0); void AddUserDataPtr(LUAUserData* data, void* data_ptr = 0);
void DeleteUserDataPtrs(bool all); void DeleteUserDataPtrs(bool all);
void DeletePendingSpells(bool all); void DeletePendingSpells(bool all, ZoneServer* zone = nullptr);
void DeletePendingSpell(LuaSpell* spell); void DeletePendingSpell(LuaSpell* spell);
Mutex* GetSpawnScriptMutex(const char* name); Mutex* GetSpawnScriptMutex(const char* name);
Mutex* GetItemScriptMutex(const char* name); Mutex* GetItemScriptMutex(const char* name);
Mutex* GetZoneScriptMutex(const char* name); Mutex* GetZoneScriptMutex(const char* name);
Mutex* GetPlayerScriptMutex(const char* name);
Mutex* GetRegionScriptMutex(const char* name); Mutex* GetRegionScriptMutex(const char* name);
Mutex* GetSpellScriptMutex(const char* name); Mutex* GetSpellScriptMutex(const char* name);
Mutex* GetQuestMutex(Quest* quest); Mutex* GetQuestMutex(Quest* quest);
@ -309,6 +559,7 @@ public:
int32 GetFreeCustomSpellID(); int32 GetFreeCustomSpellID();
void SetLuaUserDataStale(void* ptr); void SetLuaUserDataStale(void* ptr);
bool IsLuaUserDataValid(void* ptr);
private: private:
bool shutting_down; bool shutting_down;
@ -330,6 +581,7 @@ private:
map<string, map<lua_State*, bool> > item_scripts; map<string, map<lua_State*, bool> > item_scripts;
map<string, map<lua_State*, bool> > spawn_scripts; map<string, map<lua_State*, bool> > spawn_scripts;
map<string, map<lua_State*, bool> > zone_scripts; map<string, map<lua_State*, bool> > zone_scripts;
map<string, map<lua_State*, bool> > player_scripts;
map<string, map<lua_State*, bool> > region_scripts; map<string, map<lua_State*, bool> > region_scripts;
map<string, map<lua_State*, LuaSpell*> > spell_scripts; map<string, map<lua_State*, LuaSpell*> > spell_scripts;
@ -339,11 +591,13 @@ private:
map<lua_State*, string> item_inverse_scripts; map<lua_State*, string> item_inverse_scripts;
map<lua_State*, string> spawn_inverse_scripts; map<lua_State*, string> spawn_inverse_scripts;
map<lua_State*, string> zone_inverse_scripts; map<lua_State*, string> zone_inverse_scripts;
map<lua_State*, string> player_inverse_scripts;
map<lua_State*, string> region_inverse_scripts; map<lua_State*, string> region_inverse_scripts;
map<string, Mutex*> item_scripts_mutex; map<string, Mutex*> item_scripts_mutex;
map<string, Mutex*> spawn_scripts_mutex; map<string, Mutex*> spawn_scripts_mutex;
map<string, Mutex*> zone_scripts_mutex; map<string, Mutex*> zone_scripts_mutex;
map<string, Mutex*> player_scripts_mutex;
map<int32, Mutex*> quests_mutex; map<int32, Mutex*> quests_mutex;
map<string, Mutex*> region_scripts_mutex; map<string, Mutex*> region_scripts_mutex;
map<string, Mutex*> spell_scripts_mutex; map<string, Mutex*> spell_scripts_mutex;
@ -353,6 +607,7 @@ private:
Mutex MSpawnScripts; Mutex MSpawnScripts;
Mutex MItemScripts; Mutex MItemScripts;
Mutex MZoneScripts; Mutex MZoneScripts;
Mutex MPlayerScripts;
Mutex MQuests; Mutex MQuests;
Mutex MLUAMain; Mutex MLUAMain;
Mutex MSpellDelete; Mutex MSpellDelete;

View File

@ -20,7 +20,8 @@
#ifndef MUTEXHELPER_H #ifndef MUTEXHELPER_H
#define MUTEXHELPER_H #define MUTEXHELPER_H
#include "../common/timer.hpp" #include "../common/timer.h"
#include "../common/Mutex.h"
#include <list> #include <list>
#include <map> #include <map>

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,13 +17,14 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "NPC.h" #include "NPC.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
#include <math.h> #include <math.h>
#include "client.h" #include "client.h"
#include "World.h" #include "World.h"
#include "races.h" #include "races.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "NPC_AI.h" #include "NPC_AI.h"
#include "Appearances.h" #include "Appearances.h"
#include "SpellProcess.h" #include "SpellProcess.h"
@ -108,6 +109,8 @@ NPC::NPC(NPC* old_npc){
SetLootDropType(old_npc->GetLootDropType()); SetLootDropType(old_npc->GetLootDropType());
has_spells = old_npc->HasSpells(); has_spells = old_npc->HasSpells();
SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers()); SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers());
if(old_npc->GetSpawnScriptSetDB() && old_npc->GetSpawnScript())
SetSpawnScript(std::string(old_npc->GetSpawnScript()));
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -22,7 +22,7 @@
#include "Combat.h" #include "Combat.h"
#include "zoneserver.h" #include "zoneserver.h"
#include "Spells.h" #include "Spells.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "LuaInterface.h" #include "LuaInterface.h"
#include "World.h" #include "World.h"
#include "Rules/Rules.h" #include "Rules/Rules.h"
@ -271,13 +271,57 @@ void Brain::AddHate(Entity* entity, sint32 hate) {
} }
} }
void Brain::ClearHate() { bool Brain::AdjustHatePosition(int32 id, bool increase) {
// Lock the hate list, we are altering the list so use a write lock
MHateList.writelock(__FUNCTION__, __LINE__);
auto it = m_hatelist.find(id);
if (it == m_hatelist.end()) {
MHateList.releasewritelock(__FUNCTION__, __LINE__);
return false;
}
// Build a vector of (id, hate), sorted highest→lowest hate
std::vector<std::pair<int32,sint32>> sorted;
sorted.reserve(m_hatelist.size());
for (auto &kv : m_hatelist)
sorted.emplace_back(kv.first, kv.second);
std::sort(sorted.begin(), sorted.end(),
[](auto &a, auto &b){ return a.second > b.second; });
// Locate our position
auto posIt = std::find_if(sorted.begin(), sorted.end(),
[&](auto &p){ return p.first == id; });
size_t idx = std::distance(sorted.begin(), posIt);
if (increase) {
if (idx == 0) {
MHateList.releasewritelock(__FUNCTION__, __LINE__);
return false; // no higher to go
}
sint32 aboveHate = sorted[idx - 1].second;
it->second = aboveHate + 1; // move up
}
else {
if (idx + 1 >= sorted.size()) {
MHateList.releasewritelock(__FUNCTION__, __LINE__);
return false; // already at bottom
}
sint32 belowHate = sorted[idx + 1].second;
it->second = belowHate - 1; // move lower
}
MHateList.releasewritelock(__FUNCTION__, __LINE__);
return true;
}
void Brain::ClearHate(bool lockSpawnList) {
// Lock the hate list, we are altering the list so use a write lock // Lock the hate list, we are altering the list so use a write lock
MHateList.writelock(__FUNCTION__, __LINE__); MHateList.writelock(__FUNCTION__, __LINE__);
map<int32, sint32>::iterator itr; map<int32, sint32>::iterator itr;
for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first); Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first, lockSpawnList);
if (spawn && spawn->IsEntity()) if (spawn && spawn->IsEntity())
{ {
((Entity*)spawn)->MHatedBy.lock(); ((Entity*)spawn)->MHatedBy.lock();

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __NPC_AI_H__ #ifndef __NPC_AI_H__
#define __NPC_AI_H__ #define __NPC_AI_H__
#include "NPC.h" #include "NPC.h"
@ -58,8 +59,10 @@ public:
/// <param name="entity">The entity we are adding to this NPC's hate list</param> /// <param name="entity">The entity we are adding to this NPC's hate list</param>
/// <param name="hate">The amount of hate to add</param> /// <param name="hate">The amount of hate to add</param>
virtual void AddHate(Entity* entity, sint32 hate); virtual void AddHate(Entity* entity, sint32 hate);
virtual bool AdjustHatePosition(int32 id, bool increase);
/// <summary>Completely clears the hate list for this npc</summary> /// <summary>Completely clears the hate list for this npc</summary>
void ClearHate(); void ClearHate(bool lockSpawnList = false);
/// <summary>Removes the given entity from this NPC's hate list</summary> /// <summary>Removes the given entity from this NPC's hate list</summary>
/// <param name="entity">Entity to remove from this NPC's hate list</param> /// <param name="entity">Entity to remove from this NPC's hate list</param>
void ClearHate(Entity* entity); void ClearHate(Entity* entity);

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "World.h" #include "World.h"
#include "Object.h" #include "Object.h"
#include "Spells.h" #include "Spells.h"
@ -95,5 +96,7 @@ Object* Object::Copy(){
new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
new_spawn->SetLootTier(GetLootTier()); new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType()); new_spawn->SetLootDropType(GetLootDropType());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn; return new_spawn;
} }

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,14 +17,15 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Player.h" #include "Player.h"
#include "../common/misc_functions.hpp" #include "../common/MiscFunctions.h"
#include "World.h" #include "World.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
#include <math.h> #include <math.h>
#include "classes.h" #include "classes.h"
#include "LuaInterface.h" #include "LuaInterface.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "Rules/Rules.h" #include "Rules/Rules.h"
#include "Titles.h" #include "Titles.h"
#include "Languages.h" #include "Languages.h"
@ -46,6 +47,7 @@ extern MasterItemList master_item_list;
extern RuleManager rule_manager; extern RuleManager rule_manager;
extern MasterTitlesList master_titles_list; extern MasterTitlesList master_titles_list;
extern MasterLanguagesList master_languages_list; extern MasterLanguagesList master_languages_list;
std::map<int8, int32> Player::m_levelXPReq;
Player::Player(){ Player::Player(){
tutorial_step = 0; tutorial_step = 0;
@ -135,6 +137,7 @@ Player::Player(){
active_drink_unique_id = 0; active_drink_unique_id = 0;
raidsheet_changed = false; raidsheet_changed = false;
hassent_raid = false; hassent_raid = false;
house_vault_slots = 0;
} }
Player::~Player(){ Player::~Player(){
SetSaveSpellEffects(true); SetSaveSpellEffects(true);
@ -597,6 +600,7 @@ PacketStruct* PlayerInfo::serialize2(int16 version){
packet->setDataByName("unknown20", 50, 75); packet->setDataByName("unknown20", 50, 75);
*/ */
//packet->setDataByName("rain2", -102.24); //packet->setDataByName("rain2", -102.24);
player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__);
for(int i=0;i<45;i++){ for(int i=0;i<45;i++){
if(i < 30){ if(i < 30){
packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0); packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0);
@ -620,6 +624,7 @@ PacketStruct* PlayerInfo::serialize2(int16 version){
packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0); packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0);
packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0); packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0);
} }
player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__);
return packet; return packet;
} }
return 0; return 0;
@ -949,7 +954,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
packet->setDataByName("rain2", info_struct->get_wind()); //-102.24); packet->setDataByName("rain2", info_struct->get_wind()); //-102.24);
packet->setDataByName("status_points", info_struct->get_status_points()); packet->setDataByName("status_points", info_struct->get_status_points());
packet->setDataByName("guild_status", 888888); packet->setDataByName("guild_status", 888888);
packet->setDataByName("vault_slots", player->GetHouseVaultSlots());
if (house_zone_id > 0){ if (house_zone_id > 0){
string house_name = database.GetZoneName(house_zone_id); string house_name = database.GetZoneName(house_zone_id);
if(house_name.length() > 0) if(house_name.length() > 0)
@ -999,7 +1004,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
if (version <= 561) { if (version <= 561) {
packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10); packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10);
packet->setDataByName("exp_blue", info_struct->get_xp_blue()/100); packet->setDataByName("exp_blue", ((int16)info_struct->get_xp_yellow() % 100) + (info_struct->get_xp_blue() / 100));
} }
else { else {
packet->setDataByName("exp_yellow", info_struct->get_xp_yellow()); packet->setDataByName("exp_yellow", info_struct->get_xp_yellow());
@ -1065,6 +1070,27 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
packet->setDataByName("increase_max_power2", 128); packet->setDataByName("increase_max_power2", 128);
packet->setDataByName("vision", info_struct->get_vision()); packet->setDataByName("vision", info_struct->get_vision());
switch(info_struct->get_vision()) {
case 1: // ultravision
case 255:
packet->setDataByName("spell_state_ultravision", 1);
break;
case 2: // infravision
packet->setDataByName("spell_state_infravision", 1);
break;
case 3: //sonicvision
packet->setDataByName("spell_state_sonicvision", 1);
break;
case 4: // fishvision
packet->setDataByName("spell_state_fishvision", 1);
break;
case 5: // auravision
packet->setDataByName("spell_state_auravision", 1);
break;
}
packet->setDataByName("spell_prop_redlight", info_struct->get_redlight());
packet->setDataByName("spell_prop_greenlight", info_struct->get_greenlight());
packet->setDataByName("spell_prop_bluelight", info_struct->get_bluelight());
packet->setDataByName("breathe_underwater", info_struct->get_breathe_underwater()); packet->setDataByName("breathe_underwater", info_struct->get_breathe_underwater());
int32 expireTimestamp = 0; int32 expireTimestamp = 0;
@ -1480,7 +1506,7 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
SetCharSheetChanged(true); SetCharSheetChanged(true);
SetEquipment(0, equip_slot_id ? equip_slot_id : old_slot); SetEquipment(0, equip_slot_id ? equip_slot_id : old_slot);
} }
else if (item_list.AssignItemToFreeSlot(item)) { else if (item_list.AssignItemToFreeSlot(item, true)) {
if(appearance_type) if(appearance_type)
database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
else else
@ -1641,8 +1667,8 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
} }
} }
else { else {
if ((bag_id == 0 && slot < NUM_INV_SLOTS) || (bag_id == -3 && slot < NUM_BANK_SLOTS) || (bag_id == -4 && slot < NUM_SHARED_BANK_SLOTS)) { if ((bag_id == 0 && slot < NUM_INV_SLOTS) || (bag_id == InventorySlotType::BANK && slot < NUM_BANK_SLOTS) || (bag_id == InventorySlotType::SHARED_BANK && slot < NUM_SHARED_BANK_SLOTS) || (bag_id == InventorySlotType::HOUSE_VAULT && slot < GetHouseVaultSlots())) {
if (bag_id == -4 && item->CheckFlag(NO_TRADE)) { if ((bag_id == InventorySlotType::SHARED_BANK || bag_id == InventorySlotType::HOUSE_VAULT) && !item_list.SharedBankAddAllowed(item)) {
PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); PacketStruct* packet = configReader.getStruct("WS_DisplayText", version);
if (packet) { if (packet) {
packet->setDataByName("color", CHANNEL_COLOR_YELLOW); packet->setDataByName("color", CHANNEL_COLOR_YELLOW);
@ -2014,7 +2040,7 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID());
if (zone_script && lua_interface) if (zone_script && lua_interface)
lua_interface->RunZoneScript(zone_script, "item_equipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); lua_interface->RunZoneScript(zone_script, "item_equipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id);
int32 bag_id = item->details.inv_slot_id; sint32 bag_id = item->details.inv_slot_id;
if (item->generic_info.condition == 0) { if (item->generic_info.condition == 0) {
Client* client = GetClient(); Client* client = GetClient();
if (client) { if (client) {
@ -2052,19 +2078,21 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
if(slot < 255) { if(slot < 255) {
if (slot == EQ2_FOOD_SLOT && item->IsFoodFood() && get_character_flag(CF_FOOD_AUTO_CONSUME)) { if (slot == EQ2_FOOD_SLOT && item->IsFoodFood() && get_character_flag(CF_FOOD_AUTO_CONSUME)) {
Item* item = GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); Item* item = GetEquipmentList()->GetItem(EQ2_FOOD_SLOT);
if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false))
GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT);
if(item) if(item)
SetActiveFoodUniqueID(item->details.unique_id); SetActiveFoodUniqueID(item->details.unique_id);
if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false))
GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT);
} }
else if (slot == EQ2_DRINK_SLOT && item->IsFoodDrink() && get_character_flag(CF_DRINK_AUTO_CONSUME)) { else if (slot == EQ2_DRINK_SLOT && item->IsFoodDrink() && get_character_flag(CF_DRINK_AUTO_CONSUME)) {
Item* item = GetEquipmentList()->GetItem(EQ2_DRINK_SLOT); Item* item = GetEquipmentList()->GetItem(EQ2_DRINK_SLOT);
if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false))
GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT);
if(item) if(item)
SetActiveDrinkUniqueID(item->details.unique_id); SetActiveDrinkUniqueID(item->details.unique_id);
if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false))
GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT);
} }
} }
@ -2104,7 +2132,7 @@ bool Player::AddItem(Item* item, AddItemType type) {
safe_delete(item); safe_delete(item);
return false; return false;
} }
else if (item_list.AssignItemToFreeSlot(item)) { else if (item_list.AssignItemToFreeSlot(item, true)) {
item->save_needed = true; item->save_needed = true;
CalculateApplyWeight(); CalculateApplyWeight();
return true; return true;
@ -2136,7 +2164,7 @@ bool Player::AddItemToBank(Item* item) {
} }
EQ2Packet* Player::SendInventoryUpdate(int16 version) { EQ2Packet* Player::SendInventoryUpdate(int16 version) {
// assure any inventory updates are reflected in sell window // assure any inventory updates are reflected in sell window
if(GetClient() && GetClient()->GetMerchantTransaction()) if(GetClient() && GetClient()->GetMerchantTransactionID())
GetClient()->SendSellMerchantList(); GetClient()->SendSellMerchantList();
return item_list.serialize(this, version); return item_list.serialize(this, version);
@ -2156,7 +2184,7 @@ void Player::UpdateInventory(int32 bag_id) {
EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version) { EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version) {
Item* item = item_list.GetItemFromIndex(from_index); Item* item = item_list.GetItemFromIndex(from_index);
bool isOverflow = ((item != nullptr) && (item->details.inv_slot_id == -2)); bool isOverflow = ((item != nullptr) && (item->details.inv_slot_id == InventorySlotType::OVERFLOW));
int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges); int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges);
if (result == 1) { if (result == 1) {
if(isOverflow && item->details.inv_slot_id != -2) { if(isOverflow && item->details.inv_slot_id != -2) {
@ -3280,10 +3308,17 @@ PlayerInfo::PlayerInfo(Player* in_player){
for(int i=0;i<45;i++){ for(int i=0;i<45;i++){
if(i<30){ if(i<30){
info_struct->maintained_effects[i].spell_id = 0xFFFFFFFF; info_struct->maintained_effects[i].spell_id = 0xFFFFFFFF;
info_struct->maintained_effects[i].inherited_spell_id = 0;
info_struct->maintained_effects[i].icon = 0xFFFF; info_struct->maintained_effects[i].icon = 0xFFFF;
info_struct->maintained_effects[i].spell = nullptr; info_struct->maintained_effects[i].spell = nullptr;
} }
info_struct->spell_effects[i].spell_id = 0xFFFFFFFF; info_struct->spell_effects[i].spell_id = 0xFFFFFFFF;
info_struct->spell_effects[i].inherited_spell_id = 0;
info_struct->spell_effects[i].icon = 0;
info_struct->spell_effects[i].icon_backdrop = 0;
info_struct->spell_effects[i].tier = 0;
info_struct->spell_effects[i].total_time = 0.0f;
info_struct->spell_effects[i].expire_timestamp = 0;
info_struct->spell_effects[i].spell = nullptr; info_struct->spell_effects[i].spell = nullptr;
} }
@ -3315,12 +3350,12 @@ MaintainedEffects* Player::GetFreeMaintainedSpellSlot(){
return ret; return ret;
} }
MaintainedEffects* Player::GetMaintainedSpell(int32 id){ MaintainedEffects* Player::GetMaintainedSpell(int32 id, bool on_char_load){
MaintainedEffects* ret = 0; MaintainedEffects* ret = 0;
InfoStruct* info = GetInfoStruct(); InfoStruct* info = GetInfoStruct();
GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__);
for(int i=0;i<NUM_MAINTAINED_EFFECTS;i++){ for(int i=0;i<NUM_MAINTAINED_EFFECTS;i++){
if(info->maintained_effects[i].spell_id == id){ if(info->maintained_effects[i].spell_id == id || (on_char_load && info->maintained_effects[i].inherited_spell_id == id)){
ret = &info->maintained_effects[i]; ret = &info->maintained_effects[i];
break; break;
} }
@ -3423,6 +3458,11 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
if(!luaspell) if(!luaspell)
return; return;
if(luaspell->spell->GetSpellData()->not_maintained || luaspell->spell->GetSpellData()->duration1 == 0) {
LogWrite(PLAYER__INFO, 0, "NPC", "AddMaintainedSpell Spell ID: %u, Concentration: %u disallowed, not_maintained true (%u) or duration is 0 (%u).", luaspell->spell->GetSpellData()->id, luaspell->spell->GetSpellData()->req_concentration, luaspell->spell->GetSpellData()->not_maintained, luaspell->spell->GetSpellData()->duration1);
return;
}
Spell* spell = luaspell->spell; Spell* spell = luaspell->spell;
MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); MaintainedEffects* effect = GetFreeMaintainedSpellSlot();
int32 target_type = 0; int32 target_type = 0;
@ -3467,6 +3507,9 @@ void Player::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){
return; return;
Spell* spell = luaspell->spell; Spell* spell = luaspell->spell;
if(spell->GetSpellData() && spell->GetSpellData()->icon == 0 && spell->GetSpellData()->duration1 == 0 && spell->GetSpellData()->duration2 == 0)
return;
SpellEffects* old_effect = GetSpellEffect(spell->GetSpellID(), luaspell->caster); SpellEffects* old_effect = GetSpellEffect(spell->GetSpellID(), luaspell->caster);
SpellEffects* effect = 0; SpellEffects* effect = 0;
if (old_effect){ if (old_effect){
@ -3548,6 +3591,7 @@ void Player::RemoveMaintainedSpell(LuaSpell* luaspell){
if (found) { if (found) {
memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects));
GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF;
GetInfoStruct()->maintained_effects[29].inherited_spell_id = 0;
GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; GetInfoStruct()->maintained_effects[29].icon = 0xFFFF;
GetInfoStruct()->maintained_effects[29].spell = nullptr; GetInfoStruct()->maintained_effects[29].spell = nullptr;
charsheet_changed = true; charsheet_changed = true;
@ -3568,6 +3612,7 @@ void Player::RemoveSpellEffect(LuaSpell* spell){
if (found) { if (found) {
memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects));
GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF;
GetInfoStruct()->spell_effects[44].inherited_spell_id = 0;
GetInfoStruct()->spell_effects[44].spell = nullptr; GetInfoStruct()->spell_effects[44].spell = nullptr;
changed = true; changed = true;
info_changed = true; info_changed = true;
@ -4121,7 +4166,7 @@ void Player::InCombat(bool val, bool range) {
bool update_regen = false; bool update_regen = false;
if(GetInfoStruct()->get_engaged_encounter()) { if(GetInfoStruct()->get_engaged_encounter()) {
if(!IsAggroed() || !IsEngagedInEncounter()) { if(!IsEngagedInEncounter()) {
GetInfoStruct()->set_engaged_encounter(0); GetInfoStruct()->set_engaged_encounter(0);
update_regen = true; update_regen = true;
} }
@ -4419,11 +4464,20 @@ void Player::SetNeededXP(){
//GetInfoStruct()->xp_needed = GetLevel() * 100; //GetInfoStruct()->xp_needed = GetLevel() * 100;
// Get xp needed to get to the next level // Get xp needed to get to the next level
int16 level = GetLevel() + 1; int16 level = GetLevel() + 1;
// If next level is beyond what we have in the map multiply the last value we have by how many levels we are over plus one SetNeededXP(GetNeededXPByLevel(level));
if (level > 95) }
SetNeededXP(database.GetMysqlExpCurve(95)* ((level - 95) + 1));
int32 Player::GetNeededXPByLevel(int8 level) {
int32 exp_required = 0;
if (!Player::m_levelXPReq.count(level) && level > 95 && Player::m_levelXPReq.count(95)) {
exp_required = (Player::m_levelXPReq[95] * ((level - 95) + 1));
}
else if(Player::m_levelXPReq.count(level))
exp_required = Player::m_levelXPReq[level];
else else
SetNeededXP(database.GetMysqlExpCurve(level)); exp_required = 0;
return exp_required;
} }
void Player::SetXP(int32 val){ void Player::SetXP(int32 val){
@ -4519,10 +4573,24 @@ bool Player::AddXP(int32 xp_amount){
SetCharSheetChanged(true); SetCharSheetChanged(true);
return false; return false;
} }
int32 prev_xp_amount = xp_amount;
xp_amount -= GetNeededXP() - GetXP(); xp_amount -= GetNeededXP() - GetXP();
SetLevel(GetLevel() + 1); if(GetClient()->ChangeLevel(GetLevel(), GetLevel()+1, prev_xp_amount))
SetLevel(GetLevel() + 1);
else {
SetXP(GetXP() + prev_xp_amount);
SetCharSheetChanged(true);
return false;
}
} }
// set the actual end xp_amount result
SetXP(GetXP() + xp_amount); SetXP(GetXP() + xp_amount);
if(GetClient()) {
GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount);
}
GetPlayerInfo()->CalculateXPPercentages(); GetPlayerInfo()->CalculateXPPercentages();
current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100; current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100;
if(miniding_min_percent > 0.0f && current_xp_percent >= miniding_min_percent){ if(miniding_min_percent > 0.0f && current_xp_percent >= miniding_min_percent){
@ -4533,13 +4601,6 @@ bool Player::AddXP(int32 xp_amount){
GetZone()->SendCastSpellPacket(332, this, this); //send mini level up spell effect GetZone()->SendCastSpellPacket(332, this, this); //send mini level up spell effect
} }
if(GetClient()) {
GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount);
if (prev_level != GetLevel())
GetClient()->ChangeLevel(prev_level, GetLevel());
}
SetCharSheetChanged(true); SetCharSheetChanged(true);
return true; return true;
} }
@ -4550,7 +4611,16 @@ bool Player::AddTSXP(int32 xp_amount){
MStats.unlock(); MStats.unlock();
float current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100; float current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100;
float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10;
int32 mini_ding_pct = rule_manager.GetGlobalRule(R_Player, MiniDingPercentage)->GetInt32();
float miniding_min_percent = 0.0f;
if(mini_ding_pct < 10 || mini_ding_pct > 50) {
mini_ding_pct = 0;
}
else {
miniding_min_percent = ((int)(current_xp_percent/mini_ding_pct)+1)*mini_ding_pct;
}
while((xp_amount + GetTSXP()) >= GetNeededTSXP()){ while((xp_amount + GetTSXP()) >= GetNeededTSXP()){
if (!CheckLevelStatus(GetTSLevel() + 1)) { if (!CheckLevelStatus(GetTSLevel() + 1)) {
if(GetClient()) { if(GetClient()) {
@ -4558,10 +4628,18 @@ bool Player::AddTSXP(int32 xp_amount){
} }
return false; return false;
} }
int32 prev_xp_amount = xp_amount;
xp_amount -= GetNeededTSXP() - GetTSXP(); xp_amount -= GetNeededTSXP() - GetTSXP();
SetTSLevel(GetTSLevel() + 1); if(GetClient()->ChangeTSLevel(GetLevel(), GetLevel()+1, prev_xp_amount)) {
SetTSXP(0); SetTSLevel(GetTSLevel() + 1);
SetNeededTSXP(); SetTSXP(0);
SetNeededTSXP();
}
else {
SetTSXP(GetTSXP() + prev_xp_amount);
SetCharSheetChanged(true);
return false;
}
} }
SetTSXP(GetTSXP() + xp_amount); SetTSXP(GetTSXP() + xp_amount);
GetPlayerInfo()->CalculateXPPercentages(); GetPlayerInfo()->CalculateXPPercentages();
@ -4577,6 +4655,8 @@ bool Player::AddTSXP(int32 xp_amount){
GetInfoStruct()->set_tradeskill_class2(1); GetInfoStruct()->set_tradeskill_class2(1);
GetInfoStruct()->set_tradeskill_class3(1); GetInfoStruct()->set_tradeskill_class3(1);
} }
SetCharSheetChanged(true);
return true; return true;
} }
@ -6423,7 +6503,8 @@ int32 CharacterInstances::GetInstanceCount() {
return instanceList.size(); return instanceList.size();
} }
void Player::SetPlayerAdventureClass(int8 new_class){ void Player::SetPlayerAdventureClass(int8 new_class, bool set_by_gm_command ){
int8 old_class = GetAdventureClass();
SetAdventureClass(new_class); SetAdventureClass(new_class);
GetInfoStruct()->set_class1(classes.GetBaseClass(new_class)); GetInfoStruct()->set_class1(classes.GetBaseClass(new_class));
GetInfoStruct()->set_class2(classes.GetSecondaryBaseClass(new_class)); GetInfoStruct()->set_class2(classes.GetSecondaryBaseClass(new_class));
@ -6433,6 +6514,23 @@ void Player::SetPlayerAdventureClass(int8 new_class){
GetZone()->TriggerCharSheetTimer(); GetZone()->TriggerCharSheetTimer();
if(GetClient()) if(GetClient())
GetClient()->UpdateTimeStampFlag ( CLASS_UPDATE_FLAG ); GetClient()->UpdateTimeStampFlag ( CLASS_UPDATE_FLAG );
const char* playerScript = world.GetPlayerScript(0); // 0 = global script
const char* playerZoneScript = world.GetPlayerScript(GetZoneID()); // zone script
if(playerScript || playerZoneScript) {
std::vector<LuaArg> args = {
LuaArg(GetZone()),
LuaArg(this),
LuaArg(old_class),
LuaArg(new_class)
};
if(playerScript) {
lua_interface->RunPlayerScriptWithReturn(playerScript, "on_class_change", args);
}
if(playerZoneScript) {
lua_interface->RunPlayerScriptWithReturn(playerZoneScript, "on_class_change", args);
}
}
} }
void Player::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { void Player::AddSkillBonus(int32 spell_id, int32 skill_id, float value) {
@ -7266,11 +7364,115 @@ NPC* Player::InstantiateSpiritShard(float origX, float origY, float origZ, float
return npc; return npc;
} }
void Player::SaveCustomSpellFields(LuaSpell* luaspell) {
if (!luaspell || !luaspell->spell || !luaspell->spell->IsCopiedSpell())
return;
auto spell_data = luaspell->spell->GetSpellData();
std::unordered_set<std::string> modified_fields = luaspell->GetModifiedFieldsCopy();
Query savedEffects;
for (const std::string& field : modified_fields) {
auto it = SpellDataFieldAccessors.find(field);
if (it == SpellDataFieldAccessors.end())
continue;
const auto& [type, getter] = it->second;
std::string value = getter(spell_data);
std::string type_str;
switch (type) {
case SpellFieldType::Integer: type_str = "int"; break;
case SpellFieldType::Float: type_str = "float"; break;
case SpellFieldType::Boolean: type_str = "bool"; break;
case SpellFieldType::String: type_str = "string"; break;
default: continue;
}
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_custom_spell_data (charid, spell_id, field, type, value) VALUES (%u, %u, '%s', '%s', '%s')",
GetCharacterID(),
luaspell->spell->GetSpellData()->inherited_spell_id,
database.getSafeEscapeString(field.c_str()).c_str(),
type_str.c_str(),
database.getSafeEscapeString(value.c_str()).c_str());
}
}
void Player::SaveCustomSpellDataIndex(LuaSpell* luaspell) {
if (!luaspell || !luaspell->spell || !luaspell->spell->IsCopiedSpell())
return;
auto& vec = luaspell->spell->lua_data;
Query savedEffects;
for (int i = 0; i < vec.size(); ++i) {
LUAData* data = vec[i];
if (!data || !data->needs_db_save)
continue;
std::string value1, value2, type;
switch (data->type) {
case 0:
value1 = std::to_string(data->int_value);
value2 = std::to_string(data->int_value2);
type = "int";
break;
case 1:
value1 = std::to_string(data->float_value);
value2 = std::to_string(data->float_value2);
type = "float";
break;
case 2:
value1 = data->bool_value ? "1" : "0";
type = "bool";
break;
case 3:
value1 = database.getSafeEscapeString(data->string_value.c_str());
value2 = database.getSafeEscapeString(data->string_value2.c_str());
type = "string";
break;
default:
continue;
}
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_custom_spell_dataindex (charid, spell_id, idx, type, value1, value2) VALUES (%u, %u, %d, '%s', '%s', '%s')", GetCharacterID(),
luaspell->spell->GetSpellData()->inherited_spell_id,
i,
type.c_str(), value1.c_str(), value2.c_str());
}
}
void Player::SaveCustomSpellEffectsDisplay(LuaSpell* luaspell) {
if (!luaspell || !luaspell->spell || !luaspell->spell->IsCopiedSpell())
return;
auto& vec = luaspell->spell->effects;
Query savedEffects;
for (int i = 0; i < vec.size(); ++i) {
SpellDisplayEffect* eff = vec[i];
if (!eff || !eff->needs_db_save)
continue;
std::string charid = std::to_string(GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_custom_spell_display (charid, spell_id, idx, field, value) VALUES (%u, %u, %d, 'description', '%s')",
GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id, i,
database.getSafeEscapeString(eff->description.c_str()).c_str());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_custom_spell_display (charid, spell_id, idx, field, value) VALUES (%u, %u, %d, 'bullet', '%d')",
GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id, i, eff->subbullet);
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_custom_spell_display (charid, spell_id, idx, field, value) VALUES (%u, %u, %d, 'percentage', '%d')",
GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id, i, eff->percentage);
}
}
void Player::SaveSpellEffects() void Player::SaveSpellEffects()
{ {
if(stop_save_spell_effects) if(stop_save_spell_effects)
{ {
LogWrite(PLAYER__WARNING, 0, "Player", "SaveSpellEffects called while player constructing / deconstructing!"); LogWrite(PLAYER__WARNING, 0, "Player", "%s: SaveSpellEffects called while player constructing / deconstructing!", GetName());
return; return;
} }
@ -7281,9 +7483,12 @@ void Player::SaveSpellEffects()
Query savedEffects; Query savedEffects;
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effects where charid=%u", GetCharacterID()); savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effects where charid=%u", GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id=%u", GetCharacterID()); savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id=%u", GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_dataindex where charid=%u", GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_display where charid=%u", GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_data where charid=%u", GetCharacterID());
InfoStruct* info = GetInfoStruct(); InfoStruct* info = GetInfoStruct();
MSpellEffects.readlock(__FUNCTION__, __LINE__);
MMaintainedSpells.readlock(__FUNCTION__, __LINE__); MMaintainedSpells.readlock(__FUNCTION__, __LINE__);
MSpellEffects.readlock(__FUNCTION__, __LINE__);
for(int i = 0; i < 45; i++) { for(int i = 0; i < 45; i++) {
if(info->spell_effects[i].spell_id != 0xFFFFFFFF) if(info->spell_effects[i].spell_id != 0xFFFFFFFF)
{ {
@ -7311,8 +7516,13 @@ void Player::SaveSpellEffects()
info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(),
info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers, info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers,
info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str(), info->spell_effects[i].spell->initial_caster_level); info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str(), info->spell_effects[i].spell->initial_caster_level);
SaveCustomSpellFields(info->spell_effects[i].spell);
SaveCustomSpellDataIndex(info->spell_effects[i].spell);
SaveCustomSpellEffectsDisplay(info->spell_effects[i].spell);
} }
if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){ if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell && info->maintained_effects[i].spell_id != 0xFFFFFFFF){
LogWrite(PLAYER__INFO, 0, "Player", "Saving slot %u maintained effect %u", i, info->maintained_effects[i].spell_id);
Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target); Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target);
int32 target_char_id = 0; int32 target_char_id = 0;
@ -7339,13 +7549,15 @@ void Player::SaveSpellEffects()
info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers, info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers,
info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str(), info->maintained_effects[i].spell->initial_caster_level); info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str(), info->maintained_effects[i].spell->initial_caster_level);
info->maintained_effects[i].spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); SaveCustomSpellFields(info->maintained_effects[i].spell);
SaveCustomSpellDataIndex(info->maintained_effects[i].spell);
SaveCustomSpellEffectsDisplay(info->maintained_effects[i].spell);
std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values "); std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values ");
bool firstTarget = true; bool firstTarget = true;
map<Spawn*, int8> targetsInserted; map<Spawn*, int8> targetsInserted;
for (int8 t = 0; t < info->maintained_effects[i].spell->targets.size(); t++) { for (int32 id : info->maintained_effects[i].spell->GetTargets()) {
int32 spawn_id = info->maintained_effects[i].spell->targets.at(t); Spawn* spawn = GetZone()->GetSpawnByID(id);
Spawn* spawn = GetZone()->GetSpawnByID(spawn_id);
LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %u to identify for spell %s", GetName(), spawn_id, info->maintained_effects[i].spell->spell->GetName()); LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %u to identify for spell %s", GetName(), spawn_id, info->maintained_effects[i].spell->spell->GetName());
if(spawn && (spawn->IsPlayer() || spawn->IsPet())) if(spawn && (spawn->IsPlayer() || spawn->IsPet()))
{ {
@ -7381,28 +7593,27 @@ void Player::SaveSpellEffects()
", " + std::to_string(info->maintained_effects[i].slot_pos) + ")"); ", " + std::to_string(info->maintained_effects[i].slot_pos) + ")");
firstTarget = false; firstTarget = false;
} }
} }
multimap<int32,int8>::iterator entries; for (const auto& [char_id, pet_type] : info->maintained_effects[i].spell->GetCharIDTargets()) {
for(entries = info->maintained_effects[i].spell->char_id_targets.begin(); entries != info->maintained_effects[i].spell->char_id_targets.end(); entries++) {
{ if(!firstTarget)
if(!firstTarget) insertTargets.append(", ");
insertTargets.append(", ");
LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", entries->first, info->maintained_effects[i].spell->spell->GetName()); LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target (%u) added to spell %s", GetName(), char_id, info->maintained_effects[i].spell->spell->GetName());
insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(entries->first) + ", " + std::to_string(entries->second) + ", " + insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(char_id) + ", " + std::to_string(pet_type) + ", " +
std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) +
", " + std::to_string(info->maintained_effects[i].slot_pos) + ")"); ", " + std::to_string(info->maintained_effects[i].slot_pos) + ")");
firstTarget = false; firstTarget = false;
} }
info->maintained_effects[i].spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); if(!firstTarget) {
if(!firstTarget) { savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTargets.c_str());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTargets.c_str()); }
} }
} }
} }
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); MSpellEffects.releasereadlock(__FUNCTION__, __LINE__);
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
} }
void Player::MentorTarget() void Player::MentorTarget()
@ -7720,7 +7931,7 @@ void Player::CalculatePlayerHPPower(int16 new_level) {
bool Player::IsAllowedCombatEquip(int8 slot, bool send_message) { bool Player::IsAllowedCombatEquip(int8 slot, bool send_message) {
bool rule_pass = true; bool rule_pass = true;
if(EngagedInCombat() && rule_manager.GetZoneRule(GetZoneID(), R_Player, AllowPlayerEquipCombat)->GetInt8() == 0) { if(GetInfoStruct()->get_engaged_encounter() && rule_manager.GetZoneRule(GetZoneID(), R_Player, AllowPlayerEquipCombat)->GetInt8() == 0) {
switch(slot) { switch(slot) {
case EQ2_PRIMARY_SLOT: case EQ2_PRIMARY_SLOT:
case EQ2_SECONDARY_SLOT: case EQ2_SECONDARY_SLOT:

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_PLAYER__ #ifndef __EQ2_PLAYER__
#define __EQ2_PLAYER__ #define __EQ2_PLAYER__
@ -224,7 +225,7 @@ struct QuickBarItem{
int16 icon_type; int16 icon_type;
int32 id; int32 id;
int8 tier; int8 tier;
int32 unique_id; int64 unique_id;
EQ2_16BitString text; EQ2_16BitString text;
}; };
@ -588,7 +589,7 @@ public:
EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version = 1); EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version = 1);
bool IsPlayer(){ return true; } bool IsPlayer(){ return true; }
MaintainedEffects* GetFreeMaintainedSpellSlot(); MaintainedEffects* GetFreeMaintainedSpellSlot();
MaintainedEffects* GetMaintainedSpell(int32 id); MaintainedEffects* GetMaintainedSpell(int32 id, bool on_char_load = false);
MaintainedEffects* GetMaintainedSpellBySlot(int8 slot); MaintainedEffects* GetMaintainedSpellBySlot(int8 slot);
MaintainedEffects* GetMaintainedSpells(); MaintainedEffects* GetMaintainedSpells();
SpellEffects* GetFreeSpellEffectSlot(); SpellEffects* GetFreeSpellEffectSlot();
@ -608,6 +609,7 @@ public:
bool TradeskillXPEnabled(); bool TradeskillXPEnabled();
void SetNeededXP(int32 val); void SetNeededXP(int32 val);
void SetNeededXP(); void SetNeededXP();
static int32 GetNeededXPByLevel(int8 level);
void SetXP(int32 val); void SetXP(int32 val);
void SetNeededTSXP(int32 val); void SetNeededTSXP(int32 val);
void SetNeededTSXP(); void SetNeededTSXP();
@ -789,7 +791,7 @@ public:
bool GetIsTracking() const { return is_tracking; } bool GetIsTracking() const { return is_tracking; }
void SetBiography(string new_biography) { biography = new_biography; } void SetBiography(string new_biography) { biography = new_biography; }
string GetBiography() const { return biography; } string GetBiography() const { return biography; }
void SetPlayerAdventureClass(int8 new_class); void SetPlayerAdventureClass(int8 new_class, bool set_by_gm_command = false);
void SetGuild(Guild* new_guild) { guild = new_guild; } void SetGuild(Guild* new_guild) { guild = new_guild; }
Guild* GetGuild() { return guild; } Guild* GetGuild() { return guild; }
void AddSkillBonus(int32 spell_id, int32 skill_id, float value); void AddSkillBonus(int32 spell_id, int32 skill_id, float value);
@ -1048,6 +1050,9 @@ public:
void DismissAllPets(); void DismissAllPets();
void SaveSpellEffects(); void SaveSpellEffects();
void SaveCustomSpellFields(LuaSpell* luaspell);
void SaveCustomSpellDataIndex(LuaSpell* luaspell);
void SaveCustomSpellEffectsDisplay(LuaSpell* luaspell);
void SetSaveSpellEffects(bool val) { stop_save_spell_effects = val; } void SetSaveSpellEffects(bool val) { stop_save_spell_effects = val; }
AppearanceData SavedApp; AppearanceData SavedApp;
@ -1098,8 +1103,11 @@ public:
void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true); void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true);
void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true); void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true);
int32 GetActiveFoodUniqueID() { return active_food_unique_id; } int64 GetActiveFoodUniqueID() { return active_food_unique_id; }
int32 GetActiveDrinkUniqueID() { return active_drink_unique_id; } int64 GetActiveDrinkUniqueID() { return active_drink_unique_id; }
void SetHouseVaultSlots(int8 allowed_slots) { house_vault_slots = allowed_slots; }
int8 GetHouseVaultSlots() { return house_vault_slots; }
Mutex MPlayerQuests; Mutex MPlayerQuests;
float pos_packet_speed; float pos_packet_speed;
@ -1112,8 +1120,8 @@ public:
mutable std::shared_mutex trait_mutex; mutable std::shared_mutex trait_mutex;
std::atomic<bool> need_trait_update; std::atomic<bool> need_trait_update;
void InitXPTable(); static void InitXPTable();
map<int8, int32> m_levelXPReq; static map<int8, int32> m_levelXPReq;
mutable std::shared_mutex spell_packet_update_mutex; mutable std::shared_mutex spell_packet_update_mutex;
mutable std::shared_mutex raid_update_mutex; mutable std::shared_mutex raid_update_mutex;
@ -1214,8 +1222,6 @@ private:
void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast); void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast);
// void InitXPTable();
// map<int8, int32> m_levelXPReq;
//The following variables are for serializing spawn packets //The following variables are for serializing spawn packets
PacketStruct* spawn_pos_struct; PacketStruct* spawn_pos_struct;
@ -1261,8 +1267,10 @@ private:
Quest* GetAnyQuest(int32 quest_id); Quest* GetAnyQuest(int32 quest_id);
Quest* GetCompletedQuest(int32 quest_id); Quest* GetCompletedQuest(int32 quest_id);
std::atomic<int32> active_food_unique_id; std::atomic<int64> active_food_unique_id;
std::atomic<int32> active_drink_unique_id; std::atomic<int64> active_drink_unique_id;
int8 house_vault_slots;
}; };
#pragma pack() #pragma pack()
#endif #endif

View File

@ -1,25 +1,25 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "PlayerGroups.h" #include "PlayerGroups.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "World.h" #include "World.h"
#include "Spells.h" #include "Spells.h"
#include "LuaInterface.h" #include "LuaInterface.h"
@ -27,13 +27,14 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "SpellProcess.h" #include "SpellProcess.h"
#include "Rules/Rules.h" #include "Rules/Rules.h"
#include "Web/PeerManager.h" #include "Web/PeerManager.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
extern ConfigReader configReader; extern ConfigReader configReader;
extern ZoneList zone_list; extern ZoneList zone_list;
extern RuleManager rule_manager; extern RuleManager rule_manager;
extern PeerManager peer_manager; extern PeerManager peer_manager;
extern WorldDatabase database; extern WorldDatabase database;
extern LuaInterface* lua_interface;
/******************************************************** PlayerGroup ********************************************************/ /******************************************************** PlayerGroup ********************************************************/
PlayerGroup::PlayerGroup(int32 id) { PlayerGroup::PlayerGroup(int32 id) {
@ -906,25 +907,6 @@ int32 PlayerGroupManager::GetGroupSize(int32 group_id) {
return ret; return ret;
} }
void PlayerGroupManager::SendGroupQuests(int32 group_id, Client* client) {
std::shared_lock lock(MGroups);
GroupMemberInfo* info = 0;
if (m_groups.count(group_id) > 0) {
m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = m_groups[group_id]->GetMembers();
deque<GroupMemberInfo*>::iterator itr;
for (itr = members->begin(); itr != members->end(); itr++) {
info = *itr;
if (info->client) {
LogWrite(PLAYER__DEBUG, 0, "Player", "Send Quest Journal...");
info->client->SendQuestJournal(false, client);
client->SendQuestJournal(false, info->client);
}
}
m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
bool PlayerGroupManager::HasGroupCompletedQuest(int32 group_id, int32 quest_id) { bool PlayerGroupManager::HasGroupCompletedQuest(int32 group_id, int32 quest_id) {
std::shared_lock lock(MGroups); std::shared_lock lock(MGroups);
bool questComplete = true; bool questComplete = true;
@ -1113,6 +1095,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
caster = *vitr; caster = *vitr;
caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__);
// go through the player's maintained spells // go through the player's maintained spells
bool skipSpell = false;
me = caster->GetMaintainedSpells(); me = caster->GetMaintainedSpells();
for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) {
if (me[i].spell_id == 0xFFFFFFFF) if (me[i].spell_id == 0xFFFFFFFF)
@ -1132,10 +1115,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell && if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell &&
(spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) { (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) {
luaspell->ClearCharTargets();
luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
luaspell->char_id_targets.clear();
for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) { for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) {
group_member = (*target_itr)->member; group_member = (*target_itr)->member;
@ -1147,13 +1127,45 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client = (*target_itr)->client; client = (*target_itr)->client;
LuaSpell* conflictSpell = caster->HasLinkedTimerID(luaspell, group_member, false, true);
if(conflictSpell && group_member && group_member->IsEntity())
{
if(conflictSpell->spell->GetSpellData()->min_class_skill_req > 0 && spell->GetSpellData()->min_class_skill_req > 0)
{
if(conflictSpell->spell->GetSpellData()->min_class_skill_req <= spell->GetSpellData()->min_class_skill_req)
{
if(spell->GetSpellData()->duration_until_cancel && !luaspell->num_triggers)
{
for (int32 id : conflictSpell->GetTargets()) {
Spawn* tmpTarget = caster->GetZone()->GetSpawnByID(id);
if(tmpTarget && tmpTarget->IsEntity())
{
((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell);
caster->GetZone()->RemoveTargetFromSpell(conflictSpell, tmpTarget, false);
caster->GetZone()->GetSpellProcess()->CheckRemoveTargetFromSpell(conflictSpell);
lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget);
}
}
}
}
else
{
// this is a spell that is no good, have to abort!
caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
caster->GetZone()->GetSpellProcess()->SpellCannotStack(caster->GetZone(), client, caster, luaspell, conflictSpell);
skipSpell = true;
break;
}
}
}
has_effect = false; has_effect = false;
if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) { if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) {
has_effect = true; has_effect = true;
} }
if (!has_effect && (std::find(luaspell->removed_targets.begin(), std::vector<int32> removed_targets = luaspell->GetRemovedTargets();
luaspell->removed_targets.end(), group_member->GetID()) != luaspell->removed_targets.end())) { if (!has_effect && (std::find(removed_targets.begin(),
removed_targets.end(), group_member->GetID()) != removed_targets.end())) {
continue; continue;
} }
// Check if player is within range of the caster // Check if player is within range of the caster
@ -1245,14 +1257,18 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client->QueuePacket(packet); client->QueuePacket(packet);
} }
} }
if(!skipSpell) {
luaspell->targets.swap(new_target_list); luaspell->SwapTargets(new_target_list);
SpellProcess::AddSelfAndPet(luaspell, caster); SpellProcess::AddSelfAndPet(luaspell, caster);
luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); new_target_list.clear();
new_target_list.clear(); }
else
break;
} }
} }
caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
if(!skipSpell)
caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
} }
} }
} }
@ -1910,6 +1926,11 @@ bool PlayerGroupManager::IdentifyMemberInGroupOrRaid(ZoneChangeDetails* details,
succeed = true; succeed = true;
break; break;
} }
else {
succeed = zone_list.GetZoneByInstance(details, (*itr)->instance_id, (*itr)->zone_id, true, false);
if(succeed)
break;
}
} }
} }
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
@ -1951,6 +1972,11 @@ bool PlayerGroupManager::IdentifyMemberInGroupOrRaid(ZoneChangeDetails* details,
succeed = true; succeed = true;
break; break;
} }
else {
succeed = zone_list.GetZoneByInstance(details, (*itr)->instance_id, (*itr)->zone_id, true, false);
if(succeed)
break;
}
} }
} }
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);

View File

@ -27,7 +27,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include <shared_mutex> #include <shared_mutex>
#include "Spells.h" #include "Spells.h"
#include "../common/types.hpp" #include "../common/types.h"
#include "Entity.h" #include "Entity.h"
using namespace std; using namespace std;
@ -220,7 +220,6 @@ public:
int32 GetGroupSize(int32 group_id); int32 GetGroupSize(int32 group_id);
void SendGroupQuests(int32 group_id, Client* client);
bool HasGroupCompletedQuest(int32 group_id, int32 quest_id); bool HasGroupCompletedQuest(int32 group_id, int32 quest_id);
void SimpleGroupMessage(int32 group_id, const char* message); void SimpleGroupMessage(int32 group_id, const char* message);

View File

@ -18,12 +18,12 @@
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Quests.h" #include "Quests.h"
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "Player.h" #include "Player.h"
#include "LuaInterface.h" #include "LuaInterface.h"
#include "Spells.h" #include "Spells.h"
#include "RaceTypes/RaceTypes.h" #include "RaceTypes/RaceTypes.h"
#include "../common/log.hpp" #include "../common/Log.h"
#ifdef WIN32 #ifdef WIN32
#include <time.h> #include <time.h>

View File

@ -21,7 +21,7 @@
#ifndef __RACETYPES_H__ #ifndef __RACETYPES_H__
#define __RACETYPES_H__ #define __RACETYPES_H__
#include "../../common/types.hpp" #include "../../common/types.h"
#include <map> #include <map>
#define DRAGONKIND 101 #define DRAGONKIND 101

View File

@ -18,7 +18,7 @@
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "RaceTypes.h" #include "RaceTypes.h"

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -17,12 +17,13 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <assert.h> #include <assert.h>
#include "../../common/debug.hpp" #include "../../common/debug.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/database.hpp" #include "../../common/database.h"
#include "Recipe.h" #include "Recipe.h"
#include "../../common/config_reader.hpp" #include "../../common/ConfigReader.h"
#include "../Items/Items.h" #include "../Items/Items.h"
#include "../World.h" #include "../World.h"
@ -734,7 +735,12 @@ bool Recipe::PlayerHasComponentByItemID(Client* client, vector<Item*>* player_co
cur_qty = track_req_qty; cur_qty = track_req_qty;
track_req_qty -= cur_qty; track_req_qty -= cur_qty;
itemss[i]->details.item_locked = true; if(!itemss[i]->TryLockItem(LockReason::LockReason_Crafting)) {
for (int8 s = 0; s < i; s++) {
itemss[i]->TryUnlockItem(LockReason::LockReason_Crafting);
}
return false;
}
player_component_pair_qty->push_back({itemss[i]->details.unique_id, cur_qty}); player_component_pair_qty->push_back({itemss[i]->details.unique_id, cur_qty});
player_components->push_back(itemss[i]); player_components->push_back(itemss[i]);
if(have_qty >= required_qty) if(have_qty >= required_qty)

View File

@ -20,7 +20,8 @@
#ifndef RECIPE_H_ #ifndef RECIPE_H_
#define RECIPE_H_ #define RECIPE_H_
#include "../../common/types.hpp" #include "../../common/types.h"
#include "../../common/Mutex.h"
#include "../classes.h" #include "../classes.h"
#include <string.h> #include <string.h>

View File

@ -24,7 +24,7 @@
#include <mysql.h> #include <mysql.h>
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
#include "Recipe.h" #include "Recipe.h"
extern MasterRecipeList master_recipe_list; extern MasterRecipeList master_recipe_list;

View File

@ -19,9 +19,9 @@
*/ */
#include <assert.h> #include <assert.h>
#include "../../common/debug.hpp" #include "../../common/debug.h"
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../../common/database.hpp" #include "../../common/database.h"
#include "Rules.h" #include "Rules.h"
extern RuleManager rule_manager; extern RuleManager rule_manager;

View File

@ -22,8 +22,8 @@
#include <string.h> #include <string.h>
#include <map> #include <map>
#include "../../common/Mutex.h"
#include "../../common/types.hpp" #include "../../common/types.h"
using namespace std; using namespace std;

View File

@ -20,7 +20,7 @@
#include <assert.h> #include <assert.h>
#include "../../common/Log.h" #include "../../common/Log.h"
#include "../Worlddatabase.hpp" #include "../WorldDatabase.h"
extern RuleManager rule_manager; extern RuleManager rule_manager;

View File

@ -1,28 +1,29 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Sign.h" #include "Sign.h"
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "WorldDatabase.hpp" #include "WorldDatabase.h"
#include "World.h" #include "World.h"
#include "../common/log.hpp" #include "../common/Log.h"
extern World world; extern World world;
extern ConfigReader configReader; extern ConfigReader configReader;
@ -152,6 +153,8 @@ Sign* Sign::Copy(){
new_spawn->SetLootTier(GetLootTier()); new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType()); new_spawn->SetLootDropType(GetLootDropType());
new_spawn->SetLanguage(GetLanguage()); new_spawn->SetLanguage(GetLanguage());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn; return new_spawn;
} }

View File

@ -20,7 +20,7 @@
#include "Skills.h" #include "Skills.h"
#include "Spawn.h" #include "Spawn.h"
#include "LuaInterface.h" #include "LuaInterface.h"
#include "../common/log.hpp" #include "../common/Log.h"
extern ConfigReader configReader; extern ConfigReader configReader;
extern LuaInterface* lua_interface; extern LuaInterface* lua_interface;

View File

@ -24,8 +24,8 @@
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include "../common/config_reader.hpp" #include "../common/ConfigReader.h"
#include "../common/types.hpp" #include "../common/types.h"
#include "MutexMap.h" #include "MutexMap.h"
#define SKILL_TYPE_WEAPONRY 1 #define SKILL_TYPE_WEAPONRY 1

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator. This file is part of EQ2Emulator.
@ -25,8 +25,8 @@
#include "Entity.h" #include "Entity.h"
#include "Widget.h" #include "Widget.h"
#include "Sign.h" #include "Sign.h"
#include "../common/misc_functions.hpp" #include "../common/MiscFunctions.h"
#include "../common/log.hpp" #include "../common/Log.h"
#include "Rules/Rules.h" #include "Rules/Rules.h"
#include "World.h" #include "World.h"
#include "LuaInterface.h" #include "LuaInterface.h"
@ -120,6 +120,7 @@ Spawn::Spawn(){
has_spawn_proximities = false; has_spawn_proximities = false;
pickup_item_id = 0; pickup_item_id = 0;
pickup_unique_item_id = 0; pickup_unique_item_id = 0;
house_character_id = 0;
disable_sounds = false; disable_sounds = false;
has_quests_required = false; has_quests_required = false;
has_history_required = false; has_history_required = false;
@ -153,6 +154,7 @@ Spawn::Spawn(){
respawn_offset_high = 0; respawn_offset_high = 0;
duplicated_spawn = true; duplicated_spawn = true;
ResetKnockedBack(); ResetKnockedBack();
spawn_script_setdb = false;
} }
Spawn::~Spawn(){ Spawn::~Spawn(){
@ -1925,8 +1927,9 @@ const char* Spawn::GetSpawnScript(){
return 0; return 0;
} }
void Spawn::SetSpawnScript(string name){ void Spawn::SetSpawnScript(string name, bool db_set){
spawn_script = name; spawn_script = name;
spawn_script_setdb = db_set;
} }
void Spawn::SetPrimaryCommand(const char* name, const char* command, float distance){ void Spawn::SetPrimaryCommand(const char* name, const char* command, float distance){
@ -2180,7 +2183,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
packet->setDataByName("pos_grid_id", 0); packet->setDataByName("pos_grid_id", 0);
} }
else { else {
packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : GetLocation()); packet->setDataByName("pos_grid_id", (new_grid_id != 0 && player != this) ? new_grid_id : GetLocation());
} }
bool include_heading = true; bool include_heading = true;
@ -2197,17 +2200,24 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
if (size == 0) if (size == 0)
size = 32; size = 32;
float scaled = 0.0f;
sint16 new_size = size;
if(IsEntity() && !IsPlayer()) { // doesn't work correctly for Player, only non-Player (NPC/Object/etc)
scaled = static_cast<float>(size) + (((Entity*)this)->GetInfoStruct()->get_size_mod() * size);
// Round and cast to sint16
new_size = static_cast<sint16>(std::round(scaled));
// Enforce minimum size of 1
new_size = std::max<sint16>(new_size, 1);
}
if (version <= 910) { if (version <= 910) {
packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32);
packet->setDataByName("pos_size", size > 0 ? size : 32); packet->setDataByName("pos_size", new_size > 0 ? new_size : 32);
} }
else { else {
packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32);
packet->setDataByName("pos_size", new_size > 0 ? (((float)new_size) / 32.0f) : 1.0f); // float not an integer
if(!IsPlayer())
packet->setDataByName("pos_size", size > 0 ? (((float)size) / 32.0f) : 1.0f); // float not an integer
else
packet->setDataByName("pos_size", 1.0f);
// please do not remove! This makes it so NPCs for example do not resize large/small when you are in combat with them! // please do not remove! This makes it so NPCs for example do not resize large/small when you are in combat with them!
packet->setDataByName("pos_size_ratio", 1.0f); packet->setDataByName("pos_size_ratio", 1.0f);
@ -2234,7 +2244,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
else { else {
packet->setDataByName("pos_x", appearance.pos.X); packet->setDataByName("pos_x", appearance.pos.X);
float result_y = appearance.pos.Y; float result_y = appearance.pos.Y;
if(!IsWidget() && !IsSign() && !IsObject() && !(IsFlyingCreature() || IsWaterCreature() || InWater())) { if(!IsWidget() && !IsSign() && !IsObject() && !(IsFlyingCreature() || IsWaterCreature() || InWater()) && player != this) {
result_y = new_y; result_y = new_y;
} }
if(GetMap() != player->GetMap()) { if(GetMap() != player->GetMap()) {
@ -2488,11 +2498,6 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
if (GetMerchantID() > 0) if (GetMerchantID() > 0)
packet->setDataByName("merchant", 1); packet->setDataByName("merchant", 1);
if(IsEntity() && ((Entity*)this)->GetInfoStruct()->get_size_mod() != 0.0f) {
float mod = ((Entity*)this)->GetInfoStruct()->get_size_mod(); // e.g., -0.25 or +0.25
//packet->setDataByName("temporary_scale", mod); //TODO: Understand what these mod values should be, anything we send makes the client shrink to nothing
}
packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel()); packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel());
packet->setDataByName("level", (int8)GetLevel()); packet->setDataByName("level", (int8)GetLevel());
packet->setDataByName("unknown4", (int8)GetLevel()); packet->setDataByName("unknown4", (int8)GetLevel());
@ -2981,8 +2986,9 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
spell_id = info->spell_effects[i].spell_id; spell_id = info->spell_effects[i].spell_id;
if(spell_id > 0) if(spell_id > 0)
spell_id = 0xFFFFFFFF - spell_id; spell_id = 0xFFFFFFFF - spell_id;
else else {
spell_id = 0; spell_id = 0;
}
packet->setSubstructDataByName("spell_effects", "spell_id", spell_id, i); packet->setSubstructDataByName("spell_effects", "spell_id", spell_id, i);
//Change value of spell icon for this packet if spell exists //Change value of spell icon for this packet if spell exists
@ -3025,7 +3031,7 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
packet->setSubstructDataByName("spell_effects", "spell_icon_backdrop", backdrop, i); packet->setSubstructDataByName("spell_effects", "spell_icon_backdrop", backdrop, i);
spell = info->spell_effects[i].spell; spell = info->spell_effects[i].spell;
if (spell) if (spell_id && spell_id != 0xFFFFFFFF && spell)
packet->setSubstructDataByName("spell_effects", "spell_triggercount", spell->num_triggers, i); packet->setSubstructDataByName("spell_effects", "spell_triggercount", spell->num_triggers, i);
i++; i++;
} }
@ -3360,8 +3366,9 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
} }
if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) { if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) {
// 6/7/2025: Fixes glitchy movement when spawns are close together and keep warping forward and back to the original position
CalculateRunningLocation(); CalculateRunningLocation();
//last_movement_update = Timer::GetCurrentTime2(); last_movement_update = Timer::GetCurrentTime2();
} }
else if(movementCase) else if(movementCase)
{ {
@ -3866,8 +3873,6 @@ void Spawn::FaceTarget(Spawn* target, bool disable_action_state){
if(!target) if(!target)
return; return;
FaceTarget(target->GetX(), target->GetZ());
if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){ if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){
if(!IsPet() && disable_action_state) { if(!IsPet() && disable_action_state) {
if(IsNPC()) { if(IsNPC()) {
@ -3877,6 +3882,8 @@ void Spawn::FaceTarget(Spawn* target, bool disable_action_state){
SetTempActionState(0); SetTempActionState(0);
} }
} }
FaceTarget(target->GetX(), target->GetZ());
} }
bool Spawn::MeetsSpawnAccessRequirements(Player* player){ bool Spawn::MeetsSpawnAccessRequirements(Player* player){

Some files were not shown because too many files have changed in this diff Show More