This commit is contained in:
JJ 2020-03-05 21:40:33 -05:00
commit 09e9c0b504
23 changed files with 364 additions and 142 deletions

View File

@ -1,44 +0,0 @@
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
# https://github.com/microsoft/vscode-dev-containers/tree/v0.101.1/containers/ubuntu-18.04-git/.devcontainer/Dockerfile
FROM ubuntu:18.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends build-essential \
gcc-5 g++-5 libtool cmake curl debconf-utils \
git git-core libio-stringy-perl liblua5.1 \
liblua5.1-dev libluabind-dev libmysql++ \
libperl-dev libperl5i-perl libsodium-dev \
libsodium23 libmysqlclient-dev lua5.1 \
minizip make mariadb-client \
# optional, mariadb server
mariadb-server \
# optional, debugging tools
gdb valgrind \
#
nano open-vm-tools unzip uuid-dev \
zlibc wget \
# #
# # Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
ENV DEBIAN_FRONTEND=dialog
EXPOSE 3306
EXPOSE 5558
EXPOSE 5559
EXPOSE 7000
EXPOSE 7001
EXPOSE 7002
EXPOSE 7003
EXPOSE 7004
EXPOSE 7005
EXPOSE 7778
EXPOSE 9000
EXPOSE 9001
EXPOSE 9080
EXPOSE 9081

View File

@ -1,28 +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",
"dockerFile": "Dockerfile",
"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": [],
"appPort": [3306, 5558, 5559, 7000, 7001, 7002, 7003, 7004, 7005, 7778, 9000, 9001, 9080, 9081],
// "forwardPorts": [3306, 5558, 5559, 7000, 7001, 7002, 7003, 7004, 7005, 7778, 9000, 9001, 9080, 9081],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a",
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker.
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
// "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
// "remoteUser": "eqemu"
}
"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
*.app
.bash_history
# CMake
CMakeCache.txt
CMakeFiles

101
.vscode/tasks.json vendored
View File

@ -15,10 +15,22 @@
"$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 && 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": {
"kind": "build",
"isDefault": true
@ -38,9 +50,66 @@
}
},
{
"label": "zone",
"label": "download maps",
"type": "shell",
"command": "cd build/bin && ./zone",
"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
@ -49,20 +118,38 @@
{
"label": "loginserver",
"type": "shell",
"command": "cd build/bin && ./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": {
"kind": "test",
"isDefault": true
}
},
{
"label": "world",
"label": "shared_memory, world",
"type": "shell",
"command": "cd build/bin && ./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": {
"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

@ -34,7 +34,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9150
#define CURRENT_BINARY_DATABASE_VERSION 9151
#ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9026

View File

@ -404,6 +404,7 @@
9148|2020_01_28_corpse_guild_consent_id.sql|SHOW COLUMNS FROM `character_corpses` LIKE 'guild_consent_id'|empty|
9149|2020_02_06_globalloot.sql|SHOW COLUMNS FROM `global_loot` LIKE 'hot_zone'|empty|
9150|2020_02_06_aa_reset_on_death.sql|SHOW COLUMNS FROM `aa_ability` LIKE 'reset_on_death'|empty|
9151|2020_03_05_npc_always_aggro.sql|SHOW COLUMNS FROM `npc_types` LIKE 'always_aggros_foes'|empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1 @@
ALTER TABLE `npc_types` ADD COLUMN `always_aggros_foes` tinyint(4) NOT NULL DEFAULT 0;

View File

@ -139,7 +139,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) {
if (RuleB(Aggro, UseLevelAggro))
{
if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3)
if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3 && !AlwaysAggrosFoes())
{
towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(), dist2, iAggroRange2);
return;
@ -147,7 +147,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) {
}
else
{
if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY ) {
if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY && !AlwaysAggrosFoes()) {
towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(),
dist2, iAggroRange2);
return;
@ -318,7 +318,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
//old InZone check taken care of above by !mob->CastToClient()->Connected()
(
( GetLevel() >= RuleI(Aggro, MinAggroLevel))
||(GetBodyType() == 3)
||(GetBodyType() == 3) || AlwaysAggrosFoes()
||( mob->IsClient() && mob->CastToClient()->IsSitting() )
||( mob->GetLevelCon(GetLevel()) != CON_GRAY)
@ -352,6 +352,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
//old InZone check taken care of above by !mob->CastToClient()->Connected()
(
( GetINT() <= RuleI(Aggro, IntAggroThreshold) )
|| AlwaysAggrosFoes()
||( mob->IsClient() && mob->CastToClient()->IsSitting() )
||( mob->GetLevelCon(GetLevel()) != CON_GRAY)
@ -383,6 +384,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
LogAggro("Dist^2: [{}]\n", dist2);
LogAggro("Range^2: [{}]\n", iAggroRange2);
LogAggro("Faction: [{}]\n", fv);
LogAggro("AlwaysAggroFlag: [{}]\n", AlwaysAggrosFoes());
LogAggro("Int: [{}]\n", GetINT());
LogAggro("Con: [{}]\n", GetLevelCon(mob->GetLevel()));

View File

@ -56,7 +56,7 @@ Beacon::Beacon(Mob *at_mob, int lifetime)
:Mob
(
nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, at_mob->GetPosition(), 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false
),
remove_timer(lifetime),
spell_timer(0)

View File

@ -6421,32 +6421,52 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
bool taunt_time = taunt_timer.Check();
bool ca_time = classattack_timer.Check(false);
bool ma_time = monkattack_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;
}
if(ka_time){
int knightreuse = 1000;
switch(GetClass()){
case SHADOWKNIGHT:
case SHADOWKNIGHTGM: {
case SHADOWKNIGHT: {
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
knightreuse = (HarmTouchReuseTime * 1000);
knightattack_timer.Start(HarmTouchReuseTime * 1000);
break;
}
case PALADIN:
case PALADINGM: {
case PALADIN: {
if(GetHPRatio() < 20) {
CastSpell(SPELL_LAY_ON_HANDS, GetID());
knightreuse = (LayOnHandsReuseTime * 1000);
knightattack_timer.Start(LayOnHandsReuseTime * 1000);
}
else {
knightattack_timer.Start(2000);
}
else
knightreuse = 2000;
break;
}
default: {
break;
}
}
knightattack_timer.Start(knightreuse);
}
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;
}
float HasteModifier = (GetHaste() * 0.01f);
uint16 skill_to_use = -1;
@ -6493,18 +6571,22 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
break;
case MONK:
if(GetLevel() >= 30)
if (GetLevel() >= 30) {
skill_to_use = EQEmu::skills::SkillFlyingKick;
else if(GetLevel() >= 25)
}
else if (GetLevel() >= 25) {
skill_to_use = EQEmu::skills::SkillDragonPunch;
else if(GetLevel() >= 20)
}
else if (GetLevel() >= 20) {
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;
else
}
else {
skill_to_use = EQEmu::skills::SkillKick;
}
break;
case ROGUE:
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);
MonkSpecialAttack(target, skill_to_use);
uint32 bDoubleSpecialAttack = (itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack);
if(bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0, 100))) {
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);
// Live AA - Technique of Master Wu
int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
if(TripleChance > zone->random.Int(0,100))
MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]);
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)] : skill_to_use));
--extra;
}
}
reuse *= 1000;
@ -8966,6 +9083,12 @@ void Bot::CalcBotStats(bool showtext) {
skills[sindex] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)sindex, GetLevel());
}
taunt_timer.Start(1000);
if (GetClass() == MONK && GetLevel() >= 10) {
monkattack_timer.Start(1000);
}
LoadAAs();
GenerateSpecialAttacks();

