Merge pull request #4 from EQEmu/master

Update to master
This commit is contained in:
Paul Coene 2020-03-05 10:37:41 -05:00 committed by GitHub
commit 482584f95a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 925 additions and 334 deletions

View File

@ -0,0 +1,21 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.101.1/containers/ubuntu-18.04-git
{
"name": "Ubuntu 18.04 EQEMU",
// Moved from dockerfile to image so it builds faster
"image": "eqemu/devcontainer:0.0.2",
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["ms-vscode.cpptools", "ms-azuretools.vscode-docker"],
"mounts": ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"],
"remoteEnv": {
"HOST_PROJECT_PATH": "${localWorkspaceFolder}"
}
}

2
.gitignore vendored
View File

@ -17,6 +17,8 @@
*.out *.out
*.app *.app
.bash_history
# CMake # CMake
CMakeCache.txt CMakeCache.txt
CMakeFiles CMakeFiles

16
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/mysql"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}

155
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,155 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "make",
"type": "shell",
"command": "cd build && make",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "make clean",
"type": "shell",
"command": "cd build && make clean",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "cmake",
"type": "shell",
"command": "mkdir -p build && cd build && rm CMakeCache.txt && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' ..",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher":{
"owner": "cpp",
"fileLocation": "relative",
"pattern":[
{
"regexp": "([\\w+|\\\\]*\\.\\w+)\\((\\d+)\\)\\: (warning|error) (.*)$",
"file": 1,
"location": 2,
"severity": 3,
"message": 4
}
]
}
},
{
"label": "download maps",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && wget https://codeload.github.com/Akkadius/EQEmuMaps/zip/master -O maps.zip && unzip -o maps.zip && rm ./maps -rf && mv EQEmuMaps-master maps && rm maps.zip",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "download quests",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && cd server && git -C ./quests pull 2> /dev/null || git clone https://github.com/ProjectEQ/projecteqquests.git quests",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "download eqemu_config",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && wget --no-check-certificate https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/eqemu_config_docker.json -O eqemu_config.json",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "rebuild database (mariadb must be started)",
"type": "shell",
"command": "mkdir -p build/bin && cd build/bin && docker run -i --rm --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --network=eqemu -it eqemu/server:0.0.3 bash -c './eqemu_server.pl source_peq_db && ./eqemu_server.pl check_db_updates && ./eqemu_server.pl linux_login_server_setup'",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
]
},
{
"label": "zone 7000",
"type": "shell",
"command": "docker stop zone7000 | true && docker network create eqemu | true && docker run -i --rm --name zone7000 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 --network=eqemu -p 7000:7000/udp -e LD_LIBRARY_PATH=/src/ eqemu/server:0.0.3 gdb -ex run --args ./zone dynamic_zone7000:7000",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "zone 7001",
"type": "shell",
"command": "docker stop zone7001 | true && docker network create eqemu | true && docker run -i --rm --name zone7001 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 --network=eqemu -p 7001:7001/udp -e LD_LIBRARY_PATH=/src/ eqemu/server:0.0.3 gdb -ex run --args ./zone dynamic_zone7001:7001",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "loginserver",
"type": "shell",
"command": "docker stop loginserver | true && docker network create eqemu | true && docker run -i --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --privileged -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 --network=eqemu --name loginserver -p 5999:5999/udp -p 5998:5998/udp -e LD_LIBRARY_PATH=/src/ eqemu/server:0.0.3 gdb -ex run --args ./loginserver",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "shared_memory, world",
"type": "shell",
"command": "docker stop sharedmemory | true && docker stop world | true && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src --network=eqemu --name sharedmemory eqemu/server:0.0.3 ./shared_memory && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 -e LD_LIBRARY_PATH=/src/ --network=eqemu --name world -p 9000:9000 -p 9000:9000/udp -p 9001:9001 -p 9080:9080 eqemu/server:0.0.3 gdb -ex run ./world",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "queryserv",
"type": "shell",
"command": "docker stop queryserv | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src --ulimit core=10000000 -e LD_LIBRARY_PATH=/src/ --network=eqemu --name queryserv eqemu/server:0.0.3 gdb -ex run ./queryserv",
"group": {
"kind": "test",
"isDefault": true
}
},
{
"label": "mariadb",
"type": "shell",
"command": "docker stop mariadb | true && cd build/bin && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin/db:/bitnami/mariadb -p 3306:3306 -e MARIADB_DATABASE=peq -e MARIADB_USER=eqemu -e MARIADB_PASSWORD=eqemupass -e ALLOW_EMPTY_PASSWORD=yes --name mariadb --network=eqemu bitnami/mariadb:latest",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}

View File

@ -17,7 +17,7 @@
|:---:|:---:|:---:| |:---:|:---:|:---:|
|**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)| |**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)|
### > Windows ### > Windows
* [Install](https://github.com/EQEmu/Server/wiki/Windows-Server) * [Install](https://eqemu.gitbook.io/server/categories/how-to-guides/installation/server-installation-windows)
### > Debian/Ubuntu/CentOS/Fedora ### > Debian/Ubuntu/CentOS/Fedora
* You can use curl or wget to kick off the installer (whichever your OS has) * You can use curl or wget to kick off the installer (whichever your OS has)

View File

@ -36,6 +36,7 @@ static const uint32 MAX_MERC_GRADES = 10;
static const uint32 MAX_MERC_STANCES = 10; static const uint32 MAX_MERC_STANCES = 10;
static const uint32 BLOCKED_BUFF_COUNT = 20; static const uint32 BLOCKED_BUFF_COUNT = 20;
static const uint32 QUESTREWARD_COUNT = 8; static const uint32 QUESTREWARD_COUNT = 8;
static const uint32 ADVANCED_LORE_LENGTH = 8192;
/* /*
@ -2966,6 +2967,12 @@ struct ItemViewRequest_Struct {
/*046*/ char unknown046[2]; /*046*/ char unknown046[2];
}; };
struct ItemAdvancedLoreText_Struct {
int32 item_id;
char item_name[64];
char advanced_lore[ADVANCED_LORE_LENGTH];
};
struct LDONItemViewRequest_Struct { struct LDONItemViewRequest_Struct {
uint32 item_id; uint32 item_id;
uint8 unknown004[4]; uint8 unknown004[4];

View File

@ -124,6 +124,8 @@ void EQEmuLogSys::LoadLogSettingsDefaults()
log_settings[Logs::Loginserver].log_to_console = static_cast<uint8>(Logs::General); log_settings[Logs::Loginserver].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::HeadlessClient].log_to_console = static_cast<uint8>(Logs::General); log_settings[Logs::HeadlessClient].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::NPCScaling].log_to_gmsay = static_cast<uint8>(Logs::General); log_settings[Logs::NPCScaling].log_to_gmsay = static_cast<uint8>(Logs::General);
log_settings[Logs::HotReload].log_to_gmsay = static_cast<uint8>(Logs::General);
log_settings[Logs::HotReload].log_to_console = static_cast<uint8>(Logs::General);
/** /**
* RFC 5424 * RFC 5424

View File

@ -114,6 +114,7 @@ namespace Logs {
EntityManagement, EntityManagement,
Flee, Flee,
Aura, Aura,
HotReload,
MaxCategoryID /* Don't Remove this */ MaxCategoryID /* Don't Remove this */
}; };
@ -187,6 +188,7 @@ namespace Logs {
"Entity Management", "Entity Management",
"Flee", "Flee",
"Aura", "Aura",
"HotReload",
}; };
} }

View File

@ -561,6 +561,16 @@
OutF(LogSys, Logs::Detail, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::Detail, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0) } while (0)
#define LogHotReload(message, ...) do {\
if (LogSys.log_settings[Logs::HotReload].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogHotReloadDetail(message, ...) do {\
if (LogSys.log_settings[Logs::HotReload].is_category_enabled == 1)\
OutF(LogSys, Logs::Detail, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\ #define Log(debug_level, log_category, message, ...) do {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@ -894,6 +904,12 @@
#define LogAuraDetail(message, ...) do {\ #define LogAuraDetail(message, ...) do {\
} while (0) } while (0)
#define LogHotReload(message, ...) do {\
} while (0)
#define LogHotReloadDetail(message, ...) do {\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\ #define Log(debug_level, log_category, message, ...) do {\
} while (0) } while (0)

View File

