mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-30 11:45:46 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05f566ad07 | |||
| c7a8d796fc | |||
| 77163ec137 | |||
| e846bb86b6 | |||
| 3e6a3e2168 | |||
| 2aebf1a78a | |||
| 1be7e56b86 | |||
| a0ff9d67a1 | |||
| befee1c729 | |||
| 3d70063a68 | |||
| 4e28bcf85e | |||
| 687d10960a | |||
| 567d46c3d6 | |||
| 53cc2de459 | |||
| cb866cba31 | |||
| e2162c08da | |||
| e657953b8f | |||
| eb366e67b7 | |||
| 5b728a42f7 | |||
| a1d414d64c | |||
| 907029ed76 | |||
| 894f22fba0 | |||
| 7ec09d7e0f | |||
| c82266790a | |||
| ec31fddbae | |||
| fb49ce2404 | |||
| 276b7e238a | |||
| c7a463420b | |||
| a56bb52808 | |||
| 0bbdb58679 | |||
| f29478c105 | |||
| 888a88f966 | |||
| 3b617a6652 | |||
| c36c336bc7 | |||
| 4a9779635d | |||
| 20da490bda | |||
| 1221e88d92 | |||
| a2b28b2e16 | |||
| 3d607d352c | |||
| 83cd8119c8 | |||
| 4de8fbbd56 | |||
| 780120036d | |||
| f3697e633c | |||
| 24f8d88333 | |||
| 21bd906a4d | |||
| 138612bc88 | |||
| 5919bb4dea | |||
| 99d249fefd | |||
| c08f286817 | |||
| cd003ff0b7 | |||
| 1a539f6656 | |||
| 7e7fb7b758 | |||
| 5ae87b40e2 | |||
| 0ec07daebb | |||
| 9869da2a0a | |||
| 617eb4432b |
@@ -1,3 +1,90 @@
|
||||
## [23.7.0] 5/19/2025
|
||||
|
||||
### CLI
|
||||
|
||||
* Add custom database version output ([#4901](https://github.com/EQEmu/Server/pull/4901)) @joligario 2025-05-18
|
||||
* Fix MySQL check in database dumper ([#4897](https://github.com/EQEmu/Server/pull/4897)) @joligario 2025-05-16
|
||||
|
||||
### Commands
|
||||
|
||||
* Add #zonevariable Command ([#4882](https://github.com/EQEmu/Server/pull/4882)) @Kinglykrab 2025-05-15
|
||||
|
||||
### Database
|
||||
|
||||
* Add Custom Database Migrations for Operators ([#4892](https://github.com/EQEmu/Server/pull/4892)) @Akkadius 2025-05-16
|
||||
* Remove Transaction Wrapped Character Save ([#4894](https://github.com/EQEmu/Server/pull/4894)) @Akkadius 2025-05-16
|
||||
|
||||
### Fixes
|
||||
|
||||
* Deadlock on failed #copycharacter commands ([#4887](https://github.com/EQEmu/Server/pull/4887)) @Akkadius 2025-05-16
|
||||
|
||||
### Logging
|
||||
|
||||
* Auto Update Log Category Names ([#4890](https://github.com/EQEmu/Server/pull/4890)) @Akkadius 2025-05-16
|
||||
|
||||
### Netcode
|
||||
|
||||
* Resend Logic Adjustments ([#4900](https://github.com/EQEmu/Server/pull/4900)) @Akkadius 2025-05-18
|
||||
|
||||
### Player Events
|
||||
|
||||
* Add rule to ignore configured GM commands ([#4888](https://github.com/EQEmu/Server/pull/4888)) @Akkadius 2025-05-16
|
||||
|
||||
### Rules
|
||||
|
||||
* Auto Update Rule Notes from Source ([#4891](https://github.com/EQEmu/Server/pull/4891)) @Akkadius 2025-05-16
|
||||
|
||||
### World
|
||||
|
||||
* Fix Rarer Reload Deadlock ([#4893](https://github.com/EQEmu/Server/pull/4893)) @Akkadius 2025-05-16
|
||||
|
||||
### Zone State
|
||||
|
||||
* Load New Spawn2 Data When Present ([#4889](https://github.com/EQEmu/Server/pull/4889)) @Akkadius 2025-05-16
|
||||
|
||||
## [23.6.0] 5/14/2025
|
||||
|
||||
### Bots
|
||||
|
||||
* Correct ^pull logic and add checks for Enchanter pets ([#4827](https://github.com/EQEmu/Server/pull/4827)) @nytmyr 2025-05-15
|
||||
* Fix creation limit, spawn limit, level requirement checks ([#4868](https://github.com/EQEmu/Server/pull/4868)) @nytmyr 2025-05-15
|
||||
* Move all spell_id instances to uint16 ([#4876](https://github.com/EQEmu/Server/pull/4876)) @nytmyr 2025-05-15
|
||||
* Prevent non-taunters from potentially fleeing mob on TargetReflection ([#4859](https://github.com/EQEmu/Server/pull/4859)) @nytmyr 2025-04-28
|
||||
|
||||
### CLI
|
||||
|
||||
* ETL Settings Output ([#4873](https://github.com/EQEmu/Server/pull/4873)) @joligario 2025-05-15
|
||||
|
||||
### Code
|
||||
|
||||
* Fix typo in QueryNameAvailablity ([#4869](https://github.com/EQEmu/Server/pull/4869)) @nytmyr 2025-04-28
|
||||
|
||||
### Crash
|
||||
|
||||
* Fix crash bug with pbae and quest scripts spawning mobs ([#4884](https://github.com/EQEmu/Server/pull/4884)) @carolus21rex 2025-05-15
|
||||
|
||||
### Feature
|
||||
|
||||
* Add Character:TradeskillUpMinChance rule ([#4867](https://github.com/EQEmu/Server/pull/4867)) @zrix-eq 2025-05-15
|
||||
* Enable spawn attribute for NPCTintID ([#4871](https://github.com/EQEmu/Server/pull/4871)) @neckkola 2025-05-15
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add trader/buyer cleanup actions ([#4843](https://github.com/EQEmu/Server/pull/4843)) @neckkola 2025-05-15
|
||||
* Fix #copycharacter command ([#4860](https://github.com/EQEmu/Server/pull/4860)) @nytmyr 2025-04-28
|
||||
* Fix Crash with #task ([#4874](https://github.com/EQEmu/Server/pull/4874)) @Kinglykrab 2025-04-30
|
||||
* Fix Object Name Init, User Refs, and Client Sync on Close ([#4861](https://github.com/EQEmu/Server/pull/4861)) @zimp-wow 2025-05-15
|
||||
* Fix breaking change to UF patches caused by Big Bags update ([#4883](https://github.com/EQEmu/Server/pull/4883)) @hbingram 2025-05-15
|
||||
* Prevent Ranged Attack from being triggered at arbitrary rate ([#4879](https://github.com/EQEmu/Server/pull/4879)) @catapultam-habeo 2025-05-15
|
||||
|
||||
### Performance
|
||||
|
||||
* Store Player Title Sets in Client Memory ([#4836](https://github.com/EQEmu/Server/pull/4836)) @Kinglykrab 2025-05-15
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add Last Login and First Login Flags to EVENT_CONNECT ([#4866](https://github.com/EQEmu/Server/pull/4866)) @Kinglykrab 2025-05-15
|
||||
|
||||
## [23.5.0] 4/10/2025
|
||||
|
||||
### API
|
||||
|
||||
@@ -42,6 +42,7 @@ IF(USE_MAP_MMFS)
|
||||
ENDIF (USE_MAP_MMFS)
|
||||
|
||||
IF(MSVC)
|
||||
add_compile_options(/bigobj)
|
||||
ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS)
|
||||
ADD_DEFINITIONS(-DNOMINMAX)
|
||||
ADD_DEFINITIONS(-DCRASH_LOGGING)
|
||||
|
||||
@@ -1,79 +1,147 @@
|
||||
# EQEmulator Core Server
|
||||
| Drone (Linux x64) | Drone (Windows x64) |
|
||||
|:---:|:---:|
|
||||
|[](http://drone.akkadius.com/EQEmu/Server) |[](http://drone.akkadius.com/EQEmu/Server) |
|
||||
<h1 align="center">EQEmulator Server Platform</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/11942e15-b512-402d-a619-0543c7f1151e" style="border-radius: 10px">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<b>EverQuest Emulator (EQEmu) - A Fan-Made Project Honoring the Legendary MMORPG</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/eqemu/server/graphs/contributors"><img src="https://img.shields.io/github/contributors/eqemu/server" alt="Contributors"></a>
|
||||
<a href="https://discord.gg/QHsm7CD"><img src="https://img.shields.io/discord/212663220849213441?label=Discord&logo=discord&color=7289DA" alt="Discord"></a>
|
||||
<a href="https://docs.eqemu.io"><img src="https://img.shields.io/badge/docs-MkDocs%20Powered-blueviolet" alt="Docs"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/github/license/EQEmu/Server" alt="License"></a>
|
||||
<a href="https://github.com/eqemu/server/releases"><img src="https://img.shields.io/github/v/release/eqemu/server" alt="Latest Release"></a>
|
||||
<a href="https://github.com/EQEmu/Server/releases"><img src="https://img.shields.io/github/release-date/EQEmu/Server" alt="Release Date"></a>
|
||||
<img src="https://img.shields.io/github/downloads/eqemu/server/total.svg" alt="Github All Releases"></a>
|
||||
<a href="http://drone.akkadius.com/EQEmu/Server"><img src="http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg" alt="Build Status"></a>
|
||||
<img src="https://img.shields.io/github/issues-pr-closed/eqemu/server" alt="GitHub Issues or Pull Requests">
|
||||
<img src="https://img.shields.io/docker/pulls/akkadius/eqemu-server" alt="Docker Pulls">
|
||||
<a href="http://drone.akkadius.com/EQEmu/Server"><img src="http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg" alt="Build Status"></a> <img src="https://jb.gg/badges/official-plastic.svg" alt="Official">
|
||||
|
||||
</p>
|
||||
|
||||
***
|
||||
|
||||
**EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++**
|
||||
* MySQL/MariaDB is used as the database engine (over 200+ tables)
|
||||
* Perl and LUA are both supported scripting languages for NPC/Player/Quest oriented events
|
||||
* Open source database (Project EQ) has content up to expansion OoW (included in server installs)
|
||||
* Game server environments and databases can be heavily customized to create all new experiences
|
||||
* Hundreds of Quests/events created and maintained by Project EQ
|
||||
<p align="center">
|
||||
EQEmulator is a <b>passion-driven</b>, <b>open source server emulator</b> project dedicated to preserving and celebrating the groundbreaking world of <b>EverQuest</b>, the massively multiplayer online role-playing game originally developed by <b>Verant Interactive</b> and <b>Sony Online Entertainment (now Daybreak Game Company)</b>.
|
||||
</p>
|
||||
|
||||
## Server Installs
|
||||
| |Windows|Linux|
|
||||
|:---:|:---:|:---:|
|
||||
|**Install Count**|||
|
||||
### > Windows
|
||||
<p align="center">
|
||||
For over two decades and continuing, EQEmulator has served as a <strong>fan tribute</strong>, providing tools and technology that allow players to explore, customize, and experience EverQuest’s iconic gameplay in new ways. This project exists solely out of <strong>deep admiration</strong> for the original developers, artists, designers, and visionaries who created one of the most influential online worlds of all time.
|
||||
</p>
|
||||
|
||||
* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-windows/)
|
||||
<p align="center">
|
||||
We do not claim ownership of EverQuest or its assets. <strong>All credit and respect belong to the original creators and Daybreak Game Company</strong>, whose work continues to inspire generations of players and developers alike.
|
||||
</p>
|
||||
|
||||
### > Debian/Ubuntu/CentOS/Fedora
|
||||
<p align="center">
|
||||
EQEmulator has for over 20 years and always will be a <strong>fan-based, non-commercial open-source effort</strong> made by players, for players—preserving the legacy of EverQuest while empowering community-driven creativity, learning and joy that the game and its creators has so strongly inspired in us all.
|
||||
</p>
|
||||
|
||||
* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-linux/)
|
||||
***
|
||||
|
||||
* You can use curl or wget to kick off the installer (whichever your OS has)
|
||||
> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh
|
||||
<h3 align="center">
|
||||
Technical Overview & Reverse Engineering Effort
|
||||
</h1>
|
||||
|
||||
> wget --no-check-certificate https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh -O install.sh && chmod 755 install.sh && ./install.sh
|
||||
<p align="center">EQEmulator represents <strong>over two decades of collaborative reverse engineering</strong>, rebuilding the EverQuest server from the ground up without access to the original source code. This effort was achieved entirely through <strong>community-driven analysis, network protocol decoding, and in-game behavioral research</strong>.</p>
|
||||
|
||||
## Supported Clients
|
||||
<h1 align="center">
|
||||
💡 How We Did It
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/b6b48cf7-f64a-4497-9750-71f442a3d132" height="300px">
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<strong>Reverse Engineering</strong>
|
||||
Every system, packet, opcode, and game mechanic has been reconstructed through countless hours of live packet sniffing, client disassembly, and in-game experimentation by dedicated contributors over the years.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
No proprietary code or server sources were ever used.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
All implementations are the result of clean-room engineering.
|
||||
</p>
|
||||
|
||||
|
||||
<h1 align="center">
|
||||
🛠️ Technology Stack
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/df5ea809-86c5-439d-a8fa-651fb04ba477" style="border-radius: 10px">
|
||||
</p>
|
||||
|
||||
**C++ Core Engine**
|
||||
|
||||
* High-performance networking and gameplay logic built in C++
|
||||
* Cross-platform support for Linux and Windows
|
||||
|
||||
**MySQL / MariaDB Backend**
|
||||
|
||||
* Fully structured schema with over 200+ tables
|
||||
* Supports content customization, expansions, and custom worlds
|
||||
|
||||
**Scripting Engine**
|
||||
|
||||
* Native support for **Perl** and **Lua** scripting
|
||||
* Powerfully extendable for quests, NPC behaviors, and custom events
|
||||
|
||||
**Open Source Content Database**
|
||||
|
||||
* Includes ProjectEQ’s world data up through *Dragons of Norrath*
|
||||
* 100% customizable to create entirely new game worlds
|
||||
|
||||
<h1 align="center">
|
||||
🚀 Why It Matters
|
||||
</h1>
|
||||
|
||||
<p align="center">🧬 EQEmulator stands as a <strong>technical preservation project</strong>, ensuring that the magic of classic and custom EverQuest servers lives on for future generations of players, tinkerers, and game designers.
|
||||
</p>
|
||||
|
||||
> We humbly acknowledge and thank the original developers at **Verant Interactive** and **Sony Online Entertainment (now Daybreak Game Company)** for creating one of the most influential online experiences in gaming history.
|
||||
|
||||
<h1 align="center">
|
||||
🧑💻🖥️ Supported Clients
|
||||
</h1>
|
||||
|
||||
|Titanium Edition|Secrets of Faydwer|Seeds of Destruction|Underfoot|Rain of Fear|
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
|<img src="http://i.imgur.com/hrwDxoM.jpg" height="150">|<img src="http://i.imgur.com/cRDW5tn.png" height="150">|<img src="http://i.imgur.com/V48kuVn.jpg" height="150">|<img src="http://i.imgur.com/IJQ0XMa.jpg" height="150">|<img src="http://i.imgur.com/OMpHkKa.png" height="100">|
|
||||
|
||||
## Bug Reports <img src="http://i.imgur.com/daf1Vjw.png" height="20">
|
||||
* Please use the [issue tracker](https://github.com/EQEmu/Server/issues) provided by GitHub to send us bug
|
||||
reports or feature requests.
|
||||
* The [EQEmu Forums](http://www.eqemulator.org/forums/) are also a place to submit and get help with bugs.
|
||||
## 📚 Resources
|
||||
|
||||
## Contributions <img src="http://image.flaticon.com/icons/png/512/25/25231.png" width="20">
|
||||
| Resource | Badges | Link |
|
||||
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
||||
| **EQEmulator Docs** | [](https://docs.eqemu.io) | [docs.eqemu.io](https://docs.eqemu.io/) |
|
||||
| **Discord Community**| [](https://discord.gg/QHsm7CD) | [Join Discord](https://discord.gg/QHsm7CD) |
|
||||
| **Latest Release** | [](https://github.com/eqemu/server/releases) <br> [](https://github.com/EQEmu/Server/releases) <br> [](https://github.com/eqemu/server/releases) | [View Releases](https://github.com/eqemu/server/releases) |
|
||||
| **License** | [](./LICENSE) | [View License](./LICENSE) |
|
||||
| **Build Status** | [](http://drone.akkadius.com/EQEmu/Server) | [View Build Status](http://drone.akkadius.com/EQEmu/Server) |
|
||||
| **Docker Pulls** | [](https://hub.docker.com/r/akkadius/eqemu-server) | [Docker Hub](https://hub.docker.com/r/akkadius/eqemu-server) |
|
||||
| **Contributions** | [](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) | [Closed PRs & Issues](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) |
|
||||
|
||||
* The preferred way to contribute is to fork the repo and submit a pull request on
|
||||
GitHub. If you need help with your changes, you can always post on the forums or
|
||||
try Discord. You can also post unified diffs (`git diff` should do the trick) on the
|
||||
[Server Code Submissions](http://www.eqemulator.org/forums/forumdisplay.php?f=669)
|
||||
forum, although pull requests will be much quicker and easier on all parties.
|
||||
## 🛠️ Getting Started
|
||||
|
||||
## Contact <img src="http://gamerescape.com/wp-content/uploads/2015/06/discord.png" height="20">
|
||||
If you want to set up your own EQEmulator server, please refer to the current [server installation guides](https://docs.eqemu.io/#server-installation). We've had 100,000s of players and developers use our guides to set up their own servers, and we hope you will too!
|
||||
|
||||
- Discord Channel: https://discord.gg/QHsm7CD
|
||||
- **User Discord Channel**: `#general`
|
||||
- **Developer Discord Channel**: `#eqemucoders`
|
||||
## 🗂️ Related Repositories
|
||||
|
||||
## Resources
|
||||
- [EQEmulator Forums](http://www.eqemulator.org/forums)
|
||||
- [EQEmulator Wiki](https://docs.eqemu.io/)
|
||||
| Repository | Description |
|
||||
|--------------------|----------------------------------------------------------------------------------|
|
||||
| [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) | Official quests and event scripts for ProjectEQ |
|
||||
| [Maps](https://github.com/Akkadius/EQEmuMaps) | EQEmu-compatible zone maps |
|
||||
| [Installer Resources](https://github.com/Akkadius/EQEmuInstall) | Scripts and assets for setting up EQEmu servers |
|
||||
| [Zone Utilities](https://github.com/EQEmu/zone-utilities) | Utilities for parsing, rendering, and manipulating EQ zone files |
|
||||
|
||||
## Related Repositories
|
||||
* [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests)
|
||||
* [Maps](https://github.com/Akkadius/EQEmuMaps)
|
||||
* [Installer Resources](https://github.com/Akkadius/EQEmuInstall)
|
||||
* [Zone Utilities](https://github.com/EQEmu/zone-utilities) - Various utilities and libraries for parsing, rendering and manipulating EQ Zone files.
|
||||
|
||||
## Other License Info
|
||||
|
||||
* The server code and utilities are released under **GPLv3**
|
||||
* We also include some small libraries for convienence that may be under different licensing
|
||||
* SocketLib - GPL LibXML
|
||||
* zlib - zlib license
|
||||
* MariaDB/MySQL - GPL
|
||||
* GPL Perl - GPL / ActiveState (under the assumption that this is a free project)
|
||||
* CPPUnit - GLP StringUtilities - Apache
|
||||
* LUA - MIT
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ SET(common_sources
|
||||
database.cpp
|
||||
database_instances.cpp
|
||||
database/database_update_manifest.cpp
|
||||
database/database_update_manifest_custom.cpp
|
||||
database/database_update_manifest_bots.cpp
|
||||
database/database_update.cpp
|
||||
dbcore.cpp
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#include "data_bucket.h"
|
||||
#include "zonedb.h"
|
||||
#include "mob.h"
|
||||
#include "client.h"
|
||||
#include "worldserver.h"
|
||||
#include "../common/data_bucket.h"
|
||||
#include "database.h"
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
#include "../common/json/json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
extern WorldServer worldserver;
|
||||
const std::string NESTED_KEY_DELIMITER = ".";
|
||||
const std::string NESTED_KEY_DELIMITER = ".";
|
||||
std::vector<DataBucketsRepository::DataBuckets> g_data_bucket_cache = {};
|
||||
|
||||
std::vector<DataBucketsRepository::DataBuckets> g_data_bucket_cache = {};
|
||||
#if defined(ZONE)
|
||||
#include "../zone/zonedb.h"
|
||||
extern ZoneDatabase database;
|
||||
#elif defined(WORLD)
|
||||
#include "../world/worlddb.h"
|
||||
extern WorldDatabase database;
|
||||
#else
|
||||
#error "You must define either ZONE or WORLD"
|
||||
#endif
|
||||
|
||||
void DataBucket::SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time)
|
||||
{
|
||||
@@ -347,27 +352,6 @@ bool DataBucket::DeleteData(const std::string &bucket_key)
|
||||
return DeleteData(DataBucketKey{.key = bucket_key});
|
||||
}
|
||||
|
||||
// GetDataBuckets bulk loads all data buckets for a mob
|
||||
bool DataBucket::GetDataBuckets(Mob *mob)
|
||||
{
|
||||
const uint32 id = mob->GetMobTypeIdentifier();
|
||||
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mob->IsBot()) {
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
|
||||
}
|
||||
else if (mob->IsClient()) {
|
||||
uint32 account_id = mob->CastToClient()->AccountID();
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataBucket::DeleteData(const DataBucketKey &k)
|
||||
{
|
||||
bool is_nested_key = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos;
|
||||
@@ -2,11 +2,9 @@
|
||||
#define EQEMU_DATABUCKET_H
|
||||
|
||||
#include <string>
|
||||
#include "../common/types.h"
|
||||
#include "../common/repositories/data_buckets_repository.h"
|
||||
#include "mob.h"
|
||||
#include "../common/json/json_archive_single_line.h"
|
||||
#include "../common/servertalk.h"
|
||||
#include "types.h"
|
||||
#include "repositories/data_buckets_repository.h"
|
||||
#include "json/json_archive_single_line.h"
|
||||
|
||||
struct DataBucketKey {
|
||||
std::string key;
|
||||
@@ -46,8 +44,6 @@ public:
|
||||
static std::string GetDataExpires(const std::string &bucket_key);
|
||||
static std::string GetDataRemaining(const std::string &bucket_key);
|
||||
|
||||
static bool GetDataBuckets(Mob *mob);
|
||||
|
||||
// scoped bucket methods
|
||||
static void SetData(const DataBucketKey &k_);
|
||||
static bool DeleteData(const DataBucketKey &k);
|
||||
+25
-7
@@ -1095,13 +1095,13 @@ void Database::SetLFP(uint32 character_id, bool is_lfp)
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon)
|
||||
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 ingame)
|
||||
{
|
||||
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
||||
|
||||
e.firstlogon = first_logon;
|
||||
e.lfg = is_lfg ? 1 : 0;
|
||||
e.lfp = is_lfp ? 1 : 0;
|
||||
e.ingame = ingame;
|
||||
e.lfg = is_lfg ? 1 : 0;
|
||||
e.lfp = is_lfp ? 1 : 0;
|
||||
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
@@ -1115,11 +1115,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg)
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
void Database::SetFirstLogon(uint32 character_id, uint8 first_logon)
|
||||
void Database::SetIngame(uint32 character_id, uint8 ingame)
|
||||
{
|
||||
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
||||
|
||||
e.firstlogon = first_logon;
|
||||
e.ingame = ingame;
|
||||
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
@@ -1920,6 +1920,7 @@ bool Database::CopyCharacter(
|
||||
std::vector<std::string> tables_to_zero_id = {
|
||||
"keyring",
|
||||
"data_buckets",
|
||||
"character_evolving_items",
|
||||
"character_instance_safereturns",
|
||||
"character_expedition_lockouts",
|
||||
"character_instance_lockouts",
|
||||
@@ -1951,6 +1952,12 @@ bool Database::CopyCharacter(
|
||||
)
|
||||
);
|
||||
|
||||
if (!results.Success()) {
|
||||
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
|
||||
TransactionRollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> columns = {};
|
||||
int column_count = 0;
|
||||
|
||||
@@ -1969,6 +1976,12 @@ bool Database::CopyCharacter(
|
||||
)
|
||||
);
|
||||
|
||||
if (!results.Success()) {
|
||||
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
|
||||
TransactionRollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> new_rows;
|
||||
|
||||
for (auto row : results) {
|
||||
@@ -2036,13 +2049,18 @@ bool Database::CopyCharacter(
|
||||
LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied));
|
||||
|
||||
if (!insert.ErrorMessage().empty()) {
|
||||
LogError("Error copying table [{}] [{}]", table_name, insert.ErrorMessage());
|
||||
TransactionRollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransactionCommit();
|
||||
auto r = TransactionCommit();
|
||||
if (!r.Success()) {
|
||||
LogError("Transaction failed [{}] rolling back", r.ErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Character [{}] copied to [{}] total rows [{}]",
|
||||
|
||||
+1
-1
@@ -263,7 +263,7 @@ public:
|
||||
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
|
||||
void ClearMerchantTemp();
|
||||
void ClearPTimers(uint32 character_id);
|
||||
void SetFirstLogon(uint32 character_id, uint8 first_logon);
|
||||
void SetIngame(uint32 character_id, uint8 ingame);
|
||||
void SetLFG(uint32 character_id, bool is_lfg);
|
||||
void SetLFP(uint32 character_id, bool is_lfp);
|
||||
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
|
||||
|
||||
@@ -50,7 +50,7 @@ bool DatabaseDumpService::IsMySQLInstalled()
|
||||
{
|
||||
std::string version_output = GetMySQLVersion();
|
||||
|
||||
return version_output.find("mysql") != std::string::npos && version_output.find("Ver") != std::string::npos;
|
||||
return version_output.find("mysql") != std::string::npos && (version_output.find("Ver") != std::string::npos || version_output.find("from") != std::string::npos);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "../http/httplib.h"
|
||||
|
||||
#include "database_update_manifest.cpp"
|
||||
#include "database_update_manifest_custom.cpp"
|
||||
#include "database_update_manifest_bots.cpp"
|
||||
#include "database_dump_service.h"
|
||||
|
||||
@@ -14,7 +15,7 @@ constexpr int BREAK_LENGTH = 70;
|
||||
|
||||
DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
|
||||
{
|
||||
auto results = m_database->QueryDatabase("SELECT `version`, `bots_version` FROM `db_version` LIMIT 1");
|
||||
auto results = m_database->QueryDatabase("SELECT `version`, `bots_version`, `custom_version` FROM `db_version` LIMIT 1");
|
||||
if (!results.Success() || !results.RowCount()) {
|
||||
LogError("Failed to read from [db_version] table!");
|
||||
return DatabaseVersion{};
|
||||
@@ -25,6 +26,7 @@ DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
|
||||
return DatabaseVersion{
|
||||
.server_database_version = Strings::ToInt(r[0]),
|
||||
.bots_database_version = Strings::ToInt(r[1]),
|
||||
.custom_database_version = Strings::ToInt(r[2]),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,6 +35,7 @@ DatabaseVersion DatabaseUpdate::GetBinaryDatabaseVersions()
|
||||
return DatabaseVersion{
|
||||
.server_database_version = CURRENT_BINARY_DATABASE_VERSION,
|
||||
.bots_database_version = (RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0),
|
||||
.custom_database_version = CUSTOM_BINARY_DATABASE_VERSION,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,6 +46,7 @@ constexpr int LOOK_BACK_AMOUNT = 10;
|
||||
// this check will take action
|
||||
void DatabaseUpdate::CheckDbUpdates()
|
||||
{
|
||||
InjectCustomVersionColumn();
|
||||
InjectBotsVersionColumn();
|
||||
auto v = GetDatabaseVersions();
|
||||
auto b = GetBinaryDatabaseVersions();
|
||||
@@ -59,6 +63,15 @@ void DatabaseUpdate::CheckDbUpdates()
|
||||
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", b.server_database_version));
|
||||
}
|
||||
|
||||
if (UpdateManifest(manifest_entries_custom, v.custom_database_version, b.custom_database_version)) {
|
||||
LogInfo(
|
||||
"Updates ran successfully, setting database version to [{}] from [{}]",
|
||||
b.custom_database_version,
|
||||
v.custom_database_version
|
||||
);
|
||||
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `custom_version` = {}", b.custom_database_version));
|
||||
}
|
||||
|
||||
if (b.bots_database_version > 0) {
|
||||
if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) {
|
||||
LogInfo(
|
||||
@@ -344,6 +357,16 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b)
|
||||
);
|
||||
}
|
||||
|
||||
if (b.custom_database_version > 0) {
|
||||
LogInfo(
|
||||
"{:>8} | database [{}] binary [{}] {}",
|
||||
"Custom",
|
||||
v.custom_database_version,
|
||||
b.custom_database_version,
|
||||
(v.custom_database_version == b.custom_database_version) ? "up to date" : "checking updates"
|
||||
);
|
||||
}
|
||||
|
||||
LogInfo("{:>8} | [server.auto_database_updates] [<green>true]", "Config");
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
|
||||
@@ -353,7 +376,10 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b)
|
||||
// bots database version is optional, if not enabled then it is always up-to-date
|
||||
bool bots_up_to_date = RuleB(Bots, Enabled) ? v.bots_database_version >= b.bots_database_version : true;
|
||||
|
||||
return server_up_to_date && bots_up_to_date;
|
||||
// custom database version is optional, if not enabled then it is always up-to-date
|
||||
bool custom_up_to_date = v.custom_database_version >= b.custom_database_version;
|
||||
|
||||
return server_up_to_date && bots_up_to_date && custom_up_to_date;
|
||||
}
|
||||
|
||||
// checks to see if there are pending updates
|
||||
@@ -373,3 +399,12 @@ void DatabaseUpdate::InjectBotsVersionColumn()
|
||||
m_database->QueryDatabase("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version");
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseUpdate::InjectCustomVersionColumn()
|
||||
{
|
||||
auto results = m_database->QueryDatabase("SHOW COLUMNS FROM `db_version` LIKE 'custom_version'");
|
||||
if (!results.Success() || results.RowCount() == 0) {
|
||||
LogInfo("Adding custom_version column to db_version table");
|
||||
m_database->QueryDatabase("ALTER TABLE `db_version` ADD COLUMN `custom_version` INT(11) UNSIGNED NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ struct ManifestEntry {
|
||||
struct DatabaseVersion {
|
||||
int server_database_version;
|
||||
int bots_database_version;
|
||||
int custom_database_version;
|
||||
};
|
||||
|
||||
class DatabaseUpdate {
|
||||
@@ -38,6 +39,7 @@ private:
|
||||
Database *m_content_database;
|
||||
static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b);
|
||||
void InjectBotsVersionColumn();
|
||||
void InjectCustomVersionColumn();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7084,6 +7084,31 @@ ADD COLUMN `expire_at` bigint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `duration`;
|
||||
UPDATE instance_list set expire_at = `start_time` + `duration`; -- backfill existing data
|
||||
|
||||
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9322,
|
||||
.description = "2025_04_24_add_npc_tint_id.sql",
|
||||
.check = "SHOW COLUMNS FROM `npc_types` LIKE 'npc_tint_id'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `npc_types`
|
||||
ADD COLUMN `npc_tint_id` SMALLINT UNSIGNED NULL DEFAULT '0' AFTER `multiquest_enabled`;
|
||||
)",
|
||||
.content_schema_update = true
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9323,
|
||||
.description = "2025_04_16_character_data_first_login.sql",
|
||||
.check = "SHOW COLUMNS FROM `character_data` LIKE 'first_login'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `character_data`
|
||||
CHANGE COLUMN `firstlogon` `ingame` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`,
|
||||
ADD COLUMN `first_login` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`;
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "database_update.h"
|
||||
|
||||
std::vector<ManifestEntry> manifest_entries_custom = {
|
||||
ManifestEntry{
|
||||
.version = 1,
|
||||
.description = "2025_05_16_new_database_check_test",
|
||||
.check = "SHOW TABLES LIKE 'new_table'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
CREATE TABLE `new_table` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
)",
|
||||
.content_schema_update = false,
|
||||
},
|
||||
// Used for testing
|
||||
// ManifestEntry{
|
||||
// .version = 9229,
|
||||
// .description = "new_database_check_test",
|
||||
// .check = "SHOW TABLES LIKE 'new_table'",
|
||||
// .condition = "empty",
|
||||
// .match = "",
|
||||
// .sql = R"(
|
||||
//CREATE TABLE `new_table` (
|
||||
// `id` int NOT NULL AUTO_INCREMENT,
|
||||
// PRIMARY KEY (`id`)
|
||||
//);
|
||||
//CREATE TABLE `new_table1` (
|
||||
// `id` int NOT NULL AUTO_INCREMENT,
|
||||
// PRIMARY KEY (`id`)
|
||||
//);
|
||||
//CREATE TABLE `new_table2` (
|
||||
// `id` int NOT NULL AUTO_INCREMENT,
|
||||
// PRIMARY KEY (`id`)
|
||||
//);
|
||||
//CREATE TABLE `new_table3` (
|
||||
// `id` int NOT NULL AUTO_INCREMENT,
|
||||
// PRIMARY KEY (`id`)
|
||||
//);
|
||||
//)",
|
||||
// }
|
||||
|
||||
};
|
||||
|
||||
// see struct definitions for what each field does
|
||||
// struct ManifestEntry {
|
||||
// int version{}; // database version of the migration
|
||||
// std::string description{}; // description of the migration ex: "add_new_table" or "add_index_to_table"
|
||||
// std::string check{}; // query that checks against the condition
|
||||
// std::string condition{}; // condition or "match_type" - Possible values [contains|match|missing|empty|not_empty]
|
||||
// std::string match{}; // match field that is not always used, but works in conjunction with "condition" values [missing|match|contains]
|
||||
// std::string sql{}; // the SQL DDL that gets ran when the condition is true
|
||||
// };
|
||||
@@ -80,7 +80,7 @@ namespace DatabaseSchema {
|
||||
{"guild_members", "char_id"},
|
||||
{"guilds", "id"},
|
||||
{"instance_list_player", "id"},
|
||||
{"inventory", "charid"},
|
||||
{"inventory", "character_id"},
|
||||
{"inventory_snapshots", "charid"},
|
||||
{"keyring", "char_id"},
|
||||
{"mail", "charid"},
|
||||
|
||||
+2
-2
@@ -189,9 +189,9 @@ void DBcore::TransactionBegin()
|
||||
QueryDatabase("START TRANSACTION");
|
||||
}
|
||||
|
||||
void DBcore::TransactionCommit()
|
||||
MySQLRequestResult DBcore::TransactionCommit()
|
||||
{
|
||||
QueryDatabase("COMMIT");
|
||||
return QueryDatabase("COMMIT");
|
||||
}
|
||||
|
||||
void DBcore::TransactionRollback()
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ public:
|
||||
MySQLRequestResult QueryDatabase(const std::string& query, bool retryOnFailureOnce = true);
|
||||
MySQLRequestResult QueryDatabaseMulti(const std::string &query);
|
||||
void TransactionBegin();
|
||||
void TransactionCommit();
|
||||
MySQLRequestResult TransactionCommit();
|
||||
void TransactionRollback();
|
||||
std::string Escape(const std::string& s);
|
||||
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <string>
|
||||
#include "../types.h"
|
||||
#include "../http/httplib.h"
|
||||
#include "../repositories/player_event_logs_repository.h"
|
||||
#include "../events/player_events.h"
|
||||
|
||||
|
||||
@@ -324,6 +324,8 @@ union
|
||||
bool guild_show;
|
||||
bool trader;
|
||||
bool buyer;
|
||||
bool untargetable;
|
||||
uint32 npc_tint_id;
|
||||
};
|
||||
|
||||
struct PlayerState_Struct {
|
||||
|
||||
@@ -177,6 +177,21 @@ void EQEmuConfig::parse_config()
|
||||
SharedMemDir = _root["server"]["directories"].get("shared_memory", "shared/").asString();
|
||||
LogDir = _root["server"]["directories"].get("logs", "logs/").asString();
|
||||
|
||||
auto load_paths = [&](const std::string& key, std::vector<std::string>& target) {
|
||||
const auto& paths = _root["server"]["directories"][key];
|
||||
if (paths.isArray()) {
|
||||
for (const auto& dir : paths) {
|
||||
if (dir.isString()) {
|
||||
target.push_back(dir.asString());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
load_paths("quest_paths", m_quest_directories);
|
||||
load_paths("plugin_paths", m_plugin_directories);
|
||||
load_paths("lua_module_paths", m_lua_module_directories);
|
||||
|
||||
/**
|
||||
* Logs
|
||||
*/
|
||||
|
||||
@@ -120,6 +120,22 @@ class EQEmuConfig
|
||||
const std::string &GetUCSHost() const;
|
||||
uint16 GetUCSPort() const;
|
||||
|
||||
std::vector<std::string> GetQuestDirectories() const
|
||||
{
|
||||
return m_quest_directories;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetPluginsDirectories() const
|
||||
{
|
||||
return m_plugin_directories;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetLuaModuleDirectories() const
|
||||
{
|
||||
return m_lua_module_directories;
|
||||
}
|
||||
|
||||
|
||||
// uint16 DynamicCount;
|
||||
|
||||
// map<string,uint16> StaticZones;
|
||||
@@ -133,6 +149,11 @@ class EQEmuConfig
|
||||
Json::Value _root;
|
||||
static std::string ConfigFile;
|
||||
|
||||
std::vector<std::string> m_quest_directories = {};
|
||||
std::vector<std::string> m_plugin_directories = {};
|
||||
std::vector<std::string> m_lua_module_directories = {};
|
||||
|
||||
protected:
|
||||
void parse_config();
|
||||
|
||||
EQEmuConfig()
|
||||
|
||||
+27
-8
@@ -682,14 +682,33 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings(bool silent_load)
|
||||
if (is_missing_in_database && !is_deprecated_category) {
|
||||
LogInfo("Automatically adding new log category [{}] ({})", Logs::LogCategoryName[i], i);
|
||||
|
||||
auto new_category = LogsysCategoriesRepository::NewEntity();
|
||||
new_category.log_category_id = i;
|
||||
new_category.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
|
||||
new_category.log_to_console = log_settings[i].log_to_console;
|
||||
new_category.log_to_gmsay = log_settings[i].log_to_gmsay;
|
||||
new_category.log_to_file = log_settings[i].log_to_file;
|
||||
new_category.log_to_discord = log_settings[i].log_to_discord;
|
||||
db_categories_to_add.emplace_back(new_category);
|
||||
auto e = LogsysCategoriesRepository::NewEntity();
|
||||
e.log_category_id = i;
|
||||
e.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
|
||||
e.log_to_console = log_settings[i].log_to_console;
|
||||
e.log_to_gmsay = log_settings[i].log_to_gmsay;
|
||||
e.log_to_file = log_settings[i].log_to_file;
|
||||
e.log_to_discord = log_settings[i].log_to_discord;
|
||||
db_categories_to_add.emplace_back(e);
|
||||
}
|
||||
|
||||
// look to see if the category name is different in the database
|
||||
auto it = std::find_if(
|
||||
categories.begin(),
|
||||
categories.end(),
|
||||
[i](const auto &c) { return c.log_category_id == i; }
|
||||
);
|
||||
if (it != categories.end()) {
|
||||
if (it->log_category_description != Logs::LogCategoryName[i]) {
|
||||
LogInfo(
|
||||
"Updating log category [{}] ({}) to new name [{}]",
|
||||
it->log_category_description,
|
||||
i,
|
||||
Logs::LogCategoryName[i]
|
||||
);
|
||||
it->log_category_description = Logs::LogCategoryName[i];
|
||||
LogsysCategoriesRepository::ReplaceOne(*m_database, *it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-10
@@ -194,8 +194,8 @@ namespace Logs {
|
||||
"Web Interface (Deprecated)",
|
||||
"World Server (Deprecated)",
|
||||
"Zone Server (Deprecated)",
|
||||
"QueryErr",
|
||||
"Query",
|
||||
"MySQL Error",
|
||||
"MySQL Query",
|
||||
"Mercenaries",
|
||||
"Quest Debug",
|
||||
"Legacy Packet Logging (Deprecated)",
|
||||
@@ -211,15 +211,15 @@ namespace Logs {
|
||||
"Traps",
|
||||
"NPC Roam Box",
|
||||
"NPC Scaling",
|
||||
"MobAppearance",
|
||||
"Mob Appearance",
|
||||
"Info",
|
||||
"Warning",
|
||||
"Critical (Deprecated)",
|
||||
"Emergency (Deprecated)",
|
||||
"Alert (Deprecated)",
|
||||
"Notice (Deprecated)",
|
||||
"AI Scan",
|
||||
"AI Yell",
|
||||
"AI Scan Close",
|
||||
"AI Yell For Help",
|
||||
"AI CastBeneficial",
|
||||
"AOE Cast",
|
||||
"Entity Management",
|
||||
@@ -237,7 +237,7 @@ namespace Logs {
|
||||
"DialogueWindow",
|
||||
"HTTP",
|
||||
"Saylink",
|
||||
"ChecksumVer",
|
||||
"Checksum Verification",
|
||||
"CombatRecord",
|
||||
"Hate",
|
||||
"Discord",
|
||||
@@ -454,9 +454,19 @@ void OutF(
|
||||
}
|
||||
**/
|
||||
|
||||
#define OutF(ls, debug_level, log_category, file, func, line, formatStr, ...) \
|
||||
do { \
|
||||
ls.Out(debug_level, log_category, file, func, line, fmt::format(formatStr, ##__VA_ARGS__).c_str()); \
|
||||
} while(0)
|
||||
template<typename... Args>
|
||||
inline void OutF(
|
||||
EQEmuLogSys& ls,
|
||||
Logs::DebugLevel debug_level,
|
||||
uint16 log_category,
|
||||
const char* file,
|
||||
const char* func,
|
||||
int line,
|
||||
fmt::format_string<Args...> fmt_str,
|
||||
Args&&... args
|
||||
) {
|
||||
std::string formatted = fmt::format(fmt_str, std::forward<Args>(args)...);
|
||||
ls.Out(debug_level, log_category, file, func, line, formatted.c_str());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,9 +15,9 @@ const uint32 PROCESS_RETENTION_TRUNCATION_TIMER_INTERVAL = 60 * 60 * 1000; // 1
|
||||
// general initialization routine
|
||||
void PlayerEventLogs::Init()
|
||||
{
|
||||
|
||||
m_process_batch_events_timer.SetTimer(RuleI(Logging, BatchPlayerEventProcessIntervalSeconds) * 1000);
|
||||
m_process_retention_truncation_timer.SetTimer(PROCESS_RETENTION_TRUNCATION_TIMER_INTERVAL);
|
||||
m_database_ping_timer.SetTimer(10 * 1000); // 10 seconds
|
||||
|
||||
ValidateDatabaseConnection();
|
||||
|
||||
@@ -916,6 +916,10 @@ std::string PlayerEventLogs::GetDiscordPayloadFromEvent(const PlayerEvent::Playe
|
||||
// general process function, used in world or QS depending on rule Logging:PlayerEventsQSProcess
|
||||
void PlayerEventLogs::Process()
|
||||
{
|
||||
if (m_database_ping_timer.Check()) {
|
||||
m_database->ping();
|
||||
}
|
||||
|
||||
if (m_process_batch_events_timer.Check() ||
|
||||
m_record_batch_queue.size() >= RuleI(Logging, BatchPlayerEventProcessChunkSize)) {
|
||||
ProcessBatchQueue();
|
||||
|
||||
@@ -113,6 +113,7 @@ private:
|
||||
std::map<PlayerEvent::EventType, EtlSettings> m_etl_settings{};
|
||||
|
||||
// timers
|
||||
Timer m_database_ping_timer; // database ping timer
|
||||
Timer m_process_batch_events_timer; // events processing timer
|
||||
Timer m_process_retention_truncation_timer; // timer for truncating events based on retention settings
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ double EvolvingItemsManager::CalculateProgression(const uint64 current_amount, c
|
||||
|
||||
void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const
|
||||
{
|
||||
if (!inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
inst.SetEvolveEquipped(false);
|
||||
if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
|
||||
inst.SetEvolveEquipped(true);
|
||||
@@ -87,6 +91,10 @@ void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_
|
||||
|
||||
uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
|
||||
{
|
||||
if (!inst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto start_iterator = std::ranges::find_if(
|
||||
evolving_items_manager.GetEvolvingItemsCache().cbegin(),
|
||||
evolving_items_manager.GetEvolvingItemsCache().cend(),
|
||||
@@ -116,6 +124,10 @@ uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
|
||||
|
||||
uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const
|
||||
{
|
||||
if (!inst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8 const current_level = inst.GetEvolveLvl();
|
||||
|
||||
const auto iterator = std::ranges::find_if(
|
||||
@@ -191,6 +203,10 @@ uint64 EvolvingItemsManager::GetTotalEarnedXP(const EQ::ItemInstance &inst)
|
||||
EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp)
|
||||
{
|
||||
EvolveGetNextItem ets{};
|
||||
if (!inst_in) {
|
||||
return ets;
|
||||
}
|
||||
|
||||
const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID());
|
||||
uint32 max_transfer_level = 0;
|
||||
int64 xp = in_xp;
|
||||
@@ -235,6 +251,9 @@ EvolveTransfer EvolvingItemsManager::DetermineTransferResults(
|
||||
)
|
||||
{
|
||||
EvolveTransfer ets{};
|
||||
if (!inst_from || !inst_to) {
|
||||
return ets;
|
||||
}
|
||||
|
||||
auto evolving_details_inst_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID());
|
||||
auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID());
|
||||
@@ -295,6 +314,10 @@ uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_i
|
||||
|
||||
void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e)
|
||||
{
|
||||
if (!inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.item_id = inst.GetID();
|
||||
e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string();
|
||||
e.level = inst.GetEvolveLvl();
|
||||
|
||||
@@ -53,11 +53,11 @@ public:
|
||||
ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id);
|
||||
EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to);
|
||||
EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp);
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return evolving_items_cache; }
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return m_evolving_items_cache; }
|
||||
std::vector<ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> GetEvolveIDItems(uint32 evolve_id);
|
||||
|
||||
private:
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> evolving_items_cache;
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> m_evolving_items_cache;
|
||||
Database * m_db;
|
||||
Database * m_content_db;
|
||||
};
|
||||
|
||||
@@ -1127,16 +1127,37 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
auto time_since_first_sent = std::chrono::duration_cast<std::chrono::milliseconds>(now - first_packet.first_sent).count();
|
||||
|
||||
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
|
||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
auto first_sent_ms = std::chrono::duration_cast<std::chrono::milliseconds>(first_packet.first_sent.time_since_epoch()).count();
|
||||
LogNetClient(
|
||||
"Closing connection for m_endpoint [{}] m_port [{}] time_since_first_sent [{}] >= m_owner->m_options.resend_timeout [{}] now [{}] first_packet.first_sent [{}]",
|
||||
m_endpoint,
|
||||
m_port,
|
||||
time_since_first_sent,
|
||||
m_owner->m_options.resend_timeout,
|
||||
now_ms,
|
||||
first_sent_ms
|
||||
);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_last_ack - now > std::chrono::milliseconds(1000)) {
|
||||
LogNetClient(
|
||||
"Resetting m_acked_since_last_resend flag for m_endpoint [{}] m_port [{}]",
|
||||
m_endpoint,
|
||||
m_port
|
||||
);
|
||||
m_acked_since_last_resend = true;
|
||||
}
|
||||
|
||||
// make sure that the first_packet in the list first_sent time is within the resend_delay and now
|
||||
// if it is not, then we need to resend all packets in the list
|
||||
if (time_since_first_sent <= first_packet.resend_delay && !m_acked_since_last_resend) {
|
||||
LogNetClient(
|
||||
"Not resending packets for stream [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
|
||||
stream,
|
||||
LogNetClientDetail(
|
||||
"Not resending packets for m_endpoint [{}] m_port [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
|
||||
m_endpoint,
|
||||
m_port,
|
||||
s->sent_packets.size(),
|
||||
time_since_first_sent,
|
||||
first_packet.resend_delay,
|
||||
@@ -1146,15 +1167,16 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
}
|
||||
}
|
||||
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode)) {
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::NetClient)) {
|
||||
size_t total_size = 0;
|
||||
for (auto &e: s->sent_packets) {
|
||||
total_size += e.second.packet.Length();
|
||||
}
|
||||
|
||||
LogNetClient(
|
||||
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
|
||||
stream,
|
||||
LogNetClientDetail(
|
||||
"Resending packets for m_endpoint [{}] m_port [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
|
||||
m_endpoint,
|
||||
m_port,
|
||||
s->sent_packets.size(),
|
||||
total_size,
|
||||
m_acked_since_last_resend
|
||||
@@ -1165,9 +1187,12 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
|
||||
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
|
||||
LogNetClient(
|
||||
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
|
||||
"Stopping resend because we hit thresholds for m_endpoint [{}] m_port [{}] m_resend_packets_sent [{}] max [{}] in_queue [{}] m_resend_bytes_sent [{}] max [{}]",
|
||||
m_endpoint,
|
||||
m_port,
|
||||
m_resend_packets_sent,
|
||||
MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
|
||||
s->sent_packets.size(),
|
||||
m_resend_bytes_sent,
|
||||
MAX_CLIENT_RECV_BYTES_PER_WINDOW
|
||||
);
|
||||
@@ -1204,11 +1229,11 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
}
|
||||
|
||||
m_acked_since_last_resend = false;
|
||||
m_last_ack = now;
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
|
||||
{
|
||||
|
||||
auto now = Clock::now();
|
||||
auto s = &m_streams[stream];
|
||||
auto iter = s->sent_packets.begin();
|
||||
@@ -1224,12 +1249,14 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
|
||||
m_rolling_ping = (m_rolling_ping * 2 + round_time) / 3;
|
||||
|
||||
iter = s->sent_packets.erase(iter);
|
||||
m_acked_since_last_resend = true;
|
||||
}
|
||||
else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
m_acked_since_last_resend = true;
|
||||
m_last_ack = now;
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
|
||||
@@ -1247,6 +1274,9 @@ void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
|
||||
|
||||
s->sent_packets.erase(iter);
|
||||
}
|
||||
|
||||
m_acked_since_last_resend = true;
|
||||
m_last_ack = now;
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::UpdateDataBudget(double budget_add)
|
||||
|
||||
@@ -186,6 +186,7 @@ namespace EQ
|
||||
size_t m_resend_packets_sent = 0;
|
||||
size_t m_resend_bytes_sent = 0;
|
||||
bool m_acked_since_last_resend = false;
|
||||
Timestamp m_last_ack;
|
||||
|
||||
struct DaybreakSentPacket
|
||||
{
|
||||
|
||||
@@ -4688,7 +4688,7 @@ namespace RoF2
|
||||
Bitfields->linkdead = 0;
|
||||
Bitfields->showhelm = emu->showhelm;
|
||||
Bitfields->trader = emu->trader ? 1 : 0;
|
||||
Bitfields->targetable = 1;
|
||||
Bitfields->targetable = emu->NPC ? emu->untargetable : 1;
|
||||
Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0;
|
||||
Bitfields->showname = ShowName;
|
||||
|
||||
@@ -4839,13 +4839,13 @@ namespace RoF2
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId);
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^
|
||||
|
||||
if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) ||
|
||||
(emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin)
|
||||
|
||||
+11
-11
@@ -4908,12 +4908,12 @@ namespace UF
|
||||
UFSlot = serverSlot - 2;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot + 11;
|
||||
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 11;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) {
|
||||
UFSlot = serverSlot - 9;
|
||||
UFSlot = serverSlot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // - 9;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invslot::TRIBUTE_END && serverSlot >= EQ::invslot::TRIBUTE_BEGIN) {
|
||||
@@ -4933,7 +4933,7 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invbag::BANK_BAGS_END && serverSlot >= EQ::invbag::BANK_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot + 1;
|
||||
UFSlot = serverSlot - (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 1;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invslot::SHARED_BANK_END && serverSlot >= EQ::invslot::SHARED_BANK_BEGIN) {
|
||||
@@ -4941,7 +4941,7 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invbag::SHARED_BANK_BAGS_END && serverSlot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot + 1;
|
||||
UFSlot = serverSlot - (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::SHARED_BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 1;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invslot::TRADE_END && serverSlot >= EQ::invslot::TRADE_BEGIN) {
|
||||
@@ -4949,7 +4949,7 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invbag::TRADE_BAGS_END && serverSlot >= EQ::invbag::TRADE_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot;
|
||||
UFSlot = serverSlot - (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::TRADE_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 0;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invslot::WORLD_END && serverSlot >= EQ::invslot::WORLD_BEGIN) {
|
||||
@@ -4991,11 +4991,11 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (ufSlot <= invbag::GENERAL_BAGS_END && ufSlot >= invbag::GENERAL_BAGS_BEGIN) {
|
||||
ServerSlot = ufSlot - 11;
|
||||
ServerSlot = ufSlot + (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 11;
|
||||
}
|
||||
|
||||
else if (ufSlot <= invbag::CURSOR_BAG_END && ufSlot >= invbag::CURSOR_BAG_BEGIN) {
|
||||
ServerSlot = ufSlot + 9;
|
||||
ServerSlot = ufSlot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // + 9;
|
||||
}
|
||||
|
||||
else if (ufSlot <= invslot::TRIBUTE_END && ufSlot >= invslot::TRIBUTE_BEGIN) {
|
||||
@@ -5015,7 +5015,7 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (ufSlot <= invbag::BANK_BAGS_END && ufSlot >= invbag::BANK_BAGS_BEGIN) {
|
||||
ServerSlot = ufSlot - 1;
|
||||
ServerSlot = ufSlot + (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 1;
|
||||
}
|
||||
|
||||
else if (ufSlot <= invslot::SHARED_BANK_END && ufSlot >= invslot::SHARED_BANK_BEGIN) {
|
||||
@@ -5023,7 +5023,7 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (ufSlot <= invbag::SHARED_BANK_BAGS_END && ufSlot >= invbag::SHARED_BANK_BAGS_BEGIN) {
|
||||
ServerSlot = ufSlot - 1;
|
||||
ServerSlot = ufSlot + (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 1;
|
||||
}
|
||||
|
||||
else if (ufSlot <= invslot::TRADE_END && ufSlot >= invslot::TRADE_BEGIN) {
|
||||
@@ -5031,7 +5031,7 @@ namespace UF
|
||||
}
|
||||
|
||||
else if (ufSlot <= invbag::TRADE_BAGS_END && ufSlot >= invbag::TRADE_BAGS_BEGIN) {
|
||||
ServerSlot = ufSlot;
|
||||
ServerSlot = ufSlot + (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 0;
|
||||
}
|
||||
|
||||
else if (ufSlot <= invslot::WORLD_END && ufSlot >= invslot::WORLD_BEGIN) {
|
||||
|
||||
+43
-22
@@ -48,10 +48,23 @@ void PathManager::LoadPaths()
|
||||
return dir;
|
||||
};
|
||||
|
||||
auto load_many_paths_fallback = [&](const std::vector<std::string>& dirs, const std::string& fallback, std::vector<std::string>& target) {
|
||||
target.clear();
|
||||
if (!dirs.empty()) {
|
||||
for (const auto& path : dirs) {
|
||||
target.push_back(resolve_path(path));
|
||||
}
|
||||
} else {
|
||||
target.push_back(resolve_path(fallback));
|
||||
}
|
||||
};
|
||||
|
||||
load_many_paths_fallback(c->GetQuestDirectories(), c->QuestDir, m_quests_paths);
|
||||
load_many_paths_fallback(c->GetPluginsDirectories(), c->PluginDir, m_plugin_paths);
|
||||
load_many_paths_fallback(c->GetLuaModuleDirectories(), c->LuaModuleDir, m_lua_module_paths);
|
||||
|
||||
// resolve all paths
|
||||
m_maps_path = resolve_path(c->MapDir, {"maps", "Maps"});
|
||||
m_quests_path = resolve_path(c->QuestDir);
|
||||
m_plugins_path = resolve_path(c->PluginDir);
|
||||
m_lua_modules_path = resolve_path(c->LuaModuleDir);
|
||||
m_lua_mods_path = resolve_path("mods");
|
||||
m_patch_path = resolve_path(c->PatchDir);
|
||||
m_opcode_path = resolve_path(c->OpcodeDir);
|
||||
@@ -62,13 +75,10 @@ void PathManager::LoadPaths()
|
||||
std::vector<std::pair<std::string, std::string>> paths = {
|
||||
{"server", m_server_path},
|
||||
{"logs", m_log_path},
|
||||
{"lua mods", m_lua_mods_path},
|
||||
{"lua_modules", m_lua_modules_path},
|
||||
{"maps", m_maps_path},
|
||||
{"lua mods", m_lua_mods_path},
|
||||
{"patches", m_patch_path},
|
||||
{"opcode", m_opcode_path},
|
||||
{"plugins", m_plugins_path},
|
||||
{"quests", m_quests_path},
|
||||
{"shared_memory", m_shared_memory_path}
|
||||
};
|
||||
|
||||
@@ -83,6 +93,17 @@ void PathManager::LoadPaths()
|
||||
LogInfo("{:>{}} > [{:<{}}]", name, name_width, in_path, path_width);
|
||||
}
|
||||
}
|
||||
|
||||
auto log_paths = [&](const std::string& label, const std::vector<std::string>& paths) {
|
||||
if (!paths.empty()) {
|
||||
LogInfo("{:>{}} > [{:<{}}]", label, name_width - 1, Strings::Join(paths, ";"), path_width);
|
||||
}
|
||||
};
|
||||
|
||||
log_paths("quests", m_quests_paths);
|
||||
log_paths("plugins", m_plugin_paths);
|
||||
log_paths("lua_modules", m_lua_module_paths);
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
}
|
||||
|
||||
@@ -96,21 +117,26 @@ const std::string &PathManager::GetMapsPath() const
|
||||
return m_maps_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetQuestsPath() const
|
||||
{
|
||||
return m_quests_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetPluginsPath() const
|
||||
{
|
||||
return m_plugins_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetSharedMemoryPath() const
|
||||
{
|
||||
return m_shared_memory_path;
|
||||
}
|
||||
|
||||
std::vector<std::string> PathManager::GetQuestPaths() const
|
||||
{
|
||||
return m_quests_paths;
|
||||
}
|
||||
|
||||
std::vector<std::string> PathManager::GetPluginPaths() const
|
||||
{
|
||||
return m_plugin_paths;
|
||||
}
|
||||
|
||||
std::vector<std::string> PathManager::GetLuaModulePaths() const
|
||||
{
|
||||
return m_lua_module_paths;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetLogPath() const
|
||||
{
|
||||
return m_log_path;
|
||||
@@ -126,11 +152,6 @@ const std::string &PathManager::GetOpcodePath() const
|
||||
return m_opcode_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetLuaModulesPath() const
|
||||
{
|
||||
return m_lua_modules_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetLuaModsPath() const
|
||||
{
|
||||
return m_lua_mods_path;
|
||||
|
||||
+18
-12
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class PathManager {
|
||||
public:
|
||||
@@ -14,22 +15,27 @@ public:
|
||||
[[nodiscard]] const std::string &GetMapsPath() const;
|
||||
[[nodiscard]] const std::string &GetPatchPath() const;
|
||||
[[nodiscard]] const std::string &GetOpcodePath() const;
|
||||
[[nodiscard]] const std::string &GetPluginsPath() const;
|
||||
[[nodiscard]] const std::string &GetQuestsPath() const;
|
||||
[[nodiscard]] const std::string &GetServerPath() const;
|
||||
[[nodiscard]] const std::string &GetSharedMemoryPath() const;
|
||||
[[nodiscard]] std::vector<std::string> GetQuestPaths() const;
|
||||
[[nodiscard]] std::vector<std::string> GetPluginPaths() const;
|
||||
[[nodiscard]] std::vector<std::string> GetLuaModulePaths() const;
|
||||
|
||||
private:
|
||||
std::string m_log_path;
|
||||
std::string m_lua_mods_path;
|
||||
std::string m_lua_modules_path;
|
||||
std::string m_maps_path;
|
||||
std::string m_patch_path;
|
||||
std::string m_opcode_path;
|
||||
std::string m_plugins_path;
|
||||
std::string m_quests_path;
|
||||
std::string m_server_path;
|
||||
std::string m_shared_memory_path;
|
||||
std::string m_log_path;
|
||||
std::string m_lua_mods_path;
|
||||
std::string m_maps_path;
|
||||
std::string m_patch_path;
|
||||
std::string m_opcode_path;
|
||||
std::string m_quests_path;
|
||||
std::vector<std::string> m_quests_paths;
|
||||
std::vector<std::string> m_plugin_paths;
|
||||
std::vector<std::string> m_lua_module_paths;
|
||||
|
||||
|
||||
private:
|
||||
std::string m_server_path;
|
||||
std::string m_shared_memory_path;
|
||||
};
|
||||
|
||||
extern PathManager path;
|
||||
|
||||
@@ -115,7 +115,8 @@ public:
|
||||
uint8_t lfg;
|
||||
std::string mailkey;
|
||||
uint8_t xtargets;
|
||||
int8_t firstlogon;
|
||||
uint8_t ingame;
|
||||
uint32_t first_login;
|
||||
uint32_t e_aa_effects;
|
||||
uint32_t e_percent_to_aa;
|
||||
uint32_t e_expended_aa_spent;
|
||||
@@ -230,7 +231,8 @@ public:
|
||||
"lfg",
|
||||
"mailkey",
|
||||
"xtargets",
|
||||
"firstlogon",
|
||||
"ingame",
|
||||
"first_login",
|
||||
"e_aa_effects",
|
||||
"e_percent_to_aa",
|
||||
"e_expended_aa_spent",
|
||||
@@ -341,7 +343,8 @@ public:
|
||||
"lfg",
|
||||
"mailkey",
|
||||
"xtargets",
|
||||
"firstlogon",
|
||||
"ingame",
|
||||
"first_login",
|
||||
"e_aa_effects",
|
||||
"e_percent_to_aa",
|
||||
"e_expended_aa_spent",
|
||||
@@ -486,7 +489,8 @@ public:
|
||||
e.lfg = 0;
|
||||
e.mailkey = "";
|
||||
e.xtargets = 5;
|
||||
e.firstlogon = 0;
|
||||
e.ingame = 0;
|
||||
e.first_login = 0;
|
||||
e.e_aa_effects = 0;
|
||||
e.e_percent_to_aa = 0;
|
||||
e.e_expended_aa_spent = 0;
|
||||
@@ -627,15 +631,16 @@ public:
|
||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||
e.mailkey = row[94] ? row[94] : "";
|
||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
||||
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -764,15 +769,16 @@ public:
|
||||
v.push_back(columns[93] + " = " + std::to_string(e.lfg));
|
||||
v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(columns[95] + " = " + std::to_string(e.xtargets));
|
||||
v.push_back(columns[96] + " = " + std::to_string(e.firstlogon));
|
||||
v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects));
|
||||
v.push_back(columns[98] + " = " + std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(columns[99] + " = " + std::to_string(e.e_expended_aa_spent));
|
||||
v.push_back(columns[100] + " = " + std::to_string(e.aa_points_spent_old));
|
||||
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old));
|
||||
v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot));
|
||||
v.push_back(columns[103] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
|
||||
v.push_back(columns[104] + " = " + std::to_string(e.illusion_block));
|
||||
v.push_back(columns[96] + " = " + std::to_string(e.ingame));
|
||||
v.push_back(columns[97] + " = " + std::to_string(e.first_login));
|
||||
v.push_back(columns[98] + " = " + std::to_string(e.e_aa_effects));
|
||||
v.push_back(columns[99] + " = " + std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(columns[100] + " = " + std::to_string(e.e_expended_aa_spent));
|
||||
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_spent_old));
|
||||
v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old));
|
||||
v.push_back(columns[103] + " = " + std::to_string(e.e_last_invsnapshot));
|
||||
v.push_back(columns[104] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
|
||||
v.push_back(columns[105] + " = " + std::to_string(e.illusion_block));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -890,7 +896,8 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
@@ -1024,7 +1031,8 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
@@ -1162,15 +1170,16 @@ public:
|
||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||
e.mailkey = row[94] ? row[94] : "";
|
||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
||||
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1291,15 +1300,16 @@ public:
|
||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||
e.mailkey = row[94] ? row[94] : "";
|
||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
||||
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1470,7 +1480,8 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
@@ -1597,7 +1608,8 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
|
||||
@@ -149,6 +149,7 @@ public:
|
||||
uint8_t keeps_sold_items;
|
||||
uint8_t is_parcel_merchant;
|
||||
uint8_t multiquest_enabled;
|
||||
uint16_t npc_tint_id;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -289,6 +290,7 @@ public:
|
||||
"keeps_sold_items",
|
||||
"is_parcel_merchant",
|
||||
"multiquest_enabled",
|
||||
"npc_tint_id",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -425,6 +427,7 @@ public:
|
||||
"keeps_sold_items",
|
||||
"is_parcel_merchant",
|
||||
"multiquest_enabled",
|
||||
"npc_tint_id",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -595,6 +598,7 @@ public:
|
||||
e.keeps_sold_items = 1;
|
||||
e.is_parcel_merchant = 0;
|
||||
e.multiquest_enabled = 0;
|
||||
e.npc_tint_id = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -761,6 +765,7 @@ public:
|
||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -923,6 +928,7 @@ public:
|
||||
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
|
||||
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled));
|
||||
v.push_back(columns[130] + " = " + std::to_string(e.npc_tint_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -1074,6 +1080,7 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -1233,6 +1240,7 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
@@ -1396,6 +1404,7 @@ public:
|
||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1550,6 +1559,7 @@ public:
|
||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1754,6 +1764,7 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -1906,6 +1917,7 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
@@ -106,13 +106,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
||||
db,
|
||||
fmt::format("`buyer_id` = '{}'", buyer.front().id)
|
||||
);
|
||||
if (buy_lines.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto buy_lines =
|
||||
BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id));
|
||||
|
||||
std::vector<std::string> buy_line_ids{};
|
||||
for (auto const &bl: buy_lines) {
|
||||
@@ -121,23 +116,65 @@ public:
|
||||
|
||||
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
||||
if (buy_line_ids.empty()) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseBuyerBuyLinesRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
db, fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
BaseBuyerTradeItemsRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`buyer_buy_lines_id` IN({})",
|
||||
Strings::Implode(", ", buy_line_ids))
|
||||
db, fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DeleteBuyers(Database &db, uint32 char_zone_id, uint32 char_zone_instance_id)
|
||||
{
|
||||
auto buyers = GetWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", char_zone_id, char_zone_instance_id
|
||||
)
|
||||
);
|
||||
if (buyers.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> buyer_ids{};
|
||||
std::vector<std::string> buy_line_ids{};
|
||||
|
||||
for (auto const &b: buyers) {
|
||||
buyer_ids.push_back(std::to_string(b.id));
|
||||
}
|
||||
|
||||
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
||||
db, fmt::format("`buyer_id` IN({})", Strings::Implode(", ", buyer_ids))
|
||||
);
|
||||
|
||||
if (!buy_lines.empty()) {
|
||||
for (auto const &bl: buy_lines) {
|
||||
buy_line_ids.push_back(std::to_string(bl.id));
|
||||
}
|
||||
}
|
||||
|
||||
DeleteWhere(db, fmt::format("`id` IN({});", Strings::Implode(", ", buyer_ids)));
|
||||
if (buy_line_ids.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseBuyerBuyLinesRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
BaseBuyerTradeItemsRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BUYER_REPOSITORY_H
|
||||
|
||||
@@ -75,7 +75,6 @@ public:
|
||||
"JOIN character_data AS c ON t.char_id = c.id "
|
||||
"ORDER BY t.char_zone_instance_id ASC "
|
||||
"LIMIT {}",
|
||||
char_zone_instance_id,
|
||||
max_results)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -502,6 +502,19 @@ bool RuleManager::UpdateInjectedRules(Database *db, const std::string &rule_set_
|
||||
}
|
||||
}
|
||||
|
||||
// update rules in the database where the description is different
|
||||
for (auto &e : RuleValuesRepository::All(*db)) {
|
||||
auto i = rule_data.find(e.rule_name);
|
||||
if (i != rule_data.end()) {
|
||||
// if notes are different, update them
|
||||
if (i->second.second != nullptr && *i->second.second != e.notes) {
|
||||
LogInfo("Updating rule [{}] notes to [{}]", i->first, *i->second.second);
|
||||
e.notes = *i->second.second;
|
||||
RuleValuesRepository::ReplaceOne(*db, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (injected_rule_entries.size()) {
|
||||
if (!RuleValuesRepository::InjectRules(*db, injected_rule_entries)) {
|
||||
return false;
|
||||
|
||||
@@ -156,6 +156,7 @@ RULE_REAL(Character, TradeskillUpPottery, 4.0, "Pottery skillup rate adjustment.
|
||||
RULE_REAL(Character, TradeskillUpResearch, 1.0, "Research skillup rate adjustment. Lower is faster")
|
||||
RULE_REAL(Character, TradeskillUpTinkering, 2.0, "Tinkering skillup rate adjustment. Lower is faster")
|
||||
RULE_REAL(Character, TradeskillUpTailoring, 2.0, "Tailoring skillup rate adjustment. Lower is faster")
|
||||
RULE_REAL(Character, TradeskillUpMinChance, 2.5, "Determines the minimum percentage chance to gain a skill increase from a tradeskill. Cannot go below 2.5")
|
||||
RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%")
|
||||
RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars")
|
||||
RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres")
|
||||
@@ -232,6 +233,12 @@ RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over
|
||||
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
|
||||
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
|
||||
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
|
||||
RULE_BOOL(Character, EnableAutoAFK, true, "Enable or disable the auto AFK feature, cuts down on packet spam")
|
||||
RULE_BOOL(Character, AutoIdleFilterPackets, true, "Enable or disable filtering packets when auto AFK is enabled, heavily cuts down on packet spam in zones with lots of players")
|
||||
RULE_INT(Character, SecondsBeforeIdleCombatZone, 600, "Seconds before a player is considered idle in combat zones (600 = 10 minutes)")
|
||||
RULE_INT(Character, SecondsBeforeIdleNonCombatZone, 60, "Seconds before a player is considered idle in non-combat zones (60 = 1 minute)")
|
||||
RULE_INT(Character, SecondsBeforeAFKCombatZone, 1800, "Seconds before a player is considered AFK in combat zones (1800 = 30 minutes)")
|
||||
RULE_INT(Character, SecondsBeforeAFKNonCombatZone, 600, "Seconds before a player is considered AFK in non-combat zones (600 = 10 minutes)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Mercs)
|
||||
@@ -878,6 +885,7 @@ RULE_INT(Bots, MinStatusToBypassSpawnLimit, 100, "Minimum status to bypass spawn
|
||||
RULE_INT(Bots, MinStatusBypassSpawnLimit, 120, "Spawn limit with status bypass. Default 120.")
|
||||
RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass create limit. Default 100.")
|
||||
RULE_INT(Bots, MinStatusBypassCreateLimit, 120, "Create limit with status bypass. Default 120.")
|
||||
RULE_INT(Bots, MinStatusToBypassBotLevelRequirement, 100, "Minimum status to bypass level requirement for bots. Default 100.")
|
||||
RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.")
|
||||
RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.")
|
||||
RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.")
|
||||
@@ -1071,6 +1079,7 @@ RULE_CATEGORY(Logging)
|
||||
RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...")
|
||||
RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output")
|
||||
RULE_BOOL(Logging, PlayerEventsQSProcess, false, "Have query server process player events instead of world. Useful when wanting to use a dedicated server and database for processing player events on separate disk")
|
||||
RULE_STRING(Logging, PlayerEventsIgnoreGMCommands, "help,show", "This is a comma delimited list of commands to ignore when recording GM command player events.")
|
||||
RULE_INT(Logging, BatchPlayerEventProcessIntervalSeconds, 5, "This is the interval in which player events are processed in world or qs")
|
||||
RULE_INT(Logging, BatchPlayerEventProcessChunkSize, 10000, "This is the cap of events that can be inserted into the queue before a force flush. This is to keep from hitting MySQL max_allowed_packet and killing the connection")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
@@ -48,7 +48,7 @@ enum class SharedTaskRequestGroupType {
|
||||
struct ServerSharedTaskRequest_Struct {
|
||||
uint32 requested_character_id;
|
||||
uint32 requested_task_id;
|
||||
uint32 requested_npc_type_id; // original task logic passthrough
|
||||
uint32 requested_npc_entity_id; // original task logic passthrough
|
||||
uint32 accept_time;
|
||||
};
|
||||
|
||||
|
||||
+3
-2
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "23.5.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "23.7.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,8 +42,9 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9321
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9323
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||
#define CUSTOM_BINARY_DATABASE_VERSION 0
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "23.5.0",
|
||||
"version": "23.7.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
+1
-1
Submodule submodules/libuv updated: 0c1fa696aa...8fb9cb9194
@@ -1,6 +1,8 @@
|
||||
module should-release
|
||||
|
||||
go 1.18
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.5
|
||||
|
||||
require (
|
||||
github.com/google/go-github/v41 v41.0.0
|
||||
@@ -10,7 +12,7 @@ require (
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/net v0.36.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
)
|
||||
|
||||
@@ -10,12 +10,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -7,6 +7,7 @@ SET(world_sources
|
||||
cliententry.cpp
|
||||
clientlist.cpp
|
||||
console.cpp
|
||||
../common/data_bucket.cpp
|
||||
dynamic_zone.cpp
|
||||
dynamic_zone_manager.cpp
|
||||
eql_config.cpp
|
||||
@@ -42,6 +43,7 @@ SET(world_headers
|
||||
cliententry.h
|
||||
clientlist.h
|
||||
console.h
|
||||
../common/data_bucket.h
|
||||
dynamic_zone.h
|
||||
dynamic_zone_manager.h
|
||||
eql_config.h
|
||||
|
||||
@@ -12,8 +12,9 @@ void WorldserverCLI::DatabaseVersion(int argc, char **argv, argh::parser &cmd, s
|
||||
|
||||
Json::Value v;
|
||||
|
||||
v["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
|
||||
v["bots_database_version"] = RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0;
|
||||
v["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
|
||||
v["bots_database_version"] = RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0;
|
||||
v["custom_database_version"] = CUSTOM_BINARY_DATABASE_VERSION;
|
||||
|
||||
std::stringstream payload;
|
||||
payload << v;
|
||||
|
||||
@@ -16,7 +16,7 @@ void WorldserverCLI::EtlGetSettings(int argc, char **argv, argh::parser &cmd, st
|
||||
auto event_settings = player_event_logs.GetSettings();
|
||||
auto etl_details = player_event_logs.GetEtlSettings();
|
||||
|
||||
for (auto i = 0; i < PlayerEvent::EventType::MAX; i++) {
|
||||
for (int i = PlayerEvent::GM_COMMAND; i < PlayerEvent::EventType::MAX; i++) {
|
||||
player_events["event_id"] = event_settings[i].id;
|
||||
player_events["enabled"] = event_settings[i].event_enabled ? true : false;
|
||||
player_events["retention"] = event_settings[i].retention_days;
|
||||
|
||||
@@ -11,11 +11,12 @@ void WorldserverCLI::Version(int argc, char **argv, argh::parser &cmd, std::stri
|
||||
|
||||
Json::Value j;
|
||||
|
||||
j["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION;
|
||||
j["compile_date"] = COMPILE_DATE;
|
||||
j["compile_time"] = COMPILE_TIME;
|
||||
j["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
|
||||
j["server_version"] = CURRENT_VERSION;
|
||||
j["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION;
|
||||
j["compile_date"] = COMPILE_DATE;
|
||||
j["compile_time"] = COMPILE_TIME;
|
||||
j["custom_database_version"] = CUSTOM_BINARY_DATABASE_VERSION;
|
||||
j["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
|
||||
j["server_version"] = CURRENT_VERSION;
|
||||
|
||||
std::stringstream payload;
|
||||
payload << j;
|
||||
|
||||
+15
-3
@@ -95,9 +95,22 @@ void ConsoleApi(
|
||||
BenchTimer timer;
|
||||
timer.reset();
|
||||
|
||||
EQEmuApiWorldDataService::get(response, args);
|
||||
std::string method = args.empty() ? "" : args[0];
|
||||
|
||||
std::string method = args[0];
|
||||
if (method.empty()) {
|
||||
root["execution_time"] = std::to_string(timer.elapsed());
|
||||
root["method"] = method;
|
||||
root["data"] = response;
|
||||
root["error"] = "No method specified";
|
||||
|
||||
std::stringstream payload;
|
||||
payload << root;
|
||||
connection->SendLine(payload.str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Safe to call now that args[0] is known to exist
|
||||
EQEmuApiWorldDataService::get(response, args);
|
||||
|
||||
root["execution_time"] = std::to_string(timer.elapsed());
|
||||
root["method"] = method;
|
||||
@@ -105,7 +118,6 @@ void ConsoleApi(
|
||||
|
||||
std::stringstream payload;
|
||||
payload << root;
|
||||
|
||||
connection->SendLine(payload.str());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ void callGetZoneList(Json::Value &response)
|
||||
for (auto &zone: zoneserver_list.getZoneServerList()) {
|
||||
Json::Value row;
|
||||
|
||||
if (!zone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!zone->IsConnected()) {
|
||||
continue;
|
||||
}
|
||||
@@ -162,7 +166,8 @@ void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::str
|
||||
for (auto &t: ServerReload::GetTypes()) {
|
||||
if (std::to_string(t) == command || Strings::ToLower(ServerReload::GetName(t)) == command) {
|
||||
message(r, fmt::format("Reloading [{}] globally", ServerReload::GetName(t)));
|
||||
zoneserver_list.SendServerReload(t, nullptr);
|
||||
LogInfo("Queueing reload of type [{}] to zones", ServerReload::GetName(t));
|
||||
zoneserver_list.QueueServerReload(t);
|
||||
}
|
||||
found_command = true;
|
||||
}
|
||||
|
||||
+8
-12
@@ -182,7 +182,8 @@ int main(int argc, char **argv)
|
||||
EQTimeTimer.Start(600000);
|
||||
Timer parcel_prune_timer(86400000);
|
||||
parcel_prune_timer.Start(86400000);
|
||||
|
||||
Timer player_event_log_process(1000);
|
||||
player_event_log_process.Start(1000);
|
||||
|
||||
// global loads
|
||||
LogInfo("Loading launcher list");
|
||||
@@ -385,15 +386,6 @@ int main(int argc, char **argv)
|
||||
player_event_logs.Init();
|
||||
}
|
||||
|
||||
auto event_log_processor = std::jthread([](const std::stop_token& stoken) {
|
||||
while (!stoken.stop_requested()) {
|
||||
if (!RuleB(Logging, PlayerEventsQSProcess)) {
|
||||
player_event_logs.Process();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
});
|
||||
|
||||
auto loop_fn = [&](EQ::Timer* t) {
|
||||
Timer::SetCurrentTime();
|
||||
|
||||
@@ -487,6 +479,12 @@ int main(int argc, char **argv)
|
||||
shared_task_manager.Process();
|
||||
dynamic_zone_manager.Process();
|
||||
|
||||
if (!RuleB(Logging, PlayerEventsQSProcess)) {
|
||||
if (player_event_log_process.Check()) {
|
||||
player_event_logs.Process();
|
||||
}
|
||||
}
|
||||
|
||||
if (InterserverTimer.Check()) {
|
||||
InterserverTimer.Start();
|
||||
database.ping();
|
||||
@@ -506,8 +504,6 @@ int main(int argc, char **argv)
|
||||
|
||||
EQ::EventLoop::Get().Run();
|
||||
|
||||
event_log_processor.request_stop();
|
||||
|
||||
LogInfo("World main loop completed");
|
||||
LogInfo("Shutting down zone connections (if any)");
|
||||
zoneserver_list.KillAll();
|
||||
|
||||
@@ -677,10 +677,10 @@ void SharedTaskManager::SendAcceptNewSharedTaskPacket(
|
||||
);
|
||||
|
||||
auto d = reinterpret_cast<ServerSharedTaskRequest_Struct *>(p->pBuffer);
|
||||
d->requested_character_id = character_id;
|
||||
d->requested_task_id = task_id;
|
||||
d->requested_npc_type_id = npc_context_id;
|
||||
d->accept_time = accept_time;
|
||||
d->requested_character_id = character_id;
|
||||
d->requested_task_id = task_id;
|
||||
d->requested_npc_entity_id = npc_context_id;
|
||||
d->accept_time = accept_time;
|
||||
|
||||
// get requested character zone server
|
||||
ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id);
|
||||
|
||||
@@ -24,16 +24,16 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack)
|
||||
case ServerOP_SharedTaskRequest: {
|
||||
auto *r = (ServerSharedTaskRequest_Struct *) pack->pBuffer;
|
||||
LogTasksDetail(
|
||||
"[ServerOP_SharedTaskRequest] Received request from character [{}] task_id [{}] npc_type_id [{}]",
|
||||
"[ServerOP_SharedTaskRequest] Received request from character [{}] task_id [{}] npc_entity_id [{}]",
|
||||
r->requested_character_id,
|
||||
r->requested_task_id,
|
||||
r->requested_npc_type_id
|
||||
r->requested_npc_entity_id
|
||||
);
|
||||
|
||||
shared_task_manager.AttemptSharedTaskCreation(
|
||||
r->requested_task_id,
|
||||
r->requested_character_id,
|
||||
r->requested_npc_type_id
|
||||
r->requested_npc_entity_id
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
@@ -91,7 +91,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
}
|
||||
|
||||
//broadcast this packet to all zones.
|
||||
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "dynamic_zone_manager.h"
|
||||
#include "ucs.h"
|
||||
#include "clientlist.h"
|
||||
#include "../common/repositories/trader_repository.h"
|
||||
#include "../common/repositories/buyer_repository.h"
|
||||
|
||||
extern uint32 numzones;
|
||||
extern EQ::Random emu_random;
|
||||
@@ -84,6 +86,8 @@ void ZSList::Remove(const std::string &uuid)
|
||||
while (iter != zone_server_list.end()) {
|
||||
if ((*iter)->GetUUID().compare(uuid) == 0) {
|
||||
auto port = (*iter)->GetCPort();
|
||||
(*iter)->CheckToClearTraderAndBuyerTables();
|
||||
|
||||
zone_server_list.erase(iter);
|
||||
|
||||
if (port != 0) {
|
||||
@@ -128,6 +132,16 @@ void ZSList::Process() {
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (!m_queued_reloads.empty()) {
|
||||
m_queued_reloads_mutex.lock();
|
||||
for (auto &type : m_queued_reloads) {
|
||||
LogInfo("Sending reload of type [{}] to zones", ServerReload::GetName(type));
|
||||
SendServerReload(type, nullptr);
|
||||
}
|
||||
m_queued_reloads.clear();
|
||||
m_queued_reloads_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool ZSList::SendPacket(ServerPacket* pack) {
|
||||
@@ -999,3 +1013,10 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
void ZSList::QueueServerReload(ServerReload::Type &type)
|
||||
{
|
||||
m_queued_reloads_mutex.lock();
|
||||
m_queued_reloads.emplace_back(type);
|
||||
m_queued_reloads_mutex.unlock();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
class WorldTCPConnection;
|
||||
class ServerPacket;
|
||||
@@ -74,7 +75,10 @@ public:
|
||||
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const;
|
||||
inline uint32_t GetServerListCount() { return zone_server_list.size(); }
|
||||
void SendServerReload(ServerReload::Type type, uchar *packet = nullptr);
|
||||
std::mutex m_queued_reloads_mutex;
|
||||
std::vector<ServerReload::Type> m_queued_reloads = {};
|
||||
|
||||
void QueueServerReload(ServerReload::Type &type);
|
||||
private:
|
||||
void OnTick(EQ::Timer *t);
|
||||
uint32 NextID;
|
||||
|
||||
+18
-1
@@ -46,10 +46,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "../common/repositories/player_event_logs_repository.h"
|
||||
#include "../common/events/player_event_logs.h"
|
||||
#include "../common/patches/patches.h"
|
||||
#include "../zone/data_bucket.h"
|
||||
#include "../common/repositories/guild_tributes_repository.h"
|
||||
#include "../common/skill_caps.h"
|
||||
#include "../common/server_reload_types.h"
|
||||
#include "../common/repositories/trader_repository.h"
|
||||
#include "../common/repositories/buyer_repository.h"
|
||||
|
||||
extern ClientList client_list;
|
||||
extern GroupLFPList LFPGroupList;
|
||||
@@ -1860,3 +1861,19 @@ void ZoneServer::IncomingClient(Client* client) {
|
||||
SendPacket(pack);
|
||||
delete pack;
|
||||
}
|
||||
|
||||
void ZoneServer::CheckToClearTraderAndBuyerTables()
|
||||
{
|
||||
if (GetZoneID() == Zones::BAZAAR) {
|
||||
TraderRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format("`char_zone_id` = {} AND `char_zone_instance_id` = {}", GetZoneID(), GetInstanceID()
|
||||
)
|
||||
);
|
||||
BuyerRepository::DeleteBuyers(database, GetZoneID(), GetInstanceID());
|
||||
|
||||
LogTradingDetail(
|
||||
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]", GetZoneID(), GetInstanceID()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
inline const char* GetZoneName() const { return zone_name; }
|
||||
inline const char* GetZoneLongName() const { return long_name; }
|
||||
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
|
||||
void CheckToClearTraderAndBuyerTables();
|
||||
inline std::string GetCompileDate() const { return COMPILE_DATE; }
|
||||
const char* GetCompileTime() const{ return compiled; }
|
||||
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
|
||||
|
||||
+254
-59
@@ -12,7 +12,6 @@ SET(zone_sources
|
||||
bonuses.cpp
|
||||
bot.cpp
|
||||
bot_raid.cpp
|
||||
bot_command.cpp
|
||||
bot_database.cpp
|
||||
botspellsai.cpp
|
||||
cheat_manager.cpp
|
||||
@@ -23,9 +22,8 @@ SET(zone_sources
|
||||
client_packet.cpp
|
||||
client_process.cpp
|
||||
combat_record.cpp
|
||||
command.cpp
|
||||
corpse.cpp
|
||||
data_bucket.cpp
|
||||
../common/data_bucket.cpp
|
||||
doors.cpp
|
||||
dialogue_window.cpp
|
||||
dynamic_zone.cpp
|
||||
@@ -48,36 +46,6 @@ SET(zone_sources
|
||||
horse.cpp
|
||||
inventory.cpp
|
||||
loot.cpp
|
||||
lua_bot.cpp
|
||||
lua_bit.cpp
|
||||
lua_buff.cpp
|
||||
lua_corpse.cpp
|
||||
lua_client.cpp
|
||||
lua_database.cpp
|
||||
lua_door.cpp
|
||||
lua_encounter.cpp
|
||||
lua_entity.cpp
|
||||
lua_entity_list.cpp
|
||||
lua_expedition.cpp
|
||||
lua_general.cpp
|
||||
lua_group.cpp
|
||||
lua_hate_list.cpp
|
||||
lua_inventory.cpp
|
||||
lua_item.cpp
|
||||
lua_iteminst.cpp
|
||||
lua_merc.cpp
|
||||
lua_mob.cpp
|
||||
lua_mod.cpp
|
||||
lua_npc.cpp
|
||||
lua_object.cpp
|
||||
lua_packet.cpp
|
||||
lua_parser.cpp
|
||||
lua_parser_events.cpp
|
||||
lua_raid.cpp
|
||||
lua_spawn.cpp
|
||||
lua_spell.cpp
|
||||
lua_stat_bonuses.cpp
|
||||
lua_zone.cpp
|
||||
embperl.cpp
|
||||
entity.cpp
|
||||
exp.cpp
|
||||
@@ -108,30 +76,6 @@ SET(zone_sources
|
||||
pathfinder_nav_mesh.cpp
|
||||
pathfinder_null.cpp
|
||||
pathing.cpp
|
||||
perl_bot.cpp
|
||||
perl_buff.cpp
|
||||
perl_client.cpp
|
||||
perl_database.cpp
|
||||
perl_doors.cpp
|
||||
perl_entity.cpp
|
||||
perl_expedition.cpp
|
||||
perl_groups.cpp
|
||||
perl_hateentry.cpp
|
||||
perl_inventory.cpp
|
||||
perl_merc.cpp
|
||||
perl_mob.cpp
|
||||
perl_npc.cpp
|
||||
perl_object.cpp
|
||||
perl_perlpacket.cpp
|
||||
perl_player_corpse.cpp
|
||||
perl_questitem.cpp
|
||||
perl_questitem_data.cpp
|
||||
perl_raids.cpp
|
||||
perl_spawn.cpp
|
||||
perl_spell.cpp
|
||||
perl_stat_bonuses.cpp
|
||||
perl_zone.cpp
|
||||
perlpacket.cpp
|
||||
petitions.cpp
|
||||
pets.cpp
|
||||
position.cpp
|
||||
@@ -195,7 +139,7 @@ SET(zone_headers
|
||||
command.h
|
||||
common.h
|
||||
corpse.h
|
||||
data_bucket.h
|
||||
../common/data_bucket.h
|
||||
doors.h
|
||||
dialogue_window.h
|
||||
dynamic_zone.h
|
||||
@@ -296,10 +240,247 @@ SET(zone_headers
|
||||
zone_save_state.h
|
||||
zone_cli.cpp)
|
||||
|
||||
# lua unity build
|
||||
set(lua_sources
|
||||
lua_bot.cpp
|
||||
lua_bit.cpp
|
||||
lua_buff.cpp
|
||||
lua_corpse.cpp
|
||||
lua_client.cpp
|
||||
lua_database.cpp
|
||||
lua_door.cpp
|
||||
lua_encounter.cpp
|
||||
lua_entity.cpp
|
||||
lua_entity_list.cpp
|
||||
lua_expedition.cpp
|
||||
lua_general.cpp
|
||||
lua_group.cpp
|
||||
lua_hate_list.cpp
|
||||
lua_inventory.cpp
|
||||
lua_item.cpp
|
||||
lua_iteminst.cpp
|
||||
lua_merc.cpp
|
||||
lua_mob.cpp
|
||||
lua_mod.cpp
|
||||
lua_npc.cpp
|
||||
lua_object.cpp
|
||||
lua_packet.cpp
|
||||
lua_parser.cpp
|
||||
lua_parser_events.cpp
|
||||
lua_raid.cpp
|
||||
lua_spawn.cpp
|
||||
lua_spell.cpp
|
||||
lua_stat_bonuses.cpp
|
||||
lua_zone.cpp
|
||||
)
|
||||
|
||||
add_library(lua_zone STATIC ${lua_sources})
|
||||
set_target_properties(lua_zone PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 8)
|
||||
|
||||
# perl unity build
|
||||
set(perl_sources
|
||||
perl_bot.cpp
|
||||
perl_buff.cpp
|
||||
perl_client.cpp
|
||||
perl_database.cpp
|
||||
perl_doors.cpp
|
||||
perl_entity.cpp
|
||||
perl_expedition.cpp
|
||||
perl_groups.cpp
|
||||
perl_hateentry.cpp
|
||||
perl_inventory.cpp
|
||||
perl_merc.cpp
|
||||
perl_mob.cpp
|
||||
perl_npc.cpp
|
||||
perl_object.cpp
|
||||
perl_perlpacket.cpp
|
||||
perl_player_corpse.cpp
|
||||
perl_questitem.cpp
|
||||
perl_questitem_data.cpp
|
||||
perl_raids.cpp
|
||||
perl_spawn.cpp
|
||||
perl_spell.cpp
|
||||
perl_stat_bonuses.cpp
|
||||
perl_zone.cpp
|
||||
perlpacket.cpp
|
||||
)
|
||||
|
||||
add_library(perl_zone STATIC ${perl_sources})
|
||||
set_target_properties(perl_zone PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 8)
|
||||
|
||||
# gm commands
|
||||
set(gm_command_sources
|
||||
command.cpp
|
||||
bot_command.cpp
|
||||
gm_commands/acceptrules.cpp
|
||||
gm_commands/advnpcspawn.cpp
|
||||
gm_commands/aggrozone.cpp
|
||||
gm_commands/ai.cpp
|
||||
gm_commands/appearance.cpp
|
||||
gm_commands/appearanceeffects.cpp
|
||||
gm_commands/attack.cpp
|
||||
gm_commands/augmentitem.cpp
|
||||
gm_commands/ban.cpp
|
||||
gm_commands/bugs.cpp
|
||||
gm_commands/camerashake.cpp
|
||||
gm_commands/castspell.cpp
|
||||
gm_commands/chat.cpp
|
||||
gm_commands/clearxtargets.cpp
|
||||
gm_commands/copycharacter.cpp
|
||||
gm_commands/corpse.cpp
|
||||
gm_commands/corpsefix.cpp
|
||||
gm_commands/countitem.cpp
|
||||
gm_commands/damage.cpp
|
||||
gm_commands/databuckets.cpp
|
||||
gm_commands/dbspawn2.cpp
|
||||
gm_commands/delacct.cpp
|
||||
gm_commands/delpetition.cpp
|
||||
gm_commands/depop.cpp
|
||||
gm_commands/depopzone.cpp
|
||||
gm_commands/devtools.cpp
|
||||
gm_commands/disablerecipe.cpp
|
||||
gm_commands/disarmtrap.cpp
|
||||
gm_commands/doanim.cpp
|
||||
gm_commands/door.cpp
|
||||
gm_commands/door_manipulation.cpp
|
||||
gm_commands/dye.cpp
|
||||
gm_commands/dz.cpp
|
||||
gm_commands/dzkickplayers.cpp
|
||||
gm_commands/editmassrespawn.cpp
|
||||
gm_commands/emote.cpp
|
||||
gm_commands/emptyinventory.cpp
|
||||
gm_commands/enablerecipe.cpp
|
||||
gm_commands/entityvariable.cpp
|
||||
gm_commands/exptoggle.cpp
|
||||
gm_commands/faction.cpp
|
||||
gm_commands/evolving_items.cpp
|
||||
gm_commands/feature.cpp
|
||||
gm_commands/find.cpp
|
||||
gm_commands/fish.cpp
|
||||
gm_commands/fixmob.cpp
|
||||
gm_commands/flagedit.cpp
|
||||
gm_commands/fleeinfo.cpp
|
||||
gm_commands/forage.cpp
|
||||
gm_commands/gearup.cpp
|
||||
gm_commands/giveitem.cpp
|
||||
gm_commands/givemoney.cpp
|
||||
gm_commands/gmzone.cpp
|
||||
gm_commands/goto.cpp
|
||||
gm_commands/grantaa.cpp
|
||||
gm_commands/grid.cpp
|
||||
gm_commands/guild.cpp
|
||||
gm_commands/hp.cpp
|
||||
gm_commands/illusion_block.cpp
|
||||
gm_commands/instance.cpp
|
||||
gm_commands/interrogateinv.cpp
|
||||
gm_commands/interrupt.cpp
|
||||
gm_commands/invsnapshot.cpp
|
||||
gm_commands/ipban.cpp
|
||||
gm_commands/kick.cpp
|
||||
gm_commands/kill.cpp
|
||||
gm_commands/killallnpcs.cpp
|
||||
gm_commands/list.cpp
|
||||
gm_commands/lootsim.cpp
|
||||
gm_commands/loc.cpp
|
||||
gm_commands/logs.cpp
|
||||
gm_commands/makepet.cpp
|
||||
gm_commands/memspell.cpp
|
||||
gm_commands/merchantshop.cpp
|
||||
gm_commands/modifynpcstat.cpp
|
||||
gm_commands/movechar.cpp
|
||||
gm_commands/movement.cpp
|
||||
gm_commands/myskills.cpp
|
||||
gm_commands/mysql.cpp
|
||||
gm_commands/mystats.cpp
|
||||
gm_commands/npccast.cpp
|
||||
gm_commands/npcedit.cpp
|
||||
gm_commands/npceditmass.cpp
|
||||
gm_commands/npcemote.cpp
|
||||
gm_commands/npcloot.cpp
|
||||
gm_commands/npcsay.cpp
|
||||
gm_commands/npcshout.cpp
|
||||
gm_commands/npcspawn.cpp
|
||||
gm_commands/npctypespawn.cpp
|
||||
gm_commands/nudge.cpp
|
||||
gm_commands/nukebuffs.cpp
|
||||
gm_commands/nukeitem.cpp
|
||||
gm_commands/object.cpp
|
||||
gm_commands/object_manipulation.cpp
|
||||
gm_commands/parcels.cpp
|
||||
gm_commands/path.cpp
|
||||
gm_commands/peqzone.cpp
|
||||
gm_commands/petitems.cpp
|
||||
gm_commands/petname.cpp
|
||||
gm_commands/picklock.cpp
|
||||
gm_commands/profanity.cpp
|
||||
gm_commands/push.cpp
|
||||
gm_commands/raidloot.cpp
|
||||
gm_commands/randomfeatures.cpp
|
||||
gm_commands/refreshgroup.cpp
|
||||
gm_commands/reload.cpp
|
||||
gm_commands/removeitem.cpp
|
||||
gm_commands/repop.cpp
|
||||
gm_commands/resetaa.cpp
|
||||
gm_commands/resetaa_timer.cpp
|
||||
gm_commands/resetdisc_timer.cpp
|
||||
gm_commands/revoke.cpp
|
||||
gm_commands/roambox.cpp
|
||||
gm_commands/rules.cpp
|
||||
gm_commands/save.cpp
|
||||
gm_commands/scale.cpp
|
||||
gm_commands/scribespell.cpp
|
||||
gm_commands/scribespells.cpp
|
||||
gm_commands/sendzonespawns.cpp
|
||||
gm_commands/sensetrap.cpp
|
||||
gm_commands/serverrules.cpp
|
||||
gm_commands/set.cpp
|
||||
gm_commands/show.cpp
|
||||
gm_commands/shutdown.cpp
|
||||
gm_commands/spawn.cpp
|
||||
gm_commands/spawneditmass.cpp
|
||||
gm_commands/spawnfix.cpp
|
||||
gm_commands/faction_association.cpp
|
||||
gm_commands/stun.cpp
|
||||
gm_commands/summon.cpp
|
||||
gm_commands/summonburiedplayercorpse.cpp
|
||||
gm_commands/summonitem.cpp
|
||||
gm_commands/suspend.cpp
|
||||
gm_commands/suspendmulti.cpp
|
||||
gm_commands/takeplatinum.cpp
|
||||
gm_commands/task.cpp
|
||||
gm_commands/traindisc.cpp
|
||||
gm_commands/tune.cpp
|
||||
gm_commands/undye.cpp
|
||||
gm_commands/unmemspell.cpp
|
||||
gm_commands/unmemspells.cpp
|
||||
gm_commands/unscribespell.cpp
|
||||
gm_commands/unscribespells.cpp
|
||||
gm_commands/untraindisc.cpp
|
||||
gm_commands/untraindiscs.cpp
|
||||
gm_commands/wc.cpp
|
||||
gm_commands/worldshutdown.cpp
|
||||
gm_commands/worldwide.cpp
|
||||
gm_commands/wp.cpp
|
||||
gm_commands/wpadd.cpp
|
||||
gm_commands/zone.cpp
|
||||
gm_commands/zonebootup.cpp
|
||||
gm_commands/zoneshutdown.cpp
|
||||
gm_commands/zonevariable.cpp
|
||||
gm_commands/zone_instance.cpp
|
||||
gm_commands/zone_shard.cpp
|
||||
gm_commands/zsave.cpp
|
||||
)
|
||||
|
||||
add_library(gm_commands_zone STATIC ${gm_command_sources})
|
||||
set_target_properties(gm_commands_zone PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 32)
|
||||
|
||||
# zone combine sources and headers
|
||||
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
|
||||
|
||||
# binary output directory
|
||||
INSTALL(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
|
||||
# precompile headers
|
||||
IF (WIN32 AND EQEMU_BUILD_PCH)
|
||||
TARGET_PRECOMPILE_HEADERS(zone PRIVATE ../common/pch/pch.h)
|
||||
TARGET_PRECOMPILE_HEADERS(zone PRIVATE ../common/types.h ../common/eqemu_logsys.h ../common/eqemu_logsys_log_aliases.h ../common/features.h ../common/global_define.h)
|
||||
@@ -308,6 +489,20 @@ ENDIF()
|
||||
|
||||
ADD_DEFINITIONS(-DZONE)
|
||||
|
||||
TARGET_LINK_LIBRARIES(zone ${ZONE_LIBS})
|
||||
# link lua_zone unity build against luabind
|
||||
target_link_libraries(lua_zone PRIVATE luabind)
|
||||
if (EQEMU_BUILD_STATIC AND LUA_LIBRARY)
|
||||
target_link_libraries(zone PRIVATE ${LUA_LIBRARY})
|
||||
endif()
|
||||
|
||||
# perl unity build links against perl_zone
|
||||
target_link_libraries(perl_zone PRIVATE perlbind)
|
||||
if (EQEMU_BUILD_STATIC AND PERL_LIBRARY)
|
||||
target_link_libraries(zone PRIVATE ${PERL_LIBRARY})
|
||||
endif()
|
||||
|
||||
# link zone against common libraries
|
||||
target_link_libraries(zone PRIVATE ${ZONE_LIBS} lua_zone perl_zone gm_commands_zone)
|
||||
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
|
||||
|
||||
|
||||
+41
-26
@@ -3068,11 +3068,11 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
else
|
||||
SetAssistAggro(true);
|
||||
|
||||
bool wasengaged = IsEngaged();
|
||||
bool was_engaged = IsEngaged();
|
||||
Mob* owner = other->GetOwner();
|
||||
Mob* mypet = GetPet();
|
||||
Mob* myowner = GetOwner();
|
||||
Mob* targetmob = GetTarget();
|
||||
Mob* my_pet = GetPet();
|
||||
Mob* my_owner = GetOwner();
|
||||
Mob* target_mob = GetTarget();
|
||||
bool on_hatelist = CheckAggro(other);
|
||||
|
||||
AddRampage(other);
|
||||
@@ -3101,7 +3101,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
if (IsPet()) {
|
||||
if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list
|
||||
return;
|
||||
if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !wasengaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold"
|
||||
if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !was_engaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold"
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3134,7 +3134,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
return;
|
||||
}
|
||||
|
||||
if (other == myowner) {
|
||||
if (other == my_owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3236,26 +3236,39 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
}
|
||||
}
|
||||
|
||||
if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it
|
||||
if (
|
||||
!mypet->IsFamiliar() &&
|
||||
!mypet->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
|
||||
!(IsBot() && mypet->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
|
||||
!(IsClient() && mypet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
|
||||
!(IsNPC() && mypet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
|
||||
) {
|
||||
mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
if (my_pet) {
|
||||
bool aggro_immunity = my_pet->GetSpecialAbility(SpecialAbility::AggroImmunity);
|
||||
bool bot_aggro_immunity = IsBot() && my_pet->GetSpecialAbility(SpecialAbility::BotAggroImmunity);
|
||||
bool client_aggro_immunity = IsClient() && my_pet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
|
||||
bool npc_aggro_immunity = IsNPC() && my_pet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
|
||||
bool can_add_to_hatelist = !my_pet->IsFamiliar() &&
|
||||
!aggro_immunity &&
|
||||
!bot_aggro_immunity &&
|
||||
!client_aggro_immunity &&
|
||||
!npc_aggro_immunity;
|
||||
|
||||
if (can_add_to_hatelist) {
|
||||
bool bot_with_controllable_pet = IsBot() && CastToBot()->HasControllablePet(BotAnimEmpathy::Attack);
|
||||
|
||||
if (!IsBot() || bot_with_controllable_pet) {
|
||||
my_pet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (myowner) { // I am a pet, add other to owner if it's NPC/LD
|
||||
if (
|
||||
myowner->IsAIControlled() &&
|
||||
!myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
|
||||
!(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
|
||||
!(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
|
||||
!(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
|
||||
) {
|
||||
myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD
|
||||
if (my_owner->IsAIControlled()) {
|
||||
bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity);
|
||||
bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity);
|
||||
bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
|
||||
bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
|
||||
bool can_add_to_hatelist = !aggro_immunity &&
|
||||
!bot_aggro_immunity &&
|
||||
!client_aggro_immunity &&
|
||||
!npc_aggro_immunity;
|
||||
|
||||
if (can_add_to_hatelist) {
|
||||
my_owner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3264,7 +3277,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
entity_list.AddTempPetsToHateList(this, other, bFrenzy);
|
||||
}
|
||||
|
||||
if (!wasengaged) {
|
||||
if (!was_engaged) {
|
||||
if (IsNPC() && other->IsClient() && other->CastToClient()) {
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) {
|
||||
parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0);
|
||||
@@ -6677,7 +6690,9 @@ void Client::SetAttackTimer()
|
||||
else
|
||||
speed = static_cast<int>(speed + ((hhe / 100.0f) * delay));
|
||||
}
|
||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true);
|
||||
|
||||
bool reinit = !TimerToUse->Enabled();
|
||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), reinit, reinit);
|
||||
|
||||
if (i == EQ::invslot::slotPrimary) {
|
||||
primary_speed = speed;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record)
|
||||
: NPC(type_data, 0, owner->GetPosition(), GravityBehavior::Flying), spell_id(record.spell_id),
|
||||
distance(record.distance),
|
||||
remove_timer(record.duration), movement_timer(100), process_timer(1000), aura_id(-1)
|
||||
remove_timer(record.duration), movement_timer(1000), process_timer(1000), aura_id(-1)
|
||||
{
|
||||
GiveNPCTypeData(type_data); // we will delete this later on
|
||||
m_owner = owner->GetID();
|
||||
|
||||
+192
-86
@@ -2102,10 +2102,6 @@ void Bot::SetGuardMode() {
|
||||
StopMoving();
|
||||
m_GuardPoint = GetPosition();
|
||||
SetGuardFlag();
|
||||
|
||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
GetPet()->StopMoving();
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::SetHoldMode() {
|
||||
@@ -2270,7 +2266,7 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
// This causes conflicts with default pet handler (bounces between targets)
|
||||
if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||
// We don't add to hate list here because it's assumed to already be on the list
|
||||
GetPet()->SetTarget(tar);
|
||||
}
|
||||
@@ -2284,11 +2280,11 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
// COMBAT RANGE CALCS
|
||||
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
||||
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
||||
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||
|
||||
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
|
||||
// Item variables
|
||||
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
|
||||
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
|
||||
|
||||
@@ -2310,20 +2306,36 @@ void Bot::AI_Process()
|
||||
|
||||
// PULLING FLAG (ACTIONABLE RANGE)
|
||||
|
||||
if (PULLING_BOT || RETURNING_BOT) {
|
||||
if (!TargetValidation(tar)) { return; }
|
||||
if (PULLING_BOT) {
|
||||
if (!TargetValidation(tar)) {
|
||||
SetPullFlag(false);
|
||||
SetPullingFlag(false);
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
|
||||
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (at_combat_range) {
|
||||
if (
|
||||
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||
if (!at_combat_range && RuleB(Bots, UseSpellPulling)) {
|
||||
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
|
||||
|
||||
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
|
||||
at_combat_range = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (at_combat_range && DoLosChecks(tar)) {
|
||||
bool ai_cast_successful = false;
|
||||
bool can_range_attack = !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||
RuleB(Bots, AllowRangedPulling) &&
|
||||
IsBotRanged() &&
|
||||
ranged_timer.Check(false)
|
||||
) {
|
||||
ranged_timer.Check(false);
|
||||
|
||||
if (can_range_attack) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
|
||||
if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) {
|
||||
@@ -2335,30 +2347,33 @@ void Bot::AI_Process()
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
RuleB(Bots, AllowAISpellPulling) &&
|
||||
bool can_ai_spell_pull = RuleB(Bots, AllowAISpellPulling) &&
|
||||
!IsBotNonSpellFighter() &&
|
||||
AI_HasSpells()
|
||||
) {
|
||||
AI_HasSpells();
|
||||
|
||||
if (can_ai_spell_pull) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
SetPullingSpell(true);
|
||||
AI_EngagedCastCheck();
|
||||
ai_cast_successful = AI_EngagedCastCheck();
|
||||
SetPullingSpell(false);
|
||||
|
||||
return;
|
||||
if (ai_cast_successful) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (RuleB(Bots, UseSpellPulling)) {
|
||||
uint16 spell_id = RuleI(Bots, PullSpellID);
|
||||
if (RuleB(Bots, UseSpellPulling)) {
|
||||
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
|
||||
|
||||
if (tar_distance <= spells[spell_id].range) {
|
||||
StopMoving();
|
||||
SetPullingSpell(true);
|
||||
CastSpell(spell_id, tar->GetID());
|
||||
SetPullingSpell(false);
|
||||
|
||||
return;
|
||||
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
SetPullingSpell(true);
|
||||
CastSpell(pull_spell_id, tar->GetID());
|
||||
SetPullingSpell(false);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TryPursueTarget(leash_distance);
|
||||
@@ -2551,6 +2566,12 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
|
||||
if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) {
|
||||
SetPullingFlag(false);
|
||||
SetReturningFlag(false);
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (TryAutoDefend(bot_owner, leash_distance) ) {
|
||||
@@ -2559,14 +2580,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
|
||||
|
||||
SetTarget(nullptr);
|
||||
|
||||
if (
|
||||
HasPet() &&
|
||||
(
|
||||
GetClass() != Class::Enchanter ||
|
||||
GetPet()->GetPetType() != petAnimation ||
|
||||
GetAA(aaAnimationEmpathy) >= 1
|
||||
)
|
||||
) {
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
@@ -2729,7 +2743,7 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) {
|
||||
SetTarget(hater);
|
||||
SetAttackingFlag();
|
||||
|
||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
if (HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||
GetPet()->AddToHateList(hater, 1);
|
||||
GetPet()->SetTarget(hater);
|
||||
}
|
||||
@@ -2799,14 +2813,7 @@ bool Bot::TryPursueTarget(float leash_distance) {
|
||||
WipeHateList();
|
||||
SetTarget(nullptr);
|
||||
|
||||
if (
|
||||
HasPet() &&
|
||||
(
|
||||
GetClass() != Class::Enchanter ||
|
||||
GetPet()->GetPetType() != petAnimation ||
|
||||
GetAA(aaAnimationEmpathy) >= 2
|
||||
)
|
||||
) {
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
@@ -3191,7 +3198,8 @@ bool Bot::IsValidTarget(
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3225,7 +3233,8 @@ Mob* Bot::GetBotTarget(Client* bot_owner)
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3248,15 +3257,11 @@ bool Bot::TargetValidation(Mob* other) {
|
||||
}
|
||||
|
||||
bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) {
|
||||
auto engage_range = (GetBotDistanceRanged() < 30 ? 30 : GetBotDistanceRanged());
|
||||
bool target_check = !GetTarget() || Distance(GetPosition(), GetTarget()->GetPosition()) <= 75.0f;
|
||||
bool returned_check = (NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
|
||||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance());
|
||||
|
||||
if (
|
||||
(GetTarget() && Distance(GetPosition(), GetTarget()->GetPosition()) <= engage_range) &&
|
||||
(
|
||||
(NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
|
||||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance())
|
||||
)
|
||||
) { // Once we're back, clear blocking flags so everyone else can join in
|
||||
if (target_check && returned_check) { // Once we're back, clear blocking flags so everyone else can join in
|
||||
WipeHateList();
|
||||
SetTarget(nullptr);
|
||||
SetPullingFlag(false);
|
||||
@@ -3264,9 +3269,10 @@ bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_dist
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
|
||||
if (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1) {
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
@@ -3307,7 +3313,8 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -3316,11 +3323,16 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
|
||||
SetPullingFlag(false);
|
||||
SetReturningFlag();
|
||||
|
||||
if (HasPet() &&
|
||||
(GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
|
||||
Mob* my_pet = GetPet();
|
||||
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
if (my_pet) {
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
my_pet->WipeHateList();
|
||||
my_pet->SetTarget(nullptr);
|
||||
} else {
|
||||
my_pet->AddToHateList(GetTarget(), 1);
|
||||
my_pet->SetTarget(GetTarget());
|
||||
}
|
||||
}
|
||||
|
||||
if (GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive)) {
|
||||
@@ -3482,7 +3494,7 @@ Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint
|
||||
|
||||
void Bot::SetOwnerTarget(Client* bot_owner) {
|
||||
if (GetPet() && (PULLING_BOT || RETURNING_BOT)) {
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
}
|
||||
|
||||
SetAttackFlag(false);
|
||||
@@ -3502,7 +3514,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) {
|
||||
SetTarget(attack_target);
|
||||
SetAttackingFlag();
|
||||
|
||||
if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
if (HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->AddToHateList(attack_target, 1);
|
||||
GetPet()->SetTarget(attack_target);
|
||||
@@ -3520,20 +3532,21 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
||||
SetReturningFlag(false);
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
|
||||
if (NOT_HOLDING && NOT_PASSIVE) {
|
||||
auto pull_target = bot_owner->GetTarget();
|
||||
|
||||
if (pull_target) {
|
||||
if (raid) {
|
||||
const auto msg = fmt::format("Pulling {}.", pull_target->GetCleanName());
|
||||
raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100);
|
||||
} else {
|
||||
RaidGroupSay(
|
||||
fmt::format(
|
||||
"Pulling {}.",
|
||||
pull_target->GetCleanName()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
RaidGroupSay(
|
||||
fmt::format(
|
||||
"Pulling {}.",
|
||||
pull_target->GetCleanName()
|
||||
).c_str()
|
||||
);
|
||||
|
||||
InterruptSpell();
|
||||
WipeHateList();
|
||||
@@ -3542,12 +3555,15 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
||||
SetPullingFlag();
|
||||
bot_owner->SetBotPulling();
|
||||
|
||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
|
||||
if (GetPet()) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
m_previous_pet_order = GetPet()->GetPetOrder();
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
|
||||
GetPet()->SetPetOrder(SPO_Guard);
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::Guard)) {
|
||||
m_previous_pet_order = GetPet()->GetPetOrder();
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
|
||||
GetPet()->SetPetOrder(SPO_Guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3624,7 +3640,7 @@ bool Bot::Spawn(Client* botCharacterOwner) {
|
||||
entity_list.AddBot(this, true, true);
|
||||
|
||||
ClearDataBucketCache();
|
||||
DataBucket::GetDataBuckets(this);
|
||||
LoadDataBucketsCache();
|
||||
LoadBotSpellSettings();
|
||||
if (!AI_AddBotSpells(GetBotSpellID())) {
|
||||
GetBotOwner()->CastToClient()->Message(
|
||||
@@ -8715,6 +8731,86 @@ bool Bot::CheckCampSpawnConditions(Client* c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::CheckHighEnoughLevelForBots(Client* c, uint8 bot_class) {
|
||||
auto bot_character_level = c->GetBotRequiredLevel(bot_class);
|
||||
bool not_high_enough_level = bot_character_level >= 0 && c->GetLevel() < bot_character_level;
|
||||
|
||||
if (not_high_enough_level) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"You must be level {} to spawn {}bots.",
|
||||
bot_character_level,
|
||||
bot_class ? GetClassIDName(bot_class) : ""
|
||||
).c_str()
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class) {
|
||||
auto bot_creation_limit = c->GetBotCreationLimit(bot_class);
|
||||
bool is_beyond_spawn_limit = bot_creation_limit >= 0 && bot_count >= bot_creation_limit;
|
||||
|
||||
if (is_beyond_spawn_limit) {
|
||||
std::string message;
|
||||
|
||||
if (bot_creation_limit) {
|
||||
message = fmt::format(
|
||||
"You cannot create anymore than {} {}bot{}.",
|
||||
bot_creation_limit,
|
||||
bot_class ? GetClassIDName(bot_class) : "",
|
||||
bot_creation_limit != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = fmt::format(
|
||||
"You cannot create any {}bots.",
|
||||
bot_class ? GetClassIDName(bot_class) : ""
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::Yellow, message.c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::CheckSpawnLimit(Client* c, uint8 bot_class) {
|
||||
auto bot_spawn_limit = c->GetBotSpawnLimit(bot_class);
|
||||
auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID(), bot_class);
|
||||
bool is_beyond_spawn_limit = bot_spawn_limit >= 0 && spawned_bot_count >= bot_spawn_limit;
|
||||
|
||||
if (is_beyond_spawn_limit) {
|
||||
std::string message;
|
||||
|
||||
if (bot_spawn_limit) {
|
||||
message = fmt::format(
|
||||
"You cannot have more than {} spawned {}bot{}.",
|
||||
bot_spawn_limit,
|
||||
bot_class ? GetClassIDName(bot_class) : "",
|
||||
bot_spawn_limit != 1 ? "s" : ""
|
||||
);
|
||||
}
|
||||
else {
|
||||
message = fmt::format(
|
||||
"You are not currently allowed to spawn any {}bots.",
|
||||
bot_class ? GetClassIDName(bot_class) : ""
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bot::AddBotStartingItems(uint16 race_id, uint8 class_id)
|
||||
{
|
||||
if (!IsPlayerRace(race_id) || !IsPlayerClass(class_id)) {
|
||||
@@ -12009,7 +12105,7 @@ bool Bot::DoCombatPositioning(const CombatPositioningInput& input)
|
||||
}
|
||||
else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
|
||||
adjustment_needed =
|
||||
is_too_close ||
|
||||
(IsTaunting() && is_too_close) ||
|
||||
los_adjust ||
|
||||
(is_melee && !input.front_mob);
|
||||
|
||||
@@ -13358,3 +13454,13 @@ bool Bot::IsValidBotStance(uint8 stance) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bot::HasControllablePet(uint8 ranks_required) {
|
||||
if (!GetPet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetClass() != Class::Enchanter ||
|
||||
GetPet()->GetPetType() != petAnimation ||
|
||||
GetAA(aaAnimationEmpathy) >= ranks_required;
|
||||
}
|
||||
+11
-1
@@ -229,6 +229,12 @@ static std::map<uint16, std::string> botSubType_names = {
|
||||
{ CommandedSubTypes::Selo, "Selo" }
|
||||
};
|
||||
|
||||
namespace BotAnimEmpathy {
|
||||
constexpr uint8 Guard = 1;
|
||||
constexpr uint8 Attack = 2;
|
||||
constexpr uint8 BackOff = 3;
|
||||
};
|
||||
|
||||
class Bot : public NPC {
|
||||
friend class Mob;
|
||||
public:
|
||||
@@ -747,7 +753,7 @@ public:
|
||||
static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
|
||||
static BotSpell GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
|
||||
|
||||
static Mob* GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_type, bool AE);
|
||||
static Mob* GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE);
|
||||
static BotSpell GetBestBotSpellForMez(Bot* caster, uint16 spell_type = BotSpellTypes::Mez);
|
||||
static BotSpell GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet);
|
||||
static std::string GetBotMagicianPetType(Bot* caster);
|
||||
@@ -786,6 +792,7 @@ public:
|
||||
EQ::ItemInstance* GetBotItem(uint16 slot_id);
|
||||
bool GetSpawnStatus() { return _spawnStatus; }
|
||||
uint8 GetPetChooserID() { return _petChooserID; }
|
||||
bool HasControllablePet(uint8 ranks_required = 0);
|
||||
bool IsBotRanged() { return _botRangedSetting; }
|
||||
bool IsBotCharmer() { return _botCharmer; }
|
||||
bool IsBot() const override { return true; }
|
||||
@@ -1105,6 +1112,9 @@ public:
|
||||
|
||||
// Public "Refactor" Methods
|
||||
static bool CheckCampSpawnConditions(Client* c);
|
||||
static bool CheckHighEnoughLevelForBots(Client* c, uint8 bot_class = Class::None);
|
||||
static bool CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class = Class::None);
|
||||
static bool CheckSpawnLimit(Client* c, uint8 bot_class = Class::None);
|
||||
|
||||
protected:
|
||||
void BotMeditate(bool is_sitting);
|
||||
|
||||
+12
-69
@@ -468,7 +468,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
|
||||
|
||||
bool available_flag = false;
|
||||
|
||||
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
|
||||
!database.botdb.QueryNameAvailability(bot_name, available_flag);
|
||||
|
||||
if (!available_flag) {
|
||||
bot_owner->Message(
|
||||
@@ -517,88 +517,31 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
auto bot_creation_limit = bot_owner->GetBotCreationLimit();
|
||||
auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class);
|
||||
if (!Bot::CheckHighEnoughLevelForBots(bot_owner)) {
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
if (!Bot::CheckHighEnoughLevelForBots(bot_owner, bot_class)) {
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
uint32 bot_count = 0;
|
||||
uint32 bot_class_count = 0;
|
||||
|
||||
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
|
||||
bot_owner->Message(Chat::Yellow, "Failed to query bot count.");
|
||||
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
|
||||
std::string message;
|
||||
|
||||
if (bot_creation_limit) {
|
||||
message = fmt::format(
|
||||
"You cannot create anymore than {} bot{}.",
|
||||
bot_creation_limit,
|
||||
bot_creation_limit != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = "You cannot create any bots.";
|
||||
}
|
||||
|
||||
bot_owner->Message(Chat::Yellow, message.c_str());
|
||||
if (!Bot::CheckCreateLimit(bot_owner, bot_count)) {
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) {
|
||||
std::string message;
|
||||
|
||||
if (bot_creation_limit_class) {
|
||||
message = fmt::format(
|
||||
"You cannot create anymore than {} {} bot{}.",
|
||||
bot_creation_limit_class,
|
||||
GetClassIDName(bot_class),
|
||||
bot_creation_limit_class != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = fmt::format(
|
||||
"You cannot create any {} bots.",
|
||||
GetClassIDName(bot_class)
|
||||
);
|
||||
}
|
||||
|
||||
bot_owner->Message(Chat::Yellow, message.c_str());
|
||||
if (!Bot::CheckCreateLimit(bot_owner, bot_class_count, bot_class)) {
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
auto bot_character_level = bot_owner->GetBotRequiredLevel();
|
||||
|
||||
if (
|
||||
bot_character_level >= 0 &&
|
||||
bot_owner->GetLevel() < bot_character_level
|
||||
) {
|
||||
bot_owner->Message(
|
||||
Chat::Yellow,
|
||||
fmt::format(
|
||||
"You must be level {} to use bots.",
|
||||
bot_character_level
|
||||
).c_str()
|
||||
);
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
auto bot_character_level_class = bot_owner->GetBotRequiredLevel(bot_class);
|
||||
|
||||
if (
|
||||
bot_character_level_class >= 0 &&
|
||||
bot_owner->GetLevel() < bot_character_level_class
|
||||
) {
|
||||
bot_owner->Message(
|
||||
Chat::Yellow,
|
||||
fmt::format(
|
||||
"You must be level {} to use {} bots.",
|
||||
bot_character_level_class,
|
||||
GetClassIDName(bot_class)
|
||||
).c_str()
|
||||
);
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
|
||||
auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
|
||||
|
||||
if (!my_bot->Save()) {
|
||||
|
||||
+29
-121
@@ -130,7 +130,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
|
||||
bool available_flag = false;
|
||||
|
||||
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
|
||||
!database.botdb.QueryNameAvailability(bot_name, available_flag);
|
||||
|
||||
if (!available_flag) {
|
||||
c->Message(
|
||||
@@ -144,55 +144,25 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_creation_limit = c->GetBotCreationLimit();
|
||||
auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass());
|
||||
|
||||
uint32 bot_count = 0;
|
||||
uint32 bot_class_count = 0;
|
||||
|
||||
if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) {
|
||||
c->Message(Chat::White, "Failed to query bot count.");
|
||||
c->Message(Chat::Yellow, "Failed to query bot count.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
|
||||
std::string message;
|
||||
|
||||
if (bot_creation_limit) {
|
||||
message = fmt::format(
|
||||
"You have reached the maximum limit of {} bot{}.",
|
||||
bot_creation_limit,
|
||||
bot_creation_limit != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = "You cannot create any bots.";
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
if (!Bot::CheckCreateLimit(c, bot_count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) {
|
||||
std::string message;
|
||||
|
||||
if (bot_creation_limit_class) {
|
||||
message = fmt::format(
|
||||
"You cannot create anymore than {} {} bot{}.",
|
||||
bot_creation_limit_class,
|
||||
GetClassIDName(my_bot->GetClass()),
|
||||
bot_creation_limit_class != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = fmt::format(
|
||||
"You cannot create any {} bots.",
|
||||
GetClassIDName(my_bot->GetClass())
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
if (!Bot::CheckCreateLimit(c, bot_class_count, my_bot->GetClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 clone_id = 0;
|
||||
|
||||
if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -205,6 +175,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
}
|
||||
|
||||
int clone_stance = Stance::Passive;
|
||||
|
||||
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -729,6 +700,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
int NO_BOT_LIMIT = -1;
|
||||
bool Account = false;
|
||||
int seps = 1;
|
||||
uint32 filter_value[FilterCount];
|
||||
@@ -867,7 +839,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
|
||||
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) {
|
||||
auto class_creation_limit = c->GetBotCreationLimit(class_id);
|
||||
|
||||
if (class_creation_limit != overall_bot_creation_limit) {
|
||||
if (class_creation_limit != NO_BOT_LIMIT && class_creation_limit != overall_bot_creation_limit) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
@@ -938,20 +910,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_character_level = c->GetBotRequiredLevel();
|
||||
|
||||
if (
|
||||
bot_character_level >= 0 &&
|
||||
c->GetLevel() < bot_character_level &&
|
||||
!c->GetGM()
|
||||
) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"You must be level {} to spawn bots.",
|
||||
bot_character_level
|
||||
).c_str()
|
||||
);
|
||||
if (!Bot::CheckHighEnoughLevelForBots(c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -959,27 +918,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_spawn_limit = c->GetBotSpawnLimit();
|
||||
auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID());
|
||||
|
||||
if (
|
||||
bot_spawn_limit >= 0 &&
|
||||
spawned_bot_count >= bot_spawn_limit &&
|
||||
!c->GetGM()
|
||||
) {
|
||||
std::string message;
|
||||
|
||||
if (bot_spawn_limit) {
|
||||
message = fmt::format(
|
||||
"You cannot have more than {} spawned bot{}.",
|
||||
bot_spawn_limit,
|
||||
bot_spawn_limit != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = "You are not currently allowed to spawn any bots.";
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
if (!Bot::CheckSpawnLimit(c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1004,52 +943,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class);
|
||||
auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class);
|
||||
|
||||
if (
|
||||
bot_spawn_limit_class >= 0 &&
|
||||
spawned_bot_count_class >= bot_spawn_limit_class &&
|
||||
!c->GetGM()
|
||||
) {
|
||||
std::string message;
|
||||
|
||||
if (bot_spawn_limit_class) {
|
||||
message = fmt::format(
|
||||
"You cannot have more than {} spawned {} bot{}.",
|
||||
bot_spawn_limit_class,
|
||||
GetClassIDName(bot_class),
|
||||
bot_spawn_limit_class != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = fmt::format(
|
||||
"You are not currently allowed to spawn any {} bots.",
|
||||
GetClassIDName(bot_class)
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_character_level_class = c->GetBotRequiredLevel(bot_class);
|
||||
|
||||
if (
|
||||
bot_character_level_class >= 0 &&
|
||||
c->GetLevel() < bot_character_level_class &&
|
||||
!c->GetGM()
|
||||
) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"You must be level {} to spawn {} bots.",
|
||||
bot_character_level_class,
|
||||
GetClassIDName(bot_class)
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bot_id) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -1061,6 +954,14 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Bot::CheckHighEnoughLevelForBots(c, bot_class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Bot::CheckSpawnLimit(c, bot_class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity_list.GetMobByBotID(bot_id)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -1069,6 +970,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
bot_name
|
||||
).c_str()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1083,6 +985,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
bot_id
|
||||
).c_str()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1097,6 +1000,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
);
|
||||
|
||||
safe_delete(my_bot);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1121,6 +1025,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
};
|
||||
|
||||
uint8 message_index = 0;
|
||||
|
||||
if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) {
|
||||
message_index = VALIDATECLASSID(my_bot->GetClass());
|
||||
}
|
||||
@@ -1560,8 +1465,11 @@ void bot_command_summon(Client *c, const Seperator *sep)
|
||||
continue;
|
||||
}
|
||||
|
||||
bot_iter->GetPet()->WipeHateList();
|
||||
bot_iter->GetPet()->SetTarget(nullptr);
|
||||
if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
bot_iter->GetPet()->WipeHateList();
|
||||
bot_iter->GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
|
||||
bot_iter->GetPet()->Teleport(c->GetPosition());
|
||||
}
|
||||
|
||||
|
||||
+30
-61
@@ -5,8 +5,8 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) {
|
||||
return;
|
||||
}
|
||||
if (helper_is_help_or_usage(sep->arg[1])) {
|
||||
|
||||
if (helper_is_help_or_usage(sep->arg[1])) {
|
||||
c->Message(Chat::White, "usage: <enemy_target> %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]);
|
||||
return;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
|
||||
if (
|
||||
!target_mob ||
|
||||
target_mob == c ||
|
||||
target_mob->IsOfClientBotMerc() ||
|
||||
!c->IsAttackAllowed(target_mob)
|
||||
) {
|
||||
c->Message(Chat::White, "Your current target is not attackable!");
|
||||
@@ -55,12 +55,16 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
}
|
||||
|
||||
if (target_mob->IsNPC() && target_mob->GetHateList().size()) {
|
||||
|
||||
c->Message(Chat::White, "Your current target is already engaged!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Bot* bot_puller = nullptr;
|
||||
Bot* backup_bot_puller = nullptr;
|
||||
Bot* alternate_bot_puller = nullptr;
|
||||
bool backup_puller_found = false;
|
||||
bool alternate_puller_found = false;
|
||||
|
||||
for (auto bot_iter : sbl) {
|
||||
if (!bot_iter->ValidStateCheck(c)) {
|
||||
@@ -72,72 +76,37 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
case Class::Monk:
|
||||
case Class::Bard:
|
||||
case Class::Ranger:
|
||||
bot_puller = bot_iter;
|
||||
break;
|
||||
case Class::Warrior:
|
||||
case Class::ShadowKnight:
|
||||
case Class::Paladin:
|
||||
case Class::Berserker:
|
||||
case Class::Beastlord:
|
||||
if (!bot_puller) {
|
||||
bot_iter->SetPullFlag();
|
||||
|
||||
bot_puller = bot_iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (bot_puller->GetClass()) {
|
||||
case Class::Druid:
|
||||
case Class::Shaman:
|
||||
case Class::Cleric:
|
||||
case Class::Wizard:
|
||||
case Class::Necromancer:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
bot_puller = bot_iter;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
case Class::Druid:
|
||||
case Class::Shaman:
|
||||
case Class::Cleric:
|
||||
if (!bot_puller) {
|
||||
|
||||
bot_puller = bot_iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (bot_puller->GetClass()) {
|
||||
case Class::Wizard:
|
||||
case Class::Necromancer:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
bot_puller = bot_iter;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
case Class::Wizard:
|
||||
case Class::Necromancer:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
if (!bot_puller) {
|
||||
bot_puller = bot_iter;
|
||||
}
|
||||
|
||||
continue;
|
||||
return;
|
||||
default:
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!backup_puller_found) {
|
||||
switch (bot_iter->GetClass()) {
|
||||
case Class::Warrior:
|
||||
case Class::ShadowKnight:
|
||||
case Class::Paladin:
|
||||
case Class::Berserker:
|
||||
case Class::Beastlord:
|
||||
backup_bot_puller = bot_iter;
|
||||
backup_puller_found = true;
|
||||
|
||||
bot_puller = bot_iter;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
if (!backup_puller_found && !alternate_puller_found) {
|
||||
alternate_bot_puller = bot_iter;
|
||||
alternate_puller_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
bot_puller = backup_bot_puller ? backup_bot_puller : alternate_bot_puller;
|
||||
|
||||
if (bot_puller) {
|
||||
bot_puller->SetPullFlag();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ void bot_command_release(Client *c, const Seperator *sep)
|
||||
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
|
||||
for (auto bot_iter : sbl) {
|
||||
bot_iter->WipeHateList();
|
||||
|
||||
if (bot_iter->GetPet() && bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
bot_iter->GetPet()->WipeHateList();
|
||||
bot_iter->GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
|
||||
bot_iter->SetPauseAI(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag)
|
||||
bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag)
|
||||
{
|
||||
if (
|
||||
bot_name.empty() ||
|
||||
@@ -207,7 +207,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
|
||||
bot_count = BotDataRepository::Count(
|
||||
database,
|
||||
fmt::format(
|
||||
"`owner_id` = {}",
|
||||
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'",
|
||||
owner_id
|
||||
)
|
||||
);
|
||||
@@ -216,7 +216,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
|
||||
bot_class_count = BotDataRepository::Count(
|
||||
database,
|
||||
fmt::format(
|
||||
"`owner_id` = {} AND `class` = {}",
|
||||
"`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'",
|
||||
owner_id,
|
||||
class_id
|
||||
)
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
|
||||
/* Bot functions */
|
||||
bool QueryNameAvailablity(const std::string& bot_name, bool& available_flag);
|
||||
bool QueryNameAvailability(const std::string& bot_name, bool& available_flag);
|
||||
bool QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count);
|
||||
bool LoadBotsList(const uint32 owner_id, std::list<BotsAvailableList>& bots_list, bool by_account = false);
|
||||
|
||||
|
||||
+2
-2
@@ -68,7 +68,7 @@ struct BotSpellSetting {
|
||||
|
||||
struct BotSpells {
|
||||
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
||||
int16 spellid; // <= 0 = no spell
|
||||
uint16 spellid; // <= 0 = no spell
|
||||
int16 manacost; // -1 = use spdat, -2 = no cast time
|
||||
uint32 time_cancast; // when we can cast this spell next
|
||||
int32 recast_delay;
|
||||
@@ -86,7 +86,7 @@ struct BotSpells {
|
||||
struct BotSpells_wIndex {
|
||||
uint32 index; //index of AIBot_spells
|
||||
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
||||
int16 spellid; // <= 0 = no spell
|
||||
uint16 spellid; // <= 0 = no spell
|
||||
int16 manacost; // -1 = use spdat, -2 = no cast time
|
||||
uint32 time_cancast; // when we can cast this spell next
|
||||
int32 recast_delay;
|
||||
|
||||
@@ -1459,7 +1459,7 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_type, bool AE) {
|
||||
Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE) {
|
||||
Mob* result = nullptr;
|
||||
|
||||
if (caster && caster->GetOwner()) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../sidecar_api/sidecar_api.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../data_bucket.h"
|
||||
#include "../../common/data_bucket.h"
|
||||
#include "../zonedb.h"
|
||||
#include "../../common/repositories/data_buckets_repository.h"
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../../zone.h"
|
||||
|
||||
+53
-21
@@ -39,7 +39,7 @@ extern volatile bool RunLoops;
|
||||
#include "../common/strings.h"
|
||||
#include "../common/data_verification.h"
|
||||
#include "../common/profanity_manager.h"
|
||||
#include "data_bucket.h"
|
||||
#include "../common/data_bucket.h"
|
||||
#include "dynamic_zone.h"
|
||||
#include "expedition_request.h"
|
||||
#include "position.h"
|
||||
@@ -226,8 +226,8 @@ Client::Client() : Mob(
|
||||
last_reported_endurance_percent = 0;
|
||||
last_reported_mana_percent = 0;
|
||||
gm_hide_me = false;
|
||||
AFK = false;
|
||||
LFG = false;
|
||||
m_is_afk = false;
|
||||
LFG = false;
|
||||
LFGFromLevel = 0;
|
||||
LFGToLevel = 0;
|
||||
LFGMatchFilter = false;
|
||||
@@ -536,8 +536,8 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
|
||||
last_reported_endurance_percent = 0;
|
||||
last_reported_mana_percent = 0;
|
||||
gm_hide_me = false;
|
||||
AFK = false;
|
||||
LFG = false;
|
||||
m_is_afk = false;
|
||||
LFG = false;
|
||||
LFGFromLevel = 0;
|
||||
LFGToLevel = 0;
|
||||
LFGMatchFilter = false;
|
||||
@@ -1024,8 +1024,6 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
m_pp.endurance = current_endurance;
|
||||
}
|
||||
|
||||
database.TransactionBegin();
|
||||
|
||||
/* Save Character Currency */
|
||||
database.SaveCharacterCurrency(CharacterID(), &m_pp);
|
||||
|
||||
@@ -1109,8 +1107,6 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
database.botdb.SaveBotSettings(this);
|
||||
}
|
||||
|
||||
database.TransactionCommit();
|
||||
|
||||
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
|
||||
|
||||
return true;
|
||||
@@ -1179,6 +1175,10 @@ void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CO
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuleB(Character, AutoIdleFilterPackets) && m_is_idle && IsFilteredAFKPacket(app)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (client_state != CLIENT_CONNECTED && required_state == CLIENT_CONNECTED) {
|
||||
AddPacket(app, ack_req);
|
||||
return;
|
||||
@@ -2532,7 +2532,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
Mob::FillSpawnStruct(ns, ForWho);
|
||||
|
||||
// Populate client-specific spawn information
|
||||
ns->spawn.afk = AFK;
|
||||
ns->spawn.afk = m_is_afk;
|
||||
ns->spawn.lfg = LFG; // afk and lfg are cleared on zoning on live
|
||||
ns->spawn.anon = m_pp.anon;
|
||||
ns->spawn.gm = GetGM() ? 1 : 0;
|
||||
@@ -10843,15 +10843,37 @@ void Client::SetAnon(uint8 anon_flag) {
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void Client::SetAFK(uint8 afk_flag) {
|
||||
AFK = afk_flag;
|
||||
auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
|
||||
SpawnAppearance_Struct* spawn_appearance = (SpawnAppearance_Struct*)outapp->pBuffer;
|
||||
spawn_appearance->spawn_id = GetID();
|
||||
spawn_appearance->type = AppearanceType::AFK;
|
||||
spawn_appearance->parameter = afk_flag;
|
||||
entity_list.QueueClients(this, outapp);
|
||||
safe_delete(outapp);
|
||||
void Client::SetAFK(uint8 afk_flag)
|
||||
{
|
||||
if (!afk_flag) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
bool changed_afk_state = (m_is_afk && !afk_flag) || (!m_is_afk && afk_flag);
|
||||
|
||||
if (!changed_afk_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set messaging based on the state
|
||||
std::string you_are = "You are no longer AFK.";
|
||||
if (!m_is_afk && afk_flag) {
|
||||
you_are = "You are now AFK.";
|
||||
}
|
||||
|
||||
// set the state
|
||||
m_is_afk = afk_flag;
|
||||
|
||||
// inform of state change
|
||||
Message(Chat::Yellow, you_are.c_str());
|
||||
|
||||
// send the spawn appearance packet to all clients
|
||||
static EQApplicationPacket p(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
|
||||
auto *s = (SpawnAppearance_Struct *) p.pBuffer;
|
||||
s->spawn_id = GetID();
|
||||
s->type = AppearanceType::AFK;
|
||||
s->parameter = afk_flag;
|
||||
entity_list.QueueClients(this, &p);
|
||||
}
|
||||
|
||||
void Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) {
|
||||
@@ -12717,7 +12739,7 @@ void Client::SendTopLevelInventory()
|
||||
}
|
||||
}
|
||||
|
||||
void Client::CheckSendBulkNpcPositions()
|
||||
void Client::CheckSendBulkNpcPositions(bool force)
|
||||
{
|
||||
float distance_moved = DistanceNoZ(m_last_position_before_bulk_update, GetPosition());
|
||||
float update_range = RuleI(Range, MobCloseScanDistance);
|
||||
@@ -12728,7 +12750,7 @@ void Client::CheckSendBulkNpcPositions()
|
||||
|
||||
int updated_count = 0;
|
||||
int skipped_count = 0;
|
||||
if (is_ready_to_update) {
|
||||
if (is_ready_to_update || force) {
|
||||
auto &mob_movement_manager = MobMovementManager::Get();
|
||||
|
||||
for (auto &e: entity_list.GetMobList()) {
|
||||
@@ -12774,6 +12796,16 @@ void Client::CheckSendBulkNpcPositions()
|
||||
updated_count++;
|
||||
}
|
||||
|
||||
if (force) {
|
||||
static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *s = (PlayerPositionUpdateServer_Struct *) p.pBuffer;
|
||||
for (auto &e: entity_list.GetCorpseList()) {
|
||||
Corpse *c = e.second;
|
||||
MakeSpawnUpdate(s);
|
||||
QueuePacket(&p, false);
|
||||
}
|
||||
}
|
||||
|
||||
LogPositionUpdate(
|
||||
"[{}] Sent [{}] bulk updated NPC positions, skipped [{}] distance_moved [{}] update_range [{}]",
|
||||
GetCleanName(),
|
||||
|
||||
+30
-7
@@ -73,6 +73,7 @@ namespace EQ
|
||||
#include "../common/guild_base.h"
|
||||
#include "../common/repositories/buyer_buy_lines_repository.h"
|
||||
#include "../common/repositories/character_evolving_items_repository.h"
|
||||
#include "../common/repositories/player_titlesets_repository.h"
|
||||
|
||||
#include "bot_structs.h"
|
||||
|
||||
@@ -500,9 +501,19 @@ public:
|
||||
void Kick(const std::string &reason);
|
||||
void WorldKick();
|
||||
inline uint8 GetAnon() const { return m_pp.anon; }
|
||||
inline uint8 GetAFK() const { return AFK; }
|
||||
inline uint8 GetAFK() const { return m_is_afk; }
|
||||
void SetAnon(uint8 anon_flag);
|
||||
inline Client* ResetAFKTimer() {
|
||||
if (!RuleB(Character, EnableAutoAFK)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
m_afk_reset = true;
|
||||
m_last_moved = std::chrono::steady_clock::now();
|
||||
return this;
|
||||
};
|
||||
void SetAFK(uint8 afk_flag);
|
||||
inline bool IsIdle() { return m_is_idle; }
|
||||
inline PlayerProfile_Struct& GetPP() { return m_pp; }
|
||||
inline ExtendedProfile_Struct& GetEPP() { return m_epp; }
|
||||
inline EQ::InventoryProfile& GetInv() { return m_inv; }
|
||||
@@ -1255,9 +1266,10 @@ public:
|
||||
void ResetAllCastbarCooldowns();
|
||||
void ResetCastbarCooldownBySpellID(uint32 spell_id);
|
||||
|
||||
bool CheckTitle(int titleset);
|
||||
void EnableTitle(int titleset);
|
||||
void RemoveTitle(int titleset);
|
||||
bool CheckTitle(int title_set);
|
||||
void EnableTitle(int title_set, bool insert = true);
|
||||
const std::vector<PlayerTitlesetsRepository::PlayerTitlesets>& GetTitles() { return m_player_title_sets; };
|
||||
void RemoveTitle(int title_set);
|
||||
|
||||
void EnteringMessages(Client* client);
|
||||
void SendRules();
|
||||
@@ -2060,7 +2072,8 @@ private:
|
||||
uint8 LFGToLevel;
|
||||
bool LFGMatchFilter;
|
||||
char LFGComments[64];
|
||||
bool AFK;
|
||||
bool m_is_afk = false;
|
||||
bool m_is_manual_afk = false;
|
||||
bool auto_attack;
|
||||
bool auto_fire;
|
||||
bool runmode;
|
||||
@@ -2080,7 +2093,8 @@ private:
|
||||
uint16 trader_id;
|
||||
uint16 customer_id;
|
||||
uint32 account_creation;
|
||||
uint8 firstlogon;
|
||||
bool first_login;
|
||||
bool ingame;
|
||||
uint32 mercid; // current merc
|
||||
uint8 mercSlot; // selected merc slot
|
||||
time_t m_trader_transaction_date;
|
||||
@@ -2213,7 +2227,12 @@ private:
|
||||
glm::vec4 m_last_position_before_bulk_update;
|
||||
Timer m_client_bulk_npc_pos_update_timer;
|
||||
Timer m_position_update_timer;
|
||||
void CheckSendBulkNpcPositions();
|
||||
void CheckSendBulkNpcPositions(bool force = false);
|
||||
|
||||
// afk
|
||||
bool m_is_idle = false;
|
||||
bool m_afk_reset = false; // used to trigger next-tic afk reset
|
||||
std::chrono::steady_clock::time_point m_last_moved = std::chrono::steady_clock::now();
|
||||
|
||||
void BulkSendInventoryItems();
|
||||
|
||||
@@ -2257,6 +2276,7 @@ private:
|
||||
bool m_exp_enabled;
|
||||
|
||||
std::vector<EXPModifier> m_exp_modifiers;
|
||||
std::vector<PlayerTitlesetsRepository::PlayerTitlesets> m_player_title_sets;
|
||||
|
||||
//Anti Spam Stuff
|
||||
Timer *KarmaUpdateTimer;
|
||||
@@ -2406,6 +2426,9 @@ public:
|
||||
const std::string &GetMailKey() const;
|
||||
void ShowZoneShardMenu();
|
||||
void Handle_OP_ChangePetName(const EQApplicationPacket *app);
|
||||
bool IsFilteredAFKPacket(const EQApplicationPacket *p);
|
||||
void CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p);
|
||||
void SyncWorldPositionsToClient(bool ignore_idle = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+61
-67
@@ -1,6 +1,8 @@
|
||||
#include "bot.h"
|
||||
#include "client.h"
|
||||
|
||||
#define NO_BOT_LIMIT -1;
|
||||
|
||||
bool Client::GetBotOption(BotOwnerOption boo) const {
|
||||
if (boo < _booCount) {
|
||||
return bot_owner_options[boo];
|
||||
@@ -18,25 +20,25 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) {
|
||||
uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
||||
uint32 bot_creation_limit = RuleI(Bots, CreationLimit);
|
||||
|
||||
if (Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
|
||||
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
|
||||
return RuleI(Bots, MinStatusBypassCreateLimit);
|
||||
}
|
||||
|
||||
const auto bucket_name = fmt::format(
|
||||
"bot_creation_limit{}",
|
||||
(
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format(
|
||||
"_{}",
|
||||
Strings::ToLower(GetClassIDName(class_id))
|
||||
) :
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
auto bucket_value = GetBucket(bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_creation_limit = Strings::ToUnsignedInt(bucket_value);
|
||||
bot_creation_limit = Strings::ToInt(bucket_value);
|
||||
}
|
||||
|
||||
if (class_id && bucket_value.empty()) {
|
||||
bot_creation_limit = NO_BOT_LIMIT;
|
||||
}
|
||||
|
||||
return bot_creation_limit;
|
||||
@@ -45,63 +47,55 @@ uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
||||
int Client::GetBotRequiredLevel(uint8 class_id) {
|
||||
int bot_character_level = RuleI(Bots, BotCharacterLevel);
|
||||
|
||||
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassBotLevelRequirement)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto bucket_name = fmt::format(
|
||||
"bot_required_level{}",
|
||||
(
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format(
|
||||
"_{}",
|
||||
Strings::ToLower(GetClassIDName(class_id))
|
||||
) :
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
auto bucket_value = GetBucket(bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_character_level = Strings::ToInt(bucket_value);
|
||||
}
|
||||
|
||||
if (class_id && bucket_value.empty()) {
|
||||
bot_character_level = NO_BOT_LIMIT;
|
||||
}
|
||||
|
||||
return bot_character_level;
|
||||
}
|
||||
|
||||
int Client::GetBotSpawnLimit(uint8 class_id) {
|
||||
int Client::GetBotSpawnLimit(uint8 class_id)
|
||||
{
|
||||
int bot_spawn_limit = RuleI(Bots, SpawnLimit);
|
||||
|
||||
if (Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
|
||||
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
|
||||
return RuleI(Bots, MinStatusBypassSpawnLimit);
|
||||
}
|
||||
|
||||
const auto bucket_name = fmt::format(
|
||||
"bot_spawn_limit{}",
|
||||
(
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format(
|
||||
"_{}",
|
||||
Strings::ToLower(GetClassIDName(class_id))
|
||||
) :
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
auto bucket_value = GetBucket(bucket_name);
|
||||
|
||||
if (class_id && !bot_spawn_limit && bucket_value.empty()) {
|
||||
const auto new_bucket_name = "bot_spawn_limit";
|
||||
|
||||
bucket_value = GetBucket(new_bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
||||
|
||||
return bot_spawn_limit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
||||
}
|
||||
|
||||
if (class_id && bucket_value.empty()) {
|
||||
return NO_BOT_LIMIT;
|
||||
}
|
||||
|
||||
if (RuleB(Bots, QuestableSpawnLimit)) {
|
||||
const auto query = fmt::format(
|
||||
"SELECT `value` FROM `quest_globals` WHERE `name` = '{}' AND `charid` = {} LIMIT 1",
|
||||
@@ -111,53 +105,53 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
|
||||
|
||||
auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls
|
||||
|
||||
if (!results.Success() || !results.RowCount()) {
|
||||
return bot_spawn_limit;
|
||||
if (results.Success() && results.RowCount()) {
|
||||
auto row = results.begin();
|
||||
bot_spawn_limit = Strings::ToInt(row[0]);
|
||||
}
|
||||
|
||||
auto row = results.begin();
|
||||
bot_spawn_limit = Strings::ToInt(row[0]);
|
||||
}
|
||||
|
||||
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
||||
if (!class_id) {
|
||||
const auto &zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
||||
|
||||
if (!zones_list.empty()) {
|
||||
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
|
||||
if (!zones_list.empty()) {
|
||||
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
|
||||
|
||||
if (it != zones_list.end()) {
|
||||
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||
if (it != zones_list.end()) {
|
||||
const auto &zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||
|
||||
if (zones_list.size() == zones_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
|
||||
if (zones_list.size() == zones_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
|
||||
|
||||
if (new_limit < bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
if (new_limit < bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
||||
const auto &zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
||||
|
||||
if (!zones_forced_list.empty()) {
|
||||
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
|
||||
if (!zones_forced_list.empty()) {
|
||||
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
|
||||
|
||||
if (it != zones_forced_list.end()) {
|
||||
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||
if (it != zones_forced_list.end()) {
|
||||
const auto &zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||
|
||||
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
|
||||
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
|
||||
|
||||
if (new_limit != bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
if (new_limit != bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,16 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!evolving_items_manager.GetEvolvingItemsCache().contains(inst->GetID())) {
|
||||
LogEvolveItem(
|
||||
"Character ID {} has an evolving item that is not found in the db. Please check your "
|
||||
"items_evolving_details table for item id {}",
|
||||
CharacterID(),
|
||||
inst->GetID()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type;
|
||||
auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type;
|
||||
|
||||
@@ -283,24 +293,31 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app)
|
||||
}
|
||||
|
||||
std::unique_ptr<EQ::ItemInstance> const inst(database.CreateItem(item_id));
|
||||
if (!inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogEvolveItemDetail(
|
||||
"Character ID <green>[{}] requested to view final evolve item id <yellow>[{}] for evolve item id <yellow>[{}]",
|
||||
CharacterID(),
|
||||
item_id,
|
||||
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id));
|
||||
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id)
|
||||
);
|
||||
|
||||
inst->SetEvolveProgression(100);
|
||||
|
||||
if (inst) {
|
||||
LogEvolveItemDetail(
|
||||
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID());
|
||||
SendItemPacket(0, inst.get(), ItemPacketViewLink);
|
||||
}
|
||||
LogEvolveItemDetail(
|
||||
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID()
|
||||
);
|
||||
SendItemPacket(0, inst.get(), ItemPacketViewLink);
|
||||
}
|
||||
|
||||
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
|
||||
{
|
||||
if (!inst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+245
-23
@@ -42,7 +42,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "../common/data_verification.h"
|
||||
#include "../common/rdtsc.h"
|
||||
#include "data_bucket.h"
|
||||
#include "../common/data_bucket.h"
|
||||
#include "dynamic_zone.h"
|
||||
#include "event_codes.h"
|
||||
#include "guild_mgr.h"
|
||||
@@ -807,22 +807,42 @@ void Client::CompleteConnect()
|
||||
m_last_position_before_bulk_update = GetPosition();
|
||||
|
||||
/* This sub event is for if a player logs in for the first time since entering world. */
|
||||
if (firstlogon == 1) {
|
||||
if (ingame) {
|
||||
auto e = CharacterDataRepository::FindOne(
|
||||
database,
|
||||
CharacterID()
|
||||
);
|
||||
|
||||
bool is_first_login = e.first_login == 0;
|
||||
|
||||
RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{});
|
||||
|
||||
if (parse->PlayerHasQuestSub(EVENT_CONNECT)) {
|
||||
parse->EventPlayer(EVENT_CONNECT, this, "", 0);
|
||||
const std::string& export_string = fmt::format(
|
||||
"{} {} {}",
|
||||
e.last_login,
|
||||
time(nullptr) - e.last_login,
|
||||
is_first_login ? 1 : 0
|
||||
);
|
||||
parse->EventPlayer(EVENT_CONNECT, this, export_string, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last login since this doesn't get updated until a late save later so we can update online status
|
||||
*/
|
||||
database.QueryDatabase(
|
||||
StringFormat(
|
||||
"UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u",
|
||||
if (is_first_login) {
|
||||
e.first_login = time(nullptr);
|
||||
TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID()));
|
||||
BuyerRepository::DeleteBuyer(database, CharacterID());
|
||||
LogTradingDetail(
|
||||
"Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.",
|
||||
CharacterID()
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
e.last_login = time(nullptr);
|
||||
|
||||
const int updated = CharacterDataRepository::UpdateOne(database, e);
|
||||
if (!updated) {
|
||||
LogError("Failed to update login time for character_id [{}]", CharacterID());
|
||||
}
|
||||
|
||||
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
||||
InvokeChangePetName(false);
|
||||
@@ -871,7 +891,7 @@ void Client::CompleteConnect()
|
||||
entity_list.SendFindableNPCList(this);
|
||||
|
||||
if (IsInAGuild()) {
|
||||
if (firstlogon == 1) {
|
||||
if (ingame) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), true);
|
||||
SendGuildMembersList();
|
||||
}
|
||||
@@ -1307,14 +1327,14 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
|
||||
/* Load Character Data */
|
||||
query = fmt::format(
|
||||
"SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}",
|
||||
"SELECT `lfp`, `lfg`, `xtargets`, `first_login`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block`, `ingame` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}",
|
||||
cid
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
for (auto row : results) {
|
||||
if (row[4] && Strings::ToInt(row[4]) > 0) {
|
||||
guild_id = Strings::ToInt(row[4]);
|
||||
guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE;
|
||||
guild_id = Strings::ToInt(row[4]);
|
||||
guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE;
|
||||
guild_tribute_opt_in = row[7] ? Strings::ToBool(row[7]) : 0;
|
||||
}
|
||||
|
||||
@@ -1322,10 +1342,21 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
SetExtraHaste(Strings::ToInt(row[8]), false);
|
||||
SetIllusionBlock(Strings::ToBool(row[9]));
|
||||
|
||||
if (LFP) { LFP = Strings::ToInt(row[0]); }
|
||||
if (LFG) { LFG = Strings::ToInt(row[1]); }
|
||||
if (row[3])
|
||||
firstlogon = Strings::ToInt(row[3]);
|
||||
if (LFP) {
|
||||
LFP = Strings::ToInt(row[0]);
|
||||
}
|
||||
|
||||
if (LFG) {
|
||||
LFG = Strings::ToInt(row[1]);
|
||||
}
|
||||
|
||||
if (row[3]) {
|
||||
first_login = Strings::ToUnsignedInt(row[3]);
|
||||
}
|
||||
|
||||
if (row[10]) {
|
||||
ingame = Strings::ToBool(row[10]);
|
||||
}
|
||||
}
|
||||
|
||||
if (RuleB(Character, SharedBankPlat))
|
||||
@@ -1350,6 +1381,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */
|
||||
database.LoadCharacterTribute(this); /* Load CharacterTribute */
|
||||
database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */
|
||||
database.LoadCharacterTitleSets(this); /* Load Character Title Sets */
|
||||
|
||||
// this pattern is strange
|
||||
// this is remnants of the old way of doing things
|
||||
@@ -1441,7 +1473,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
|
||||
// Load Data Buckets
|
||||
ClearDataBucketCache();
|
||||
DataBucket::GetDataBuckets(this);
|
||||
LoadDataBucketsCache();
|
||||
|
||||
// Max Level for Character:PerCharacterQglobalMaxLevel and Character:PerCharacterBucketMaxLevel
|
||||
uint8 client_max_level = 0;
|
||||
@@ -1713,7 +1745,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
|
||||
// Taunt persists when zoning on newer clients, overwrite default.
|
||||
if (m_ClientVersionBit & EQ::versions::maskUFAndLater) {
|
||||
if (!firstlogon) {
|
||||
if (!ingame) {
|
||||
pet->SetTaunting(m_petinfo.taunting);
|
||||
}
|
||||
}
|
||||
@@ -4351,6 +4383,14 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app)
|
||||
|
||||
m_TargetRing = glm::vec3(castspell->x_pos, castspell->y_pos, castspell->z_pos);
|
||||
|
||||
if (castspell->spell_id && IsValidSpell(castspell->spell_id)) {
|
||||
bool is_non_combat_zone = !zone->CanDoCombat() || zone->BuffTimersSuspended();
|
||||
bool is_excluded_reset = is_non_combat_zone && IsBardSong(castspell->spell_id);
|
||||
if (!is_excluded_reset) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
}
|
||||
|
||||
LogSpells("OP CastSpell: slot [{}] spell [{}] target [{}] inv [{}]", castspell->slot, castspell->spell_id, castspell->target_id, (unsigned long)castspell->inventoryslot);
|
||||
CastingSlot slot = static_cast<CastingSlot>(castspell->slot);
|
||||
|
||||
@@ -4534,6 +4574,12 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app)
|
||||
return;
|
||||
}
|
||||
|
||||
// reject automatic AFK messages from resetting /afk
|
||||
std::string message = cm->message;
|
||||
if (!Strings::Contains(message, "Sorry, I am A.F.K.")) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
if (IsAIControlled() && !GetGM()) {
|
||||
Message(Chat::Red, "You try to speak but can't move your mouth!");
|
||||
return;
|
||||
@@ -4960,6 +5006,10 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
|
||||
|
||||
SetMoving(!(cy == m_Position.y && cx == m_Position.x));
|
||||
|
||||
if (RuleB(Character, EnableAutoAFK)) {
|
||||
CheckAutoIdleAFK(ppu);
|
||||
}
|
||||
|
||||
CheckClientToNpcAggroTimer();
|
||||
|
||||
if (m_mob_check_moving_timer.Check()) {
|
||||
@@ -10760,6 +10810,8 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app)
|
||||
return;
|
||||
}
|
||||
|
||||
ResetAFKTimer();
|
||||
|
||||
BenchTimer bench;
|
||||
|
||||
MoveItem_Struct* mi = (MoveItem_Struct*) app->pBuffer;
|
||||
@@ -14700,7 +14752,9 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
|
||||
}
|
||||
else if (sa->type == AppearanceType::AFK) {
|
||||
if (afk_toggle_timer.Check()) {
|
||||
AFK = (sa->parameter == 1);
|
||||
m_is_afk = (sa->parameter == 1);
|
||||
m_is_manual_afk = (sa->parameter == 1);
|
||||
ResetAFKTimer();
|
||||
entity_list.QueueClients(this, app, true);
|
||||
}
|
||||
}
|
||||
@@ -15519,6 +15573,14 @@ void Client::Handle_OP_TradeRequest(const EQApplicationPacket *app)
|
||||
|
||||
// Pass trade request on to recipient
|
||||
if (tradee && tradee->IsClient()) {
|
||||
// if we are idling we need to sync client positions otherwise clients will not be aware of each other
|
||||
if (m_is_idle) {
|
||||
SyncWorldPositionsToClient(true);
|
||||
}
|
||||
if (tradee->CastToClient()->IsIdle()) {
|
||||
tradee->CastToClient()->SyncWorldPositionsToClient(true);
|
||||
}
|
||||
|
||||
tradee->CastToClient()->QueuePacket(app);
|
||||
}
|
||||
else if (tradee && (tradee->IsNPC() || tradee->IsBot())) {
|
||||
@@ -15548,6 +15610,14 @@ void Client::Handle_OP_TradeRequestAck(const EQApplicationPacket *app)
|
||||
Mob* tradee = entity_list.GetMob(msg->to_mob_id);
|
||||
|
||||
if (tradee && tradee->IsClient()) {
|
||||
// if we are idling we need to sync client positions otherwise clients will not be aware of each other
|
||||
if (m_is_idle) {
|
||||
SyncWorldPositionsToClient(true);
|
||||
}
|
||||
if (tradee->CastToClient()->IsIdle()) {
|
||||
tradee->CastToClient()->SyncWorldPositionsToClient(true);
|
||||
}
|
||||
|
||||
trade->Start(msg->to_mob_id);
|
||||
tradee->CastToClient()->QueuePacket(app);
|
||||
}
|
||||
@@ -15567,7 +15637,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
|
||||
switch (in->Code) {
|
||||
case ClickTrader: {
|
||||
LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_TraderShop, sizeof(TraderClick_Struct));
|
||||
auto outapp =
|
||||
std::make_unique<EQApplicationPacket>(OP_TraderShop, static_cast<uint32>(sizeof(TraderClick_Struct))
|
||||
);
|
||||
auto data = (TraderClick_Struct *) outapp->pBuffer;
|
||||
auto trader_client = entity_list.GetClientByID(in->TraderID);
|
||||
|
||||
@@ -17132,3 +17204,153 @@ void Client::Handle_OP_EvolveItem(const EQApplicationPacket *app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::IsFilteredAFKPacket(const EQApplicationPacket *p)
|
||||
{
|
||||
if (p->GetOpcode() == OP_ClientUpdate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p)
|
||||
{
|
||||
if (!RuleB(Character, EnableAutoAFK)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_non_combat_zone = !zone->CanDoCombat() || zone->BuffTimersSuspended();
|
||||
|
||||
int seconds_before_afk =
|
||||
is_non_combat_zone ?
|
||||
RuleI(Character, SecondsBeforeAFKNonCombatZone) :
|
||||
RuleI(Character, SecondsBeforeAFKCombatZone);
|
||||
|
||||
int seconds_before_idle =
|
||||
is_non_combat_zone ?
|
||||
RuleI(Character, SecondsBeforeIdleNonCombatZone) :
|
||||
RuleI(Character, SecondsBeforeIdleCombatZone);
|
||||
|
||||
// seconds_before_idle can't be greater than seconds_before_afk
|
||||
if (seconds_before_idle > seconds_before_afk) {
|
||||
seconds_before_idle = seconds_before_afk;
|
||||
}
|
||||
|
||||
bool has_moved =
|
||||
m_Position.x != p->x_pos ||
|
||||
m_Position.y != p->y_pos ||
|
||||
m_Position.z != p->z_pos ||
|
||||
m_Position.w != EQ12toFloat(p->heading);
|
||||
|
||||
bool triggered_reset = m_afk_reset;
|
||||
bool was_idle = m_is_idle;
|
||||
bool is_idle_or_afk = m_is_idle || m_is_afk;
|
||||
|
||||
if (!has_moved && (!m_is_idle || !m_is_afk)) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto since_last_moved = now - m_last_moved;
|
||||
|
||||
if (!m_is_manual_afk && !m_is_afk && since_last_moved > std::chrono::seconds(seconds_before_afk)) {
|
||||
bool is_client_excluded_from_afk = (IsBuyer() || IsTrader() || GetGM());
|
||||
if (is_client_excluded_from_afk) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Client [{}] has been AFK for [{}] seconds",
|
||||
GetCleanName(),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(since_last_moved).count()
|
||||
);
|
||||
SetAFK(true);
|
||||
return;
|
||||
}
|
||||
else if (!m_is_idle && since_last_moved > std::chrono::seconds(seconds_before_idle)) {
|
||||
bool is_client_excluded_from_idle = GetGM() && !is_non_combat_zone;
|
||||
if (is_client_excluded_from_idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Client [{}] has been idle for [{}] seconds",
|
||||
GetCleanName(),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(since_last_moved).count()
|
||||
);
|
||||
m_is_idle = true;
|
||||
Message(Chat::Yellow, "You are now idle. Updates will be sent to you less frequently.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we triggered a reset, but didn't move, we are still idling but not AFK
|
||||
if (triggered_reset && was_idle) {
|
||||
m_is_idle = true;
|
||||
}
|
||||
|
||||
// if we moved or triggered reset through other actions, we are no longer AFK.
|
||||
// we could trigger resetting AFK status through actions like message, cast, attack etc but still by idle until we move
|
||||
if (!m_is_manual_afk && (has_moved || triggered_reset) && m_is_afk) {
|
||||
LogInfo("AFK [{}] is no longer idle, syncing positions", GetCleanName());
|
||||
SetAFK(false);
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
// we could be not AFK and idle at the same time
|
||||
if (has_moved && m_is_idle) {
|
||||
LogInfo("Idle [{}] is no longer idle, syncing positions", GetCleanName());
|
||||
m_is_idle = false;
|
||||
Message(Chat::Yellow, "You are no longer idle.");
|
||||
SyncWorldPositionsToClient();
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
m_afk_reset = false;
|
||||
}
|
||||
|
||||
void Client::SyncWorldPositionsToClient(bool ignore_idle)
|
||||
{
|
||||
// if we are idle currently, we need to force updates (which bypasses idle status) and reset idle status
|
||||
bool reset_idle = false;
|
||||
if (ignore_idle && m_is_idle) {
|
||||
m_is_idle = false;
|
||||
reset_idle = true;
|
||||
}
|
||||
|
||||
LogInfo("Syncing positions for client [{}]", GetCleanName());
|
||||
CheckSendBulkNpcPositions(true);
|
||||
|
||||
static EQApplicationPacket cu(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
|
||||
for (auto &e: entity_list.GetClientList()) {
|
||||
auto c = e.second;
|
||||
|
||||
// skip if not in range
|
||||
if (Distance(c->GetPosition(), GetPosition()) > RuleI(Range, ClientPositionUpdates)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip self
|
||||
if (c == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *spu = (PlayerPositionUpdateServer_Struct *) cu.pBuffer;
|
||||
|
||||
memset(spu, 0x00, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
spu->spawn_id = c->GetID();
|
||||
spu->x_pos = FloatToEQ19(c->GetX());
|
||||
spu->y_pos = FloatToEQ19(c->GetY());
|
||||
spu->z_pos = FloatToEQ19(c->GetZ());
|
||||
spu->heading = FloatToEQ12(c->GetHeading());
|
||||
spu->delta_x = FloatToEQ13(0);
|
||||
spu->delta_y = FloatToEQ13(0);
|
||||
spu->delta_z = FloatToEQ13(0);
|
||||
spu->delta_heading = FloatToEQ10(0);
|
||||
spu->animation = 0;
|
||||
QueuePacket(&cu);
|
||||
}
|
||||
|
||||
if (ignore_idle && reset_idle) {
|
||||
m_is_idle = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,6 +536,10 @@ bool Client::Process() {
|
||||
DoEnduranceRegen();
|
||||
BuffProcess();
|
||||
|
||||
if (auto_attack) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
if (tribute_timer.Check()) {
|
||||
ToggleTribute(true); //re-activate the tribute.
|
||||
}
|
||||
@@ -727,7 +731,7 @@ void Client::OnDisconnect(bool hard_disconnect) {
|
||||
o->trade->Reset();
|
||||
}
|
||||
|
||||
database.SetFirstLogon(CharacterID(), 0); //We change firstlogon status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world.
|
||||
database.SetIngame(CharacterID(), 0); //We change ingame status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world.
|
||||
|
||||
/* Remove from all proximities */
|
||||
ClearAllProximities();
|
||||
|
||||
+11
-159
@@ -19,7 +19,7 @@
|
||||
#include "../common/file.h"
|
||||
#include "../common/repositories/dynamic_zones_repository.h"
|
||||
|
||||
#include "data_bucket.h"
|
||||
#include "../common/data_bucket.h"
|
||||
#include "command.h"
|
||||
#include "dynamic_zone.h"
|
||||
#include "queryserv.h"
|
||||
@@ -246,6 +246,7 @@ int command_init(void)
|
||||
command_add("zoneinstance", "[Instance ID] [X] [Y] [Z] - Teleport to specified Instance by ID (coordinates are optional)", AccountStatus::Guide, command_zone_instance) ||
|
||||
command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
|
||||
command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
||||
command_add("zonevariable", "[clear|delete|set|view] - Modify zone variables for your current zone", AccountStatus::GMAdmin, command_zonevariable) ||
|
||||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
|
||||
) {
|
||||
command_deinit();
|
||||
@@ -513,7 +514,15 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status)
|
||||
parse->EventPlayer(EVENT_GM_COMMAND, c, message, 0);
|
||||
}
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && message != "#help") {
|
||||
bool log_command = true;
|
||||
for (auto &cmd: Strings::Split(RuleS(Logging, PlayerEventsIgnoreGMCommands), ",")) {
|
||||
if (Strings::Contains(command, Strings::ToLower(cmd))) {
|
||||
log_command = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && log_command) {
|
||||
auto e = PlayerEvent::GMCommandEvent{
|
||||
.message = message,
|
||||
.target = c->GetTarget() ? c->GetTarget()->GetName() : "NONE"
|
||||
@@ -774,160 +783,3 @@ void command_bot(Client *c, const Seperator *sep)
|
||||
c->Message(Chat::Red, "Bots are disabled on this server.");
|
||||
}
|
||||
}
|
||||
|
||||
#include "gm_commands/acceptrules.cpp"
|
||||
#include "gm_commands/advnpcspawn.cpp"
|
||||
#include "gm_commands/aggrozone.cpp"
|
||||
#include "gm_commands/ai.cpp"
|
||||
#include "gm_commands/appearance.cpp"
|
||||
#include "gm_commands/appearanceeffects.cpp"
|
||||
#include "gm_commands/attack.cpp"
|
||||
#include "gm_commands/augmentitem.cpp"
|
||||
#include "gm_commands/ban.cpp"
|
||||
#include "gm_commands/bugs.cpp"
|
||||
#include "gm_commands/camerashake.cpp"
|
||||
#include "gm_commands/castspell.cpp"
|
||||
#include "gm_commands/chat.cpp"
|
||||
#include "gm_commands/clearxtargets.cpp"
|
||||
#include "gm_commands/copycharacter.cpp"
|
||||
#include "gm_commands/corpse.cpp"
|
||||
#include "gm_commands/corpsefix.cpp"
|
||||
#include "gm_commands/countitem.cpp"
|
||||
#include "gm_commands/damage.cpp"
|
||||
#include "gm_commands/databuckets.cpp"
|
||||
#include "gm_commands/dbspawn2.cpp"
|
||||
#include "gm_commands/delacct.cpp"
|
||||
#include "gm_commands/delpetition.cpp"
|
||||
#include "gm_commands/depop.cpp"
|
||||
#include "gm_commands/depopzone.cpp"
|
||||
#include "gm_commands/devtools.cpp"
|
||||
#include "gm_commands/disablerecipe.cpp"
|
||||
#include "gm_commands/disarmtrap.cpp"
|
||||
#include "gm_commands/doanim.cpp"
|
||||
#include "gm_commands/door.cpp"
|
||||
#include "gm_commands/door_manipulation.cpp"
|
||||
#include "gm_commands/dye.cpp"
|
||||
#include "gm_commands/dz.cpp"
|
||||
#include "gm_commands/dzkickplayers.cpp"
|
||||
#include "gm_commands/editmassrespawn.cpp"
|
||||
#include "gm_commands/emote.cpp"
|
||||
#include "gm_commands/emptyinventory.cpp"
|
||||
#include "gm_commands/enablerecipe.cpp"
|
||||
#include "gm_commands/entityvariable.cpp"
|
||||
#include "gm_commands/exptoggle.cpp"
|
||||
#include "gm_commands/faction.cpp"
|
||||
#include "gm_commands/evolving_items.cpp"
|
||||
#include "gm_commands/feature.cpp"
|
||||
#include "gm_commands/find.cpp"
|
||||
#include "gm_commands/fish.cpp"
|
||||
#include "gm_commands/fixmob.cpp"
|
||||
#include "gm_commands/flagedit.cpp"
|
||||
#include "gm_commands/fleeinfo.cpp"
|
||||
#include "gm_commands/forage.cpp"
|
||||
#include "gm_commands/gearup.cpp"
|
||||
#include "gm_commands/giveitem.cpp"
|
||||
#include "gm_commands/givemoney.cpp"
|
||||
#include "gm_commands/gmzone.cpp"
|
||||
#include "gm_commands/goto.cpp"
|
||||
#include "gm_commands/grantaa.cpp"
|
||||
#include "gm_commands/grid.cpp"
|
||||
#include "gm_commands/guild.cpp"
|
||||
#include "gm_commands/hp.cpp"
|
||||
#include "gm_commands/illusion_block.cpp"
|
||||
#include "gm_commands/instance.cpp"
|
||||
#include "gm_commands/interrogateinv.cpp"
|
||||
#include "gm_commands/interrupt.cpp"
|
||||
#include "gm_commands/invsnapshot.cpp"
|
||||
#include "gm_commands/ipban.cpp"
|
||||
#include "gm_commands/kick.cpp"
|
||||
#include "gm_commands/kill.cpp"
|
||||
#include "gm_commands/killallnpcs.cpp"
|
||||
#include "gm_commands/list.cpp"
|
||||
#include "gm_commands/lootsim.cpp"
|
||||
#include "gm_commands/loc.cpp"
|
||||
#include "gm_commands/logs.cpp"
|
||||
#include "gm_commands/makepet.cpp"
|
||||
#include "gm_commands/memspell.cpp"
|
||||
#include "gm_commands/merchantshop.cpp"
|
||||
#include "gm_commands/modifynpcstat.cpp"
|
||||
#include "gm_commands/movechar.cpp"
|
||||
#include "gm_commands/movement.cpp"
|
||||
#include "gm_commands/myskills.cpp"
|
||||
#include "gm_commands/mysql.cpp"
|
||||
#include "gm_commands/mystats.cpp"
|
||||
#include "gm_commands/npccast.cpp"
|
||||
#include "gm_commands/npcedit.cpp"
|
||||
#include "gm_commands/npceditmass.cpp"
|
||||
#include "gm_commands/npcemote.cpp"
|
||||
#include "gm_commands/npcloot.cpp"
|
||||
#include "gm_commands/npcsay.cpp"
|
||||
#include "gm_commands/npcshout.cpp"
|
||||
#include "gm_commands/npcspawn.cpp"
|
||||
#include "gm_commands/npctypespawn.cpp"
|
||||
#include "gm_commands/nudge.cpp"
|
||||
#include "gm_commands/nukebuffs.cpp"
|
||||
#include "gm_commands/nukeitem.cpp"
|
||||
#include "gm_commands/object.cpp"
|
||||
#include "gm_commands/object_manipulation.cpp"
|
||||
#include "gm_commands/parcels.cpp"
|
||||
#include "gm_commands/path.cpp"
|
||||
#include "gm_commands/peqzone.cpp"
|
||||
#include "gm_commands/petitems.cpp"
|
||||
#include "gm_commands/petname.cpp"
|
||||
#include "gm_commands/picklock.cpp"
|
||||
#include "gm_commands/profanity.cpp"
|
||||
#include "gm_commands/push.cpp"
|
||||
#include "gm_commands/raidloot.cpp"
|
||||
#include "gm_commands/randomfeatures.cpp"
|
||||
#include "gm_commands/refreshgroup.cpp"
|
||||
#include "gm_commands/reload.cpp"
|
||||
#include "gm_commands/removeitem.cpp"
|
||||
#include "gm_commands/repop.cpp"
|
||||
#include "gm_commands/resetaa.cpp"
|
||||
#include "gm_commands/resetaa_timer.cpp"
|
||||
#include "gm_commands/resetdisc_timer.cpp"
|
||||
#include "gm_commands/revoke.cpp"
|
||||
#include "gm_commands/roambox.cpp"
|
||||
#include "gm_commands/rules.cpp"
|
||||
#include "gm_commands/save.cpp"
|
||||
#include "gm_commands/scale.cpp"
|
||||
#include "gm_commands/scribespell.cpp"
|
||||
#include "gm_commands/scribespells.cpp"
|
||||
#include "gm_commands/sendzonespawns.cpp"
|
||||
#include "gm_commands/sensetrap.cpp"
|
||||
#include "gm_commands/serverrules.cpp"
|
||||
#include "gm_commands/set.cpp"
|
||||
#include "gm_commands/show.cpp"
|
||||
#include "gm_commands/shutdown.cpp"
|
||||
#include "gm_commands/spawn.cpp"
|
||||
#include "gm_commands/spawneditmass.cpp"
|
||||
#include "gm_commands/spawnfix.cpp"
|
||||
#include "gm_commands/faction_association.cpp"
|
||||
#include "gm_commands/stun.cpp"
|
||||
#include "gm_commands/summon.cpp"
|
||||
#include "gm_commands/summonburiedplayercorpse.cpp"
|
||||
#include "gm_commands/summonitem.cpp"
|
||||
#include "gm_commands/suspend.cpp"
|
||||
#include "gm_commands/suspendmulti.cpp"
|
||||
#include "gm_commands/takeplatinum.cpp"
|
||||
#include "gm_commands/task.cpp"
|
||||
#include "gm_commands/traindisc.cpp"
|
||||
#include "gm_commands/tune.cpp"
|
||||
#include "gm_commands/undye.cpp"
|
||||
#include "gm_commands/unmemspell.cpp"
|
||||
#include "gm_commands/unmemspells.cpp"
|
||||
#include "gm_commands/unscribespell.cpp"
|
||||
#include "gm_commands/unscribespells.cpp"
|
||||
#include "gm_commands/untraindisc.cpp"
|
||||
#include "gm_commands/untraindiscs.cpp"
|
||||
#include "gm_commands/wc.cpp"
|
||||
#include "gm_commands/worldshutdown.cpp"
|
||||
#include "gm_commands/worldwide.cpp"
|
||||
#include "gm_commands/wp.cpp"
|
||||
#include "gm_commands/wpadd.cpp"
|
||||
#include "gm_commands/zone.cpp"
|
||||
#include "gm_commands/zonebootup.cpp"
|
||||
#include "gm_commands/zoneshutdown.cpp"
|
||||
#include "gm_commands/zone_instance.cpp"
|
||||
#include "gm_commands/zone_shard.cpp"
|
||||
#include "gm_commands/zsave.cpp"
|
||||
|
||||
@@ -198,6 +198,7 @@ void command_zone_instance(Client *c, const Seperator *sep);
|
||||
void command_zone_shard(Client *c, const Seperator *sep);
|
||||
void command_zonebootup(Client *c, const Seperator *sep);
|
||||
void command_zoneshutdown(Client *c, const Seperator *sep);
|
||||
void command_zonevariable(Client *c, const Seperator *sep);
|
||||
void command_zsave(Client *c, const Seperator *sep);
|
||||
|
||||
#include "bot.h"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#include <regex>
|
||||
|
||||
#include "dialogue_window.h"
|
||||
|
||||
void DialogueWindow::Render(Client *c, std::string markdown)
|
||||
@@ -529,12 +527,19 @@ std::string DialogueWindow::CenterMessage(std::string message)
|
||||
return std::string();
|
||||
}
|
||||
|
||||
auto cleaned_message = message;
|
||||
std::string cleaned_message;
|
||||
cleaned_message.reserve(message.size());
|
||||
|
||||
std::regex tags("<[^>]*>");
|
||||
|
||||
if (std::regex_search(cleaned_message, tags)) {
|
||||
std::regex_replace(cleaned_message, tags, cleaned_message);
|
||||
// Strip HTML-like tags
|
||||
bool in_tag = false;
|
||||
for (char c : message) {
|
||||
if (c == '<') {
|
||||
in_tag = true;
|
||||
} else if (c == '>' && in_tag) {
|
||||
in_tag = false;
|
||||
} else if (!in_tag) {
|
||||
cleaned_message += c;
|
||||
}
|
||||
}
|
||||
|
||||
auto message_len = cleaned_message.length();
|
||||
|
||||
+3
-1
@@ -852,11 +852,13 @@ void Doors::CreateDatabaseEntry()
|
||||
const auto& l = DoorsRepository::GetWhere(
|
||||
content_db,
|
||||
fmt::format(
|
||||
"zone = '{}' AND doorid = {}",
|
||||
"zone = '{}' AND (version = {} OR version = -1) AND doorid = {}",
|
||||
zone->GetShortName(),
|
||||
zone->GetInstanceVersion(),
|
||||
GetDoorID()
|
||||
)
|
||||
);
|
||||
|
||||
if (!l.empty()) {
|
||||
auto e = l[0];
|
||||
|
||||
|
||||
+2
-2
@@ -1125,8 +1125,8 @@ void EntityList::AESpell(
|
||||
RuleI(Range, MobCloseScanDistance),
|
||||
distance
|
||||
);
|
||||
|
||||
for (auto& it: caster_mob->GetCloseMobList(distance)) {
|
||||
auto list = caster_mob->GetCloseMobList(distance);
|
||||
for (auto& it: list) {
|
||||
current_mob = it.second;
|
||||
if (!current_mob) {
|
||||
continue;
|
||||
|
||||
@@ -2543,6 +2543,14 @@ void PerlembParser::ExportEventVariables(
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_CONNECT: {
|
||||
Seperator sep(data);
|
||||
ExportVar(package_name.c_str(), "last_login", sep.arg[0]);
|
||||
ExportVar(package_name.c_str(), "seconds_since_last_login", sep.arg[1]);
|
||||
ExportVar(package_name.c_str(), "is_first_login", sep.arg[2]);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "queryserv.h"
|
||||
#include "questmgr.h"
|
||||
#include "zone.h"
|
||||
#include "data_bucket.h"
|
||||
#include "../common/data_bucket.h"
|
||||
#include "../common/events/player_event_logs.h"
|
||||
#include "worldserver.h"
|
||||
|
||||
|
||||
+22
-19
@@ -137,25 +137,28 @@ void Embperl::DoInit()
|
||||
catch (std::string& e) {
|
||||
LogQuests("Warning [{}]: [{}]", Config->PluginPlFile, e);
|
||||
}
|
||||
try {
|
||||
//should probably read the directory in c, instead, so that
|
||||
//I can echo filenames as I do it, but c'mon... I'm lazy and this 1 line reads in all the plugins
|
||||
const std::string& perl_command = (
|
||||
"if(opendir(D,'" +
|
||||
path.GetPluginsPath() +
|
||||
"')) { "
|
||||
" my @d = readdir(D);"
|
||||
" closedir(D);"
|
||||
" foreach(@d){ "
|
||||
" main::eval_file('plugin','" +
|
||||
path.GetPluginsPath() +
|
||||
"/'.$_)if/\\.pl$/;"
|
||||
" }"
|
||||
"}");
|
||||
eval_pv(perl_command.c_str(), FALSE);
|
||||
}
|
||||
catch (std::string& e) {
|
||||
LogQuests("Warning [{}]", e);
|
||||
|
||||
for (auto & dir : path.GetPluginPaths()) {
|
||||
try {
|
||||
//should probably read the directory in c, instead, so that
|
||||
//I can echo filenames as I do it, but c'mon... I'm lazy and this 1 line reads in all the plugins
|
||||
const std::string& perl_command = (
|
||||
"if(opendir(D,'" +
|
||||
dir +
|
||||
"')) { "
|
||||
" my @d = readdir(D);"
|
||||
" closedir(D);"
|
||||
" foreach(@d){ "
|
||||
" main::eval_file('plugin','" +
|
||||
dir +
|
||||
"/'.$_)if/\\.pl$/;"
|
||||
" }"
|
||||
"}");
|
||||
eval_pv(perl_command.c_str(), FALSE);
|
||||
}
|
||||
catch (std::string& e) {
|
||||
LogQuests("Warning [{}]", e);
|
||||
}
|
||||
}
|
||||
#endif //EMBPERL_PLUGIN
|
||||
}
|
||||
|
||||
+23
-1
@@ -505,6 +505,9 @@ void EntityList::MobProcess()
|
||||
zone->GetSecondsBeforeIdle(),
|
||||
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
|
||||
);
|
||||
|
||||
CheckToClearTraderAndBuyerTables();
|
||||
|
||||
mob_settle_timer->Disable();
|
||||
}
|
||||
|
||||
@@ -2335,7 +2338,7 @@ void EntityList::QueueClientsGuild(const EQApplicationPacket *app, uint32 guild_
|
||||
void EntityList::QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 guild_id)
|
||||
{
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct));
|
||||
auto data = reinterpret_cast<GuildBankItemUpdate_Struct *>(outapp->pBuffer);
|
||||
auto data = reinterpret_cast<GuildBankItemUpdate_Struct *>(outapp->pBuffer);
|
||||
|
||||
memcpy(data, gbius, sizeof(GuildBankItemUpdate_Struct));
|
||||
|
||||
@@ -6009,3 +6012,22 @@ void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
|
||||
c->SetDecayTimer(decay_time);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityList::CheckToClearTraderAndBuyerTables()
|
||||
{
|
||||
if (zone->GetZoneID() == Zones::BAZAAR) {
|
||||
TraderRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", zone->GetZoneID(), zone->GetInstanceID()
|
||||
)
|
||||
);
|
||||
BuyerRepository::DeleteBuyers(database, zone->GetZoneID(), zone->GetInstanceID());
|
||||
|
||||
LogTradingDetail(
|
||||
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]",
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,6 +581,7 @@ public:
|
||||
void SendMerchantEnd(Mob* merchant);
|
||||
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
||||
void RestoreCorpse(NPC* npc, uint32_t decay_time);
|
||||
void CheckToClearTraderAndBuyerTables();
|
||||
|
||||
protected:
|
||||
friend class Zone;
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include "../common/strings.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "data_bucket.h"
|
||||
#include "../common/data_bucket.h"
|
||||
#include "groups.h"
|
||||
#include "mob.h"
|
||||
#include "raids.h"
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
#include "../client.h"
|
||||
#include "../data_bucket.h"
|
||||
#include "../../common/data_bucket.h"
|
||||
#include "../dialogue_window.h"
|
||||
#include "../../common/repositories/data_buckets_repository.h"
|
||||
|
||||
void SendDataBucketsSubCommands(Client *c)
|
||||
{
|
||||
c->Message(Chat::White, "Usage: #databuckets delete [Key] [Character ID] [NPC ID] [Bot ID]");
|
||||
c->Message(Chat::White, "Usage: #databuckets edit [Key] [Character ID] [NPC ID] [Bot ID] [Value] [Expires]");
|
||||
c->Message(Chat::White, "Usage: #databuckets view [Partial Key] [Character ID] [NPC ID] [Bot ID]");
|
||||
c->Message(Chat::White, "Note: Character ID, NPC ID, and Bot ID are optional if not needed, if needed they are required for specificity");
|
||||
c->Message(Chat::White, "Note: Edit requires Character ID, NPC ID, Bot ID, and Value, Expires is optional and does not modify the existing expiration time if not provided");
|
||||
}
|
||||
|
||||
void command_databuckets(Client *c, const Seperator *sep)
|
||||
{
|
||||
const int arguments = sep->argnum;
|
||||
@@ -251,12 +260,3 @@ void command_databuckets(Client *c, const Seperator *sep)
|
||||
c->Message(Chat::White, response.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void SendDataBucketsSubCommands(Client *c)
|
||||
{
|
||||
c->Message(Chat::White, "Usage: #databuckets delete [Key] [Character ID] [NPC ID] [Bot ID]");
|
||||
c->Message(Chat::White, "Usage: #databuckets edit [Key] [Character ID] [NPC ID] [Bot ID] [Value] [Expires]");
|
||||
c->Message(Chat::White, "Usage: #databuckets view [Partial Key] [Character ID] [NPC ID] [Bot ID]");
|
||||
c->Message(Chat::White, "Note: Character ID, NPC ID, and Bot ID are optional if not needed, if needed they are required for specificity");
|
||||
c->Message(Chat::White, "Note: Edit requires Character ID, NPC ID, Bot ID, and Value, Expires is optional and does not modify the existing expiration time if not provided");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../client.h"
|
||||
#include "../data_bucket.h"
|
||||
#include "../../common/data_bucket.h"
|
||||
|
||||
void command_devtools(Client *c, const Seperator *sep)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "../../object.h"
|
||||
#include "../../client.h"
|
||||
|
||||
void FindObjectType(Client *c, const Seperator *sep)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "../client.h"
|
||||
#include "../data_bucket.h"
|
||||
#include "../../common/data_bucket.h"
|
||||
|
||||
void command_gmzone(Client *c, const Seperator *sep)
|
||||
{
|
||||
|
||||
+17
-17
@@ -8,6 +8,22 @@ extern QueryServ *QServ;
|
||||
#include "../guild_mgr.h"
|
||||
#include "../doors.h"
|
||||
|
||||
void SendGuildSubCommands(Client* c)
|
||||
{
|
||||
c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]");
|
||||
c->Message(Chat::White, "#guild delete [Guild ID]");
|
||||
c->Message(Chat::White, "#guild details [Guild ID]");
|
||||
c->Message(Chat::White, "#guild help");
|
||||
c->Message(Chat::White, "#guild info [Guild ID]");
|
||||
c->Message(Chat::White, "#guild list");
|
||||
c->Message(Chat::White, "#guild rename [Guild ID] [New Name]");
|
||||
c->Message(Chat::White, "#guild search [Search Criteria]");
|
||||
c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)");
|
||||
c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]");
|
||||
c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]");
|
||||
c->Message(Chat::White, "#guild status [Character ID|Character Name]");
|
||||
}
|
||||
|
||||
void command_guild(Client* c, const Seperator* sep)
|
||||
{
|
||||
const auto arguments = sep->argnum;
|
||||
@@ -675,20 +691,4 @@ void command_guild(Client* c, const Seperator* sep)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendGuildSubCommands(Client* c)
|
||||
{
|
||||
c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]");
|
||||
c->Message(Chat::White, "#guild delete [Guild ID]");
|
||||
c->Message(Chat::White, "#guild details [Guild ID]");
|
||||
c->Message(Chat::White, "#guild help");
|
||||
c->Message(Chat::White, "#guild info [Guild ID]");
|
||||
c->Message(Chat::White, "#guild list");
|
||||
c->Message(Chat::White, "#guild rename [Guild ID] [New Name]");
|
||||
c->Message(Chat::White, "#guild search [Search Criteria]");
|
||||
c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)");
|
||||
c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]");
|
||||
c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]");
|
||||
c->Message(Chat::White, "#guild status [Character ID|Character Name]");
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "../dialogue_window.h"
|
||||
#include "../client.h"
|
||||
|
||||
void command_illusion_block(Client* c, const Seperator* sep)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../corpse.h"
|
||||
#include "../object.h"
|
||||
#include "../doors.h"
|
||||
#include "../command.h"
|
||||
|
||||
struct UniqueEntity {
|
||||
uint16 entity_id;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "../client.h"
|
||||
#include "../water_map.h"
|
||||
|
||||
void command_loc(Client *c, const Seperator *sep)
|
||||
{
|
||||
|
||||
@@ -1,63 +1,5 @@
|
||||
#include "../client.h"
|
||||
|
||||
void command_modifynpcstat(Client *c, const Seperator *sep)
|
||||
{
|
||||
auto arguments = sep->argnum;
|
||||
if (!arguments) {
|
||||
c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]");
|
||||
ListModifyNPCStatMap(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->GetTarget() || !c->GetTarget()->IsNPC()) {
|
||||
c->Message(Chat::White, "You must target an NPC to use this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto target = c->GetTarget()->CastToNPC();
|
||||
|
||||
const std::string& stat = sep->arg[1] ? sep->arg[1] : "";
|
||||
const std::string& value = sep->arg[2] ? sep->arg[2] : "";
|
||||
|
||||
if (stat.empty() || value.empty()) {
|
||||
c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]");
|
||||
ListModifyNPCStatMap(c);
|
||||
return;
|
||||
}
|
||||
|
||||
auto stat_description = GetModifyNPCStatDescription(stat);
|
||||
if (!stat_description.length()) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Stat '{}' does not exist.",
|
||||
stat
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
target->ModifyNPCStat(stat, value);
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Stat Modified | Target: {}",
|
||||
c->GetTargetDescription(target)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Stat Modified | Stat: {} ({}) Value: {}",
|
||||
GetModifyNPCStatDescription(stat),
|
||||
stat,
|
||||
value
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> GetModifyNPCStatMap()
|
||||
{
|
||||
std::map<std::string, std::string> identifiers_map = {
|
||||
@@ -134,3 +76,61 @@ void ListModifyNPCStatMap(Client *c)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void command_modifynpcstat(Client *c, const Seperator *sep)
|
||||
{
|
||||
auto arguments = sep->argnum;
|
||||
if (!arguments) {
|
||||
c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]");
|
||||
ListModifyNPCStatMap(c);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->GetTarget() || !c->GetTarget()->IsNPC()) {
|
||||
c->Message(Chat::White, "You must target an NPC to use this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto target = c->GetTarget()->CastToNPC();
|
||||
|
||||
const std::string& stat = sep->arg[1] ? sep->arg[1] : "";
|
||||
const std::string& value = sep->arg[2] ? sep->arg[2] : "";
|
||||
|
||||
if (stat.empty() || value.empty()) {
|
||||
c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]");
|
||||
ListModifyNPCStatMap(c);
|
||||
return;
|
||||
}
|
||||
|
||||
auto stat_description = GetModifyNPCStatDescription(stat);
|
||||
if (!stat_description.length()) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Stat '{}' does not exist.",
|
||||
stat
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
target->ModifyNPCStat(stat, value);
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Stat Modified | Target: {}",
|
||||
c->GetTargetDescription(target)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Stat Modified | Stat: {} ({}) Value: {}",
|
||||
GetModifyNPCStatDescription(stat),
|
||||
stat,
|
||||
value
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "../bot.h"
|
||||
#include "../client.h"
|
||||
|
||||
void command_mystats(Client *c, const Seperator *sep)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user