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() {
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 <assert.h>
#include "achievements.hpp"
#include "../../common/Log.h"
#include "../WorldDatabase.h"
#include "../../common/log.hpp"
#include "Achievements.h"
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 achievement requirements", aReqs_total);
LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards", aRewards_total);
}
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 "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
#include "../../common/Log.h"
#include "../Spells.h"
#include "../classes.h"
@ -9,8 +27,8 @@
#include <map>
#include <assert.h>
#include <mysql.h>
#include "../../common/database_new.hpp"
#include "../Worlddatabase.hpp"
#include "../../common/DatabaseNew.h"
#include "../WorldDatabase.h"
extern ConfigReader configReader;
extern MasterSpellList master_spell_list;
extern Classes classes;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#include "../Worlddatabase.hpp"
#include "../WorldDatabase.h"
#include "../../common/Log.h"
#include "Bot.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 "../../common/Log.h"
#include "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
#include "../../common/PacketStruct.h"
#include "../Rules/Rules.h"
extern RuleManager rule_manager;

View File

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

View File

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

View File

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

View File

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

View File

@ -18,12 +18,12 @@
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ClientPacketFunctions.h"
#include "WorldDatabase.hpp"
#include "../common/config_reader.hpp"
#include "WorldDatabase.h"
#include "../common/ConfigReader.h"
#include "Variables.h"
#include "World.h"
#include "classes.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#include "Traits/Traits.h"
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){
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;
PacketStruct* packet = configReader.getStruct("WS_UpdateMaintainedExamine", client->GetVersion());

View File

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

View File

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

View File

@ -19,10 +19,10 @@
*/
#include "Combat.h"
#include "client.h"
#include "../common/config_reader.hpp"
#include "../common/ConfigReader.h"
#include "classes.h"
#include "../common/debug.hpp"
#include "../common/log.hpp"
#include "../common/debug.h"
#include "../common/Log.h"
#include "zoneserver.h"
#include "Skills.h"
#include "classes.h"
@ -140,6 +140,10 @@ bool Entity::AttackAllowed(Entity* target, float distance, bool range_attack) {
return false;
}
}
if(attacker->IsNPC() && target->IsNPC() && attacker->GetFactionID() > 10 && attacker->GetFactionID() == target->GetFactionID()) {
return false;
}
if (attacker->IsPlayer() && target->IsPlayer())
{
@ -275,6 +279,8 @@ void Entity::MeleeAttack(Spawn* victim, float distance, bool primary, bool multi
CheckEncounterState((Entity*)victim);
}
CheckProcs(PROC_TYPE_PHYSICAL_ATTEMPT, victim);
if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL){
/*if(GetAdventureClass() == MONK){
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);
}
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) {
CheckProcs(PROC_TYPE_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)
return;
return false;
CheckProcs(PROC_TYPE_PHYSICAL_ATTEMPT, victim);
bool item_deleted = false;
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) {
int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, ammo->thrown_info->hit_bonus, false);
@ -367,7 +376,10 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
}
else
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 (IsPlayer()) {
if (ammo->details.count > 1) {
@ -376,11 +388,15 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
}
else {
if(ammo->details.inv_slot_id >= 6) {
((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, false);
((Player*)this)->item_list.DestroyItem(ammo->details.index);
((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id);
((Player*)this)->item_list.DestroyItem(ammo->details.index);
ammo = nullptr; // item is gone
item_deleted = true;
}
else {
((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)
client->QueuePacket(outapp);
if(ammo->details.inv_slot_id > 6) {
if(inv_slot_incl) {
EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
if (outapp)
client->QueuePacket(outapp);
@ -433,26 +449,28 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
}
}
//Multi Attack roll
if(!multi_attack){
if(!multi_attack && !item_deleted){
float multi_attack = info_struct.get_multi_attack();
if(multi_attack > 0){
float chance = multi_attack;
if (multi_attack > 100){
int8 automatic_multi = (int8)floor((float)(multi_attack / 100));
chance = (multi_attack - (floor((float)(multi_attack / 100) * 100)));
while(automatic_multi > 0){
RangeAttack(victim, 100, weapon, ammo, true);
while(!item_deleted && automatic_multi > 0){
item_deleted = RangeAttack(victim, 100, weapon, ammo, true);
automatic_multi--;
}
}
if (MakeRandomFloat(0, 100) <= chance)
RangeAttack(victim, 100, weapon, ammo, true);
if (!item_deleted && MakeRandomFloat(0, 100) <= chance)
item_deleted = RangeAttack(victim, 100, weapon, ammo, true);
}
}
//Apply attack speed mods
if(!multi_attack)
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){
@ -481,6 +499,9 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
if(victim->IsEntity()) {
CheckEncounterState((Entity*)victim);
}
CheckProcs(PROC_TYPE_MAGICAL_ATTEMPT, victim);
bool successful_hit = true;
if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) {
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_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 {
successful_hit = false;
@ -603,15 +604,15 @@ bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32
if(IsPlayer())
client = ((Player*)this)->GetClient();
if(client) {
if(success_msg.find("%t") < 0xFFFFFFFF)
success_msg.replace(success_msg.find("%t"), 2, victim->GetName());
client->Message(CHANNEL_YOU_CAST, success_msg.c_str());
std::string castMsg = std::string(success_msg);
SpellProcess::ReplaceEffectTokens(castMsg, (Spawn*)this, victim);
client->Message(CHANNEL_YOU_CAST, castMsg.c_str());
}
}
if (effect_msg.length() > 0) {
if(effect_msg.find("%t") < 0xFFFFFFFF)
effect_msg.replace(effect_msg.find("%t"), 2, victim->GetName());
GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_msg.c_str(), victim, 50);
std::string effectMsg = std::string(effect_msg);
SpellProcess::ReplaceEffectTokens(effectMsg, (Spawn*)this, victim);
GetZone()->SimpleMessage(CHANNEL_SPELLS, effectMsg.c_str(), victim, 50);
}
}
else {
@ -1063,7 +1064,7 @@ Skill* Entity::GetSkillByWeaponType(int8 type, int8 damage_type, bool update) {
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) {
spell->is_damage_spell = true;
}
@ -1178,7 +1179,8 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
if(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)
useWards = true;
@ -1241,7 +1243,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
}
if (victim->GetHP() <= 0)
KillSpawn(victim, type, damage_type, blow_type);
KillSpawn(victim, type, damage_type, blow_type, is_melee_spawn);
else {
victim->CheckProcs(PROC_TYPE_DEFENSIVE, this);
if (spell_name)
@ -1327,7 +1329,7 @@ float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_l
void Entity::AddHate(Entity* attacker, sint32 hate, bool ignore_pet_behavior) {
if(!attacker || GetHP() <= 0 || attacker->GetHP() <= 0)
return;
if(IsInSpawnGroup(attacker))
return; // can't aggro your own members
@ -1444,7 +1446,7 @@ bool Entity::CheckInterruptSpell(Entity* attacker) {
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)
return;
@ -1484,7 +1486,7 @@ void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow
dead->ClearRunningLocations();
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)
@ -1553,11 +1555,11 @@ void NPC::ProcessCombat() {
void Player::ProcessCombat() {
// 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;
//If no target delete combat_target and return out
Spawn* Target = GetZone()->GetSpawnByID(target);
Spawn* Target = GetZone()->GetSpawnByID(target, true);
if (!Target) {
combat_target = 0;
if (target > 0) {
@ -1931,7 +1933,10 @@ sint32 Entity::CalculateHateAmount(Spawn* target, sint32 amt) {
amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE);
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;
}

View File

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

View File

@ -1,22 +1,23 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
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.
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 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.
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/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <boost/algorithm/string/predicate.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/seperator.h"
#include "../../common/servertalk.h"
#include "../Worlddatabase.hpp"
#include "../WorldDatabase.h"
#include "../World.h"
#include "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
#include "../VisualStates.h"
#include "../../common/debug.hpp"
#include "../../common/debug.h"
#include "../LuaInterface.h"
#include "../Quests.h"
#include "../client.h"
@ -39,7 +40,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "../SpellProcess.h"
#include "../Tradeskills/Tradeskills.h"
#include "../../common/Log.h"
#include "../../common/misc_functions.hpp"
#include "../../common/MiscFunctions.h"
#include "../Languages.h"
#include "../Traits/Traits.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 "../Web/PeerManager.h"
#include "../../common/GlobalHeaders.h"
#include "../Broker/BrokerManager.h"
extern WorldDatabase database;
extern MasterSpellList master_spell_list;
@ -75,6 +77,7 @@ extern MasterAAList master_aa_list;
extern MasterRaceTypeList race_types_list;
extern Classes classes;
extern PeerManager peer_manager;
extern BrokerManager broker;
//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp
#if defined(__GNUC__)
@ -1987,6 +1990,18 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
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: {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands...");
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);
}
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]);
Item* item = master_item_list.GetItem(item_id);
if(item){
int64 item_id = strtoull(sep->arg[1], NULL, 0);
Item* item = nullptr;
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());
client->QueuePacket(app);
}
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) {
sint32 spell_id = atol(sep->arg[1]);
@ -2679,7 +2715,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
break;
}
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;
string deposit = string(sep->arg[0]);
amount = atoi64(deposit.c_str());
@ -2688,7 +2725,8 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
break;
}
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;
string deposit = string(sep->arg[0]);
amount = atoi64(deposit.c_str());
@ -2891,7 +2929,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
}
case COMMAND_CLASS:{
if(sep && sep->arg[ndx][0]){
client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx]));
client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx]), true);
}else
client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /class {class_id}");
break;
@ -3137,6 +3175,9 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
world.ResetZoneScripts();
database.LoadZoneScriptData();
world.ResetPlayerScripts();
world.LoadPlayerScripts();
if(lua_interface) {
lua_interface->DestroySpawnScripts();
@ -3144,6 +3185,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
lua_interface->DestroyQuests();
lua_interface->DestroyItemScripts();
lua_interface->DestroyZoneScripts();
lua_interface->DestroyPlayerScripts();
}
int32 quest_count = database.LoadQuests();
@ -3173,21 +3215,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
int32 quest_id = 0;
if(sep && sep->arg[0] && sep->IsNumber(0))
quest_id = atoul(sep->arg[0]);
if(quest_id > 0){
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__);
}
}
client->DeleteQuest(quest_id);
break;
}
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())
break;
if(client->AddItem(spawn->GetPickupItemID(), 1)) {
Item* tmpItem = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(spawn->GetPickupUniqueItemID());
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.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: {
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 = player->GetEquipmentList()->GetItem(slot);
if (item && item->IsHouseItem())
Item* item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(uniqueid);
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())
{
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();
Spawn* spawn = (Spawn*)obj;
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->SetY(client->GetPlayer()->GetY());
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:{
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]);
Item* item = master_item_list.GetItem(item_id);
if(item && item->generic_info.max_charges > 1)
quantity = item->generic_info.max_charges;
client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER);
if(client->IsGMStoreSearch()) {
Item* item = master_item_list.GetItem(item_id);
if(item && item->generic_info.max_charges > 1)
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;
}
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)){
int32 page = atoul(sep->arg[0]);
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;
}
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];
if(values){
LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values);
map<string, string> str_values = TranslateBrokerRequest(values);
vector<Item*>* items = master_item_list.GetItems(str_values, client);
if(items){
client->SetItemSearch(items);
client->SetItemSearch(items, str_values);
client->SetSearchPage(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());
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))
{
@ -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 += "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 += "HPRegen: " + to_string(ent->GetInfoStruct()->get_hp_regen()) + "\n";
details3 += "PowerRegen: " + to_string(ent->GetInfoStruct()->get_power_regen()) + "\n";
}
string details4;
@ -5372,14 +5450,14 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
}
case COMMAND_BROADCAST: {
if (sep && sep->arg[0])
zone_list.HandleGlobalBroadcast(sep->argplus[0]);
zone_list.TransmitBroadcast(sep->argplus[0]);
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /broadcast {message}");
break;
}
case COMMAND_ANNOUNCE: {
if (sep && sep->arg[0])
zone_list.HandleGlobalAnnouncement(sep->argplus[0]);
zone_list.TransmitGlobalAnnouncement(sep->argplus[0]);
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /announce {message}");
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
}
peer_manager.sendPeersMessage("/reloadcommand", command->handler, item_id);
if(item_id > 0) {
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_FROMBROKER:{
if(command->handler == COMMAND_ITEMSEARCH) {
client->SetGMStoreSearch(true);
}
else {
client->SetGMStoreSearch(false);
}
PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion());
if (packet) {
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", 40, 4);
client->QueuePacket(packet->serialize());
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);
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);
}
safe_delete(packet);
}
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_RAIDSAY: { Command_RaidSay(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:
{
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");
LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Surrender Duel Command");
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->details.item_locked) {
if(item->IsItemLocked()) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use.");
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.");
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)
lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer());
//reobtain item make sure it wasn't removed
item = player->item_list.GetItemFromIndex(index);
int32 bag_id = 0;
if(item){
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()->UpdateInventory(bag_id);
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))
@ -7056,33 +7155,34 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
sint32 bag_id = atol(sep->arg[3]);
int8 charges = atoi(sep->arg[4]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index);
int64 unique_id = 0;
int16 count = 0;
if(!item) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item.");
return;
}
if(to_slot == item->details.slot_id && (bag_id < 0 || bag_id == item->details.inv_slot_id)) {
unique_id = item->details.unique_id;
count = item->details.count;
if(to_slot == item->details.slot_id && (bag_id == item->details.inv_slot_id)) {
return;
}
if(item->details.item_locked)
if(item->IsItemLocked())
{
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use.");
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.");
return;
}
sint32 old_inventory_id = 0;
if(item)
old_inventory_id = item->details.inv_slot_id;
//autobank
if (bag_id == -3 && to_slot == -1)
if (bag_id == InventorySlotType::BANK && to_slot == -1)
{
if (player->HasFreeBankSlot())
to_slot = player->FindFreeBankSlot();
@ -7127,6 +7227,16 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
}
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))
{
@ -7157,6 +7267,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->QueuePacket(characterSheetPackets);
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))
{
@ -7166,7 +7277,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
int16 index = atoi(sep->arg[1]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index);
if (item) {
if(item->details.item_locked)
if(item->IsItemLocked())
{
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use.");
return;
@ -7184,6 +7295,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
}
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->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))
{
@ -7274,9 +7387,10 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
// Send the inventory update packet
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return;
}
else if (bag_id == -3 && to_slot == -1) {
else if (bag_id == InventorySlotType::BANK && to_slot == -1) {
// Auto Bank
if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) {
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);
}
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
if (!player->item_list.SharedBankAddAllowed(item)) {
client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared.");
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) {
client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot");
return;
@ -7309,6 +7424,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item);
}
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return;
}
}
@ -7328,6 +7444,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item);
}
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return;
}
}
@ -9974,14 +10091,18 @@ void Commands::Command_TradeAddItem(Client* client, Seperator* sep)
int32 index = atoi(sep->arg[0]);
item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index);
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.");
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.");
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]));
if (result == 1)
@ -11039,6 +11160,23 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
else if(atoi(sep->arg[0]) == 38) {
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 {
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) {
Spawn* spawn = client->GetPlayer()->GetTarget();
if(client->GetVersion() < 561) {
sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window
}
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.");
if(spawn)
client->SendMerchantWindow(spawn, sell);
}
@ -12944,4 +13053,301 @@ void Commands::Command_RaidSay(Client* client, Seperator* sep) {
*/
void Commands::Command_ReloadZoneInfo(Client* client, Seperator* sep) {
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
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.
@ -17,16 +17,17 @@
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_COMMANDS__
#define __EQ2_COMMANDS__
#include "../../common/data_buffer.hpp"
#include "../../common/misc_functions.hpp"
#include "../../common/types.hpp"
#include "../../common/opcodes/opcode_manager.hpp"
#include "../../common/DataBuffer.h"
#include "../../common/MiscFunctions.h"
#include "../../common/types.h"
#include "../../common/opcodemgr.h"
#include <vector>
#include <string>
#include <map>
#include "../../common/debug.hpp"
#include "../../common/debug.h"
using namespace std;
class Client;
@ -466,7 +467,16 @@ public:
void Command_RaidSay(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
void Get_AA_Xml(Client* client, Seperator* sep);
void Add_AA(Client* client, Seperator* sep);
@ -981,9 +991,18 @@ private:
#define CANCEL_AA_PROFILE 757
#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_CHARACTER 1001
#define COMMAND_MODIFY_FACTION 1002

View File

@ -1,6 +1,6 @@
/*
/*
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.
@ -17,6 +17,7 @@
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>
@ -24,7 +25,7 @@
#include <mysql.h>
#include <assert.h>
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "../WorldDatabase.h"
#include "Commands.h"
#include "ConsoleCommands.h"
@ -118,6 +119,9 @@ bool WorldDatabase::RemoveSpawnTemplate(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;
MYSQL_ROW row;
int32 spawn_location_id = 0;

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/*
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.
@ -20,6 +20,7 @@
#ifndef __EQ2_ENTITY__
#define __EQ2_ENTITY__
#include "Spawn.h"
#include "../common/Mutex.h"
#include "Skills.h"
#include "MutexList.h"
#include "MutexVector.h"
@ -27,6 +28,8 @@
#include <set>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <optional>
#include <boost/function.hpp>
#include <boost/lambda/bind.hpp>
@ -54,6 +57,7 @@ struct MaintainedEffects{
int32 target;
int8 target_type;
int32 spell_id;
int32 inherited_spell_id;
int32 slot_pos;
int16 icon;
int16 icon_backdrop;
@ -66,6 +70,7 @@ struct MaintainedEffects{
struct SpellEffects{
int32 spell_id;
int32 inherited_spell_id;
Entity* caster;
float total_time;
int32 expire_timestamp;
@ -77,6 +82,7 @@ struct SpellEffects{
struct DetrimentalEffects {
int32 spell_id;
int32 inherited_spell_id;
Entity* caster;
int32 expire_timestamp;
int16 icon;
@ -209,6 +215,8 @@ struct InfoStruct{
recovery_speed_ = 0;
spell_reuse_speed_ = 0;
spell_multi_attack_ = 0;
size_mod_ = 0.0f;
ignore_size_mod_calc_ = 0;
dps_ = 0;
dps_multiplier_ = 0;
attackspeed_ = 0;
@ -229,6 +237,11 @@ struct InfoStruct{
pet_movement_ = 0;
pet_behavior_ = 0;
vision_ = 0;
redlight_ = 0;
greenlight_ = 0;
bluelight_ = 0;
breathe_underwater_ = 0;
biography_ = std::string("");
drunk_ = 0;
@ -419,6 +432,10 @@ struct InfoStruct{
spell_reuse_speed_ = oldStruct->get_spell_reuse_speed();
spell_multi_attack_ = oldStruct->get_spell_multi_attack();
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();
attackspeed_ = oldStruct->get_attackspeed();
haste_ = oldStruct->get_haste();
@ -438,6 +455,11 @@ struct InfoStruct{
pet_movement_ = oldStruct->get_pet_movement();
pet_behavior_ = oldStruct->get_pet_behavior();
vision_ = oldStruct->get_vision();
redlight_ = oldStruct->get_redlight();
greenlight_ = oldStruct->get_greenlight();
bluelight_ = oldStruct->get_bluelight();
breathe_underwater_ = oldStruct->get_breathe_underwater();
biography_ = std::string(oldStruct->get_biography());
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_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_; }
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_multiplier() { std::lock_guard<std::mutex> lk(classMutex); return dps_multiplier_; }
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_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_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_; }
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_; }
@ -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_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_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_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; }
@ -975,6 +1004,11 @@ struct InfoStruct{
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_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_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++){
if(i<30){
maintained_effects[i].spell_id = 0xFFFFFFFF;
maintained_effects[i].inherited_spell_id = 0;
if (spawn->IsPlayer())
maintained_effects[i].icon = 0xFFFF;
maintained_effects[i].spell = nullptr;
}
spell_effects[i].icon = 0;
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;
}
}
@ -1071,6 +1112,9 @@ struct InfoStruct{
// maintained via their own mutex
SpellEffects spell_effects[45];
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:
std::string name_;
int8 class1_;
@ -1190,6 +1234,7 @@ private:
float spell_reuse_speed_;
float spell_multi_attack_;
float size_mod_;
int8 ignore_size_mod_calc_;
float dps_;
float dps_multiplier_;
float attackspeed_;
@ -1212,6 +1257,11 @@ private:
int8 pet_behavior_;
int32 vision_;
int32 redlight_;
int32 greenlight_;
int32 bluelight_;
int8 breathe_underwater_;
std::string biography_;
float drunk_;
@ -1283,8 +1333,6 @@ private:
int8 max_spell_reduction_override_;
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 {
@ -1347,6 +1395,8 @@ struct Proc {
#define PROC_TYPE_DAMAGED_MAGIC 17
#define PROC_TYPE_RANGED_ATTACK 18
#define PROC_TYPE_RANGED_DEFENSE 19
#define PROC_TYPE_PHYSICAL_ATTEMPT 20
#define PROC_TYPE_MAGICAL_ATTEMPT 21
struct ThreatTransfer {
int32 Target;
@ -1402,6 +1452,9 @@ public:
void DeleteSpellEffects(bool removeClient = false);
void RemoveSpells(bool unfriendlyOnly = false);
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 void AddMaintainedSpell(LuaSpell* spell);
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);
void AddDetrimentalSpell(LuaSpell* spell, int32 override_expire_timestamp = 0);
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 SetDeity(int8 new_deity){
deity = new_deity;
@ -1474,10 +1527,10 @@ public:
void DoRegenUpdate();
MaintainedEffects* GetFreeMaintainedSpellSlot();
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* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0);
LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true);
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, bool checkNotCaster = false);
//flags
int32 GetFlags() { return info_struct.get_flags(); }
@ -1536,19 +1589,19 @@ public:
bool SecondaryWeaponReady();
bool RangeWeaponReady();
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 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="");
int8 DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr);
float GetDamageTypeResistPercentage(int8 damage_type);
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);
void AddHate(Entity* attacker, sint32 hate, bool ignore_pet_behavior = false);
bool CheckInterruptSpell(Entity* attacker);
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 SetAttackDelay(bool primary = false, bool ranged = false);
float CalculateAttackSpeedMod();
@ -1835,6 +1888,7 @@ public:
/// <summary>Removes the ward with the given spell id</summary>
/// <param name='spellID'>The spell id of the ward to remove</param>
void RemoveWard(int32 spellID);
void RemoveWard(LuaSpell* spell);
/// <summary>Subtracts the given damage from the wards</summary>
/// <param name='damage'>The damage to subtract from the wards</param>
@ -1944,15 +1998,8 @@ public:
set<int32> HatedBy;
std::mutex MHatedBy;
bool IsAggroed() {
int32 size = 0;
MHatedBy.lock();
size = HatedBy.size();
MHatedBy.unlock();
return size > 0;
}
bool IsAggroed();
void SendHatedByList(Client* client);
Mutex MCommandMutex;
@ -2164,6 +2211,8 @@ private:
map<string, boost::function<void(sint8)> > set_sint8_funcs;
map<string, boost::function<void(std::string)> > set_string_funcs;
mutable std::shared_mutex propertiesMutex;
};
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,15 +17,17 @@
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_ITEMS__
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include <ctime>
#include "../../common/types.hpp"
#include "../../common/data_buffer.hpp"
#include <shared_mutex>
#include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../Commands/Commands.h"
#include "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
using namespace std;
class MasterItemList;
@ -647,6 +649,47 @@ enum ItemEffectType {
EFFECT_CURE_TYPE_MAGIC=6,
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)
struct ItemStatsValues{
sint16 str;
@ -710,10 +753,11 @@ struct ItemCore{
int16 count;
int8 tier;
int8 num_slots;
int32 unique_id;
int64 unique_id;
int8 num_free_slots;
int16 recommended_level;
bool item_locked;
int32 lock_flags;
bool new_item;
int16 new_index;
};
@ -934,6 +978,8 @@ public:
#pragma pack()
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();
string lowername;
string name;
@ -942,10 +988,16 @@ public:
int32 sell_price;
int32 sell_status;
int32 max_sell_value;
int64 broker_price;
bool is_search_store_item;
bool is_search_in_inventory;
bool save_needed;
int8 weapon_type;
string adornment;
string creator;
string seller_name;
int32 seller_char_id;
int64 seller_house_id;
int32 adorn0;
int32 adorn1;
int32 adorn2;
@ -986,6 +1038,7 @@ public:
bool crafted;
bool tinkered;
int8 book_language;
mutable std::shared_mutex item_lock_mtx_;
void AddEffect(string effect, int8 percentage, int8 subbulletflag);
void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);
@ -1067,6 +1120,10 @@ public:
void AddSlot(int8 slot_id);
void SetSlots(int32 slots);
int16 GetIcon(int16 version);
bool TryLockItem(LockReason reason);
bool TryUnlockItem(LockReason reason);
bool IsItemLocked();
bool IsItemLockedFor(LockReason reason);
};
class MasterItemList{
public:
@ -1079,14 +1136,16 @@ public:
Item* GetAllItemsByClassification(const char* name);
ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 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(map<string, string> criteria, Client* client_to_map);
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
static int64 NextUniqueID();
int32 GetItemStatIDByName(std::string name);
std::string GetItemStatNameByID(int32 id);
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
bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges);
void EraseItem(Item* item);
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);
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 GetNumberOfItems();
int32 GetWeight();
@ -1132,7 +1199,7 @@ public:
void DestroyItem(int16 index);
Item* CanStack(Item* item, bool include_bank = 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);
Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0);
@ -1143,7 +1210,10 @@ public:
map<int32, Item*>* GetAllItems();
bool HasFreeBankSlot();
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>
///<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>

View File

@ -1,6 +1,6 @@
/*
/*
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.
@ -17,6 +17,7 @@
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>
@ -24,7 +25,7 @@
#include <mysql.h>
#include <assert.h>
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "../WorldDatabase.h"
#include "Items.h"
#include "../World.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->details.recommended_level = result->GetInt16Str("recommended_level");
item->details.item_locked = false;
item->details.lock_flags = 0;
item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level");
item->generic_info.max_charges = result->GetInt16Str("max_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", "\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->coin_rent_reduction = atof(row[3]);
item->houseitem_info->house_only = atoi(row[4]);
@ -541,7 +543,7 @@ int32 WorldDatabase::LoadHouseContainers(int32 item_id){
if (item)
{
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->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->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++;
}
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 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 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", "\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);
}
int32 WorldDatabase::LoadNextUniqueItemID()
int64 WorldDatabase::LoadNextUniqueItemID()
{
Query query;
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(row[0])
{
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, atoul(row[0]));
return strtoul(row[0], NULL, 0);
int64 max_ = strtoull(row[0], NULL, 0);
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, max_);
return max_;
}
else
return 0;
@ -1119,6 +1127,31 @@ int32 WorldDatabase::LoadNextUniqueItemID()
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)
{
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;
item->details.count = remainder;
if (item->details.inv_slot_id == -2)
if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item);
else {
if(!player->item_list.AddItem(item))
@ -1398,7 +1431,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
}
}
else {
if (item->details.inv_slot_id == -2)
if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item);
else
player->item_list.AddItem(item);

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,15 +17,16 @@
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_ITEMS__
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include "../../common/types.hpp"
#include "../../common/data_buffer.hpp"
#include "../../common/misc_functions.hpp"
#include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../../common/MiscFunctions.h"
#include "../Commands/Commands.h"
#include "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
using namespace std;
class MasterItemList;
@ -730,9 +731,7 @@ public:
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
static int64 NextUniqueID();
};
class PlayerItemList {
public:

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,15 +17,16 @@
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_ITEMS__
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include "../../common/types.hpp"
#include "../../common/data_buffer.hpp"
#include "../../common/misc_functions.hpp"
#include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../../common/MiscFunctions.h"
#include "../Commands/Commands.h"
#include "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
using namespace std;
class MasterItemList;
@ -824,9 +825,7 @@ public:
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
static int64 NextUniqueID();
};
class PlayerItemList {
public:

View File

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

View File

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

View File

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

View File

@ -17,8 +17,8 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../common/debug.hpp"
#include "../common/log.hpp"
#include "../common/debug.h"
#include "../common/Log.h"
#include <iostream>
using namespace std;
#include <string.h>
@ -58,13 +58,13 @@ extern int errno;
#include "../common/servertalk.h"
#include "LoginServer.h"
#include "../common/packet/packet_dump.hpp"
#include "../common/packet_dump.h"
#include "net.h"
#include "zoneserver.h"
#include "WorldDatabase.hpp"
#include "WorldDatabase.h"
#include "Variables.h"
#include "World.h"
#include "../common/config_reader.hpp"
#include "../common/ConfigReader.h"
#include "Rules/Rules.h"
#include "Web/PeerManager.h"
#include "Web/HTTPSClientPool.h"
@ -583,7 +583,7 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u
else
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;
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 );
@ -592,7 +592,7 @@ int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* u
response = PLAY_ERROR_CHAR_NOT_LOADED;
break;
case -9:
response = 0;//PLAY_ERROR_ACCOUNT_IN_USE;
response = PLAY_ERROR_ACCOUNT_IN_USE;
break;
case -8:
response = PLAY_ERROR_LOADING_ERROR;

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef 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_RegisterQuest(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_AddQuestPrereqQuest(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_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

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,11 +17,12 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LuaInterface.h"
#include "LuaFunctions.h"
#include "WorldDatabase.hpp"
#include "WorldDatabase.h"
#include "SpellProcess.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#include "World.h"
#ifndef WIN32
@ -38,6 +39,149 @@ extern WorldDatabase database;
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() {
shutting_down = false;
lua_system_reloading = false;
@ -45,6 +189,7 @@ LuaInterface::LuaInterface() {
MSpells.SetName("LuaInterface::MSpells");
MSpawnScripts.SetName("LuaInterface::MSpawnScripts");
MZoneScripts.SetName("LuaInterface::MZoneScripts");
MPlayerScripts.SetName("LuaInterface::MPlayerScripts");
MQuests.SetName("LuaInterface::MQuests");
MLUAMain.SetName("LuaInterface::MLUAMain");
MItemScripts.SetName("LuaInterface::MItemScripts");
@ -119,6 +264,7 @@ LuaInterface::~LuaInterface() {
DestroyQuests();
DestroyItemScripts();
DestroyZoneScripts();
DestroyPlayerScripts();
DestroyRegionScripts();
DeleteUserDataPtrs(true);
DeletePendingSpells(true);
@ -241,6 +387,25 @@ void LuaInterface::DestroyZoneScripts() {
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() {
map<string, map<lua_State*, bool> >::iterator itr;
map<lua_State*, bool>::iterator state_itr;
@ -306,6 +471,20 @@ bool LuaInterface::LoadZoneScript(const char* name) {
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 ret = false;
if (name) {
@ -469,6 +648,16 @@ const char* LuaInterface::GetScriptName(lua_State* state)
}
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__);
itr = region_inverse_scripts.find(state);
if (itr != region_inverse_scripts.end())
@ -500,6 +689,10 @@ bool LuaInterface::LoadZoneScript(string name) {
return LoadZoneScript(name.c_str());
}
bool LuaInterface::LoadPlayerScript(string name) {
return LoadPlayerScript(name.c_str());
}
bool LuaInterface::LoadRegionScript(string name) {
return LoadRegionScript(name.c_str());
}
@ -822,6 +1015,20 @@ lua_State* LuaInterface::LoadLuaFile(const char* name) {
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) {
if(call_remove_function){
lua_getglobal(spell->state, "remove");
@ -894,46 +1101,32 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
if(return_after_call_remove) {
if(overrideTarget && overrideTarget->IsEntity()) {
((Entity*)overrideTarget)->RemoveProc(0, spell);
((Entity*)overrideTarget)->RemoveSpellEffect(spell);
((Entity*)overrideTarget)->RemoveSpellBonus(spell);
((Entity*)overrideTarget)->RemoveEffectsFromLuaSpell(spell);
RemoveSpawnFromSpell(spell, overrideTarget);
}
return;
}
spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
for (int8 i = 0; i < spell->targets.size(); i++) {
for (int32 id : spell->GetTargets()) {
if(!spell->zone)
break;
Spawn* target = spell->zone->GetSpawnByID(spell->targets.at(i));
Spawn* target = spell->zone->GetSpawnByID(id);
if (!target || !target->IsEntity())
continue;
((Entity*)target)->RemoveProc(0, spell);
((Entity*)target)->RemoveSpellEffect(spell);
((Entity*)target)->RemoveSpellBonus(spell);
((Entity*)target)->RemoveEffectsFromLuaSpell(spell);
RemoveSpawnFromSpell(spell, target);
}
multimap<int32,int8>::iterator entries;
for(entries = spell->char_id_targets.begin(); entries != spell->char_id_targets.end(); entries++)
{
Client* tmpClient = zone_list.GetClientByCharID(entries->first);
for (const auto& [char_id, pet_type] : spell->GetCharIDTargets()) {
Client* tmpClient = zone_list.GetClientByCharID(char_id);
if(tmpClient && tmpClient->GetPlayer())
{
tmpClient->GetPlayer()->RemoveProc(0, spell);
tmpClient->GetPlayer()->RemoveSpellEffect(spell);
tmpClient->GetPlayer()->RemoveSpellBonus(spell);
tmpClient->GetPlayer()->RemoveEffectsFromLuaSpell(spell);
RemoveSpawnFromSpell(spell, tmpClient->GetPlayer());
}
}
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->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
if(removing_all_spells) {
if(spell->zone && spell->zone->GetSpellProcess()) {
@ -947,10 +1140,7 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
if(spell->zone && spell->zone->GetSpellProcess()) {
spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell, false);
}
spell->caster->RemoveProc(0, spell);
spell->caster->RemoveSpellEffect(spell);
spell->caster->RemoveMaintainedSpell(spell);
spell->caster->RemoveEffectsFromLuaSpell(spell);
RemoveSpawnFromSpell(spell, spell->caster);
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, "SetCompletedDescription", EQ2Emu_lua_SetCompletedDescription);
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, "HasQuest", EQ2Emu_lua_HasQuest);
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,"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, ...) {
@ -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
}
void LuaInterface::DeletePendingSpells(bool all) {
void LuaInterface::DeletePendingSpells(bool all, ZoneServer* zone) {
MSpells.lock();
MSpellDelete.lock();
if (spells_pending_delete.size() > 0) {
@ -1670,7 +1877,7 @@ void LuaInterface::DeletePendingSpells(bool all) {
vector<LuaSpell*> tmp_deletes;
vector<LuaSpell*>::iterator del_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);
}
LuaSpell* spell = 0;
@ -1684,27 +1891,21 @@ void LuaInterface::DeletePendingSpells(bool all) {
if(!all) {
// rely on targets the spell->caster could be corrupt
bool spellDeleted = false;
if(spell->targets.size() > 0) {
spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
for (int8 i = 0; i < spell->targets.size(); i++) {
Spawn* target = spell->zone->GetSpawnByID(spell->targets.at(i));
for (int32 id : spell->GetTargets()) {
Spawn* target = spell->zone->GetSpawnByID(id);
if (!target || !target->IsEntity())
continue;
if(!spellDeleted && spell->zone && spell->zone->GetSpellProcess())
spell->zone->GetSpellProcess()->DeleteActiveSpell(spell, true);
spellDeleted = true;
if(target->IsEntity()) {
RemoveSpawnFromSpell(spell, target);
}
}
spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
}
if(!spellDeleted && spell->zone && spell->zone->GetSpellProcess()) {
if(spell->zone && spell->zone->GetSpellProcess()) {
spell->zone->GetSpellProcess()->DeleteActiveSpell(spell, true);
}
}
if (spell->spell->IsCopiedSpell())
{
RemoveCustomSpell(spell->spell->GetSpellID());
@ -2111,7 +2312,6 @@ LuaSpell* LuaInterface::LoadSpellScript(const char* name) {
spell->interrupted = false;
spell->last_spellattack_hit = false;
spell->crit = false;
spell->MSpellTargets.SetName("LuaSpell.MSpellTargets");
spell->cancel_after_all_triggers = false;
spell->num_triggers = 0;
spell->num_calls = 0;
@ -2127,6 +2327,7 @@ LuaSpell* LuaInterface::LoadSpellScript(const char* name) {
spell->initial_target_char_id = 0;
spell->zone = nullptr;
spell->initial_caster_level = 0;
spell->is_loaded_recast = false;
MSpells.lock();
current_spells[spell->state] = spell;
@ -2172,6 +2373,17 @@ Mutex* LuaInterface::GetZoneScriptMutex(const char* name) {
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* mutex = 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__);
}
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) {
MRegionScripts.writelock(__FUNCTION__, __LINE__);
@ -2328,6 +2548,40 @@ lua_State* LuaInterface::GetZoneScript(const char* name, bool create_new, bool u
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) {
map<string, map<lua_State*, bool> >::iterator 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->crit = false;
new_spell->last_spellattack_hit = false;
new_spell->MSpellTargets.SetName("LuaSpell.MSpellTargets");
new_spell->cancel_after_all_triggers = false;
new_spell->num_triggers = 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->zone = nullptr;
new_spell->initial_caster_level = 0;
new_spell->is_loaded_recast = false;
current_spells[new_spell->state] = new_spell;
return new_spell;
@ -2699,6 +2953,57 @@ bool LuaInterface::RunZoneScriptWithReturn(string script_name, const char* funct
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) {
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(){
correctly_initialized = false;
quest = 0;
@ -2912,4 +3226,41 @@ LUASpellWrapper::LUASpellWrapper() {
bool LUASpellWrapper::IsSpell() {
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
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.
@ -17,15 +17,17 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LUA_INTERFACE_H
#define LUA_INTERFACE_H
#include <mutex>
#include <shared_mutex>
#include <unordered_set>
#include "Spawn.h"
#include "Spells.h"
#include "../common/Mutex.h"
#include "Quests.h"
#include "zoneserver.h"
#include "client.h"
@ -73,13 +75,27 @@ struct OptionWindowOption {
#define EFFECT_FLAG_FEAR_IMMUNE 2097152
#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{
Entity* caster;
int32 initial_caster_char_id;
int32 initial_target;
int32 initial_target_char_id;
mutable std::shared_mutex targets_mutex;
vector<int32> targets;
vector<int32> removed_targets; // previously cancelled, expired, used, so on
mutable std::shared_mutex char_id_targets_mutex;
multimap<int32, int8> char_id_targets;
Spell* spell;
lua_State* state;
@ -99,13 +115,239 @@ struct LuaSpell{
bool cancel_after_all_triggers;
bool had_triggers;
bool had_dmg_remaining;
Mutex MSpellTargets;
Mutex MScriptMutex;
int32 effect_bitmask;
bool restored; // restored spell cross zone
std::atomic<bool> has_proc;
ZoneServer* zone;
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{
@ -192,10 +434,13 @@ public:
bool LoadSpawnScript(const char* name);
bool LoadZoneScript(string name);
bool LoadZoneScript(const char* name);
bool LoadPlayerScript(string name);
bool LoadPlayerScript(const char* name);
bool LoadRegionScript(string name);
bool LoadRegionScript(const char* name);
LuaSpell* LoadSpellScript(string 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);
Spawn* GetSpawn(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 UseSpawnScript(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);
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* 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);
LuaSpell* GetSpellScript(const char* name, bool create_new = true, bool use = true);
LuaSpell* CreateSpellScript(const char* name, lua_State* existState);
@ -263,6 +510,7 @@ public:
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 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 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);
@ -273,6 +521,7 @@ public:
void DestroyItemScripts();
void DestroyQuests(bool reload = false);
void DestroyZoneScripts();
void DestroyPlayerScripts();
void DestroyRegionScripts();
void SimpleLogError(const char* error);
void LogError(const char* error, ...);
@ -285,11 +534,12 @@ public:
map<Client*, int32> GetDebugClients(){ return debug_clients; }
void AddUserDataPtr(LUAUserData* data, void* data_ptr = 0);
void DeleteUserDataPtrs(bool all);
void DeletePendingSpells(bool all);
void DeletePendingSpells(bool all, ZoneServer* zone = nullptr);
void DeletePendingSpell(LuaSpell* spell);
Mutex* GetSpawnScriptMutex(const char* name);
Mutex* GetItemScriptMutex(const char* name);
Mutex* GetZoneScriptMutex(const char* name);
Mutex* GetPlayerScriptMutex(const char* name);
Mutex* GetRegionScriptMutex(const char* name);
Mutex* GetSpellScriptMutex(const char* name);
Mutex* GetQuestMutex(Quest* quest);
@ -309,6 +559,7 @@ public:
int32 GetFreeCustomSpellID();
void SetLuaUserDataStale(void* ptr);
bool IsLuaUserDataValid(void* ptr);
private:
bool shutting_down;
@ -330,6 +581,7 @@ private:
map<string, map<lua_State*, bool> > item_scripts;
map<string, map<lua_State*, bool> > spawn_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*, LuaSpell*> > spell_scripts;
@ -339,11 +591,13 @@ private:
map<lua_State*, string> item_inverse_scripts;
map<lua_State*, string> spawn_inverse_scripts;
map<lua_State*, string> zone_inverse_scripts;
map<lua_State*, string> player_inverse_scripts;
map<lua_State*, string> region_inverse_scripts;
map<string, Mutex*> item_scripts_mutex;
map<string, Mutex*> spawn_scripts_mutex;
map<string, Mutex*> zone_scripts_mutex;
map<string, Mutex*> player_scripts_mutex;
map<int32, Mutex*> quests_mutex;
map<string, Mutex*> region_scripts_mutex;
map<string, Mutex*> spell_scripts_mutex;
@ -353,6 +607,7 @@ private:
Mutex MSpawnScripts;
Mutex MItemScripts;
Mutex MZoneScripts;
Mutex MPlayerScripts;
Mutex MQuests;
Mutex MLUAMain;
Mutex MSpellDelete;

View File

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

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,13 +17,14 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NPC.h"
#include "WorldDatabase.hpp"
#include "WorldDatabase.h"
#include <math.h>
#include "client.h"
#include "World.h"
#include "races.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#include "NPC_AI.h"
#include "Appearances.h"
#include "SpellProcess.h"
@ -108,6 +109,8 @@ NPC::NPC(NPC* old_npc){
SetLootDropType(old_npc->GetLootDropType());
has_spells = old_npc->HasSpells();
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
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.
@ -22,7 +22,7 @@
#include "Combat.h"
#include "zoneserver.h"
#include "Spells.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#include "LuaInterface.h"
#include "World.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
MHateList.writelock(__FUNCTION__, __LINE__);
map<int32, sint32>::iterator 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())
{
((Entity*)spawn)->MHatedBy.lock();

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __NPC_AI_H__
#define __NPC_AI_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="hate">The amount of hate to add</param>
virtual void AddHate(Entity* entity, sint32 hate);
virtual bool AdjustHatePosition(int32 id, bool increase);
/// <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>
/// <param name="entity">Entity to remove from this NPC's hate list</param>
void ClearHate(Entity* entity);

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "World.h"
#include "Object.h"
#include "Spells.h"
@ -95,5 +96,7 @@ Object* Object::Copy(){
new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn;
}

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,14 +17,15 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Player.h"
#include "../common/misc_functions.hpp"
#include "../common/MiscFunctions.h"
#include "World.h"
#include "WorldDatabase.hpp"
#include "WorldDatabase.h"
#include <math.h>
#include "classes.h"
#include "LuaInterface.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#include "Rules/Rules.h"
#include "Titles.h"
#include "Languages.h"
@ -46,6 +47,7 @@ extern MasterItemList master_item_list;
extern RuleManager rule_manager;
extern MasterTitlesList master_titles_list;
extern MasterLanguagesList master_languages_list;
std::map<int8, int32> Player::m_levelXPReq;
Player::Player(){
tutorial_step = 0;
@ -135,6 +137,7 @@ Player::Player(){
active_drink_unique_id = 0;
raidsheet_changed = false;
hassent_raid = false;
house_vault_slots = 0;
}
Player::~Player(){
SetSaveSpellEffects(true);
@ -597,6 +600,7 @@ PacketStruct* PlayerInfo::serialize2(int16 version){
packet->setDataByName("unknown20", 50, 75);
*/
//packet->setDataByName("rain2", -102.24);
player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__);
for(int i=0;i<45;i++){
if(i < 30){
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_type", info_struct->spell_effects[i].icon_backdrop, i, 0);
}
player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__);
return packet;
}
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("status_points", info_struct->get_status_points());
packet->setDataByName("guild_status", 888888);
packet->setDataByName("vault_slots", player->GetHouseVaultSlots());
if (house_zone_id > 0){
string house_name = database.GetZoneName(house_zone_id);
if(house_name.length() > 0)
@ -998,8 +1003,8 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
packet->setDataByName("decrease_falling_dmg", 169);
if (version <= 561) {
packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10);
packet->setDataByName("exp_blue", info_struct->get_xp_blue()/100);
packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10);
packet->setDataByName("exp_blue", ((int16)info_struct->get_xp_yellow() % 100) + (info_struct->get_xp_blue() / 100));
}
else {
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("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());
int32 expireTimestamp = 0;
@ -1480,7 +1506,7 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
SetCharSheetChanged(true);
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)
database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
else
@ -1641,8 +1667,8 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
}
}
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 == -4 && item->CheckFlag(NO_TRADE)) {
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 == InventorySlotType::SHARED_BANK || bag_id == InventorySlotType::HOUSE_VAULT) && !item_list.SharedBankAddAllowed(item)) {
PacketStruct* packet = configReader.getStruct("WS_DisplayText", version);
if (packet) {
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());
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);
int32 bag_id = item->details.inv_slot_id;
sint32 bag_id = item->details.inv_slot_id;
if (item->generic_info.condition == 0) {
Client* client = GetClient();
if (client) {
@ -2052,19 +2078,21 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
if(slot < 255) {
if (slot == EQ2_FOOD_SLOT && item->IsFoodFood() && get_character_flag(CF_FOOD_AUTO_CONSUME)) {
Item* item = GetEquipmentList()->GetItem(EQ2_FOOD_SLOT);
if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false))
GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT);
if(item)
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)) {
Item* item = GetEquipmentList()->GetItem(EQ2_DRINK_SLOT);
if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false))
GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT);
if(item)
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);
return false;
}
else if (item_list.AssignItemToFreeSlot(item)) {
else if (item_list.AssignItemToFreeSlot(item, true)) {
item->save_needed = true;
CalculateApplyWeight();
return true;
@ -2136,7 +2164,7 @@ bool Player::AddItemToBank(Item* item) {
}
EQ2Packet* Player::SendInventoryUpdate(int16 version) {
// assure any inventory updates are reflected in sell window
if(GetClient() && GetClient()->GetMerchantTransaction())
if(GetClient() && GetClient()->GetMerchantTransactionID())
GetClient()->SendSellMerchantList();
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) {
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);
if (result == 1) {
if(isOverflow && item->details.inv_slot_id != -2) {
@ -3280,11 +3308,18 @@ PlayerInfo::PlayerInfo(Player* in_player){
for(int i=0;i<45;i++){
if(i<30){
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].spell = nullptr;
}
info_struct->spell_effects[i].spell_id = 0xFFFFFFFF;
info_struct->spell_effects[i].spell = nullptr;
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;
}
house_zone_id = 0;
@ -3315,12 +3350,12 @@ MaintainedEffects* Player::GetFreeMaintainedSpellSlot(){
return ret;
}
MaintainedEffects* Player::GetMaintainedSpell(int32 id){
MaintainedEffects* Player::GetMaintainedSpell(int32 id, bool on_char_load){
MaintainedEffects* ret = 0;
InfoStruct* info = GetInfoStruct();
GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__);
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];
break;
}
@ -3423,6 +3458,11 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
if(!luaspell)
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;
MaintainedEffects* effect = GetFreeMaintainedSpellSlot();
int32 target_type = 0;
@ -3465,8 +3505,11 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
void Player::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){
if(!luaspell || !luaspell->caster)
return;
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* effect = 0;
if (old_effect){
@ -3548,6 +3591,7 @@ void Player::RemoveMaintainedSpell(LuaSpell* luaspell){
if (found) {
memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects));
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].spell = nullptr;
charsheet_changed = true;
@ -3568,6 +3612,7 @@ void Player::RemoveSpellEffect(LuaSpell* spell){
if (found) {
memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects));
GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF;
GetInfoStruct()->spell_effects[44].inherited_spell_id = 0;
GetInfoStruct()->spell_effects[44].spell = nullptr;
changed = true;
info_changed = true;
@ -4121,7 +4166,7 @@ void Player::InCombat(bool val, bool range) {
bool update_regen = false;
if(GetInfoStruct()->get_engaged_encounter()) {
if(!IsAggroed() || !IsEngagedInEncounter()) {
if(!IsEngagedInEncounter()) {
GetInfoStruct()->set_engaged_encounter(0);
update_regen = true;
}
@ -4419,11 +4464,20 @@ void Player::SetNeededXP(){
//GetInfoStruct()->xp_needed = GetLevel() * 100;
// Get xp needed to get to the next level
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
if (level > 95)
SetNeededXP(database.GetMysqlExpCurve(95)* ((level - 95) + 1));
SetNeededXP(GetNeededXPByLevel(level));
}
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
SetNeededXP(database.GetMysqlExpCurve(level));
exp_required = 0;
return exp_required;
}
void Player::SetXP(int32 val){
@ -4519,10 +4573,24 @@ bool Player::AddXP(int32 xp_amount){
SetCharSheetChanged(true);
return false;
}
int32 prev_xp_amount = xp_amount;
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);
if(GetClient()) {
GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount);
}
GetPlayerInfo()->CalculateXPPercentages();
current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100;
if(miniding_min_percent > 0.0f && current_xp_percent >= miniding_min_percent){
@ -4532,15 +4600,8 @@ bool Player::AddXP(int32 xp_amount){
SetPower(GetTotalPower());
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;
}
@ -4550,7 +4611,16 @@ bool Player::AddTSXP(int32 xp_amount){
MStats.unlock();
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()){
if (!CheckLevelStatus(GetTSLevel() + 1)) {
if(GetClient()) {
@ -4558,10 +4628,18 @@ bool Player::AddTSXP(int32 xp_amount){
}
return false;
}
int32 prev_xp_amount = xp_amount;
xp_amount -= GetNeededTSXP() - GetTSXP();
SetTSLevel(GetTSLevel() + 1);
SetTSXP(0);
SetNeededTSXP();
if(GetClient()->ChangeTSLevel(GetLevel(), GetLevel()+1, prev_xp_amount)) {
SetTSLevel(GetTSLevel() + 1);
SetTSXP(0);
SetNeededTSXP();
}
else {
SetTSXP(GetTSXP() + prev_xp_amount);
SetCharSheetChanged(true);
return false;
}
}
SetTSXP(GetTSXP() + xp_amount);
GetPlayerInfo()->CalculateXPPercentages();
@ -4577,6 +4655,8 @@ bool Player::AddTSXP(int32 xp_amount){
GetInfoStruct()->set_tradeskill_class2(1);
GetInfoStruct()->set_tradeskill_class3(1);
}
SetCharSheetChanged(true);
return true;
}
@ -6423,7 +6503,8 @@ int32 CharacterInstances::GetInstanceCount() {
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);
GetInfoStruct()->set_class1(classes.GetBaseClass(new_class));
GetInfoStruct()->set_class2(classes.GetSecondaryBaseClass(new_class));
@ -6433,6 +6514,23 @@ void Player::SetPlayerAdventureClass(int8 new_class){
GetZone()->TriggerCharSheetTimer();
if(GetClient())
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) {
@ -7266,11 +7364,115 @@ NPC* Player::InstantiateSpiritShard(float origX, float origY, float origZ, float
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()
{
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;
}
@ -7281,9 +7483,12 @@ void Player::SaveSpellEffects()
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_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();
MSpellEffects.readlock(__FUNCTION__, __LINE__);
MMaintainedSpells.readlock(__FUNCTION__, __LINE__);
MSpellEffects.readlock(__FUNCTION__, __LINE__);
for(int i = 0; i < 45; i++) {
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].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);
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);
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->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 ");
bool firstTarget = true;
map<Spawn*, int8> targetsInserted;
for (int8 t = 0; t < info->maintained_effects[i].spell->targets.size(); t++) {
int32 spawn_id = info->maintained_effects[i].spell->targets.at(t);
Spawn* spawn = GetZone()->GetSpawnByID(spawn_id);
for (int32 id : info->maintained_effects[i].spell->GetTargets()) {
Spawn* spawn = GetZone()->GetSpawnByID(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());
if(spawn && (spawn->IsPlayer() || spawn->IsPet()))
{
@ -7381,28 +7593,27 @@ void Player::SaveSpellEffects()
", " + std::to_string(info->maintained_effects[i].slot_pos) + ")");
firstTarget = false;
}
}
multimap<int32,int8>::iterator entries;
for(entries = info->maintained_effects[i].spell->char_id_targets.begin(); entries != info->maintained_effects[i].spell->char_id_targets.end(); entries++)
{
if(!firstTarget)
insertTargets.append(", ");
}
for (const auto& [char_id, pet_type] : info->maintained_effects[i].spell->GetCharIDTargets()) {
{
if(!firstTarget)
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());
insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(entries->first) + ", " + std::to_string(entries->second) + ", " +
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) + ")");
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(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(info->maintained_effects[i].slot_pos) + ")");
firstTarget = false;
}
info->maintained_effects[i].spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
if(!firstTarget) {
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTargets.c_str());
firstTarget = false;
}
if(!firstTarget) {
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTargets.c_str());
}
}
}
}
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
MSpellEffects.releasereadlock(__FUNCTION__, __LINE__);
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
}
void Player::MentorTarget()
@ -7720,7 +7931,7 @@ void Player::CalculatePlayerHPPower(int16 new_level) {
bool Player::IsAllowedCombatEquip(int8 slot, bool send_message) {
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) {
case EQ2_PRIMARY_SLOT:
case EQ2_SECONDARY_SLOT:

View File

@ -1,6 +1,6 @@
/*
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.
@ -17,6 +17,7 @@
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_PLAYER__
#define __EQ2_PLAYER__
@ -224,7 +225,7 @@ struct QuickBarItem{
int16 icon_type;
int32 id;
int8 tier;
int32 unique_id;
int64 unique_id;
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);
bool IsPlayer(){ return true; }
MaintainedEffects* GetFreeMaintainedSpellSlot();
MaintainedEffects* GetMaintainedSpell(int32 id);
MaintainedEffects* GetMaintainedSpell(int32 id, bool on_char_load = false);
MaintainedEffects* GetMaintainedSpellBySlot(int8 slot);
MaintainedEffects* GetMaintainedSpells();
SpellEffects* GetFreeSpellEffectSlot();
@ -608,6 +609,7 @@ public:
bool TradeskillXPEnabled();
void SetNeededXP(int32 val);
void SetNeededXP();
static int32 GetNeededXPByLevel(int8 level);
void SetXP(int32 val);
void SetNeededTSXP(int32 val);
void SetNeededTSXP();
@ -789,7 +791,7 @@ public:
bool GetIsTracking() const { return is_tracking; }
void SetBiography(string new_biography) { biography = new_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; }
Guild* GetGuild() { return guild; }
void AddSkillBonus(int32 spell_id, int32 skill_id, float value);
@ -1048,7 +1050,10 @@ public:
void DismissAllPets();
void SaveSpellEffects();
void SaveCustomSpellFields(LuaSpell* luaspell);
void SaveCustomSpellDataIndex(LuaSpell* luaspell);
void SaveCustomSpellEffectsDisplay(LuaSpell* luaspell);
void SetSaveSpellEffects(bool val) { stop_save_spell_effects = val; }
AppearanceData SavedApp;
CharFeatures SavedFeatures;
@ -1098,8 +1103,11 @@ public:
void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true);
void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true);
int32 GetActiveFoodUniqueID() { return active_food_unique_id; }
int32 GetActiveDrinkUniqueID() { return active_drink_unique_id; }
int64 GetActiveFoodUniqueID() { return active_food_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;
float pos_packet_speed;
@ -1112,8 +1120,8 @@ public:
mutable std::shared_mutex trait_mutex;
std::atomic<bool> need_trait_update;
void InitXPTable();
map<int8, int32> m_levelXPReq;
static void InitXPTable();
static map<int8, int32> m_levelXPReq;
mutable std::shared_mutex spell_packet_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 RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast);
// void InitXPTable();
// map<int8, int32> m_levelXPReq;
//The following variables are for serializing spawn packets
PacketStruct* spawn_pos_struct;
@ -1261,8 +1267,10 @@ private:
Quest* GetAnyQuest(int32 quest_id);
Quest* GetCompletedQuest(int32 quest_id);
std::atomic<int32> active_food_unique_id;
std::atomic<int32> active_drink_unique_id;
std::atomic<int64> active_food_unique_id;
std::atomic<int64> active_drink_unique_id;
int8 house_vault_slots;
};
#pragma pack()
#endif

View File

@ -1,25 +1,25 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
/*
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.
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 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.
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/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PlayerGroups.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#include "World.h"
#include "Spells.h"
#include "LuaInterface.h"
@ -27,13 +27,14 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "SpellProcess.h"
#include "Rules/Rules.h"
#include "Web/PeerManager.h"
#include "WorldDatabase.hpp"
#include "WorldDatabase.h"
extern ConfigReader configReader;
extern ZoneList zone_list;
extern RuleManager rule_manager;
extern PeerManager peer_manager;
extern WorldDatabase database;
extern LuaInterface* lua_interface;
/******************************************************** PlayerGroup ********************************************************/
PlayerGroup::PlayerGroup(int32 id) {
@ -906,25 +907,6 @@ int32 PlayerGroupManager::GetGroupSize(int32 group_id) {
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) {
std::shared_lock lock(MGroups);
bool questComplete = true;
@ -1113,6 +1095,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
caster = *vitr;
caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__);
// go through the player's maintained spells
bool skipSpell = false;
me = caster->GetMaintainedSpells();
for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) {
if (me[i].spell_id == 0xFFFFFFFF)
@ -1132,10 +1115,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
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)) {
luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
luaspell->char_id_targets.clear();
luaspell->ClearCharTargets();
for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) {
group_member = (*target_itr)->member;
@ -1147,13 +1127,45 @@ void PlayerGroupManager::UpdateGroupBuffs() {
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;
if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) {
has_effect = true;
}
if (!has_effect && (std::find(luaspell->removed_targets.begin(),
luaspell->removed_targets.end(), group_member->GetID()) != luaspell->removed_targets.end())) {
std::vector<int32> removed_targets = luaspell->GetRemovedTargets();
if (!has_effect && (std::find(removed_targets.begin(),
removed_targets.end(), group_member->GetID()) != removed_targets.end())) {
continue;
}
// Check if player is within range of the caster
@ -1245,14 +1257,18 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client->QueuePacket(packet);
}
}
luaspell->targets.swap(new_target_list);
SpellProcess::AddSelfAndPet(luaspell, caster);
luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
new_target_list.clear();
if(!skipSpell) {
luaspell->SwapTargets(new_target_list);
SpellProcess::AddSelfAndPet(luaspell, caster);
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;
break;
}
else {
succeed = zone_list.GetZoneByInstance(details, (*itr)->instance_id, (*itr)->zone_id, true, false);
if(succeed)
break;
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
@ -1951,6 +1972,11 @@ bool PlayerGroupManager::IdentifyMemberInGroupOrRaid(ZoneChangeDetails* details,
succeed = true;
break;
}
else {
succeed = zone_list.GetZoneByInstance(details, (*itr)->instance_id, (*itr)->zone_id, true, false);
if(succeed)
break;
}
}
}
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 "Spells.h"
#include "../common/types.hpp"
#include "../common/types.h"
#include "Entity.h"
using namespace std;
@ -220,7 +220,6 @@ public:
int32 GetGroupSize(int32 group_id);
void SendGroupQuests(int32 group_id, Client* client);
bool HasGroupCompletedQuest(int32 group_id, int32 quest_id);
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/>.
*/
#include "Quests.h"
#include "../common/config_reader.hpp"
#include "../common/ConfigReader.h"
#include "Player.h"
#include "LuaInterface.h"
#include "Spells.h"
#include "RaceTypes/RaceTypes.h"
#include "../common/log.hpp"
#include "../common/Log.h"
#ifdef WIN32
#include <time.h>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
/*
/*
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.
@ -17,12 +17,13 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include "../../common/debug.hpp"
#include "../../common/debug.h"
#include "../../common/Log.h"
#include "../../common/database.hpp"
#include "../../common/database.h"
#include "Recipe.h"
#include "../../common/config_reader.hpp"
#include "../../common/ConfigReader.h"
#include "../Items/Items.h"
#include "../World.h"
@ -734,7 +735,12 @@ bool Recipe::PlayerHasComponentByItemID(Client* client, vector<Item*>* player_co
cur_qty = track_req_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_components->push_back(itemss[i]);
if(have_qty >= required_qty)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +1,29 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
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.
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 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.
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/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Sign.h"
#include "../common/config_reader.hpp"
#include "WorldDatabase.hpp"
#include "../common/ConfigReader.h"
#include "WorldDatabase.h"
#include "World.h"
#include "../common/log.hpp"
#include "../common/Log.h"
extern World world;
extern ConfigReader configReader;
@ -152,6 +153,8 @@ Sign* Sign::Copy(){
new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType());
new_spawn->SetLanguage(GetLanguage());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn;
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
/*
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.
@ -25,8 +25,8 @@
#include "Entity.h"
#include "Widget.h"
#include "Sign.h"
#include "../common/misc_functions.hpp"
#include "../common/log.hpp"
#include "../common/MiscFunctions.h"
#include "../common/Log.h"
#include "Rules/Rules.h"
#include "World.h"
#include "LuaInterface.h"
@ -120,6 +120,7 @@ Spawn::Spawn(){
has_spawn_proximities = false;
pickup_item_id = 0;
pickup_unique_item_id = 0;
house_character_id = 0;
disable_sounds = false;
has_quests_required = false;
has_history_required = false;
@ -153,6 +154,7 @@ Spawn::Spawn(){
respawn_offset_high = 0;
duplicated_spawn = true;
ResetKnockedBack();
spawn_script_setdb = false;
}
Spawn::~Spawn(){
@ -1925,8 +1927,9 @@ const char* Spawn::GetSpawnScript(){
return 0;
}
void Spawn::SetSpawnScript(string name){
void Spawn::SetSpawnScript(string name, bool db_set){
spawn_script = name;
spawn_script_setdb = db_set;
}
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);
}
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;
@ -2197,18 +2200,25 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
if (size == 0)
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) {
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 {
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!
packet->setDataByName("pos_size_ratio", 1.0f);
}
@ -2234,7 +2244,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
else {
packet->setDataByName("pos_x", appearance.pos.X);
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;
}
if(GetMap() != player->GetMap()) {
@ -2487,11 +2497,6 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
packet->setDataByName("npc", 1);
if (GetMerchantID() > 0)
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("level", (int8)GetLevel());
@ -2981,8 +2986,9 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
spell_id = info->spell_effects[i].spell_id;
if(spell_id > 0)
spell_id = 0xFFFFFFFF - spell_id;
else
else {
spell_id = 0;
}
packet->setSubstructDataByName("spell_effects", "spell_id", spell_id, i);
//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);
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);
i++;
}
@ -3360,8 +3366,9 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
}
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();
//last_movement_update = Timer::GetCurrentTime2();
last_movement_update = Timer::GetCurrentTime2();
}
else if(movementCase)
{
@ -3866,8 +3873,6 @@ void Spawn::FaceTarget(Spawn* target, bool disable_action_state){
if(!target)
return;
FaceTarget(target->GetX(), target->GetZ());
if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){
if(!IsPet() && disable_action_state) {
if(IsNPC()) {
@ -3877,6 +3882,8 @@ void Spawn::FaceTarget(Spawn* target, bool disable_action_state){
SetTempActionState(0);
}
}
FaceTarget(target->GetX(), target->GetZ());
}
bool Spawn::MeetsSpawnAccessRequirements(Player* player){

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