@ -1047,12 +1047,14 @@ void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t leng
uint8_t new_buffer[2048] = { 0 }; uint8_t new_buffer[2048] = { 0 };
uint8_t *buffer = (uint8_t*)p.Data() + offset; uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0; uint32_t new_length = 0;
bool send_uncompressed = true;
if (length > 30) { if (length > 30) {
new_length = Deflate(buffer, (uint32_t)length, new_buffer + 1, 2048) + 1; new_length = Deflate(buffer, (uint32_t)length, new_buffer + 1, 2048) + 1;
new_buffer[0] = 0x5a; new_buffer[0] = 0x5a;
send_uncompressed = (new_length > length);
} }
else { if (send_uncompressed) {
memcpy(new_buffer + 1, buffer, length); memcpy(new_buffer + 1, buffer, length);
new_buffer[0] = 0xa5; new_buffer[0] = 0xa5;
new_length = length + 1; new_length = length + 1;
@ -1380,7 +1382,7 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
} }
auto stream = &m_streams[stream_id]; auto stream = &m_streams[stream_id];
auto max_raw_size = m_max_packet_size - m_crc_bytes - DaybreakReliableHeader::size(); auto max_raw_size = m_max_packet_size - m_crc_bytes - DaybreakReliableHeader::size() - 1; // -1 for compress flag
size_t length = p.Length(); size_t length = p.Length();
if (length > max_raw_size) { if (length > max_raw_size) {
DaybreakReliableFragmentHeader first_header; DaybreakReliableFragmentHeader first_header;

View File

@ -768,6 +768,12 @@ RULE_CATEGORY(Logging)
RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...") RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...")
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(HotReload)
RULE_BOOL(HotReload, QuestsRepopWithReload, true, "When a hot reload is triggered, the zone will repop")
RULE_BOOL(HotReload, QuestsRepopWhenPlayersNotInCombat, true, "When a hot reload is triggered, the zone will repop when no clients are in combat")
RULE_BOOL(HotReload, QuestsResetTimersWithReload, true, "When a hot reload is triggered, quest timers will be reset")
RULE_CATEGORY_END()
#undef RULE_CATEGORY #undef RULE_CATEGORY
#undef RULE_INT #undef RULE_INT
#undef RULE_REAL #undef RULE_REAL

View File

@ -197,7 +197,11 @@
#define ServerOP_CZSetEntityVariableByClientName 0x4012 #define ServerOP_CZSetEntityVariableByClientName 0x4012
#define ServerOP_UCSServerStatusRequest 0x4013 #define ServerOP_UCSServerStatusRequest 0x4013
#define ServerOP_UCSServerStatusReply 0x4014 #define ServerOP_UCSServerStatusReply 0x4014
/* Query Server OP Codes */ #define ServerOP_HotReloadQuests 0x4015
/**
* QueryServer
*/
#define ServerOP_QSPlayerLogTrades 0x5010 #define ServerOP_QSPlayerLogTrades 0x5010
#define ServerOP_QSPlayerLogHandins 0x5011 #define ServerOP_QSPlayerLogHandins 0x5011
#define ServerOP_QSPlayerLogNPCKills 0x5012 #define ServerOP_QSPlayerLogNPCKills 0x5012
@ -1351,12 +1355,16 @@ struct CZSetEntVarByClientName_Struct {
char m_var[256]; char m_var[256];
}; };
struct ReloadWorld_Struct{ struct ReloadWorld_Struct {
uint32 Option; uint32 Option;
}; };
struct HotReloadQuestsStruct {
char zone_short_name[200];
};
struct ServerRequestTellQueue_Struct { struct ServerRequestTellQueue_Struct {
char name[64]; char name[64];
}; };
struct UCSServerStatus_Struct { struct UCSServerStatus_Struct {

View File

@ -331,7 +331,7 @@ OP_LDoNButton=0x596e
OP_SetStartCity=0x7936 # Was 0x2d1b OP_SetStartCity=0x7936 # Was 0x2d1b
OP_VoiceMacroIn=0x202e OP_VoiceMacroIn=0x202e
OP_VoiceMacroOut=0x3920 OP_VoiceMacroOut=0x3920
OP_ItemViewUnknown=0x0b64 OP_ItemAdvancedLoreText=0x0b64
OP_VetRewardsAvaliable=0x05d9 OP_VetRewardsAvaliable=0x05d9
OP_VetClaimRequest=0xcdde OP_VetClaimRequest=0xcdde
OP_VetClaimReply=0x361b OP_VetClaimReply=0x361b

View File

@ -448,6 +448,8 @@ OP_FinishWindow2=0x40ef
OP_ItemVerifyRequest=0x189c OP_ItemVerifyRequest=0x189c
OP_ItemVerifyReply=0x097b OP_ItemVerifyReply=0x097b
OP_ItemAdvancedLoreText=0x023b
# merchant stuff # merchant stuff
OP_ShopPlayerSell=0x791b OP_ShopPlayerSell=0x791b
OP_ShopRequest=0x4fed OP_ShopRequest=0x4fed

View File

@ -327,7 +327,7 @@ OP_LDoNButton=0x41b5 # C
OP_SetStartCity=0x7bf6 # C OP_SetStartCity=0x7bf6 # C
OP_VoiceMacroIn=0x31b1 # C OP_VoiceMacroIn=0x31b1 # C
OP_VoiceMacroOut=0x7880 # C OP_VoiceMacroOut=0x7880 # C
OP_ItemViewUnknown=0x21c7 # C OP_ItemAdvancedLoreText=0x21c7 # C
OP_VetRewardsAvaliable=0x4e4e # C OP_VetRewardsAvaliable=0x4e4e # C
OP_VetClaimRequest=0x771f # C OP_VetClaimRequest=0x771f # C
OP_VetClaimReply=0x2f95 # C OP_VetClaimReply=0x2f95 # C

View File

@ -576,7 +576,7 @@ OP_QueryResponseThing=0x0000 #
# realityincarnate: these are just here to stop annoying several thousand byte packet dumps # realityincarnate: these are just here to stop annoying several thousand byte packet dumps
OP_LoginUnknown1=0x22cf OP_LoginUnknown1=0x22cf
OP_LoginUnknown2=0x43ba OP_LoginUnknown2=0x43ba
OP_ItemViewUnknown=0x4db4 OP_ItemAdvancedLoreText=0x4db4
#Petition Opcodes #Petition Opcodes
OP_PetitionSearch=0x0000 #search term for petition OP_PetitionSearch=0x0000 #search term for petition

View File

@ -336,7 +336,7 @@ OP_LDoNButton=0x1031 # C
OP_SetStartCity=0x68f0 # C OP_SetStartCity=0x68f0 # C
OP_VoiceMacroIn=0x1524 # C OP_VoiceMacroIn=0x1524 # C
OP_VoiceMacroOut=0x1d99 # C OP_VoiceMacroOut=0x1d99 # C
OP_ItemViewUnknown=0x4eb3 # C OP_ItemAdvancedLoreText=0x4eb3 # C
OP_VetRewardsAvaliable=0x0baa # C Mispelled? OP_VetRewardsAvaliable=0x0baa # C Mispelled?
OP_VetClaimRequest=0x34f8 # C OP_VetClaimRequest=0x34f8 # C
OP_VetClaimReply=0x6a5d # C OP_VetClaimReply=0x6a5d # C

View File

@ -493,9 +493,7 @@ sub do_installer_routines {
#::: Download PEQ latest #::: Download PEQ latest
fetch_peq_db_full(); fetch_peq_db_full();
print "[Database] Fetching Latest Database Updates...\n"; print "[Database] Fetching and Applying Latest Database Updates...\n";
main_db_management();
print "[Database] Applying Latest Database Updates...\n";
main_db_management(); main_db_management();
remove_duplicate_rule_values(); remove_duplicate_rule_values();
@ -531,31 +529,7 @@ sub check_for_world_bootup_database_update {
} }
$binary_database_version = trim($db_version[1]); $binary_database_version = trim($db_version[1]);
$local_database_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); $local_database_version = get_main_db_version();
#::: Bots
$bots_binary_version = trim($db_version[2]);
if ($bots_binary_version > 0) {
$bots_local_db_version = get_bots_db_version();
#::: We ran world - Database needs to update, lets backup and run updates and continue world bootup
if ($bots_local_db_version < $bots_binary_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Bots Database not up to date with binaries... Automatically updating...\n";
print "[Update] Issuing database backup first...\n";
database_dump_compress();
print "[Update] Updating bots database...\n";
sleep(1);
bots_db_management();
run_database_check();
print "[Update] Continuing bootup\n";
analytics_insertion("auto database bots upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version);
exit;
}
else {
print "[Update] Bots database up to Date: Continuing World Bootup...\n";
}
}
if ($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world") { if ($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Database up to date...\n"; print "[Update] Database up to date...\n";
@ -567,22 +541,61 @@ sub check_for_world_bootup_database_update {
print "[Update] Database not up to date with binaries... Automatically updating...\n"; print "[Update] Database not up to date with binaries... Automatically updating...\n";
print "[Update] Issuing database backup first...\n"; print "[Update] Issuing database backup first...\n";
database_dump_compress(); database_dump_compress();
$db_already_backed_up = 1;
print "[Update] Updating database...\n"; print "[Update] Updating database...\n";
sleep(1); sleep(1);
main_db_management(); main_db_management();
main_db_management();
print "[Update] Continuing bootup\n";
analytics_insertion("auto database upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version); analytics_insertion("auto database upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version);
exit;
} }
#::: Make sure that we didn't pass any arugments to the script #::: Make sure that we didn't pass any arugments to the script
else { else {
if ($local_database_version > $binary_database_version) {
print "[Update] Database version is ahead of current binaries...\n";
}
if (!$db) { print "[eqemu_server.pl] No database connection found... Running without\n"; } if (!$db) { print "[eqemu_server.pl] No database connection found... Running without\n"; }
show_menu_prompt(); show_menu_prompt();
} }
} }
#::: Bots
$binary_database_version = trim($db_version[2]);
if ($binary_database_version > 0) {
$local_database_version = get_bots_db_version();
#::: We ran world - Database needs to update, lets backup and run updates and continue world bootup
if ($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Bots database up to date...\n";
}
else {
if ($local_database_version < $binary_database_version && $ARGV[0] eq "ran_from_world") {
print "[Update] Bots Database not up to date with binaries... Automatically updating...\n";
if (!$db_already_backed_up) {
print "[Update] Issuing database backup first...\n";
database_dump_compress();
}
print "[Update] Updating bots database...\n";
sleep(1);
bots_db_management();
analytics_insertion("auto database bots upgrade world", $db . " :: Binary DB Version / Local DB Version :: " . $binary_database_version . " / " . $local_database_version);
}
#::: Make sure that we didn't pass any arugments to the script
else {
if ($local_database_version > $binary_database_version) {
print "[Update] Bots database version is ahead of current binaries...\n";
}
if (!$db) { print "[eqemu_server.pl] No database connection found... Running without\n"; }
show_menu_prompt();
}
}
}
print "[Update] Continuing bootup\n";
} }
sub check_internet_connection { sub check_internet_connection {
@ -629,7 +642,7 @@ sub do_self_update_check_routine {
#::: Check for internet connection before updating #::: Check for internet connection before updating
if (!$has_internet_connection) { if (!$has_internet_connection) {
print "[Update] Cannot check update without internet connection...\n"; print "[Update] Cannot check self-update without internet connection...\n";
return; return;
} }
@ -819,7 +832,6 @@ sub setup_bots {
build_linux_source("bots"); build_linux_source("bots");
} }
bots_db_management(); bots_db_management();
run_database_check();
print "Bots should be setup, run your server and the bot command should be available in-game (type '^help')\n"; print "Bots should be setup, run your server and the bot command should be available in-game (type '^help')\n";
} }
@ -953,13 +965,11 @@ sub show_menu_prompt {
$dc = 1; $dc = 1;
} }
elsif ($input eq "check_db_updates") { elsif ($input eq "check_db_updates") {
main_db_management();
main_db_management(); main_db_management();
$dc = 1; $dc = 1;
} }
elsif ($input eq "check_bot_db_updates") { elsif ($input eq "check_bot_db_updates") {
bots_db_management(); bots_db_management();
run_database_check();
$dc = 1; $dc = 1;
} }
elsif ($input eq "setup_loginserver") { elsif ($input eq "setup_loginserver") {
@ -1400,6 +1410,7 @@ sub remove_duplicate_rule_values {
sub copy_file { sub copy_file {
$l_source_file = $_[0]; $l_source_file = $_[0];
$l_destination_file = $_[1]; $l_destination_file = $_[1];
if ($l_destination_file =~ /\//i) { if ($l_destination_file =~ /\//i) {
my @directory_path = split('/', $l_destination_file); my @directory_path = split('/', $l_destination_file);
$build_path = ""; $build_path = "";
@ -1418,6 +1429,7 @@ sub copy_file {
$directory_index++; $directory_index++;
} }
} }
copy $l_source_file, $l_destination_file; copy $l_source_file, $l_destination_file;
} }
@ -2001,15 +2013,6 @@ sub do_bots_db_schema_drop {
print get_mysql_result_from_file("db_update/drop_bots.sql"); print get_mysql_result_from_file("db_update/drop_bots.sql");
print "[Database] Removing bot database tables...\n"; print "[Database] Removing bot database tables...\n";
print get_mysql_result("DELETE FROM `rule_values` WHERE `rule_name` LIKE 'Bots:%';");
if (get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && $db) {
print get_mysql_result("DELETE FROM `commands` WHERE `command` LIKE 'bot';");
}
if (get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && $db) {
print get_mysql_result("DELETE FROM `command_settings` WHERE `command` LIKE 'bot';");
}
if (get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db) { if (get_mysql_result("SHOW KEYS FROM `group_id` WHERE `Key_name` LIKE 'PRIMARY'") ne "" && $db) {
print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;"); print get_mysql_result("ALTER TABLE `group_id` DROP PRIMARY KEY;");
@ -2043,70 +2046,6 @@ sub modify_db_for_bots {
} }
print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY USING BTREE(`groupid`, `charid`, `name`, `ismerc`);"); print get_mysql_result("ALTER TABLE `group_id` ADD PRIMARY KEY USING BTREE(`groupid`, `charid`, `name`, `ismerc`);");
if (get_mysql_result("SHOW TABLES LIKE 'command_settings'") ne "" && get_mysql_result("SELECT `command` FROM `command_settings` WHERE `command` LIKE 'bot'") eq "" && $db) {
print get_mysql_result("INSERT INTO `command_settings` VALUES ('bot', '0', '');");
}
if (get_mysql_result("SHOW TABLES LIKE 'commands'") ne "" && get_mysql_result("SELECT `command` FROM `commands` WHERE `command` LIKE 'bot'") eq "" && $db) {
print get_mysql_result("INSERT INTO `commands` VALUES ('bot', '0');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotAAExpansion'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:AAExpansion' WHERE `rule_name` LIKE 'Bots:BotAAExpansion';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:AAExpansion'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:AAExpansion', '8', 'The expansion through which bots will obtain AAs');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreateBotCount'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:CreationLimit' WHERE `rule_name` LIKE 'Bots:CreateBotCount';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CreationLimit'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:CreationLimit', '150', 'Number of bots that each account can create');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotFinishBuffing'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:FinishBuffing' WHERE `rule_name` LIKE 'Bots:BotFinishBuffing';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:FinishBuffing'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:FinishBuffing', 'false', 'Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotGroupBuffing'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:GroupBuffing' WHERE `rule_name` LIKE 'Bots:BotGroupBuffing';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:GroupBuffing'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:GroupBuffing', 'false', 'Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotManaRegen'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:ManaRegen' WHERE `rule_name` LIKE 'Bots:BotManaRegen';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:ManaRegen'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:ManaRegen', '3.0', 'Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotQuest'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpawnLimit' WHERE `rule_name` LIKE 'Bots:BotQuest';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpawnLimit'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpawnLimit', 'false', 'Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:BotSpellQuest'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:QuestableSpells' WHERE `rule_name` LIKE 'Bots:BotSpellQuest';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:QuestableSpells'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:QuestableSpells', 'false', 'Anita Thrall\\\'s (Anita_Thrall.pl) Bot Spell Scriber quests.');");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnBotCount'") ne "" && $db) {
print get_mysql_result("UPDATE `rule_values` SET `rule_name` = 'Bots:SpawnLimit' WHERE `rule_name` LIKE 'Bots:SpawnBotCount';");
}
if (get_mysql_result("SELECT `rule_name` FROM `rule_values` WHERE `rule_name` LIKE 'Bots:SpawnLimit'") eq "" && $db) {
print get_mysql_result("INSERT INTO `rule_values` VALUES ('1', 'Bots:SpawnLimit', '71', 'Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid');");
}
convert_existing_bot_data(); convert_existing_bot_data();
} }
@ -2205,54 +2144,49 @@ sub convert_existing_bot_data {
} }
} }
sub get_main_db_version {
$main_local_db_version = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1"));
return $main_local_db_version;
}
sub get_bots_db_version { sub get_bots_db_version {
#::: Check if bots_version column exists... #::: Check if bots_version column exists...
if (get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'bots_version'") eq "" && $db) { if (get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'bots_version'") eq "" && $db) {
print get_mysql_result("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version;"); print get_mysql_result("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version;");
print "[Database] Column 'bots_version' does not exists.... Adding to 'db_version' table...\n\n"; print "[Database] Column 'bots_version' does not exists.... Adding to 'db_version' table...\n\n";
} }
$bots_local_db_version = trim(get_mysql_result("SELECT bots_version FROM db_version LIMIT 1")); $bots_local_db_version = trim(get_mysql_result("SELECT bots_version FROM db_version LIMIT 1"));
return $bots_local_db_version; return $bots_local_db_version;
} }
#::: Safe for call from world startup or menu option
sub bots_db_management { sub bots_db_management {
my $world_path = "world";
if (-e "bin/world") {
$world_path = "bin/world";
}
#::: Get Binary DB version
if ($OS eq "Windows") {
@db_version = split(': ', `$world_path db_version`);
}
if ($OS eq "Linux") {
@db_version = split(': ', `./$world_path db_version`);
}
#::: Main Binary Database version
$binary_database_version = trim($db_version[2]);
#::: If we have stale data from main db run #::: If we have stale data from main db run
if ($db_run_stage > 0 && $bots_db_management == 0) { if ($db_run_stage > 0 && $bots_db_management == 0) {
clear_database_runs(); clear_database_runs();
} }
#::: Main Binary Database version
$binary_database_version = trim($db_version[2]);
if ($binary_database_version == 0) { if ($binary_database_version == 0) {
print "[Database] Your server binaries (world/zone) are not compiled for bots...\n\n"; print "[Database] Your server binaries (world/zone) are not compiled for bots...\n\n";
return; return;
} }
$local_database_version = get_bots_db_version();
#::: Set on flag for running bot updates... #::: Set on flag for running bot updates...
$bots_db_management = 1; $bots_db_management = 1;
$bots_local_db_version = get_bots_db_version(); if ($local_database_version > $binary_database_version) {
print "[Update] Bots database version is ahead of current binaries...\n";
$local_database_version = $bots_local_db_version; return;
}
run_database_check(); run_database_check();
} }
#::: Safe for call from world startup or menu option
sub main_db_management { sub main_db_management {
#::: If we have stale data from bots db run #::: If we have stale data from bots db run
if ($db_run_stage > 0 && $bots_db_management == 1) { if ($db_run_stage > 0 && $bots_db_management == 1) {
@ -2261,8 +2195,15 @@ sub main_db_management {
#::: Main Binary Database version #::: Main Binary Database version
$binary_database_version = trim($db_version[1]); $binary_database_version = trim($db_version[1]);
$local_database_version = get_main_db_version();
$bots_db_management = 0; $bots_db_management = 0;
if ($local_database_version > $binary_database_version) {
print "[Update] Database version is ahead of current binaries...\n";
return;
}
run_database_check(); run_database_check();
} }
@ -2272,148 +2213,70 @@ sub clear_database_runs {
%m_d = (); %m_d = ();
#::: Clear updates... #::: Clear updates...
@total_updates = (); @total_updates = ();
#::: Clear stage
$db_run_stage = 0;
} }
#::: Responsible for Database Upgrade Routines #::: Responsible for Database Upgrade Routines
sub run_database_check { sub run_database_check {
if (!$db) { if (!$db) {
print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n"; print "No database present, check your eqemu_config.xml for proper MySQL/MariaDB configuration...\n";
return; return;
} }
if (!@total_updates) { #::: Pull down bots database manifest
#::: Pull down bots database manifest if ($bots_db_management == 1) {
if ($bots_db_management == 1) { print "[Database] Retrieving latest bots database manifest...\n";
print "[Database] Retrieving latest bots database manifest...\n"; get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt");
get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/bots_db_update_manifest.txt", "db_update/db_update_manifest.txt");
}
#::: Pull down mainstream database manifest
else {
print "[Database] Retrieving latest database manifest...\n";
get_remote_file($eqemu_repository_request_url . "utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt");
}
} }
#::: Pull down mainstream database manifest
#::: Run 2 - Running pending updates...
if (@total_updates || $db_run_stage == 1) {
@total_updates = sort @total_updates;
foreach my $val (@total_updates) {
$file_name = trim($m_d{$val}[1]);
print "[Database] Running Update: " . $val . " - " . $file_name . "\n";
print get_mysql_result_from_file("db_update/$file_name");
print get_mysql_result("UPDATE db_version SET version = $val WHERE version < $val");
if ($bots_db_management == 1 && $val == 9000) {
modify_db_for_bots();
}
if ($val == 9138) {
fix_quest_factions();
}
}
$db_run_stage = 2;
}
#::: Run 1 - Initial checking of needed updates...
else { else {
print "[Database] Reading manifest...\n"; print "[Database] Retrieving latest database manifest...\n";
get_remote_file($eqemu_repository_request_url . "utils/sql/db_update_manifest.txt", "db_update/db_update_manifest.txt");
use Data::Dumper;
open(FILE, "db_update/db_update_manifest.txt");
while (<FILE>) {
chomp;
$o = $_;
if ($o =~ /#/i) {
next;
}
@manifest = split('\|', $o);
$m_d{$manifest[0]} = [ @manifest ];
}
#::: Setting Manifest stage...
$db_run_stage = 1;
} }
@total_updates = (); #::: Parse manifest
print "[Database] Reading manifest...\n";
use Data::Dumper;
open(FILE, "db_update/db_update_manifest.txt");
while (<FILE>) {
chomp;
$o = $_;
if ($o =~ /#/i) {
next;
}
@manifest = split('\|', $o);
$m_d{$manifest[0]} = [ @manifest ];
}
#::: This is where we set checkpoints for where a database might be so we don't check so far back in the manifest... #::: This is where we set checkpoints for where a database might be so we don't check so far back in the manifest...
if ($local_database_version >= 9000) { if ($local_database_version >= 9000) {
$revision_check = $local_database_version; $revision_check = $local_database_version + 1;
} }
else { else {
#::: This does not negatively affect bots
$revision_check = 1000; $revision_check = 1000;
if (get_mysql_result("SHOW TABLES LIKE 'character_data'") ne "") { if (get_mysql_result("SHOW TABLES LIKE 'character_data'") ne "") {
$revision_check = 8999; $revision_check = 8999;
} }
} }
#::: Iterate through Manifest backwards from binary version down to local version... @total_updates = ();
#::: Fetch and register sqls for this database update cycle
for ($i = $revision_check; $i <= $binary_database_version; $i++) { for ($i = $revision_check; $i <= $binary_database_version; $i++) {
if (!defined($m_d{$i}[0])) { if (!defined($m_d{$i}[0])) {
next; next;
} }
$file_name = trim($m_d{$i}[1]); $file_name = trim($m_d{$i}[1]);
$query_check = trim($m_d{$i}[2]); print "[Database] fetching update: " . $i . " '" . $file_name . "' \n";
$match_type = trim($m_d{$i}[3]); fetch_missing_db_update($i, $file_name);
$match_text = trim($m_d{$i}[4]); push(@total_updates, $i);
#::: Match type update
if ($match_type eq "contains") {
if (trim(get_mysql_result($query_check)) =~ /$match_text/i) {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
else {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if ($match_type eq "missing") {
if (get_mysql_result($query_check) =~ /$match_text/i) {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
next;
}
else {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
print_match_debug();
print_break();
}
if ($match_type eq "empty") {
if (get_mysql_result($query_check) eq "") {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
else {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if ($match_type eq "not_empty") {
if (get_mysql_result($query_check) ne "") {
print "[Database] missing update: " . $i . " '" . $file_name . "' \n";
fetch_missing_db_update($i, $file_name);
push(@total_updates, $i);
}
else {
print "[Database] has update (" . $i . ") '" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
} }
print "\n";
if (scalar(@total_updates) == 0) {
if (scalar(@total_updates) == 0 && $db_run_stage == 2) {
print "[Database] No updates need to be run...\n"; print "[Database] No updates need to be run...\n";
if ($bots_db_management == 1) { if ($bots_db_management == 1) {
print "[Database] Setting Database to Bots Binary Version (" . $binary_database_version . ") if not already...\n\n"; print "[Database] Setting Database to Bots Binary Version (" . $binary_database_version . ") if not already...\n\n";
@ -2423,24 +2286,106 @@ sub run_database_check {
print "[Database] Setting Database to Binary Version (" . $binary_database_version . ") if not already...\n\n"; print "[Database] Setting Database to Binary Version (" . $binary_database_version . ") if not already...\n\n";
get_mysql_result("UPDATE db_version SET version = $binary_database_version "); get_mysql_result("UPDATE db_version SET version = $binary_database_version ");
} }
clear_database_runs(); clear_database_runs();
return;
}
#::: Execute pending updates
@total_updates = sort @total_updates;
foreach my $val (@total_updates) {
$file_name = trim($m_d{$val}[1]);
$query_check = trim($m_d{$val}[2]);
$match_type = trim($m_d{$val}[3]);
$match_text = trim($m_d{$val}[4]);
#::: Match type update
if ($match_type eq "contains") {
if (trim(get_mysql_result($query_check)) =~ /$match_text/i) {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
else {
print "[Database] Has update [" . $val . "]:[" . $file_name . "]\n";
}
print_match_debug();
print_break();
}
if ($match_type eq "missing") {
if (get_mysql_result($query_check) =~ /$match_text/i) {
print "[Database] Has update [" . $val . "]:[" . $file_name . "]\n";
}
else {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
print_match_debug();
print_break();
}
if ($match_type eq "empty") {
if (get_mysql_result($query_check) eq "") {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
else {
print "[Database] Has update [" . $val . "]:[" . $file_name . "' \n";
}
print_match_debug();
print_break();
}
if ($match_type eq "not_empty") {
if (get_mysql_result($query_check) ne "") {
print "[Database] Applying update [" . $val . "]:[" . $file_name . "]\n";
print get_mysql_result_from_file("db_update/$file_name");
}
else {
print "[Database] Has update [" . $val . "]:[" . $file_name . "]\n";
}
print_match_debug();
print_break();
}
if ($bots_db_management == 1) {
print get_mysql_result("UPDATE db_version SET bots_version = $val WHERE bots_version < $val");
if ($val == 9000) {
modify_db_for_bots();
}
}
else {
print get_mysql_result("UPDATE db_version SET version = $val WHERE version < $val");
if ($val == 9138) {
fix_quest_factions();
}
}
}
if ($bots_db_management == 1) {
print "[Database] Bots database update cycle complete at version [" . get_bots_db_version() . "]\n";
}
else {
print "[Database] Mainstream database update cycle complete at version [" . get_main_db_version() . "]\n";
} }
} }
sub fetch_missing_db_update { sub fetch_missing_db_update {
$db_update = $_[0]; $db_update = $_[0];
$update_file = $_[1]; $update_file = $_[1];
if ($db_update >= 9000) {
if ($bots_db_management == 1) { if ($bots_db_management == 1) {
if ($db_update >= 9000) {
get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/required/" . $update_file, "db_update/" . $update_file . ""); get_remote_file($eqemu_repository_request_url . "utils/sql/git/bots/required/" . $update_file, "db_update/" . $update_file . "");
} }
else { }
else {
if ($db_update >= 9000) {
get_remote_file($eqemu_repository_request_url . "utils/sql/git/required/" . $update_file, "db_update/" . $update_file . ""); get_remote_file($eqemu_repository_request_url . "utils/sql/git/required/" . $update_file, "db_update/" . $update_file . "");
} }
} elsif ($db_update >= 5000 && $db_update <= 9000) {
elsif ($db_update >= 5000 && $db_update <= 9000) { get_remote_file($eqemu_repository_request_url . "utils/sql/svn/" . $update_file, "db_update/" . $update_file . "");
get_remote_file($eqemu_repository_request_url . "utils/sql/svn/" . $update_file, "db_update/" . $update_file . ""); }
} }
} }

View File

@ -802,9 +802,9 @@ void ConsoleIpLookup(
const std::vector<std::string> &args const std::vector<std::string> &args
) )
{ {
if (args.size() > 0) { if (!args.empty()) {
WorldConsoleTCPConnection console_connection(connection); WorldConsoleTCPConnection console_connection(connection);
client_list.SendCLEList(connection->Admin(), 0, &console_connection, args[0].c_str()); client_list.SendCLEList(connection->Admin(), nullptr, &console_connection, args[0].c_str());
} }
} }
@ -855,6 +855,34 @@ void ConsoleReloadWorld(
safe_delete(pack); safe_delete(pack);
} }
/**
* @param connection
* @param command
* @param args
*/
void ConsoleReloadZoneQuests(
EQ::Net::ConsoleServerConnection *connection,
const std::string &command,
const std::vector<std::string> &args
)
{
if (args.empty()) {
connection->SendLine("[zone_short_name] required as argument");
return;
}
std::string zone_short_name = args[0];
connection->SendLine(fmt::format("Reloading Zone [{}]...", zone_short_name));
auto pack = new ServerPacket(ServerOP_HotReloadQuests, sizeof(HotReloadQuestsStruct));
auto *hot_reload_quests = (HotReloadQuestsStruct *) pack->pBuffer;
strn0cpy(hot_reload_quests->zone_short_name, (char *) zone_short_name.c_str(), 200);
zoneserver_list.SendPacket(pack);
safe_delete(pack);
}
/** /**
* @param connection * @param connection
* @param command * @param command
@ -892,18 +920,19 @@ void RegisterConsoleFunctions(std::unique_ptr<EQ::Net::ConsoleServer>& console)
console->RegisterCall("md5", 50, "md5", std::bind(ConsoleMd5, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("md5", 50, "md5", std::bind(ConsoleMd5, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("ooc", 50, "ooc [message]", std::bind(ConsoleOOC, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("ooc", 50, "ooc [message]", std::bind(ConsoleOOC, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("reloadworld", 200, "reloadworld", std::bind(ConsoleReloadWorld, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("reloadworld", 200, "reloadworld", std::bind(ConsoleReloadWorld, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("setpass", 200, "setpass [accountname] [newpass]", std::bind(ConsoleSetPass, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("reloadzonequests", 200, "reloadzonequests [zone_short_name]", std::bind(ConsoleReloadZoneQuests, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("setpass", 200, "setpass [account_name] [new_password]", std::bind(ConsoleSetPass, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("signalcharbyname", 50, "signalcharbyname charname ID", std::bind(ConsoleSignalCharByName, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("signalcharbyname", 50, "signalcharbyname charname ID", std::bind(ConsoleSignalCharByName, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("tell", 50, "tell [name] [message]", std::bind(ConsoleTell, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("tell", 50, "tell [name] [message]", std::bind(ConsoleTell, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("unlock", 150, "unlock", std::bind(ConsoleUnlock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("unlock", 150, "unlock", std::bind(ConsoleUnlock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("uptime", 50, "uptime [zoneID#]", std::bind(ConsoleUptime, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("uptime", 50, "uptime [zone_server_id]", std::bind(ConsoleUptime, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("version", 50, "version", std::bind(ConsoleVersion, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("version", 50, "version", std::bind(ConsoleVersion, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("who", 50, "who", std::bind(ConsoleWho, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("who", 50, "who", std::bind(ConsoleWho, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("whoami", 50, "whoami", std::bind(ConsoleWhoami, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("whoami", 50, "whoami", std::bind(ConsoleWhoami, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("worldshutdown", 200, "worldshutdown", std::bind(ConsoleWorldShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("worldshutdown", 200, "worldshutdown", std::bind(ConsoleWorldShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonebootup", 150, "zonebootup [ZoneServerID] [zonename]", std::bind(ConsoleZoneBootup, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("zonebootup", 150, "zonebootup [zone_server_id] [zone_short_name]", std::bind(ConsoleZoneBootup, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonelock", 150, "zonelock [list|lock|unlock] [zonename]", std::bind(ConsoleZoneLock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("zonelock", 150, "zonelock [list|lock|unlock] [zone_short_name]", std::bind(ConsoleZoneLock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zoneshutdown", 150, "zoneshutdown [zonename or ZoneServerID]", std::bind(ConsoleZoneShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("zoneshutdown", 150, "zoneshutdown [zone_short_name or zone_server_id]", std::bind(ConsoleZoneShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("zonestatus", 50, "zonestatus", std::bind(ConsoleZoneStatus, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));console->RegisterCall("ping", 50, "ping", std::bind(ConsoleNull, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("zonestatus", 50, "zonestatus", std::bind(ConsoleZoneStatus, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));console->RegisterCall("ping", 50, "ping", std::bind(ConsoleNull, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("quit", 50, "quit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("quit", 50, "quit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("exit", 50, "exit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("exit", 50, "exit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

View File

@ -141,6 +141,7 @@ SET(zone_sources
zone.cpp zone.cpp
zone_config.cpp zone_config.cpp
zonedb.cpp zonedb.cpp
zone_reload.cpp
zoning.cpp zoning.cpp
) )
@ -247,7 +248,8 @@ SET(zone_headers
zone.h zone.h
zone_config.h zone_config.h
zonedb.h zonedb.h
zonedump.h) zonedump.h
zone_reload.h )
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers}) ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})

View File

@ -6421,32 +6421,52 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
bool taunt_time = taunt_timer.Check(); bool taunt_time = taunt_timer.Check();
bool ca_time = classattack_timer.Check(false); bool ca_time = classattack_timer.Check(false);
bool ma_time = monkattack_timer.Check(false);
bool ka_time = knightattack_timer.Check(false); bool ka_time = knightattack_timer.Check(false);
if((taunt_time || ca_time || ka_time) && !IsAttackAllowed(target))
if (taunt_time) {
// Bots without this skill shouldn't be 'checking' on this timer..let's just disable it and avoid the extra IsAttackAllowed() checks
// Note: this is done here instead of NPC::ctor() because taunt skill can be acquired during level ups (the timer is re-enabled in CalcBotStats())
if (!GetSkill(EQEmu::skills::SkillTaunt)) {
taunt_timer.Disable();
return;
}
if (!IsAttackAllowed(target)) {
return;
}
}
if ((ca_time || ma_time || ka_time) && !IsAttackAllowed(target)) {
return; return;
}
if(ka_time){ if(ka_time){
int knightreuse = 1000;
switch(GetClass()){ switch(GetClass()){
case SHADOWKNIGHT: case SHADOWKNIGHT: {
case SHADOWKNIGHTGM: {
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID()); CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
knightreuse = (HarmTouchReuseTime * 1000); knightattack_timer.Start(HarmTouchReuseTime * 1000);
break; break;
} }
case PALADIN: case PALADIN: {
case PALADINGM: {
if(GetHPRatio() < 20) { if(GetHPRatio() < 20) {
CastSpell(SPELL_LAY_ON_HANDS, GetID()); CastSpell(SPELL_LAY_ON_HANDS, GetID());
knightreuse = (LayOnHandsReuseTime * 1000); knightattack_timer.Start(LayOnHandsReuseTime * 1000);
}
else {
knightattack_timer.Start(2000);
} }
else
knightreuse = 2000;
break; break;
} }
default: {
break;
}
} }
knightattack_timer.Start(knightreuse);
} }
if(taunting && target && target->IsNPC() && taunt_time) { if(taunting && target && target->IsNPC() && taunt_time) {
@ -6457,8 +6477,66 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
} }
} }
if(!ca_time) if (ma_time) {
switch (GetClass()) {
case MONK: {
int reuse = (MonkSpecialAttack(target, EQEmu::skills::SkillTigerClaw) - 1);
// Live AA - Technique of Master Wu
int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if (wuchance) {
const int MonkSPA[5] = {
EQEmu::skills::SkillFlyingKick,
EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike,
EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick
};
int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) {
if (zone->random.Roll(wuchance)) {
++extra;
}
else {
break;
}
wuchance /= 4;
}
Mob* bo = GetBotOwner();
if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) {
bo->Message(
GENERIC_EMOTE,
"The spirit of Master Wu fills %s! %s gains %d additional attack(s).",
GetCleanName(),
GetCleanName(),
extra
);
}
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQEmu::skills::SkillTigerClaw));
--extra;
}
}
float HasteModifier = (GetHaste() * 0.01f);
monkattack_timer.Start((reuse * 1000) / HasteModifier);
break;
}
default:
break;;
}
}
if (!ca_time) {
return; return;
}
float HasteModifier = (GetHaste() * 0.01f); float HasteModifier = (GetHaste() * 0.01f);
uint16 skill_to_use = -1; uint16 skill_to_use = -1;
@ -6493,18 +6571,22 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
} }
break; break;
case MONK: case MONK:
if(GetLevel() >= 30) if (GetLevel() >= 30) {
skill_to_use = EQEmu::skills::SkillFlyingKick; skill_to_use = EQEmu::skills::SkillFlyingKick;
else if(GetLevel() >= 25) }
else if (GetLevel() >= 25) {
skill_to_use = EQEmu::skills::SkillDragonPunch; skill_to_use = EQEmu::skills::SkillDragonPunch;
else if(GetLevel() >= 20) }
else if (GetLevel() >= 20) {
skill_to_use = EQEmu::skills::SkillEagleStrike; skill_to_use = EQEmu::skills::SkillEagleStrike;
else if(GetLevel() >= 10) }
skill_to_use = EQEmu::skills::SkillTigerClaw; else if (GetLevel() >= 5) {
else if(GetLevel() >= 5)
skill_to_use = EQEmu::skills::SkillRoundKick; skill_to_use = EQEmu::skills::SkillRoundKick;
else }
else {
skill_to_use = EQEmu::skills::SkillKick; skill_to_use = EQEmu::skills::SkillKick;
}
break; break;
case ROGUE: case ROGUE:
skill_to_use = EQEmu::skills::SkillBackstab; skill_to_use = EQEmu::skills::SkillBackstab;
@ -6555,19 +6637,54 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
} }
} }
if (skill_to_use == EQEmu::skills::SkillFlyingKick || skill_to_use == EQEmu::skills::SkillDragonPunch || skill_to_use == EQEmu::skills::SkillEagleStrike || skill_to_use == EQEmu::skills::SkillTigerClaw || skill_to_use == EQEmu::skills::SkillRoundKick) { if (
skill_to_use == EQEmu::skills::SkillFlyingKick ||
skill_to_use == EQEmu::skills::SkillDragonPunch ||
skill_to_use == EQEmu::skills::SkillEagleStrike ||
skill_to_use == EQEmu::skills::SkillRoundKick
) {
reuse = (MonkSpecialAttack(target, skill_to_use) - 1); reuse = (MonkSpecialAttack(target, skill_to_use) - 1);
MonkSpecialAttack(target, skill_to_use);
uint32 bDoubleSpecialAttack = (itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack); // Live AA - Technique of Master Wu
if(bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0, 100))) { int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
int MonkSPA[5] = { EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillRoundKick };
MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]);
int TripleChance = 25;
if (bDoubleSpecialAttack > 100)
TripleChance += (TripleChance * (100 - bDoubleSpecialAttack) / 100);
if(TripleChance > zone->random.Int(0,100)) if (wuchance) {
MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]); const int MonkSPA[5] = {
EQEmu::skills::SkillFlyingKick,
EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike,
EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick
};
int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) {
if (zone->random.Roll(wuchance)) {
++extra;
}
else {
break;
}
wuchance /= 4;
}
Mob* bo = GetBotOwner();
if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) {
bo->Message(
GENERIC_EMOTE,
"The spirit of Master Wu fills %s! %s gains %d additional attack(s).",
GetCleanName(),
GetCleanName(),
extra
);
}
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : skill_to_use));
--extra;
}
} }
reuse *= 1000; reuse *= 1000;
@ -8966,6 +9083,12 @@ void Bot::CalcBotStats(bool showtext) {
skills[sindex] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)sindex, GetLevel()); skills[sindex] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)sindex, GetLevel());
} }
taunt_timer.Start(1000);
if (GetClass() == MONK && GetLevel() >= 10) {
monkattack_timer.Start(1000);
}
LoadAAs(); LoadAAs();
GenerateSpecialAttacks(); GenerateSpecialAttacks();

View File

@ -3928,6 +3928,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<td><c \"#00CCCC\">null</td>" "<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>" "<td><c \"#888888\">(toggles)</td>"
"</tr>" "</tr>"
"<tr>"
"<td><c \"#CCCCCC\">monkwumessage</td>"
"<td><c \"#00CC00\">enable <c \"#CCCCCC\">| <c \"#00CC00\">disable</td>"
"<td><c \"#888888\">displays monk wu trigger messages</td>"
"</tr>"
"<tr>"
"<td></td>"
"<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>"
"</tr>"
"<tr>" "<tr>"
"<td><c \"#CCCCCC\">current</td>" "<td><c \"#CCCCCC\">current</td>"
"<td></td>" "<td></td>"
@ -4103,6 +4113,22 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
c->Message(m_action, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled")); c->Message(m_action, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled"));
} }
else if (!owner_option.compare("monkwumessage")) {
if (!argument.compare("enable")) {
c->SetBotOption(Client::booMonkWuMessage, true);
}
else if (!argument.compare("disable")) {
c->SetBotOption(Client::booMonkWuMessage, false);
}
else {
c->SetBotOption(Client::booMonkWuMessage, !c->GetBotOption(Client::booMonkWuMessage));
}
database.botdb.SaveOwnerOption(c->CharacterID(), Client::booMonkWuMessage, c->GetBotOption(Client::booMonkWuMessage));
c->Message(m_action, "Bot 'monk wu message' is now %s.", (c->GetBotOption(Client::booMonkWuMessage) == true ? "enabled" : "disabled"));
}
else if (!owner_option.compare("current")) { else if (!owner_option.compare("current")) {
std::string window_title = "Current Bot Owner Options Settings"; std::string window_title = "Current Bot Owner Options Settings";
@ -4112,13 +4138,14 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<td><c \"#FFFFFF\">Option<br>------</td>" "<td><c \"#FFFFFF\">Option<br>------</td>"
"<td><c \"#00FF00\">Argument<br>-------</td>" "<td><c \"#00FF00\">Argument<br>-------</td>"
"</tr>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">deathmarquee</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">deathmarquee</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">statsupdate</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">statsupdate</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">altcombat</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">altcombat</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">autodefend</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">autodefend</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">buffcounter</td>" "<td><c \"#00CC00\">{}</td>" "</tr>" "<tr>" "<td><c \"#CCCCCC\">buffcounter</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">monkwumessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"</table>", "</table>",
(c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"), (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"), (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"),
@ -4126,7 +4153,8 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
(c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"), (c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"),
(RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"), (RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"),
(RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"), (RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"),
(c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled") (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled")
); );
c->SendPopupToClient(window_title.c_str(), window_text.c_str()); c->SendPopupToClient(window_title.c_str(), window_text.c_str());

View File

@ -2257,6 +2257,7 @@ bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool
case Client::booAltCombat: case Client::booAltCombat:
case Client::booAutoDefend: case Client::booAutoDefend:
case Client::booBuffCounter: case Client::booBuffCounter:
case Client::booMonkWuMessage:
{ {
query = fmt::format( query = fmt::format(
"REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')", "REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')",

View File

@ -358,6 +358,7 @@ Client::Client(EQStreamInterface* ieqs)
bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat); bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat);
bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend); bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend);
bot_owner_options[booBuffCounter] = false; bot_owner_options[booBuffCounter] = false;
bot_owner_options[booMonkWuMessage] = false;
SetBotPulling(false); SetBotPulling(false);
SetBotPrecombat(false); SetBotPrecombat(false);

View File

@ -1646,6 +1646,7 @@ public:
booAltCombat, booAltCombat,
booAutoDefend, booAutoDefend,
booBuffCounter, booBuffCounter,
booMonkWuMessage,
_booCount _booCount
}; };

View File

@ -547,6 +547,12 @@ void Lua_NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_del
self->SetSimpleRoamBox(box_size, move_distance, move_delay); self->SetSimpleRoamBox(box_size, move_distance, move_delay);
} }
void Lua_NPC::RecalculateSkills()
{
Lua_Safe_Call_Void();
self->RecalculateSkills();
}
luabind::scope lua_register_npc() { luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC") return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
@ -657,7 +663,8 @@ luabind::scope lua_register_npc() {
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop) .def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
.def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop) .def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop)
.def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC) .def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC)
.def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating); .def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating)
.def("RecalculateSkills", (void(Lua_NPC::*)(void))&Lua_NPC::RecalculateSkills);
} }
#endif #endif

View File

@ -134,6 +134,7 @@ public:
void SetSimpleRoamBox(float box_size); void SetSimpleRoamBox(float box_size);
void SetSimpleRoamBox(float box_size, float move_distance); void SetSimpleRoamBox(float box_size, float move_distance);
void SetSimpleRoamBox(float box_size, float move_distance, int move_delay); void SetSimpleRoamBox(float box_size, float move_distance, int move_delay);
void RecalculateSkills();
}; };
#endif #endif

View File

@ -118,6 +118,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
attacked_timer(CombatEventTimer_expire), attacked_timer(CombatEventTimer_expire),
swarm_timer(100), swarm_timer(100),
classattack_timer(1000), classattack_timer(1000),
monkattack_timer(1000),
knightattack_timer(1000), knightattack_timer(1000),
assist_timer(AIassistcheck_delay), assist_timer(AIassistcheck_delay),
qglobal_purge_timer(30000), qglobal_purge_timer(30000),
@ -307,7 +308,15 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
// some overrides -- really we need to be able to set skills for mobs in the DB // some overrides -- really we need to be able to set skills for mobs in the DB
// There are some known low level SHM/BST pets that do not follow this, which supports // There are some known low level SHM/BST pets that do not follow this, which supports
// the theory of needing to be able to set skills for each mob separately // the theory of needing to be able to set skills for each mob separately
if (!IsBot()) { if (IsBot()) {
if (GetClass() != PALADIN && GetClass() != SHADOWKNIGHT) {
knightattack_timer.Disable();
}
else if (GetClass() != MONK || GetLevel() < 10) {
monkattack_timer.Disable();
}
}
else {
if (moblevel > 50) { if (moblevel > 50) {
skills[EQEmu::skills::SkillDoubleAttack] = 250; skills[EQEmu::skills::SkillDoubleAttack] = 250;
skills[EQEmu::skills::SkillDualWield] = 250; skills[EQEmu::skills::SkillDualWield] = 250;
@ -3209,3 +3218,28 @@ void NPC::AIYellForHelp(Mob *sender, Mob *attacker)
} }
} }
void NPC::RecalculateSkills()
{
int r;
for (r = 0; r <= EQEmu::skills::HIGHEST_SKILL; r++) {
skills[r] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)r, level);
}
// some overrides -- really we need to be able to set skills for mobs in the DB
// There are some known low level SHM/BST pets that do not follow this, which supports
// the theory of needing to be able to set skills for each mob separately
if (!IsBot()) {
if (level > 50) {
skills[EQEmu::skills::SkillDoubleAttack] = 250;
skills[EQEmu::skills::SkillDualWield] = 250;
}
else if (level > 3) {
skills[EQEmu::skills::SkillDoubleAttack] = level * 5;
skills[EQEmu::skills::SkillDualWield] = skills[EQEmu::skills::SkillDoubleAttack];
}
else {
skills[EQEmu::skills::SkillDoubleAttack] = level * 5;
}
}
}

View File

@ -476,6 +476,8 @@ public:
inline bool IsSkipAutoScale() const { return skip_auto_scale; } inline bool IsSkipAutoScale() const { return skip_auto_scale; }
void RecalculateSkills();
protected: protected:
const NPCType* NPCTypedata; const NPCType* NPCTypedata;
@ -497,6 +499,7 @@ protected:
Timer attacked_timer; //running while we are being attacked (damaged) Timer attacked_timer; //running while we are being attacked (damaged)
Timer swarm_timer; Timer swarm_timer;
Timer monkattack_timer; //additional timer for tiger claw usage
Timer classattack_timer; Timer classattack_timer;
Timer knightattack_timer; Timer knightattack_timer;
Timer assist_timer; //ask for help from nearby mobs Timer assist_timer; //ask for help from nearby mobs

View File

@ -179,6 +179,11 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
if (path[npoly - 1] != end_ref) { if (path[npoly - 1] != end_ref) {
m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0);
partial = true; partial = true;
auto dist = DistanceSquared(epos, current_location);
if (dist < 10000.0f) {
stuck = true;
}
} }
int n_straight_polys; int n_straight_polys;

View File

@ -2451,6 +2451,28 @@ XS(XS_NPC_SetSimpleRoamBox) {
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }
XS(XS_NPC_RecalculateSkills); /* prototype to pass -Wmissing-prototypes */
XS(XS_NPC_RecalculateSkills) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: NPC::RecalculateSkills(THIS)");
{
NPC *THIS;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV *) SvRV(ST(0)));
THIS = INT2PTR(NPC *, tmp);
} else
Perl_croak(aTHX_ "THIS is not of type NPC");
if (THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->RecalculateSkills();
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
#endif #endif
@ -2565,6 +2587,7 @@ XS(boot_NPC) {
newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$"); newXSproto(strcpy(buf, "ClearLastName"), XS_NPC_ClearLastName, file, "$");
newXSproto(strcpy(buf, "GetCombatState"), XS_NPC_GetCombatState, file, "$"); newXSproto(strcpy(buf, "GetCombatState"), XS_NPC_GetCombatState, file, "$");
newXSproto(strcpy(buf, "SetSimpleRoamBox"), XS_NPC_SetSimpleRoamBox, file, "$$;$$"); newXSproto(strcpy(buf, "SetSimpleRoamBox"), XS_NPC_SetSimpleRoamBox, file, "$$;$$");
newXSproto(strcpy(buf, "RecalculateSkills"), XS_NPC_RecalculateSkills, file, "$");
XSRETURN_YES; XSRETURN_YES;
} }

View File

@ -379,31 +379,35 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk)
ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction;
// Live AA - Technique of Master Wu // Live AA - Technique of Master Wu
int wuchance = int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if (wuchance) { if (wuchance) {
const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, const int MonkSPA[5] = {
EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillFlyingKick,
EQEmu::skills::SkillRoundKick}; EQEmu::skills::SkillDragonPunch,
EQEmu::skills::SkillEagleStrike,
EQEmu::skills::SkillTigerClaw,
EQEmu::skills::SkillRoundKick
};
int extra = 0; int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4) // always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) { while (wuchance > 0) {
if (zone->random.Roll(wuchance)) if (zone->random.Roll(wuchance)) {
extra++; ++extra;
else }
else {
break; break;
}
wuchance /= 4; wuchance /= 4;
} }
// They didn't add a string ID for this. // They didn't add a string ID for this.
std::string msg = StringFormat( std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
"The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
// live uses 400 here -- not sure if it's the best for all clients though // live uses 400 here -- not sure if it's the best for all clients though
SendColoredText(400, msg); SendColoredText(400, msg);
auto classic = RuleB(Combat, ClassicMasterWu); auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) { while (extra) {
MonkSpecialAttack(GetTarget(), MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill));
classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); --extra;
extra--;
} }
} }
@ -1294,7 +1298,6 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
//consume ammo //consume ammo
DeleteItemInInventory(ammo_slot, 1, true); DeleteItemInInventory(ammo_slot, 1, true);
CheckIncreaseSkill(EQEmu::skills::SkillThrowing, GetTarget());
CommonBreakInvisibleFromCombat(); CommonBreakInvisibleFromCombat();
} }
@ -1408,6 +1411,9 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon
else else
TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, false, EQEmu::invslot::slotRange); TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, false, EQEmu::invslot::slotRange);
} }
if (IsClient()) {
CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillThrowing, GetTarget());
}
} }
void Mob::SendItemAnimation(Mob *to, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, float velocity) { void Mob::SendItemAnimation(Mob *to, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, float velocity) {

View File

@ -1230,7 +1230,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
// handle the components for traditional casters // handle the components for traditional casters
else { else {
if (!RuleB(Character, PetsUseReagents) && IsEffectInSpell(spell_id, SE_SummonPet)) { if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet))) {
//bypass reagent cost //bypass reagent cost
} }
else if(c->GetInv().HasItem(component, component_count, invWhereWorn|invWherePersonal) == -1) // item not found else if(c->GetInv().HasItem(component, component_count, invWhereWorn|invWherePersonal) == -1) // item not found
@ -1263,7 +1263,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
return; return;
} }
} }
else if (!RuleB(Character, PetsUseReagents) && IsEffectInSpell(spell_id, SE_SummonPet)) { else if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet))) {
//bypass reagent cost //bypass reagent cost
} }
else if (!bard_song_mode) else if (!bard_song_mode)