View File

@ -3928,6 +3928,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>"
"</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>"
"<td><c \"#CCCCCC\">current</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"));
}
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")) {
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 \"#00FF00\">Argument<br>-------</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\">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\">autodefend</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">buffcounter</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\">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\">autodefend</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>",
(c->GetBotOption(Client::booDeathMarquee) ? "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"),
(RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "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());

View File

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

View File

@ -121,7 +121,8 @@ Client::Client(EQStreamInterface* ieqs)
0,
0,
0,
0
0,
false
),
hpupdate_timer(2000),
camp_timer(29000),
@ -358,6 +359,7 @@ Client::Client(EQStreamInterface* ieqs)
bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat);
bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend);
bot_owner_options[booBuffCounter] = false;
bot_owner_options[booMonkWuMessage] = false;
SetBotPulling(false);
SetBotPrecombat(false);

View File

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

View File

@ -154,7 +154,7 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP
in_npc->GetPosition(), in_npc->GetInnateLightType(), in_npc->GetTexture(),in_npc->GetHelmTexture(),
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,EQEmu::TintProfile(),0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
(*in_npctypedata)->use_model),
(*in_npctypedata)->use_model, false),
corpse_decay_timer(in_decaytime),
corpse_rez_timer(0),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
@ -260,8 +260,9 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
0, // uint8 in_bracertexture,
0, // uint8 in_handtexture,
0, // uint8 in_legtexture,
0,
0 // uint8 in_feettexture,
0, // uint8 in_feettexture,
0, // uint8 in_usemodel,
0 // bool in_always_aggros_foes
),
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
@ -500,7 +501,8 @@ EQEmu::TintProfile(),
0,
0,
0,
0),
0,
false),
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),

View File

