mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-31 13:16:39 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd01ed87df | |||
| 8a79994b4e | |||
| 93a661a434 | |||
| 3e8796bb4c | |||
| c23fc4ade7 | |||
| 3b628c1ace |
Vendored
+2
-1
@@ -9,7 +9,8 @@
|
|||||||
"defines": [],
|
"defines": [],
|
||||||
"compilerPath": "/usr/bin/gcc",
|
"compilerPath": "/usr/bin/gcc",
|
||||||
"cStandard": "c11",
|
"cStandard": "c11",
|
||||||
"cppStandard": "c++17"
|
"cppStandard": "c++17",
|
||||||
|
"configurationProvider": "ms-vscode.cmake-tools"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": 4
|
"version": 4
|
||||||
|
|||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"chrono": "cpp",
|
||||||
|
"xutility": "cpp",
|
||||||
|
"iterator": "cpp",
|
||||||
|
"*.ipp": "cpp"
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+14
-14
@@ -6,7 +6,7 @@
|
|||||||
{
|
{
|
||||||
"label": "make",
|
"label": "make",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd bin && make",
|
"command": "mkdir -p build && cd build && make",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{
|
{
|
||||||
"label": "make clean",
|
"label": "make clean",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd bin && make clean",
|
"command": "mkdir -p build && cd build && make clean",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
{
|
{
|
||||||
"label": "cmake",
|
"label": "cmake",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "mkdir -p bin && cd bin && rm CMakeCache.txt && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' ..",
|
"command": "mkdir -p build && cd build && rm CMakeCache.txt && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' ..",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
{
|
{
|
||||||
"label": "download maps",
|
"label": "download maps",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "mkdir -p bin && cd 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",
|
"command": "mkdir -p build && 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": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
{
|
{
|
||||||
"label": "download quests",
|
"label": "download quests",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "mkdir -p bin && cd bin && cd server && git -C ./quests pull 2> /dev/null || git clone https://github.com/ProjectEQ/projecteqquests.git quests",
|
"command": "mkdir -p build && cd build/bin && cd server && git -C ./quests pull 2> /dev/null || git clone https://github.com/ProjectEQ/projecteqquests.git quests",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
{
|
{
|
||||||
"label": "download eqemu_config",
|
"label": "download eqemu_config",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "mkdir -p bin && cd bin && wget --no-check-certificate https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/eqemu_config_docker.json -O eqemu_config.json",
|
"command": "mkdir -p build && cd build/bin && wget --no-check-certificate https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/eqemu_config_docker.json -O eqemu_config.json",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
{
|
{
|
||||||
"label": "rebuild database (mariadb must be started)",
|
"label": "rebuild database (mariadb must be started)",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "mkdir -p bin && cd bin && docker run -i --rm --privileged -v ${HOST_PROJECT_PATH}/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'",
|
"command": "mkdir -p build && 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 assets && ./eqemu_server.pl lua_modules && ./eqemu_server.pl opcodes && ./eqemu_server.pl linux_login_server_setup'",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
{
|
{
|
||||||
"label": "zone 7000",
|
"label": "zone 7000",
|
||||||
"type": "shell",
|
"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}/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",
|
"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": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
{
|
{
|
||||||
"label": "zone 7001",
|
"label": "zone 7001",
|
||||||
"type": "shell",
|
"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}/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",
|
"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": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
{
|
{
|
||||||
"label": "loginserver",
|
"label": "loginserver",
|
||||||
"type": "shell",
|
"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}/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",
|
"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": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
{
|
{
|
||||||
"label": "shared_memory, world",
|
"label": "shared_memory, world",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "docker stop sharedmemory | true && docker stop world | true && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/bin:/src --network=eqemu --name sharedmemory eqemu/server:0.0.3 ./shared_memory && docker run --rm -v ${HOST_PROJECT_PATH}/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",
|
"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": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
{
|
{
|
||||||
"label": "queryserv",
|
"label": "queryserv",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "docker stop queryserv | true && docker run --rm -v ${HOST_PROJECT_PATH}/bin:/src --ulimit core=10000000 -e LD_LIBRARY_PATH=/src/ --network=eqemu --name queryserv eqemu/server:0.0.3 gdb -ex run ./queryserv",
|
"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": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
{
|
{
|
||||||
"label": "mariadb",
|
"label": "mariadb",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "docker stop mariadb | true && cd bin && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/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",
|
"command": "docker stop mariadb | true && cd 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": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
{
|
{
|
||||||
"label": "ucs",
|
"label": "ucs",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "docker stop ucs | true && cd bin && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/bin:/src -p 7778:7778 --name ucs --network=eqemu eqemu/server:0.0.3 gdb -ex run ./ucs",
|
"command": "docker stop ucs | true && cd bin && docker network create eqemu | true && docker run --rm -v ${HOST_PROJECT_PATH}/build/bin:/src -p 7778:7778 --name ucs --network=eqemu eqemu/server:0.0.3 gdb -ex run ./ucs",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
|||||||
@@ -188,6 +188,9 @@ RULE_INT(Mercs, AggroRadiusPuller, 25, "Determines the distance from which a mer
|
|||||||
RULE_INT(Mercs, ResurrectRadius, 50, "Determines the distance from which a healer merc will attempt to resurrect a group member's corpse")
|
RULE_INT(Mercs, ResurrectRadius, 50, "Determines the distance from which a healer merc will attempt to resurrect a group member's corpse")
|
||||||
RULE_INT(Mercs, ScaleRate, 100, "Merc scale factor")
|
RULE_INT(Mercs, ScaleRate, 100, "Merc scale factor")
|
||||||
RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat")
|
RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat")
|
||||||
|
RULE_BOOL(Mercs, IsMercsElixirEnabled, false, "Override AI with elixir logic")
|
||||||
|
RULE_INT(Mercs, MercsElixirHealPercent, 90, "Heal allies at this percent health")
|
||||||
|
RULE_INT(Mercs, MercsElixirAEMinimum, 3, "AE Minimum to trigger AE spells (heals and nukes)")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Guild)
|
RULE_CATEGORY(Guild)
|
||||||
@@ -599,6 +602,9 @@ RULE_BOOL(Bots, OldRaceRezEffects, false, "Older clients had ID 757 for races wi
|
|||||||
RULE_BOOL(Bots, ResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.")
|
RULE_BOOL(Bots, ResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.")
|
||||||
RULE_INT(Bots, OldResurrectionSicknessSpell, 757, "757 is Default Old Resurrection Sickness Spell")
|
RULE_INT(Bots, OldResurrectionSicknessSpell, 757, "757 is Default Old Resurrection Sickness Spell")
|
||||||
RULE_INT(Bots, ResurrectionSicknessSpell, 756, "756 is Default Resurrection Sickness Spell")
|
RULE_INT(Bots, ResurrectionSicknessSpell, 756, "756 is Default Resurrection Sickness Spell")
|
||||||
|
RULE_BOOL(Bots, IsBotsElixirEnabled, false, "Override AI with elixir logic")
|
||||||
|
RULE_INT(Bots, BotsElixirHealPercent, 90, "Heal allies at this percent health")
|
||||||
|
RULE_INT(Bots, BotsElixirAEMinimum, 3, "AE Minimum to trigger AE spells (heals and nukes)")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ SET(zone_sources
|
|||||||
dialogue_window.cpp
|
dialogue_window.cpp
|
||||||
dynamic_zone.cpp
|
dynamic_zone.cpp
|
||||||
effects.cpp
|
effects.cpp
|
||||||
|
elixir.cpp
|
||||||
embparser.cpp
|
embparser.cpp
|
||||||
embparser_api.cpp
|
embparser_api.cpp
|
||||||
embperl.cpp
|
embperl.cpp
|
||||||
@@ -187,6 +188,7 @@ SET(zone_headers
|
|||||||
doors.h
|
doors.h
|
||||||
dialogue_window.h
|
dialogue_window.h
|
||||||
dynamic_zone.h
|
dynamic_zone.h
|
||||||
|
elixir.h
|
||||||
embparser.h
|
embparser.h
|
||||||
embperl.h
|
embperl.h
|
||||||
embxs.h
|
embxs.h
|
||||||
|
|||||||
@@ -8821,6 +8821,10 @@ void Bot::CalcBotStats(bool showtext) {
|
|||||||
CalcBonuses();
|
CalcBonuses();
|
||||||
|
|
||||||
AI_AddNPCSpells(this->GetBotSpellID());
|
AI_AddNPCSpells(this->GetBotSpellID());
|
||||||
|
if (RuleB(Bot, IsBotsElixirEnabled)) {
|
||||||
|
isElixirSpellCacheBuilt = false;
|
||||||
|
ElixirSpellCacheRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
if(showtext) {
|
if(showtext) {
|
||||||
GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName());
|
GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName());
|
||||||
|
|||||||
@@ -321,6 +321,8 @@ public:
|
|||||||
void SetStopMeleeLevel(uint8 level);
|
void SetStopMeleeLevel(uint8 level);
|
||||||
void SetGuardMode();
|
void SetGuardMode();
|
||||||
void SetHoldMode();
|
void SetHoldMode();
|
||||||
|
bool ElixirAIDetermineSpellToCast();
|
||||||
|
bool ElixirAITryCastSpell(uint16 spellID, bool isHeal = false);
|
||||||
|
|
||||||
// Mob AI Virtual Override Methods
|
// Mob AI Virtual Override Methods
|
||||||
virtual void AI_Process();
|
virtual void AI_Process();
|
||||||
@@ -606,6 +608,7 @@ protected:
|
|||||||
virtual int32 CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 spell_id);
|
virtual int32 CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 spell_id);
|
||||||
virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client);
|
virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client);
|
||||||
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
|
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
|
||||||
|
virtual bool AIElixirDoSpellCast(uint16 spellID, Mob* tar, int32 mana_cost);
|
||||||
|
|
||||||
BotCastingRoles& GetCastingRoles() { return m_CastingRoles; }
|
BotCastingRoles& GetCastingRoles() { return m_CastingRoles; }
|
||||||
void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; }
|
void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; }
|
||||||
@@ -655,6 +658,8 @@ private:
|
|||||||
int32 max_end;
|
int32 max_end;
|
||||||
int32 end_regen;
|
int32 end_regen;
|
||||||
uint32 timers[MaxTimer];
|
uint32 timers[MaxTimer];
|
||||||
|
bool isElixirSpellCacheBuilt;
|
||||||
|
int elixirCacheSpells[15]{ -1,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||||
|
|
||||||
Timer m_evade_timer; // can be moved to pTimers at some point
|
Timer m_evade_timer; // can be moved to pTimers at some point
|
||||||
Timer m_alt_combat_hate_timer;
|
Timer m_alt_combat_hate_timer;
|
||||||
@@ -714,6 +719,7 @@ private:
|
|||||||
void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; }
|
void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; }
|
||||||
void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; }
|
void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; }
|
||||||
void SetReturningFlag(bool flag = true) { m_returning_flag = flag; }
|
void SetReturningFlag(bool flag = true) { m_returning_flag = flag; }
|
||||||
|
void ElixirSpellCacheRefresh();
|
||||||
|
|
||||||
// Private "Inventory" Methods
|
// Private "Inventory" Methods
|
||||||
void GetBotItems(EQ::InventoryProfile &inv, std::string* errorMessage);
|
void GetBotItems(EQ::InventoryProfile &inv, std::string* errorMessage);
|
||||||
|
|||||||
+215
-3
@@ -89,7 +89,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
|||||||
|
|
||||||
if (!addMob) {
|
if (!addMob) {
|
||||||
//Say("!addMob.");
|
//Say("!addMob.");
|
||||||
break;}
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
|
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
|
||||||
break;
|
break;
|
||||||
@@ -1058,7 +1059,6 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
|
|||||||
|
|
||||||
bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) {
|
bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
|
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
|
||||||
int32 manaCost = mana_cost;
|
int32 manaCost = mana_cost;
|
||||||
|
|
||||||
@@ -1070,6 +1070,8 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
|||||||
int32 extraMana = 0;
|
int32 extraMana = 0;
|
||||||
int32 hasMana = GetMana();
|
int32 hasMana = GetMana();
|
||||||
|
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "Attempting to cast %s on %s", spells[AIspells[i].spellid].name, tar == nullptr ? "no target" : tar->GetCleanName());
|
||||||
|
|
||||||
// Allow bots to cast buff spells even if they are out of mana
|
// Allow bots to cast buff spells even if they are out of mana
|
||||||
if (RuleB(Bots, FinishBuffing)) {
|
if (RuleB(Bots, FinishBuffing)) {
|
||||||
if (manaCost > hasMana) {
|
if (manaCost > hasMana) {
|
||||||
@@ -1085,7 +1087,8 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
|||||||
|
|
||||||
if (AIspells[i].type & SpellType_Escape) {
|
if (AIspells[i].type & SpellType_Escape) {
|
||||||
dist2 = 0;
|
dist2 = 0;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
dist2 = DistanceSquared(m_Position, tar->GetPosition());
|
dist2 = DistanceSquared(m_Position, tar->GetPosition());
|
||||||
|
|
||||||
if (((((spells[AIspells[i].spellid].target_type == ST_GroupTeleport && AIspells[i].type == 2)
|
if (((((spells[AIspells[i].spellid].target_type == ST_GroupTeleport && AIspells[i].type == 2)
|
||||||
@@ -1132,8 +1135,16 @@ bool Bot::AI_PursueCastCheck() {
|
|||||||
|
|
||||||
if (AIautocastspell_timer->Check(false)) {
|
if (AIautocastspell_timer->Check(false)) {
|
||||||
|
|
||||||
|
if (RuleB(Bots, IsBotsElixirEnabled)) {
|
||||||
|
AIautocastspell_timer->Start(3000, false); // avg human response is much less than 5 seconds..even for non-combat situations...
|
||||||
|
if (ElixirAIDetermineSpellToCast()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
||||||
|
|
||||||
|
|
||||||
LogAI("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells");
|
LogAI("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells");
|
||||||
|
|
||||||
if (!AICastSpell(GetTarget(), 100, SpellType_Snare)) {
|
if (!AICastSpell(GetTarget(), 100, SpellType_Snare)) {
|
||||||
@@ -1164,8 +1175,17 @@ bool Bot::AI_IdleCastCheck() {
|
|||||||
#if BotAI_DEBUG_Spells >= 25
|
#if BotAI_DEBUG_Spells >= 25
|
||||||
LogAI("Bot Non-Engaged autocast check triggered: [{}]", this->GetCleanName());
|
LogAI("Bot Non-Engaged autocast check triggered: [{}]", this->GetCleanName());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (RuleB(Bots, IsBotsElixirEnabled)) {
|
||||||
|
AIautocastspell_timer->Start(3000, false); // avg human response is much less than 5 seconds..even for non-combat situations...
|
||||||
|
if (ElixirAIDetermineSpellToCast()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
||||||
|
|
||||||
|
|
||||||
bool pre_combat = false;
|
bool pre_combat = false;
|
||||||
Client* test_against = nullptr;
|
Client* test_against = nullptr;
|
||||||
|
|
||||||
@@ -1307,6 +1327,13 @@ bool Bot::AI_EngagedCastCheck() {
|
|||||||
|
|
||||||
if (GetTarget() && AIautocastspell_timer->Check(false)) {
|
if (GetTarget() && AIautocastspell_timer->Check(false)) {
|
||||||
|
|
||||||
|
if (RuleB(Bots, IsBotsElixirEnabled)) {
|
||||||
|
AIautocastspell_timer->Start(3000, false); // avg human response is much less than 5 seconds..even for non-combat situations...
|
||||||
|
if (ElixirAIDetermineSpellToCast()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
||||||
|
|
||||||
uint8 botClass = GetClass();
|
uint8 botClass = GetClass();
|
||||||
@@ -2674,4 +2701,189 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
|
|||||||
return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index);
|
return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ElixirAIDetermineSpellToCast is called during AI bot logics
|
||||||
|
// It determines by class which spell to cast
|
||||||
|
bool Bot::ElixirAIDetermineSpellToCast() {
|
||||||
|
int8 spellAIResult;
|
||||||
|
Group* grp = GetGroup();
|
||||||
|
|
||||||
|
ElixirSpellCacheRefresh();
|
||||||
|
|
||||||
|
BotSpell botSpell{};
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
if (elixirCacheSpells[i] == -1) continue;
|
||||||
|
|
||||||
|
if (ElixirAITryCastSpell(elixirCacheSpells[i], true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bot::ElixirSpellCacheRefresh() {
|
||||||
|
if (isElixirSpellCacheBuilt) return;
|
||||||
|
isElixirSpellCacheBuilt = true;
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
elixirCacheSpells[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 0 is group heal all classes
|
||||||
|
// 1 ae nukes all classes
|
||||||
|
// 2 evade spells
|
||||||
|
// 3 slows all classes, hot for dru/clr/shm
|
||||||
|
//
|
||||||
|
// 4 shm/clr/dru/pal single target heal
|
||||||
|
// 5 nukes all classes
|
||||||
|
// 6 all non-priests single target heal
|
||||||
|
// 7 hot for everyone
|
||||||
|
// 10 hp buffs
|
||||||
|
// 11 ac buffs
|
||||||
|
// 12 haste buffs
|
||||||
|
// 13
|
||||||
|
|
||||||
|
|
||||||
|
for (int spellID = 1; spellID < SPDAT_RECORDS; spellID++) {
|
||||||
|
int spellSlot = -1;
|
||||||
|
if (GetSpellLevel(spellID, GetClass()) <= 0) continue;
|
||||||
|
if (GetSpellLevel(spellID, GetClass()) > GetLevel()) continue;
|
||||||
|
|
||||||
|
if (spells[spellID].target_type == ST_Target && IsEffectInSpell(spellID, SE_CurrentHP) && spellSlot == -1 && spells[spellID].base_value > 0) {
|
||||||
|
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) spellSlot = 4;
|
||||||
|
else spellSlot = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsEffectInSpell(spellID, SE_HealOverTime) && spellSlot == -1) {
|
||||||
|
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) spellSlot = 3;
|
||||||
|
else spellSlot = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsRegularGroupHealSpell(spellID) && spellSlot == -1) {
|
||||||
|
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) spellSlot = 0;
|
||||||
|
else spellSlot = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAENukeSpell(spellID) && spellSlot == -1) {
|
||||||
|
spellSlot = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsSlowSpell(spellID) && spellSlot == -1) {
|
||||||
|
spellSlot = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsPureNukeSpell(spellID) && spellSlot == -1) {
|
||||||
|
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) {
|
||||||
|
if (GetManaRatio() > 90) {
|
||||||
|
spellSlot = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
spellSlot = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
//TODO: escape spell slot 2
|
||||||
|
if (GetTarget() && HasOrMayGetAggro()) {
|
||||||
|
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_Escape);
|
||||||
|
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (IsBuffSpell(spellID) && spellSlot == -1) {
|
||||||
|
spellSlot = 10;
|
||||||
|
}
|
||||||
|
if (IsHasteSpell(spellID) && spellSlot == -1) {
|
||||||
|
spellSlot = 12;
|
||||||
|
}
|
||||||
|
if (spellSlot == -1) continue;
|
||||||
|
|
||||||
|
if (elixirCacheSpells[spellSlot] == -1) {
|
||||||
|
elixirCacheSpells[spellSlot] = spellID;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetSpellLevel(spellID, GetClass()) < GetSpellLevel(elixirCacheSpells[spellSlot], GetClass())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elixirCacheSpells[spellSlot] = spellID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElixirAITryCastSpell takes a provided spell id and does a spell check to determine if the spell is valid
|
||||||
|
// Once valid, it will cast on returned mob candidate
|
||||||
|
bool Bot::ElixirAITryCastSpell(uint16 spellID, bool isHeal) {
|
||||||
|
if (spellID == 0) return false;
|
||||||
|
|
||||||
|
Mob* outMob = nullptr;
|
||||||
|
auto spellAIResult = ElixirCastSpellCheck(spellID, &outMob);
|
||||||
|
|
||||||
|
if (spellAIResult < 0) return false;
|
||||||
|
|
||||||
|
if (spellAIResult == 0) {
|
||||||
|
|
||||||
|
if (outMob == nullptr && GetTarget() != nullptr) outMob = GetTarget();
|
||||||
|
if (outMob == nullptr) outMob = this;
|
||||||
|
AIElixirDoSpellCast(spellID, outMob, -1);
|
||||||
|
if (GetTarget() == this) return true;
|
||||||
|
BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outMob == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AIElixirDoSpellCast(spellID, outMob, -1);
|
||||||
|
if (outMob == this) return true;
|
||||||
|
BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bot::AIElixirDoSpellCast(uint16 spellID, Mob* tar, int32 mana_cost) {
|
||||||
|
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
|
||||||
|
int32 manaCost = mana_cost;
|
||||||
|
|
||||||
|
if (manaCost == -1)
|
||||||
|
manaCost = spells[spellID].mana;
|
||||||
|
else if (manaCost == -2)
|
||||||
|
manaCost = 0;
|
||||||
|
|
||||||
|
int32 extraMana = 0;
|
||||||
|
int32 hasMana = GetMana();
|
||||||
|
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "Attempting to cast %s on %s", spells[spellID].name, tar == nullptr ? "no target" : tar->GetCleanName());
|
||||||
|
|
||||||
|
// Allow bots to cast buff spells even if they are out of mana
|
||||||
|
if (RuleB(Bots, FinishBuffing)) {
|
||||||
|
if (manaCost > hasMana) {
|
||||||
|
//TODO: fix FinishBuffing no mana cost system
|
||||||
|
if (false) {
|
||||||
|
extraMana = manaCost - hasMana;
|
||||||
|
SetMana(manaCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = NPC::CastSpell(spellID, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[spellID].mana == -2 ? 0 : -1, mana_cost, 0, -1, -1, 0, 0);
|
||||||
|
if (IsCasting() && IsSitting()) Stand();
|
||||||
|
|
||||||
|
// if the spell wasn't casted, then take back any extra mana that was given to the bot to cast that spell
|
||||||
|
if (!result) {
|
||||||
|
SetMana(hasMana);
|
||||||
|
extraMana = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time;
|
||||||
|
if (spells[spellID].timer_id > 0) {
|
||||||
|
SetSpellRecastTimer(spells[spellID].timer_id, spells[spellID].recast_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
#include "../common/shared_tasks.h"
|
#include "../common/shared_tasks.h"
|
||||||
#include "gm_commands/door_manipulation.h"
|
#include "gm_commands/door_manipulation.h"
|
||||||
#include "../common/languages.h"
|
#include "../common/languages.h"
|
||||||
|
#include "elixir.h"
|
||||||
|
|
||||||
extern QueryServ* QServ;
|
extern QueryServ* QServ;
|
||||||
extern WorldServer worldserver;
|
extern WorldServer worldserver;
|
||||||
@@ -209,6 +210,7 @@ int command_init(void)
|
|||||||
command_add("dz", "Manage expeditions and dynamic zone instances", 80, command_dz) ||
|
command_add("dz", "Manage expeditions and dynamic zone instances", 80, command_dz) ||
|
||||||
command_add("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", 0, command_dzkickplayers) ||
|
command_add("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", 0, command_dzkickplayers) ||
|
||||||
command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", 100, command_editmassrespawn) ||
|
command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", 100, command_editmassrespawn) ||
|
||||||
|
command_add("elixircheck", "[spellid] - Check how elixir AI works with provided spell id", 100, command_elixircheck) ||
|
||||||
command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", 80, command_emote) ||
|
command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", 80, command_emote) ||
|
||||||
command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) ||
|
command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) ||
|
||||||
command_add("emoteview", "Lists all NPC Emotes", 80, command_emoteview) ||
|
command_add("emoteview", "Lists all NPC Emotes", 80, command_emoteview) ||
|
||||||
@@ -15900,6 +15902,118 @@ void command_emptyinventory(Client *c, const Seperator *sep)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void command_elixircheck(Client* c, const Seperator* sep)
|
||||||
|
{
|
||||||
|
if (sep->arg[1][0] == 0) {
|
||||||
|
c->Message(Chat::White, "Usage: #elixircheck [spellid]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spellid = atoi(sep->arg[1]);
|
||||||
|
if (spellid < 1) {
|
||||||
|
c->Message(Chat::Red, "Invalid spell id: %d", spellid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Mob* outMob = nullptr;
|
||||||
|
const SPDat_Spell_Struct& spDat = spells[spellid];
|
||||||
|
auto result = c->ElixirCastSpellCheck(spellid, &outMob);
|
||||||
|
switch (result) {
|
||||||
|
case ELIXIR_TARGET_CHANGE:
|
||||||
|
c->Message(Chat::White, "Elixir AI would return OK if target changes to %s to cast %s", outMob->GetCleanName(), spDat.name);
|
||||||
|
return;
|
||||||
|
case ELIXIR_OK:
|
||||||
|
c->Message(Chat::White, "Elixir AI would return OK to cast %s", spDat.name);
|
||||||
|
return;
|
||||||
|
case ELIXIR_UNHANDLED_SPELL:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (UNHANDLED_SPELL)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_CANNOT_CAST_BAD_STATE:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (CANNOT_CAST_BAD_STATE)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NOT_ENOUGH_MANA:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_ENOUGH_MANA)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_LULL_IGNORED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (LULL_IGNORED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_MEZ_IGNORED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (MEZ_IGNORED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_CHARM_IGNORED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (CHARM_IGNORED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NO_TARGET:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NO_TARGET)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_INVALID_TARGET_BODYTYPE:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (INVALID_TARGET_BODYTYPE)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_TRANSPORT_IGNORED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (TRANSPORT_IGNORED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NOT_LINE_OF_SIGHT:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_LINE_OF_SIGHT)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_COMPONENT_REQUIRED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (COMPONENT_REQUIRED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_ALREADY_HAVE_BUFF:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ALREADY_HAVE_BUFF)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_ZONETYPE_FAIL:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ZONETYPE_FAIL)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_CANNOT_USE_IN_COMBAT:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (CANNOT_USE_IN_COMBAT)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NOT_ENOUGH_ENDURANCE:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_ENOUGH_ENDURANCE)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_ALREADY_HAVE_PET:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ALREADY_HAVE_PET)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_OUT_OF_RANGE:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (OUT_OF_RANGE)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NO_PET:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NO_PET)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NOT_NEEDED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_NEEDED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NOT_BEHIND_MOB:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_NOT_BEHIND_MOB)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_HP_NOT_LOW:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_HP_NOT_LOW)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_MANA_NOT_LOW:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_MANA_NOT_LOW)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_AE_LIMIT_NOT_MET:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_AE_LIMIT_NOT_MET)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_NO_HATE:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_NO_HATE)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_TARGET_MEZZED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_TARGET_MEZZED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_ATTACK_NOT_ALLOWED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_ATTACK_NOT_ALLOWED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
case ELIXIR_TARGET_ALREADY_STUNNED:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ELIXIR_TARGET_ALREADY_STUNNED)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (UNKNOWN)", spDat.name, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block.
|
// All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block.
|
||||||
#ifdef BOTS
|
#ifdef BOTS
|
||||||
#include "bot_command.h"
|
#include "bot_command.h"
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ void command_dye(Client *c, const Seperator *sep);
|
|||||||
void command_dz(Client *c, const Seperator *sep);
|
void command_dz(Client *c, const Seperator *sep);
|
||||||
void command_dzkickplayers(Client *c, const Seperator *sep);
|
void command_dzkickplayers(Client *c, const Seperator *sep);
|
||||||
void command_editmassrespawn(Client* c, const Seperator* sep);
|
void command_editmassrespawn(Client* c, const Seperator* sep);
|
||||||
|
void command_elixircheck(Client* c, const Seperator* sep);
|
||||||
void command_emote(Client *c, const Seperator *sep);
|
void command_emote(Client *c, const Seperator *sep);
|
||||||
void command_emotesearch(Client* c, const Seperator *sep);
|
void command_emotesearch(Client* c, const Seperator *sep);
|
||||||
void command_emoteview(Client* c, const Seperator *sep);
|
void command_emoteview(Client* c, const Seperator *sep);
|
||||||
|
|||||||
+871
@@ -0,0 +1,871 @@
|
|||||||
|
#include "../common/rulesys.h"
|
||||||
|
#include "../common/global_define.h"
|
||||||
|
#include "../common/eqemu_logsys.h"
|
||||||
|
|
||||||
|
#include "mob.h"
|
||||||
|
#include "elixir.h"
|
||||||
|
#include "zone.h"
|
||||||
|
#include "groups.h"
|
||||||
|
|
||||||
|
extern Zone* zone;
|
||||||
|
|
||||||
|
// ElixirCastSpell determines if a spell can be casted by Mob.
|
||||||
|
// If 0 is returned, spell is valid and no target changing is required
|
||||||
|
// If a negative value is returned, an error occured. See elixir.h ELIXIR_ prefix const error lookup of reasons
|
||||||
|
// If 1 is returned, outMob is set to the suggested mob entity
|
||||||
|
int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob** outMob)
|
||||||
|
{
|
||||||
|
int manaCurrent = GetMana();
|
||||||
|
int manaMax = GetMaxMana();
|
||||||
|
int hpCurrent = GetHP();
|
||||||
|
int hpMax = GetMaxHP();
|
||||||
|
int endCurrent = GetEndurance();
|
||||||
|
int endMax = GetMaxEndurance();
|
||||||
|
|
||||||
|
int healPercent = 90;
|
||||||
|
if (IsMerc()) healPercent = RuleI(Mercs, MercsElixirHealPercent);
|
||||||
|
#ifdef BOTS
|
||||||
|
if (IsBot()) healPercent = RuleI(Bots, BotsElixirHealPercent);
|
||||||
|
#endif
|
||||||
|
if (healPercent < 1) healPercent = 1;
|
||||||
|
if (healPercent > 99) healPercent = 99;
|
||||||
|
|
||||||
|
int aeMinimum = 3;
|
||||||
|
if (IsMerc()) aeMinimum = RuleI(Mercs, MercsElixirAEMinimum);
|
||||||
|
#ifdef BOTS
|
||||||
|
if (IsBot()) aeMinimum = RuleI(Bots, BotsElixirAEMinimum);
|
||||||
|
#endif
|
||||||
|
if (aeMinimum < 1) aeMinimum = 1;
|
||||||
|
if (aeMinimum > 99) aeMinimum = 99;
|
||||||
|
|
||||||
|
if (IsCorpse()) return ELIXIR_CANNOT_CAST_BAD_STATE;
|
||||||
|
|
||||||
|
bool isHeal = false;
|
||||||
|
bool isDebuff = false;
|
||||||
|
bool isBuff = false;
|
||||||
|
bool isLifetap = false;
|
||||||
|
bool isMana = false;
|
||||||
|
bool isCharm = false;
|
||||||
|
bool isSnare = false;
|
||||||
|
bool isSow = false;
|
||||||
|
bool isTaunt = false;
|
||||||
|
bool isSingleTargetSpell = false;
|
||||||
|
bool isPetSummon = false;
|
||||||
|
bool isTransport = false;
|
||||||
|
bool isGroupSpell = false;
|
||||||
|
bool isBardSong = false;
|
||||||
|
bool isBeneficial = false;
|
||||||
|
bool isStun = false;
|
||||||
|
bool isMez = false;
|
||||||
|
bool isLull = false;
|
||||||
|
long stunDuration = 0;
|
||||||
|
long damageAmount = 0;
|
||||||
|
long healAmount = 0;
|
||||||
|
bool isGroupHated = false;
|
||||||
|
|
||||||
|
int skillID = 0;
|
||||||
|
|
||||||
|
bodyType targetBodyType = BT_NoTarget2;
|
||||||
|
|
||||||
|
const SPDat_Spell_Struct& spDat = spells[spellID];
|
||||||
|
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "%s checking %s vs %s", GetName(), spDat.name, target == nullptr ? "no target" : target->GetCleanName());
|
||||||
|
|
||||||
|
int spellgroup = spDat.type_description_id;
|
||||||
|
uint32 ticks = spDat.buff_duration;
|
||||||
|
int targets = spDat.aoe_max_targets;
|
||||||
|
SpellTargetType targettype = spDat.target_type;
|
||||||
|
EQ::skills::SkillType skill = spDat.skill;
|
||||||
|
uint16 recourseID = spDat.recourse_link;
|
||||||
|
int category = spDat.spell_category;
|
||||||
|
int subcategory = spDat.effect_description_id;
|
||||||
|
|
||||||
|
uint32 buffCount = 0;
|
||||||
|
Group* grp = GetGroup();
|
||||||
|
Raid* raid = GetRaid();
|
||||||
|
|
||||||
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||||
|
if (IsBlankSpellEffect(spellID, i)) continue;
|
||||||
|
|
||||||
|
int attr = spDat.effect_id[i];
|
||||||
|
int base = spDat.base_value[i];
|
||||||
|
int base2 = spDat.limit_value[i];
|
||||||
|
int max = spDat.max_value[i];
|
||||||
|
int calc = spDat.formula[i];
|
||||||
|
|
||||||
|
if (attr == SE_CurrentHP) { //0
|
||||||
|
if (max > 0) { //Heal / HoT
|
||||||
|
if (ticks < 5 && base > 0) { //regen
|
||||||
|
isHeal = true;
|
||||||
|
healAmount = base;
|
||||||
|
}
|
||||||
|
if (ticks > 0) {
|
||||||
|
isBuff = true;
|
||||||
|
}
|
||||||
|
if (category == 114) { //taps like touch of zlandicar
|
||||||
|
isLifetap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (base < 0 && damageAmount == 0) {
|
||||||
|
damageAmount = -base;
|
||||||
|
}
|
||||||
|
if (max < 0) { //Nuke / DoT
|
||||||
|
damageAmount = -max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_ArmorClass || // 1 ac
|
||||||
|
attr == SE_ATK || //2 attack
|
||||||
|
attr == SE_STR || //4 str
|
||||||
|
attr == SE_DEX || //5 dex
|
||||||
|
attr == SE_AGI || //6 agi
|
||||||
|
attr == SE_STA || //7 sta
|
||||||
|
attr == SE_INT || //8 int
|
||||||
|
attr == SE_WIS //9 wis
|
||||||
|
) {
|
||||||
|
if (base > 0) { //+stat
|
||||||
|
isBuff = true;
|
||||||
|
}
|
||||||
|
if (base < 0) { //-stat
|
||||||
|
isDebuff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_MovementSpeed) { //3
|
||||||
|
if (base > 0) { //+Movement
|
||||||
|
isBuff = true;
|
||||||
|
isSow = true;
|
||||||
|
}
|
||||||
|
if (base < 0) { //-Movement
|
||||||
|
isDebuff = true;
|
||||||
|
isSnare = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_CHA) { //10 CHA
|
||||||
|
if (base > 0 && base < 254) { //+CHA
|
||||||
|
isBuff = true;
|
||||||
|
}
|
||||||
|
if (base < 0) { //-CHA
|
||||||
|
isDebuff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_AttackSpeed) { //11 attackspeed
|
||||||
|
if (base > 0) { //+Haste
|
||||||
|
isBuff = true;
|
||||||
|
}
|
||||||
|
if (base < 0) { //-Haste
|
||||||
|
isDebuff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_CurrentMana) { //15 Mana
|
||||||
|
isMana = true;
|
||||||
|
}
|
||||||
|
if (attr == SE_Lull) { // pacify (lull)
|
||||||
|
isLull = true;
|
||||||
|
}
|
||||||
|
if (attr == SE_Stun) { //21 stun
|
||||||
|
stunDuration = base2;
|
||||||
|
if (targettype == ST_AEClientV1 || targettype == ST_AECaster) { // 2 or
|
||||||
|
isSingleTargetSpell = true; //hack to make ae stuns work
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attr == SE_Charm) { //23 charm
|
||||||
|
isCharm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_Gate) { //26 Gate
|
||||||
|
isTransport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_ChangeFrenzyRad) { //30 frenzy radius reduction (lull)
|
||||||
|
isLull = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_Mez) { //31 Mesmerization
|
||||||
|
isMez = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_SummonPet) { //33 Summon Elemental Pet
|
||||||
|
isPetSummon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_NecPet) { //71 Summon Skeleton Pet
|
||||||
|
isPetSummon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_Teleport) { //83 Transport
|
||||||
|
isTransport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_Harmony) { //86 reaction radius reduction (lull)
|
||||||
|
isLull = true;
|
||||||
|
}
|
||||||
|
if (attr == SE_Succor) { //88 Evac
|
||||||
|
isTransport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (attr == SE_HealOverTime) { //100 Heal over times
|
||||||
|
isHeal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (attr == SE_CallPet) { //103 Summon Shaman pet
|
||||||
|
isPetSummon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_SummonBSTPet) { // 106 Bst Pet
|
||||||
|
isPetSummon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (attr == SE_Familiar) { //108 Summon Familiar
|
||||||
|
isPetSummon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_Hate) { //192 taunt
|
||||||
|
isTaunt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr == SE_SkillAttack) { //193 skill attack
|
||||||
|
skillID = spDat.skill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcategory == 43 && ticks < 10) { //Health
|
||||||
|
isHeal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == 126) { //Taps
|
||||||
|
if (subcategory == 43) { //Health
|
||||||
|
isLifetap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isBeneficial = IsBeneficialSpell(spellID);
|
||||||
|
isStun = IsStunSpell(spellID);
|
||||||
|
|
||||||
|
/*
|
||||||
|
//TODO TargetTypes:
|
||||||
|
case 40: return "AE PC v2";
|
||||||
|
case 25: return "AE Summoned";
|
||||||
|
case 24: return "AE Undead";
|
||||||
|
case 20: return "Targetted AE Tap";
|
||||||
|
case 8: return "Targetted AE";
|
||||||
|
case 2: return "AE PC v1";
|
||||||
|
case 1: return "Line of Sight";
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if (targettype == ST_Group) { // 41 Group v2
|
||||||
|
isGroupSpell = true;
|
||||||
|
}
|
||||||
|
if (targettype == ST_GroupTeleport) { //3 Group v1
|
||||||
|
isGroupSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGroupSpell && IsBardSong(spellID)) {
|
||||||
|
isBardSong = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Target) { //5 Single
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetTarget()) {
|
||||||
|
targetBodyType = target->bodytype;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Animal) { //9 Animal
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Undead) { //10 Undead
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Summoned) { //11 Summoned
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Tap) { //13 Lifetap
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Pet) { //14 Pet
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Corpse) { //15 Corpse
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Plant) { //16 Plant
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Giant) { //17 Uber Giants
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Dragon) { //18 Uber Dragons
|
||||||
|
isSingleTargetSpell = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spDat.mana > 0 && manaCurrent < GetActSpellCost(spellID, spDat.mana)) return ELIXIR_NOT_ENOUGH_MANA;
|
||||||
|
if (spDat.endurance_cost > 0 && endCurrent < spDat.endurance_cost) return ELIXIR_NOT_ENOUGH_ENDURANCE;
|
||||||
|
|
||||||
|
if (isLull) return ELIXIR_LULL_IGNORED;
|
||||||
|
if (isMez) return ELIXIR_MEZ_IGNORED;
|
||||||
|
if (isCharm) return ELIXIR_CHARM_IGNORED;
|
||||||
|
|
||||||
|
if (targettype == ST_Animal) { //16 Animal
|
||||||
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
|
if (targetBodyType != BT_Animal) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Undead) { //10 Undead
|
||||||
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
|
if (targetBodyType != BT_Undead) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Summoned) { //11 Summoned
|
||||||
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
|
if (targetBodyType != BT_Summoned) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Plant) { //Plant
|
||||||
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
|
if (targetBodyType != BT_Plant) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTransport) return ELIXIR_TRANSPORT_IGNORED;
|
||||||
|
|
||||||
|
if (spDat.npc_no_los == 0 && target && isSingleTargetSpell && !isBeneficial && !CheckLosFN(target)) return ELIXIR_NOT_LINE_OF_SIGHT;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) { // Reagent check
|
||||||
|
if (spDat.component[i] < 1) continue;
|
||||||
|
if (spDat.component_count[i] < 1) continue;
|
||||||
|
if (IsMerc()) continue; //mercs don't have inventory nor require reagents
|
||||||
|
#ifdef BOTS
|
||||||
|
if (IsBot()) continue; //bots don't have inventory nor require reagents
|
||||||
|
#endif
|
||||||
|
return ELIXIR_COMPONENT_REQUIRED;
|
||||||
|
//TODO: teach elixir how to check inventory for component in cases it's a client calling this function
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: CasterRequirement logic
|
||||||
|
//DWORD ReqID = pSpell->CasterRequirementID;
|
||||||
|
//if (ReqID == 518 && SpawnPctHPs(pChar->pSpawn) > 89) return "not < 90% hp";
|
||||||
|
|
||||||
|
|
||||||
|
if (skillID == EQ::skills::SkillBackstab) { // 8 backstab
|
||||||
|
if (target == nullptr) {
|
||||||
|
return ELIXIR_NO_TARGET;
|
||||||
|
}
|
||||||
|
if (!BehindMob(target)) {
|
||||||
|
return ELIXIR_NOT_BEHIND_MOB;
|
||||||
|
}
|
||||||
|
if (!IsWithinSpellRange(target, spDat.range, spellID)) {
|
||||||
|
return ELIXIR_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recourseID > 0) { //recourse buff attached
|
||||||
|
const SPDat_Spell_Struct& spDatRecourse = spells[recourseID];
|
||||||
|
if (spDatRecourse.buff_duration > 0) {
|
||||||
|
buffCount = GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.ticsremaining < 2) continue;
|
||||||
|
if (buff.spellid == recourseID) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, recourseID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticks > 0 && !isBeneficial && targettype == ST_Target) { // debuff
|
||||||
|
if (target == nullptr) {
|
||||||
|
return ELIXIR_NO_TARGET;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffCount = target->GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = target->buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.ticsremaining < 2) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spDat.zone_type == 1 && !zone->CanCastOutdoor()) {
|
||||||
|
return ELIXIR_ZONETYPE_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target) { //do aggro check
|
||||||
|
if (target->GetHateAmount(this) > 0) isGroupHated = true;
|
||||||
|
if (!isGroupHated) {
|
||||||
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||||
|
if (!grp) break;
|
||||||
|
if (!grp->members[i]) continue;
|
||||||
|
if (target->GetHateAmount(grp->members[i]) == 0) continue;
|
||||||
|
isGroupHated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO: zone_type 2 check (can't cast outdoors indoor only)
|
||||||
|
|
||||||
|
if (IsEffectInSpell(spellID, SE_Levitate) && !zone->CanLevitate()) {
|
||||||
|
return ELIXIR_ZONETYPE_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spDat.can_cast_in_combat) {
|
||||||
|
if (IsEngaged()) {
|
||||||
|
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||||
|
}
|
||||||
|
if (target == nullptr) {
|
||||||
|
return ELIXIR_NO_TARGET;
|
||||||
|
}
|
||||||
|
buffCount = target->GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = target->buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (IsDetrimentalSpell(buff.spellid) && buff.ticsremaining > 0 && !DetrimentalSpellAllowsRest(buff.spellid)) {
|
||||||
|
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDisciplineBuff(spellID)) {
|
||||||
|
buffCount = GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.ticsremaining < 2) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isPetSummon) {
|
||||||
|
if (HasPet()) return ELIXIR_ALREADY_HAVE_PET;
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Pet && isHeal) {
|
||||||
|
if (!HasPet()) {
|
||||||
|
return ELIXIR_NO_PET;
|
||||||
|
}
|
||||||
|
if (GetPet()->GetHPRatio() > healPercent) {
|
||||||
|
return ELIXIR_HP_NOT_LOW;
|
||||||
|
}
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isBuff && targettype == ST_Pet && isBeneficial) {
|
||||||
|
if (!HasPet()) {
|
||||||
|
return ELIXIR_NO_PET;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffCount = GetPet()->GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = GetPet()->buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.ticsremaining < 2) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||||
|
}
|
||||||
|
if (!IsWithinSpellRange(GetPet(), spDat.range, spellID)) {
|
||||||
|
return ELIXIR_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMana && targettype == ST_Self && ticks <= 0 && !isPetSummon) { // self only regen, like harvest, canni
|
||||||
|
if (stunDuration > 0 && isGroupHated) {
|
||||||
|
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetManaRatio() > 50) {
|
||||||
|
return ELIXIR_MANA_NOT_LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLifetap && GetHPRatio() > healPercent) {
|
||||||
|
return ELIXIR_HP_NOT_LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHeal) { //heal logic
|
||||||
|
int groupHealCount = 0;
|
||||||
|
int healIDPercent = 100;
|
||||||
|
// figure out who is lowest HP party member
|
||||||
|
if (GetHPRatio() <= healPercent) {
|
||||||
|
if (ticks == 0) { // instant heal, just apply
|
||||||
|
*outMob = this;
|
||||||
|
healIDPercent = GetHPRatio();
|
||||||
|
}
|
||||||
|
else { // it's a heal buff, check if player already has it
|
||||||
|
bool isBuffNeeded = true;
|
||||||
|
buffCount = GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isBuffNeeded) {
|
||||||
|
*outMob = this;
|
||||||
|
healIDPercent = GetHPRatio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||||
|
if (!grp) break;
|
||||||
|
if (!grp->members[i]) continue;
|
||||||
|
if (grp->members[i]->GetHPRatio() > healPercent) continue;
|
||||||
|
if (grp->members[i]->GetHPRatio() > healIDPercent) continue;
|
||||||
|
if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue;
|
||||||
|
|
||||||
|
groupHealCount++;
|
||||||
|
if (ticks == 0) { // instant heal, just apply
|
||||||
|
*outMob = grp->members[i];
|
||||||
|
healIDPercent = grp->members[i]->GetHPRatio();
|
||||||
|
}
|
||||||
|
else { // it's a heal buff, check if player already has it
|
||||||
|
bool isBuffNeeded = true;
|
||||||
|
buffCount = grp->members[i]->GetMaxTotalSlots();
|
||||||
|
for (uint32 j = 0; j < buffCount; j++) {
|
||||||
|
auto buff = grp->members[i]->buffs[j];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: Immune Check
|
||||||
|
}
|
||||||
|
if (isBuffNeeded) {
|
||||||
|
*outMob = grp->members[i];
|
||||||
|
healIDPercent = GetHPRatio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGroupSpell && groupHealCount < aeMinimum) {
|
||||||
|
return ELIXIR_AE_LIMIT_NOT_MET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*outMob) {
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
return ELIXIR_TARGET_CHANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((targettype == ST_Self || isGroupSpell) && isBeneficial) { //self/group beneficial spell
|
||||||
|
if (isGroupHated && !isBardSong) {
|
||||||
|
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticks <= 4 && GetClass() != BARD) { //don't bother with short duration buffs
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticks == 0) {
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
bool isBuffNeeded = true;
|
||||||
|
|
||||||
|
buffCount = GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.ticsremaining < 2) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isBuffNeeded) return ELIXIR_OK;
|
||||||
|
if (!isGroupSpell) return ELIXIR_NOT_NEEDED;
|
||||||
|
|
||||||
|
isBuffNeeded = true;
|
||||||
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||||
|
|
||||||
|
if (!grp) break;
|
||||||
|
if (!grp->members[i]) continue;
|
||||||
|
buffCount = grp->members[i]->GetMaxTotalSlots();
|
||||||
|
for (uint32 j = 0; j < buffCount; j++) {
|
||||||
|
auto buff = grp->members[i]->buffs[j];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.ticsremaining < 2) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isBuffNeeded) continue;
|
||||||
|
*outMob = grp->members[i];
|
||||||
|
return ELIXIR_TARGET_CHANGE;
|
||||||
|
}
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Target && isBeneficial && damageAmount == 0) { //single target beneficial spell
|
||||||
|
|
||||||
|
// TODO: add exceptions for combat buffs situation
|
||||||
|
bool isCombatBuff = false;
|
||||||
|
|
||||||
|
if (isGroupHated && !isCombatBuff) {
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for the time being, any beneficial non-heal single target spells without a duration are skipped
|
||||||
|
// later, we need to add in things like necro mana flow, etc
|
||||||
|
if (ticks == 0) {
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
if (ticks <= 4 && GetClass() != BARD) { //don't bother with short duration buffs
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
// always self buff first
|
||||||
|
bool isBuffNeeded = true;
|
||||||
|
buffCount = GetMaxBuffSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: Immune Check
|
||||||
|
}
|
||||||
|
if (isBuffNeeded) {
|
||||||
|
if (target && target->GetID() == GetID()) {
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
*outMob = this;
|
||||||
|
return ELIXIR_TARGET_CHANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||||
|
if (!grp) break;
|
||||||
|
if (!grp->members[i]) continue;
|
||||||
|
if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue;
|
||||||
|
|
||||||
|
bool isBuffNeeded = true;
|
||||||
|
buffCount = grp->members[i]->GetMaxTotalSlots();
|
||||||
|
for (uint32 j = 0; j < buffCount; j++) {
|
||||||
|
auto buff = grp->members[i]->buffs[j];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: Immune Check
|
||||||
|
}
|
||||||
|
if (isBuffNeeded) {
|
||||||
|
if (target && target->GetID() == grp->members[i]->GetID()) {
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
*outMob = grp->members[i];
|
||||||
|
return ELIXIR_TARGET_CHANGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStun && (targettype == ST_AEClientV1 || targettype == ST_AreaClientOnly || targettype == ST_AECaster)) { // PB AE stun
|
||||||
|
int targetCount = 0;
|
||||||
|
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
||||||
|
auto hates = GetHateList();
|
||||||
|
auto iter = hates.begin();
|
||||||
|
for (auto iter : hates) {
|
||||||
|
if (sqDistance > 0 && DistanceSquaredNoZ(GetPosition(), iter->entity_on_hatelist->GetPosition()) > sqDistance) continue;
|
||||||
|
if (iter->entity_on_hatelist->IsStunned()) continue;
|
||||||
|
targetCount++;
|
||||||
|
}
|
||||||
|
if (targetCount < aeMinimum) {
|
||||||
|
return ELIXIR_AE_LIMIT_NOT_MET;
|
||||||
|
}
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageAmount > 0 && (targettype == ST_AEClientV1 || targettype == ST_AreaClientOnly || targettype == ST_AECaster)) { // PB AE DD
|
||||||
|
int targetCount = 0;
|
||||||
|
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
||||||
|
auto hates = GetHateList();
|
||||||
|
auto iter = hates.begin();
|
||||||
|
for (auto iter : hates) {
|
||||||
|
if (sqDistance > 0 && DistanceSquaredNoZ(GetPosition(), iter->entity_on_hatelist->GetPosition()) > sqDistance) continue;
|
||||||
|
targetCount++;
|
||||||
|
}
|
||||||
|
if (targetCount < aeMinimum) {
|
||||||
|
return ELIXIR_AE_LIMIT_NOT_MET;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (damageAmount > 0 && (targettype == ST_TargetAETap || targettype == ST_AETarget)) { // Target AE DD
|
||||||
|
if (target == nullptr) {
|
||||||
|
return ELIXIR_NO_TARGET;
|
||||||
|
}
|
||||||
|
if (!IsWithinSpellRange(target, spDat.range, spellID)) {
|
||||||
|
return ELIXIR_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetCount = 0;
|
||||||
|
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
||||||
|
auto hates = GetHateList();
|
||||||
|
auto iter = hates.begin();
|
||||||
|
for (auto iter : hates) {
|
||||||
|
if (sqDistance > 0 && DistanceSquaredNoZ(target->GetPosition(), iter->entity_on_hatelist->GetPosition()) > sqDistance) continue;
|
||||||
|
targetCount++;
|
||||||
|
}
|
||||||
|
if (targetCount < aeMinimum) {
|
||||||
|
return ELIXIR_AE_LIMIT_NOT_MET;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((targettype == ST_Target || targettype == ST_Tap) && !isBeneficial) { // single target detrimental spell
|
||||||
|
if (target == nullptr) {
|
||||||
|
return ELIXIR_NO_TARGET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGroupHated) {
|
||||||
|
return ELIXIR_NO_HATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target->GetHPRatio() <= 0) {
|
||||||
|
return ELIXIR_HP_NOT_LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsWithinSpellRange(target, spDat.range, spellID)) {
|
||||||
|
return ELIXIR_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target->IsMezzed()) {
|
||||||
|
return ELIXIR_TARGET_MEZZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsAttackAllowed(target)) {
|
||||||
|
return ELIXIR_ATTACK_NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStun) {
|
||||||
|
if (target->IsStunned()) {
|
||||||
|
return ELIXIR_TARGET_ALREADY_STUNNED;
|
||||||
|
}
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticks == 0) {
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
buffCount = target->GetMaxTotalSlots();
|
||||||
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
|
auto buff = target->buffs[i];
|
||||||
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
|
if (buff.spellid == spellID) {
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
|
if (stackResult == -1) {
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
// TODO: Immune Check
|
||||||
|
}
|
||||||
|
return ELIXIR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ELIXIR_UNHANDLED_SPELL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
|
||||||
|
if (!raid) break;
|
||||||
|
if (!raid->members[i]) continue;
|
||||||
|
if (!raid->members[i].member) continue;
|
||||||
|
//raid->members[i].GroupNumber == gid
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
enum ElixirError {
|
||||||
|
ELIXIR_TARGET_CHANGE = 1,
|
||||||
|
ELIXIR_OK = 0,
|
||||||
|
ELIXIR_UNHANDLED_SPELL = -1,
|
||||||
|
ELIXIR_CANNOT_CAST_BAD_STATE = -2,
|
||||||
|
ELIXIR_NOT_ENOUGH_MANA = -3,
|
||||||
|
ELIXIR_LULL_IGNORED = -4,
|
||||||
|
ELIXIR_MEZ_IGNORED = -5,
|
||||||
|
ELIXIR_CHARM_IGNORED = -6,
|
||||||
|
ELIXIR_NO_TARGET = -7,
|
||||||
|
ELIXIR_INVALID_TARGET_BODYTYPE = -8,
|
||||||
|
ELIXIR_TRANSPORT_IGNORED = -9,
|
||||||
|
ELIXIR_NOT_LINE_OF_SIGHT = -10,
|
||||||
|
ELIXIR_COMPONENT_REQUIRED = -11,
|
||||||
|
ELIXIR_ALREADY_HAVE_BUFF = -12,
|
||||||
|
ELIXIR_ZONETYPE_FAIL = -13,
|
||||||
|
ELIXIR_CANNOT_USE_IN_COMBAT = -14,
|
||||||
|
ELIXIR_NOT_ENOUGH_ENDURANCE = -15,
|
||||||
|
ELIXIR_ALREADY_HAVE_PET = -16,
|
||||||
|
ELIXIR_OUT_OF_RANGE = -17,
|
||||||
|
ELIXIR_NO_PET = -18,
|
||||||
|
ELIXIR_NOT_NEEDED = -19,
|
||||||
|
ELIXIR_NOT_BEHIND_MOB = -20,
|
||||||
|
ELIXIR_HP_NOT_LOW = -21,
|
||||||
|
ELIXIR_MANA_NOT_LOW = -22,
|
||||||
|
ELIXIR_AE_LIMIT_NOT_MET = -23,
|
||||||
|
ELIXIR_NO_HATE = -24,
|
||||||
|
ELIXIR_TARGET_MEZZED = -25,
|
||||||
|
ELIXIR_ATTACK_NOT_ALLOWED = -26,
|
||||||
|
ELIXIR_TARGET_ALREADY_STUNNED = -27
|
||||||
|
};
|
||||||
+172
-8
@@ -1128,7 +1128,8 @@ void Merc::DoEnduranceUpkeep() {
|
|||||||
if ((upkeep + upkeep_sum) > GetEndurance()) {
|
if ((upkeep + upkeep_sum) > GetEndurance()) {
|
||||||
//they do not have enough to keep this one going.
|
//they do not have enough to keep this one going.
|
||||||
BuffFadeBySlot(buffs_i);
|
BuffFadeBySlot(buffs_i);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
upkeep_sum += upkeep;
|
upkeep_sum += upkeep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1781,7 +1782,8 @@ void Merc::AI_Start(int32 iMoveDelay) {
|
|||||||
if (merc_spells.empty()) {
|
if (merc_spells.empty()) {
|
||||||
AIautocastspell_timer->SetTimer(1000);
|
AIautocastspell_timer->SetTimer(1000);
|
||||||
AIautocastspell_timer->Disable();
|
AIautocastspell_timer->Disable();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
AIautocastspell_timer->SetTimer(750);
|
AIautocastspell_timer->SetTimer(750);
|
||||||
AIautocastspell_timer->Start(RandomTimer(0, 2000), false);
|
AIautocastspell_timer->Start(RandomTimer(0, 2000), false);
|
||||||
}
|
}
|
||||||
@@ -1979,7 +1981,8 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon
|
|||||||
|
|
||||||
if (mercSpell.type & SpellType_Escape) {
|
if (mercSpell.type & SpellType_Escape) {
|
||||||
dist2 = 0;
|
dist2 = 0;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
dist2 = DistanceSquared(m_Position, tar->GetPosition());
|
dist2 = DistanceSquared(m_Position, tar->GetPosition());
|
||||||
|
|
||||||
if (((((spells[spellid].target_type == ST_GroupTeleport && mercSpell.type == SpellType_Heal)
|
if (((((spells[spellid].target_type == ST_GroupTeleport && mercSpell.type == SpellType_Heal)
|
||||||
@@ -2020,6 +2023,10 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) {
|
|||||||
if (!AI_HasSpells())
|
if (!AI_HasSpells())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (RuleB(Mercs, IsMercsElixirEnabled)) {
|
||||||
|
return ElixirAIDetermineSpellToCast();
|
||||||
|
}
|
||||||
|
|
||||||
if (iChance < 100) {
|
if (iChance < 100) {
|
||||||
if (zone->random.Int(0, 100) > iChance) {
|
if (zone->random.Int(0, 100) > iChance) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2591,7 +2598,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
|
|||||||
focus_max_real = focus_max;
|
focus_max_real = focus_max;
|
||||||
UsedItem = TempItem;
|
UsedItem = TempItem;
|
||||||
UsedFocusID = TempItem->Focus.Effect;
|
UsedFocusID = TempItem->Focus.Effect;
|
||||||
} else if (focus_max < 0 && focus_max < focus_max_real) {
|
}
|
||||||
|
else if (focus_max < 0 && focus_max < focus_max_real) {
|
||||||
focus_max_real = focus_max;
|
focus_max_real = focus_max;
|
||||||
UsedItem = TempItem;
|
UsedItem = TempItem;
|
||||||
UsedFocusID = TempItem->Focus.Effect;
|
UsedFocusID = TempItem->Focus.Effect;
|
||||||
@@ -2603,7 +2611,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
|
|||||||
realTotal = Total;
|
realTotal = Total;
|
||||||
UsedItem = TempItem;
|
UsedItem = TempItem;
|
||||||
UsedFocusID = TempItem->Focus.Effect;
|
UsedFocusID = TempItem->Focus.Effect;
|
||||||
} else if (Total < 0 && Total < realTotal) {
|
}
|
||||||
|
else if (Total < 0 && Total < realTotal) {
|
||||||
realTotal = Total;
|
realTotal = Total;
|
||||||
UsedItem = TempItem;
|
UsedItem = TempItem;
|
||||||
UsedFocusID = TempItem->Focus.Effect;
|
UsedFocusID = TempItem->Focus.Effect;
|
||||||
@@ -2643,7 +2652,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
|
|||||||
focus_max_real2 = focus_max2;
|
focus_max_real2 = focus_max2;
|
||||||
buff_tracker = buff_slot;
|
buff_tracker = buff_slot;
|
||||||
focusspell_tracker = focusspellid;
|
focusspell_tracker = focusspellid;
|
||||||
} else if (focus_max2 < 0 && focus_max2 < focus_max_real2) {
|
}
|
||||||
|
else if (focus_max2 < 0 && focus_max2 < focus_max_real2) {
|
||||||
focus_max_real2 = focus_max2;
|
focus_max_real2 = focus_max2;
|
||||||
buff_tracker = buff_slot;
|
buff_tracker = buff_slot;
|
||||||
focusspell_tracker = focusspellid;
|
focusspell_tracker = focusspellid;
|
||||||
@@ -2655,7 +2665,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
|
|||||||
realTotal2 = Total2;
|
realTotal2 = Total2;
|
||||||
buff_tracker = buff_slot;
|
buff_tracker = buff_slot;
|
||||||
focusspell_tracker = focusspellid;
|
focusspell_tracker = focusspellid;
|
||||||
} else if (Total2 < 0 && Total2 < realTotal2) {
|
}
|
||||||
|
else if (Total2 < 0 && Total2 < realTotal2) {
|
||||||
realTotal2 = Total2;
|
realTotal2 = Total2;
|
||||||
buff_tracker = buff_slot;
|
buff_tracker = buff_slot;
|
||||||
focusspell_tracker = focusspellid;
|
focusspell_tracker = focusspellid;
|
||||||
@@ -4017,7 +4028,8 @@ bool Merc::UseDiscipline(int32 spell_id, int32 target) {
|
|||||||
|
|
||||||
if (GetEndurance() > spell.endurance_cost) {
|
if (GetEndurance() > spell.endurance_cost) {
|
||||||
SetEndurance(GetEndurance() - spell.endurance_cost);
|
SetEndurance(GetEndurance() - spell.endurance_cost);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
//too fatigued to use this skill right now.
|
//too fatigued to use this skill right now.
|
||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
@@ -6362,3 +6374,155 @@ uint32 Merc::CalcUpkeepCost(uint32 templateID , uint8 level, uint8 currency_type
|
|||||||
|
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ElixirAIDetermineSpellToCast is called during Merc::AICastSpell and overrides normal logic
|
||||||
|
// It determines by class which spell to cast
|
||||||
|
bool Merc::ElixirAIDetermineSpellToCast() {
|
||||||
|
MercSpell selectedMercSpell;
|
||||||
|
int8 spellAIResult;
|
||||||
|
Group* grp = GetGroup();
|
||||||
|
|
||||||
|
switch (GetClass()) {
|
||||||
|
case HEALER:
|
||||||
|
selectedMercSpell = GetBestMercSpellForGroupHeal(this);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetBestMercSpellForHealOverTime(this);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetBestMercSpellForFastHeal(this);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||||
|
if (!grp) break;
|
||||||
|
if (!grp->members[i]) continue;
|
||||||
|
if (!grp->members[i]->qglobal) continue;
|
||||||
|
if (!GetNeedsCured(grp->members[i])) continue;
|
||||||
|
if (grp->members[i]->DontCureMeBefore() > Timer::GetCurrentTime()) continue;
|
||||||
|
selectedMercSpell = GetBestMercSpellForCure(this, grp->members[i]);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetManaRatio() > 50) { // healers only offensive or buff at > 50% mana
|
||||||
|
selectedMercSpell = GetBestMercSpellForStun(this);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetBestMercSpellForNuke(this);
|
||||||
|
if (ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buffSpells = GetMercSpellsBySpellType(this, SpellType_Buff);
|
||||||
|
for (auto buffSpell : buffSpells) {
|
||||||
|
if (!ElixirAITryCastSpell(selectedMercSpell)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case MELEEDPS:
|
||||||
|
if (GetTarget() && HasOrMayGetAggro()) {
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Escape);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Nuke);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_InCombatBuff);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case TANK:
|
||||||
|
if (CheckAETaunt()) {
|
||||||
|
selectedMercSpell = GetBestMercSpellForAETaunt(this);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
Log(Logs::General, Logs::Mercenaries, "%s AE Taunting.", GetName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CheckTaunt()) {
|
||||||
|
selectedMercSpell = GetBestMercSpellForTaunt(this);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetBestMercSpellForHate(this);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Nuke);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_InCombatBuff);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case CASTERDPS:
|
||||||
|
if (GetTarget() && HasOrMayGetAggro()) {
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Escape);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Nuke);
|
||||||
|
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElixirAITryCastSpell takes a provided spell id and does a spell check to determine if the spell is valid
|
||||||
|
// Once valid, it will cast on returned mob candidate
|
||||||
|
bool Merc::ElixirAITryCastSpell(MercSpell mercSpell, bool isHeal) {
|
||||||
|
auto spellID = mercSpell.spellid;
|
||||||
|
if (spellID == 0) return false;
|
||||||
|
|
||||||
|
Mob* outMob = nullptr;
|
||||||
|
auto spellAIResult = ElixirCastSpellCheck(spellID, &outMob);
|
||||||
|
|
||||||
|
if (spellAIResult < 0) return false;
|
||||||
|
|
||||||
|
if (spellAIResult == 0) {
|
||||||
|
AIDoSpellCast(spellID, GetTarget(), -1);
|
||||||
|
if (GetTarget() == this) return true;
|
||||||
|
if (isHeal) MercGroupSay(this, "Casting %s on %s.", spells[spellID].name, GetTarget()->GetCleanName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outMob == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AIDoSpellCast(spellID, outMob, -1);
|
||||||
|
if (outMob == this) return true;
|
||||||
|
if (isHeal) MercGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ public:
|
|||||||
bool CheckAETaunt();
|
bool CheckAETaunt();
|
||||||
bool CheckConfidence();
|
bool CheckConfidence();
|
||||||
bool TryHide();
|
bool TryHide();
|
||||||
|
bool ElixirAIDetermineSpellToCast();
|
||||||
|
bool ElixirAITryCastSpell(MercSpell mercSpell, bool isHeal = false);
|
||||||
|
|
||||||
// stat functions
|
// stat functions
|
||||||
virtual void ScaleStats(int scalepercent, bool setmax = false);
|
virtual void ScaleStats(int scalepercent, bool setmax = false);
|
||||||
|
|||||||
+2
-1
@@ -355,6 +355,7 @@ public:
|
|||||||
void BeamDirectional(uint16 spell_id, int16 resist_adjust);
|
void BeamDirectional(uint16 spell_id, int16 resist_adjust);
|
||||||
void ConeDirectional(uint16 spell_id, int16 resist_adjust);
|
void ConeDirectional(uint16 spell_id, int16 resist_adjust);
|
||||||
void TryOnSpellFinished(Mob *caster, Mob *target, uint16 spell_id);
|
void TryOnSpellFinished(Mob *caster, Mob *target, uint16 spell_id);
|
||||||
|
bool IsWithinSpellRange(Mob *target, float spellRange, uint16 spellID);
|
||||||
|
|
||||||
//Buff
|
//Buff
|
||||||
void BuffProcess();
|
void BuffProcess();
|
||||||
@@ -854,7 +855,7 @@ public:
|
|||||||
inline bool HasBaseEffectFocus() const { return (spellbonuses.FocusEffects[focusFcBaseEffects] || aabonuses.FocusEffects[focusFcBaseEffects] || itembonuses.FocusEffects[focusFcBaseEffects]); }
|
inline bool HasBaseEffectFocus() const { return (spellbonuses.FocusEffects[focusFcBaseEffects] || aabonuses.FocusEffects[focusFcBaseEffects] || itembonuses.FocusEffects[focusFcBaseEffects]); }
|
||||||
int32 GetDualWieldingSameDelayWeapons() const { return dw_same_delay; }
|
int32 GetDualWieldingSameDelayWeapons() const { return dw_same_delay; }
|
||||||
inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; }
|
inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; }
|
||||||
|
int8 ElixirCastSpellCheck(uint16 spellID, Mob** outMob);
|
||||||
bool TryDoubleMeleeRoundEffect();
|
bool TryDoubleMeleeRoundEffect();
|
||||||
bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; }
|
bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; }
|
||||||
inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; }
|
inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; }
|
||||||
|
|||||||
@@ -6327,3 +6327,24 @@ int Client::GetNextAvailableDisciplineSlot(int starting_slot) {
|
|||||||
|
|
||||||
return -1; // Return -1 if No Slots open
|
return -1; // Return -1 if No Slots open
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsWithinRange returns true if target is within range of spell ID casted by mob
|
||||||
|
bool Mob::IsWithinSpellRange(Mob *target, float spellRange, uint16 spellID) {
|
||||||
|
float range = GetActSpellRange(spellID, spellRange, false);
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target->GetID() == GetID()) return true;
|
||||||
|
|
||||||
|
float dist2 = DistanceSquared(GetPosition(), target->GetPosition());
|
||||||
|
float range2 = spellRange * spellRange;
|
||||||
|
float min_range2 = spells[spellID].min_range * spells[spellID].min_range;
|
||||||
|
if(dist2 > range2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dist2 < min_range2){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user