View File

@ -51,6 +51,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "worldserver.h" #include "worldserver.h"
#include "zone.h" #include "zone.h"
#include "zone_config.h" #include "zone_config.h"
#include "zone_reload.h"
extern EntityList entity_list; extern EntityList entity_list;
@ -1945,15 +1946,40 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
iter++; iter++;
} }
} }
case ServerOP_ReloadWorld: case ServerOP_ReloadWorld:
{ {
ReloadWorld_Struct* RW = (ReloadWorld_Struct*)pack->pBuffer; auto* reload_world = (ReloadWorld_Struct*)pack->pBuffer;
if (zone) { if (zone) {
zone->ReloadWorld(RW->Option); zone->ReloadWorld(reload_world->Option);
} }
break; break;
} }
case ServerOP_HotReloadQuests:
{
if (!zone) {
break;
}
auto *hot_reload_quests = (HotReloadQuestsStruct *) pack->pBuffer;
LogHotReloadDetail(
"Receiving request [HotReloadQuests] | request_zone [{}] current_zone [{}]",
hot_reload_quests->zone_short_name,
zone->GetShortName()
);
std::string request_zone_short_name = hot_reload_quests->zone_short_name;
std::string local_zone_short_name = zone->GetShortName();
if (request_zone_short_name == local_zone_short_name || request_zone_short_name == "all"){
zone->SetQuestHotReloadQueued(true);
}
break;
}
case ServerOP_ChangeSharedMem: case ServerOP_ChangeSharedMem:
{ {
std::string hotfix_name = std::string((char*)pack->pBuffer); std::string hotfix_name = std::string((char*)pack->pBuffer);

View File

@ -55,6 +55,7 @@
#include "mob_movement_manager.h" #include "mob_movement_manager.h"
#include "npc_scale_manager.h" #include "npc_scale_manager.h"
#include "../common/data_verification.h" #include "../common/data_verification.h"
#include "zone_reload.h"
#include <time.h> #include <time.h>
#include <ctime> #include <ctime>
@ -771,6 +772,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
autoshutdown_timer((RuleI(Zone, AutoShutdownDelay))), autoshutdown_timer((RuleI(Zone, AutoShutdownDelay))),
clientauth_timer(AUTHENTICATION_TIMEOUT * 1000), clientauth_timer(AUTHENTICATION_TIMEOUT * 1000),
spawn2_timer(1000), spawn2_timer(1000),
hot_reload_timer(1000),
qglobal_purge_timer(30000), qglobal_purge_timer(30000),
hotzone_timer(120000), hotzone_timer(120000),
m_SafePoint(0.0f,0.0f,0.0f), m_SafePoint(0.0f,0.0f,0.0f),
@ -874,6 +876,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
mMovementManager = &MobMovementManager::Get(); mMovementManager = &MobMovementManager::Get();
SetNpcPositionUpdateDistance(0); SetNpcPositionUpdateDistance(0);
SetQuestHotReloadQueued(false);
} }
Zone::~Zone() { Zone::~Zone() {
@ -1231,6 +1234,27 @@ bool Zone::Process() {
} }
} }
if (hot_reload_timer.Check() && IsQuestHotReloadQueued()) {
LogHotReloadDetail("Hot reload timer check...");
bool perform_reload = true;
if (RuleB(HotReload, QuestsRepopWhenPlayersNotInCombat)) {
for (auto &it : entity_list.GetClientList()) {
auto client = it.second;
if (client->GetAggroCount() > 0) {
perform_reload = false;
break;
}
}
}
if (perform_reload) {
ZoneReload::HotReloadQuests();
}
}
if(initgrids_timer.Check()) { if(initgrids_timer.Check()) {
//delayed grid loading stuff. //delayed grid loading stuff.
initgrids_timer.Disable(); initgrids_timer.Disable();
@ -1540,7 +1564,6 @@ void Zone::RepopClose(const glm::vec4& client_position, uint32 repop_distance)
void Zone::Repop(uint32 delay) void Zone::Repop(uint32 delay)
{ {
if (!Depop()) { if (!Depop()) {
return; return;
} }
@ -2422,3 +2445,13 @@ void Zone::CalculateNpcUpdateDistanceSpread()
combined_spread combined_spread
); );
} }
bool Zone::IsQuestHotReloadQueued() const
{
return quest_hot_reload_queued;
}
void Zone::SetQuestHotReloadQueued(bool in_quest_hot_reload_queued)
{
quest_hot_reload_queued = in_quest_hot_reload_queued;
}

