character properties not hard coded are now dynamically fed into the info struct string, we can now SetInfoStruct(Player, "somevar", "avalue") and it will save to the DB to persist cross zone with GetInfoStruct(Player, "somevar")

This commit is contained in:
Emagi 2025-08-18 13:56:53 -04:00
parent 05f5061c19
commit fae7cf7773
5 changed files with 416 additions and 263 deletions

View File

@ -32,6 +32,7 @@
#include "Skills.h"
#include "Rules/Rules.h"
#include "LuaInterface.h"
#include "WorldDatabase.h"
extern World world;
extern MasterItemList master_item_list;
@ -40,6 +41,7 @@ extern MasterSkillList master_skill_list;
extern RuleManager rule_manager;
extern Classes classes;
extern LuaInterface* lua_interface;
extern WorldDatabase database;
Entity::Entity(){
MapInfoStruct();
@ -616,6 +618,65 @@ void Entity::MapInfoStruct()
set_float_funcs["max_chase_distance"] = l::bind(&InfoStruct::set_max_chase_distance, &info_struct, l::_1);
}
void Entity::RegisterProperty(const std::string& name) {
int32 charID = 0;
if(IsPlayer())
charID = ((Player*)this)->GetCharacterID();
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
if (set_string_funcs.find(name) != set_string_funcs.end() ||
get_string_funcs.find(name) != get_string_funcs.end())
return;
}
{
std::unique_lock<std::shared_mutex> wlock(propertiesMutex);
if (set_string_funcs.find(name) != set_string_funcs.end() ||
get_string_funcs.find(name) != get_string_funcs.end()) {
return;
}
set_string_funcs.emplace(name, [this, name, database, charID](std::string v) {
{
std::lock_guard<std::mutex> lk(GetInfoStruct()->classMutex);
GetInfoStruct()->props[name] = v;
}
if(charID)
database.insertCharacterProperty(charID, const_cast<char*>(name.c_str()), const_cast<char*>(v.c_str()));
});
get_string_funcs.emplace(name, [this, name]() -> std::string {
std::lock_guard<std::mutex> lk(GetInfoStruct()->classMutex);
auto it = GetInfoStruct()->props.find(name);
return (it == GetInfoStruct()->props.end()) ? std::string{} : it->second;
});
}
}
void Entity::SetProperty(const std::string& name, const std::string& value) {
auto it = set_string_funcs.find(name);
if (it == set_string_funcs.end())
return;
int32 charID = 0;
if(IsPlayer())
charID = ((Player*)this)->GetCharacterID();
if(charID)
database.insertCharacterProperty(charID, const_cast<char*>(name.c_str()), const_cast<char*>(value.c_str()));
return it->second(value);
}
std::optional<std::string> Entity::GetProperty(const std::string& name) const {
auto it = get_string_funcs.find(name);
if (it == get_string_funcs.end()) return std::nullopt;
return it->second();
}
bool Entity::HasMoved(bool include_heading){
if(GetX() == last_x && GetY() == last_y && GetZ() == last_z && ((!include_heading) || (include_heading && GetHeading() == last_heading)))
return false;
@ -3780,6 +3841,7 @@ void Entity::HaltMovement()
std::string Entity::GetInfoStructString(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<std::string()>>::const_iterator itr = get_string_funcs.find(field);
if(itr != get_string_funcs.end())
{
@ -3791,6 +3853,7 @@ std::string Entity::GetInfoStructString(std::string field)
int8 Entity::GetInfoStructInt8(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<int8()>>::const_iterator itr = get_int8_funcs.find(field);
if(itr != get_int8_funcs.end())
{
@ -3802,6 +3865,7 @@ int8 Entity::GetInfoStructInt8(std::string field)
int16 Entity::GetInfoStructInt16(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<int16()>>::const_iterator itr = get_int16_funcs.find(field);
if(itr != get_int16_funcs.end())
{
@ -3813,6 +3877,7 @@ int16 Entity::GetInfoStructInt16(std::string field)
int32 Entity::GetInfoStructInt32(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<int32()>>::const_iterator itr = get_int32_funcs.find(field);
if(itr != get_int32_funcs.end())
{
@ -3824,6 +3889,7 @@ int32 Entity::GetInfoStructInt32(std::string field)
int64 Entity::GetInfoStructInt64(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<int64()>>::const_iterator itr = get_int64_funcs.find(field);
if(itr != get_int64_funcs.end())
{
@ -3835,6 +3901,7 @@ int64 Entity::GetInfoStructInt64(std::string field)
sint8 Entity::GetInfoStructSInt8(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<sint8()>>::const_iterator itr = get_sint8_funcs.find(field);
if(itr != get_sint8_funcs.end())
{
@ -3846,6 +3913,7 @@ sint8 Entity::GetInfoStructSInt8(std::string field)
sint16 Entity::GetInfoStructSInt16(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<sint16()>>::const_iterator itr = get_sint16_funcs.find(field);
if(itr != get_sint16_funcs.end())
{
@ -3857,6 +3925,7 @@ sint16 Entity::GetInfoStructSInt16(std::string field)
sint32 Entity::GetInfoStructSInt32(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<sint32()>>::const_iterator itr = get_sint32_funcs.find(field);
if(itr != get_sint32_funcs.end())
{
@ -3868,6 +3937,7 @@ sint32 Entity::GetInfoStructSInt32(std::string field)
sint64 Entity::GetInfoStructSInt64(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<sint64()>>::const_iterator itr = get_sint64_funcs.find(field);
if(itr != get_sint64_funcs.end())
{
@ -3879,6 +3949,7 @@ sint64 Entity::GetInfoStructSInt64(std::string field)
float Entity::GetInfoStructFloat(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<float()>>::const_iterator itr = get_float_funcs.find(field);
if(itr != get_float_funcs.end())
{
@ -3890,6 +3961,7 @@ float Entity::GetInfoStructFloat(std::string field)
int64 Entity::GetInfoStructUInt(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<int8()>>::const_iterator itr = get_int8_funcs.find(field);
if(itr != get_int8_funcs.end())
{
@ -3919,6 +3991,7 @@ int64 Entity::GetInfoStructUInt(std::string field)
sint64 Entity::GetInfoStructSInt(std::string field)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<sint8()>>::const_iterator itr = get_sint8_funcs.find(field);
if(itr != get_sint8_funcs.end())
{
@ -3949,18 +4022,24 @@ sint64 Entity::GetInfoStructSInt(std::string field)
bool Entity::SetInfoStructString(std::string field, std::string value)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<void(std::string)>>::const_iterator itr = set_string_funcs.find(field);
if(itr != set_string_funcs.end())
{
(itr->second)(value);
return true;
}
else {
RegisterProperty(field);
SetProperty(field, value);
}
return false;
}
bool Entity::SetInfoStructUInt(std::string field, int64 value)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<void(int8)>>::const_iterator itr = set_int8_funcs.find(field);
if(itr != set_int8_funcs.end())
{
@ -3990,6 +4069,7 @@ bool Entity::SetInfoStructUInt(std::string field, int64 value)
bool Entity::SetInfoStructSInt(std::string field, sint64 value)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<void(sint8)>>::const_iterator itr = set_sint8_funcs.find(field);
if(itr != set_sint8_funcs.end())
{
@ -4019,6 +4099,7 @@ bool Entity::SetInfoStructSInt(std::string field, sint64 value)
bool Entity::SetInfoStructFloat(std::string field, float value)
{
std::shared_lock<std::shared_mutex> rlock(propertiesMutex);
map<string, boost::function<void(float)>>::const_iterator itr = set_float_funcs.find(field);
if(itr != set_float_funcs.end())
{

View File

@ -28,6 +28,8 @@
#include <set>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <optional>
#include <boost/function.hpp>
#include <boost/lambda/bind.hpp>
@ -1110,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_;
@ -1328,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 {
@ -1449,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);
@ -2205,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

@ -2146,14 +2146,34 @@ bool WorldDatabase::insertCharacterProperty(Client* client, char* propName, char
return true;
}
bool WorldDatabase::loadCharacterProperties(Client* client) {
bool WorldDatabase::insertCharacterProperty(int32 charID, char* propName, char* propValue) {
Query query, query2;
string update_status = string("update character_properties set propvalue='%s' where charid=%i and propname='%s'");
query.RunQuery2(Q_UPDATE, update_status.c_str(), propValue, charID, propName);
if (!query.GetAffectedRows())
{
query2.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", charID, propName, propValue);
if (query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "World", "Error in insertCharacterProperty query '%s': %s", query.GetQuery(), query.GetError());
return false;
}
}
return true;
}
bool WorldDatabase::loadCharacterProperties(Client* client, bool preload) {
Query query;
MYSQL_ROW row;
int32 id = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT propname, propvalue FROM character_properties where charid = %i", client->GetCharacterID());
MYSQL_RES* result = query.RunQuery2(Q_SELECT,
"SELECT propname, propvalue FROM character_properties WHERE charid = %i",
client->GetCharacterID());
// no character found
if (result == NULL) {
LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character properties for '%s'", client->GetPlayer()->GetName());
LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character properties for '%s'",
client->GetPlayer()->GetName());
return false;
}
@ -2164,109 +2184,151 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
if (!prop_name || !prop_value)
continue;
if (!stricmp(prop_name, CHAR_PROPERTY_SPEED))
{
float new_speed = atof(prop_value);
bool matched = false;
if (!stricmp(prop_name, CHAR_PROPERTY_SPEED)) {
matched = true;
if (!preload) {
float new_speed = (float)atof(prop_value);
client->GetPlayer()->SetSpeed(new_speed, true);
client->GetPlayer()->SetCharSheetChanged(true);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_FLYMODE))
{
int8 flymode = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_FLYMODE)) {
matched = true;
if (!preload) {
int8 flymode = (int8)atoul(prop_value);
if (flymode) // avoid fly mode notification unless enabled
ClientPacketFunctions::SendFlyMode(client, flymode, false);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_INVUL))
{
int8 invul = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_INVUL)) {
matched = true;
if (!preload) {
int8 invul = (int8)atoul(prop_value);
client->GetPlayer()->SetInvulnerable(invul == 1);
if (client->GetPlayer()->GetInvulnerable())
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are now invulnerable!");
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GMVISION))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GMVISION)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->SetGMVision(val == 1);
client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
if (val)
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Enabled!");
}
else if (!stricmp(prop_name, CHAR_PROPERTY_REGIONDEBUG))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_REGIONDEBUG)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->SetRegionDebug(val == 1);
if (val)
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!");
}
else if (!stricmp(prop_name, CHAR_PROPERTY_LUADEBUG))
{
int8 val = atoul(prop_value);
if (val)
{
}
else if (!stricmp(prop_name, CHAR_PROPERTY_LUADEBUG)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
if (val) {
client->SetLuaDebugClient(true);
if (lua_interface)
lua_interface->UpdateDebugClients(client);
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages.");
}
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTMETHOD))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTMETHOD)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_loot_method(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTITEMRARITY))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTITEMRARITY)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOSPLIT))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOSPLIT)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_auto_split(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPDEFAULTYELL))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPDEFAULTYELL)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_default_yell(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOLOCK))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOLOCK)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_autolock(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPSOLOAUTOLOCK))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPSOLOAUTOLOCK)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_solo_autolock(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_AUTOLOOTMETHOD))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_AUTOLOOTMETHOD)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOCKMETHOD))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOCKMETHOD)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_lock_method(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_ASSISTAUTOATTACK))
{
int8 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_ASSISTAUTOATTACK)) {
matched = true;
if (!preload) {
int8 val = (int8)atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_assist_auto_attack(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEFOOD))
{
int32 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEFOOD)) {
matched = true;
if (!preload) {
int32 val = (int32)atoul(prop_value);
client->GetPlayer()->SetActiveFoodUniqueID(val, false);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEDRINK))
{
int32 val = atoul(prop_value);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEDRINK)) {
matched = true;
if (!preload) {
int32 val = (int32)atoul(prop_value);
client->GetPlayer()->SetActiveDrinkUniqueID(val, false);
}
}
else {
// Unknown property: only act during preload AND only if no stricmp matched
// (we're in the 'else', so matched == false by definition)
if (preload) {
client->GetPlayer()->RegisterProperty(std::string(prop_name));
client->GetPlayer()->SetProperty(std::string(prop_name), std::string(prop_value));
}
// When preload == false, we intentionally ignore unknown properties.
}
}
return true;
}

View File

@ -333,7 +333,8 @@ public:
bool InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id);
bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp);
bool insertCharacterProperty(Client* client, char* propName, char* propValue);
bool loadCharacterProperties(Client* client);
bool insertCharacterProperty(int32 charID, char* propName, char* propValue);
bool loadCharacterProperties(Client* client, bool preload = false);
string GetPlayerName(char* name);
int32 GetCharacterTimeStamp(int32 character_id, int32 account_id,bool* char_exists);
int32 GetCharacterTimeStamp(int32 character_id);

View File

@ -12249,6 +12249,7 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
new_client_login = NewLoginState::LOGIN_ALLOWED;
}
database.loadCharacterProperties(this, true);
// vault slots
RefreshVaultSlotCount();