@ -36,7 +36,7 @@ Encounter::Encounter(const char* enc_name)
:Mob
(
nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, glm::vec4(0,0,0,0), 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQEmu::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false
)
{
encounter_name[0] = 0;

View File

@ -93,7 +93,8 @@ Mob::Mob(
uint8 in_handtexture,
uint8 in_legtexture,
uint8 in_feettexture,
uint16 in_usemodel
uint16 in_usemodel,
bool in_always_aggros_foes
) :
attack_timer(2000),
attack_dw_timer(2000),
@ -275,6 +276,7 @@ Mob::Mob(
qglobal = 0;
spawned = false;
rare_spawn = false;
always_aggros_foes = in_always_aggros_foes;
InitializeBuffSlots();

View File

@ -161,7 +161,8 @@ public:
uint8 in_handtexture,
uint8 in_legtexture,
uint8 in_feettexture,
uint16 in_usemodel
uint16 in_usemodel,
bool in_always_aggros_foes
);
virtual ~Mob();
@ -578,6 +579,7 @@ public:
inline const GravityBehavior GetFlyMode() const { return flymode; }
bool IsBoat() const;
bool IsControllableBoat() const;
inline const bool AlwaysAggrosFoes() const { return always_aggros_foes; }
//Group
virtual bool HasRaid() = 0;
@ -1389,6 +1391,7 @@ protected:
Timer ranged_timer;
float attack_speed; //% increase/decrease in attack speed (not haste)
int attack_delay; //delay between attacks in 10ths of seconds
bool always_aggros_foes;
int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%)
Timer tic_timer;
Timer mana_timer;

View File

@ -113,11 +113,13 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
npc_type_data->handtexture,
npc_type_data->legtexture,
npc_type_data->feettexture,
npc_type_data->use_model
npc_type_data->use_model,
npc_type_data->always_aggros_foes
),
attacked_timer(CombatEventTimer_expire),
swarm_timer(100),
classattack_timer(1000),
monkattack_timer(1000),
knightattack_timer(1000),
assist_timer(AIassistcheck_delay),
qglobal_purge_timer(30000),
@ -307,7 +309,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
// 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 (IsBot()) {
if (GetClass() != PALADIN && GetClass() != SHADOWKNIGHT) {
knightattack_timer.Disable();
}
else if (GetClass() != MONK || GetLevel() < 10) {
monkattack_timer.Disable();
}
}
else {
if (moblevel > 50) {
skills[EQEmu::skills::SkillDoubleAttack] = 250;
skills[EQEmu::skills::SkillDualWield] = 250;
@ -3233,4 +3243,4 @@ void NPC::RecalculateSkills()
skills[EQEmu::skills::SkillDoubleAttack] = level * 5;
}
}
}
}

View File

@ -499,6 +499,7 @@ protected:
Timer attacked_timer; //running while we are being attacked (damaged)
Timer swarm_timer;
Timer monkattack_timer; //additional timer for tiger claw usage
Timer classattack_timer;
Timer knightattack_timer;
Timer assist_timer; //ask for help from nearby mobs

View File

@ -379,31 +379,35 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk)
ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction;
// Live AA - Technique of Master Wu
int wuchance =
itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
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};
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
if (zone->random.Roll(wuchance)) {
++extra;
}
else {
break;
}
wuchance /= 4;
}
// They didn't add a string ID for this.
std::string msg = StringFormat(
"The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
std::string msg = StringFormat("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
SendColoredText(400, msg);
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
MonkSpecialAttack(GetTarget(),
classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill);
extra--;
MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill));
--extra;
}
}

View File

@ -2506,7 +2506,8 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
"npc_types.rare_spawn, "
"npc_types.stuck_behavior, "
"npc_types.model, "
"npc_types.flymode "
"npc_types.flymode, "
"npc_types.always_aggros_foes "
"FROM npc_types %s",
where_condition.c_str()
);
@ -2703,11 +2704,12 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
temp_npctype_data->charm_avoidance_rating = atoi(row[105]);
temp_npctype_data->charm_atk = atoi(row[106]);
temp_npctype_data->skip_global_loot = atoi(row[107]) != 0;
temp_npctype_data->rare_spawn = atoi(row[108]) != 0;
temp_npctype_data->stuck_behavior = atoi(row[109]);
temp_npctype_data->use_model = atoi(row[110]);
temp_npctype_data->flymode = atoi(row[111]);
temp_npctype_data->skip_global_loot = atoi(row[107]) != 0;
temp_npctype_data->rare_spawn = atoi(row[108]) != 0;
temp_npctype_data->stuck_behavior = atoi(row[109]);
temp_npctype_data->use_model = atoi(row[110]);
temp_npctype_data->flymode = atoi(row[111]);
temp_npctype_data->always_aggros_foes = atoi(row[112]);
temp_npctype_data->skip_auto_scale = false; // hardcoded here for now

View File

@ -147,6 +147,7 @@ struct NPCType
int8 stuck_behavior;
uint16 use_model;
int8 flymode;
bool always_aggros_foes;
};
namespace player_lootitem {