View File

@ -204,6 +204,7 @@ public:
time_t weather_timer; time_t weather_timer;
Timer spawn2_timer; Timer spawn2_timer;
Timer hot_reload_timer;
uint8 weather_intensity; uint8 weather_intensity;
uint8 zone_weather; uint8 zone_weather;
@ -270,6 +271,9 @@ public:
void UpdateQGlobal(uint32 qid, QGlobal newGlobal); void UpdateQGlobal(uint32 qid, QGlobal newGlobal);
void weatherSend(Client *client = nullptr); void weatherSend(Client *client = nullptr);
bool IsQuestHotReloadQueued() const;
void SetQuestHotReloadQueued(bool in_quest_hot_reload_queued);
WaterMap *watermap; WaterMap *watermap;
ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f); ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f);
ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f); ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f);
@ -340,6 +344,9 @@ private:
bool m_ucss_available; bool m_ucss_available;
bool staticzone; bool staticzone;
bool zone_has_current_time; bool zone_has_current_time;
bool quest_hot_reload_queued;
private:
double max_movement_update_range; double max_movement_update_range;
char *long_name; char *long_name;
char *map_name; char *map_name;

46
zone/zone_reload.cpp Normal file
View File

@ -0,0 +1,46 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "zone_reload.h"
#include "quest_parser_collection.h"
void ZoneReload::HotReloadQuests()
{
BenchTimer timer;
entity_list.ClearAreas();
parse->ReloadQuests(RuleB(HotReload, QuestsResetTimersWithReload));
if (RuleB(HotReload, QuestsRepopWithReload)) {
zone->Repop(0);
}
zone->SetQuestHotReloadQueued(false);
LogHotReload(
"[Quests] Reloading [{}] repop [{}] reset_timers [{}] repop_when_not_in_combat [{}] Time [{:.4f}]",
zone->GetShortName(),
(RuleB(HotReload, QuestsRepopWithReload) ? "true" : "false"),
(RuleB(HotReload, QuestsResetTimersWithReload) ? "true" : "false"),
(RuleB(HotReload, QuestsRepopWhenPlayersNotInCombat) ? "true" : "false"),
timer.elapsed()
);
}

31
zone/zone_reload.h Normal file
View File

@ -0,0 +1,31 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EQEMU_ZONE_RELOAD_H
#define EQEMU_ZONE_RELOAD_H
class ZoneReload {
public:
static void HotReloadQuests();
};
#endif //EQEMU_ZONE_RELOAD_H