mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-28 01:26:01 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||
|
||||
@@ -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
|
||||
|
||||
+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);
|
||||
|
||||
@@ -324,6 +324,8 @@ union
|
||||
bool guild_show;
|
||||
bool trader;
|
||||
bool buyer;
|
||||
bool untargetable;
|
||||
uint32 npc_tint_id;
|
||||
};
|
||||
|
||||
struct PlayerState_Struct {
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
@@ -878,6 +879,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 +1073,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()
|
||||
|
||||
+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"
|
||||
|
||||
@@ -10,7 +10,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=
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -162,7 +162,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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -50,6 +50,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#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 +1862,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); }
|
||||
|
||||
+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;
|
||||
|
||||
+191
-85
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+8
-4
@@ -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"
|
||||
|
||||
@@ -1255,9 +1256,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();
|
||||
@@ -2080,7 +2082,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;
|
||||
@@ -2257,6 +2260,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;
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
+54
-20
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -15567,7 +15599,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);
|
||||
|
||||
|
||||
@@ -727,7 +727,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
-1
@@ -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"
|
||||
@@ -928,6 +937,7 @@ void command_bot(Client *c, const Seperator *sep)
|
||||
#include "gm_commands/zone.cpp"
|
||||
#include "gm_commands/zonebootup.cpp"
|
||||
#include "gm_commands/zoneshutdown.cpp"
|
||||
#include "gm_commands/zonevariable.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"
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
+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;
|
||||
|
||||
@@ -1693,6 +1693,24 @@ void command_npcedit(Client *c, const Seperator *sep)
|
||||
c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID");
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(sep->arg[1], "npc_tint_id")) {
|
||||
if (sep->IsNumber(2)) {
|
||||
const uint32 npc_tint_id = (Strings::ToUnsignedInt(sep->arg[2]));
|
||||
|
||||
n.npc_tint_id = npc_tint_id;
|
||||
|
||||
d = fmt::format(
|
||||
"Set NPCTintID {} for {}",
|
||||
npc_tint_id,
|
||||
npc_id_string
|
||||
);
|
||||
} else {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
"Usage: #npcedit npc_tint_id [id] - Sets an NPC's NPCTintID [0 - 78 for RoF2]"
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
SendNPCEditSubCommands(c);
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,11 @@ extern WorldServer worldserver;
|
||||
|
||||
void command_task(Client *c, const Seperator *sep)
|
||||
{
|
||||
if (!RuleB(TaskSystem, EnableTaskSystem)) {
|
||||
c->Message(Chat::White, "This command cannot be used while the Task system is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
const int arguments = sep->argnum;
|
||||
if (!arguments) {
|
||||
c->Message(Chat::White, "Syntax: #task [subcommand]");
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "../client.h"
|
||||
|
||||
void command_zonevariable(Client *c, const Seperator *sep)
|
||||
{
|
||||
const uint16 arguments = sep->argnum;
|
||||
|
||||
if (!arguments) {
|
||||
c->Message(Chat::White, "Usage: #zonevariable clear - Clear all zone variables");
|
||||
c->Message(Chat::White, "Usage: #zonevariable delete [Variable Name] - Delete a zone variable");
|
||||
c->Message(Chat::White, "Usage: #zonevariable set [Variable Name] [Variable Value] - Set a zone variable");
|
||||
c->Message(Chat::White, "Usage: #zonevariable view [Variable Name] - View a zone variable");
|
||||
c->Message(Chat::White, "Note: You can have spaces in variable names and values by wrapping in double quotes like this");
|
||||
c->Message(Chat::White, "Example: #zonevariable set \"Test Variable\" \"Test Value\"");
|
||||
c->Message(Chat::White, "Note: Variable Value is optional and can be set to empty by not providing a value");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* action = arguments >= 1 ? sep->arg[1] : "";
|
||||
const bool is_clear = !strcasecmp(action, "clear");
|
||||
const bool is_delete = !strcasecmp(action, "delete");
|
||||
const bool is_set = !strcasecmp(action, "set");
|
||||
const bool is_view = !strcasecmp(action, "view");
|
||||
|
||||
if (!is_clear && !is_delete && !is_set && !is_view) {
|
||||
c->Message(Chat::White, "Usage: #zonevariable clear - Clear all zone variables");
|
||||
c->Message(Chat::White, "Usage: #zonevariable delete [Variable Name] - Delete a zone variable");
|
||||
c->Message(Chat::White, "Usage: #zonevariable set [Variable Name] [Variable Value] - Set a zone variable");
|
||||
c->Message(Chat::White, "Usage: #zonevariable view [Variable Name] - View a zone variable");
|
||||
c->Message(Chat::White, "Note: You can have spaces in variable names and values by wrapping in double quotes like this");
|
||||
c->Message(Chat::White, "Example: #zonevariable set \"Test Variable\" \"Test Value\"");
|
||||
c->Message(Chat::White, "Note: Variable Value is optional and can be set to empty by not providing a value");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_clear) {
|
||||
const bool cleared = zone->ClearVariables();
|
||||
c->Message(Chat::White, cleared ? "Cleared all zone variables." : "There are no zone variables to clear.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_delete) {
|
||||
const std::string variable_name = arguments >= 2 ? sep->argplus[2] : "";
|
||||
if (variable_name.empty() || !zone->VariableExists(variable_name)) {
|
||||
c->Message(Chat::White, fmt::format("A zone variable named '{}' does not exist.", variable_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
zone->DeleteVariable(variable_name);
|
||||
c->Message(Chat::White, fmt::format("Deleted a zone variable named '{}'.", variable_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_set) {
|
||||
const std::string variable_name = arguments >= 2 ? sep->arg[2] : "";
|
||||
const std::string variable_value = arguments >= 3 ? sep->arg[3] : "";
|
||||
zone->SetVariable(variable_name, variable_value);
|
||||
c->Message(Chat::White, fmt::format("Set a zone variable named '{}' to a value of '{}'.", variable_name, variable_value).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_view) {
|
||||
const auto& l = zone->GetVariables();
|
||||
const std::string search_criteria = arguments >= 2 ? sep->argplus[2] : "";
|
||||
|
||||
uint32 variable_count = 0;
|
||||
uint32 variable_number = 1;
|
||||
|
||||
for (const auto& e : l) {
|
||||
if (search_criteria.empty() || Strings::Contains(Strings::ToLower(e), Strings::ToLower(search_criteria))) {
|
||||
c->Message(Chat::White, fmt::format(
|
||||
"Variable {} | Name: {} Value: {} | {}",
|
||||
variable_number,
|
||||
e,
|
||||
zone->GetVariable(e),
|
||||
Saylink::Silent(fmt::format("#zonevariable delete {}", e), "Delete")
|
||||
).c_str());
|
||||
|
||||
variable_count++;
|
||||
variable_number++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!variable_count) {
|
||||
c->Message(Chat::White, fmt::format(
|
||||
"There are no zone variables{}.",
|
||||
(!search_criteria.empty() ? fmt::format(" matching '{}'", search_criteria) : "")
|
||||
).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
c->Message(Chat::White, fmt::format(
|
||||
"There {} {} zone variable{}{}, would you like to {} zone variables?",
|
||||
variable_count != 1 ? "are" : "is",
|
||||
variable_count,
|
||||
variable_count != 1 ? "s" : "",
|
||||
(!search_criteria.empty() ? fmt::format(" matching '{}'", search_criteria) : ""),
|
||||
Saylink::Silent("#zonevariable clear", "clear")
|
||||
).c_str());
|
||||
}
|
||||
}
|
||||
@@ -406,6 +406,7 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack)
|
||||
c.second->SendGuildDeletePacket(s->guild_id);
|
||||
c.second->RefreshGuildInfo();
|
||||
c.second->MessageString(Chat::Guild, GUILD_DISBANDED);
|
||||
c.second->SendGuildList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1094,8 +1094,8 @@ int lua_faction_value() {
|
||||
return quest_manager.FactionValue();
|
||||
}
|
||||
|
||||
void lua_check_title(uint32 title_set) {
|
||||
quest_manager.checktitle(title_set);
|
||||
bool lua_check_title(uint32 title_set) {
|
||||
return quest_manager.checktitle(title_set);
|
||||
}
|
||||
|
||||
void lua_enable_title(uint32 title_set) {
|
||||
|
||||
@@ -945,6 +945,12 @@ bool Lua_NPC::IsResumedFromZoneSuspend()
|
||||
return self->IsResumedFromZoneSuspend();
|
||||
}
|
||||
|
||||
void Lua_NPC::SetNPCTintIndex(uint32 id)
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
|
||||
}
|
||||
|
||||
luabind::scope lua_register_npc() {
|
||||
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -1091,6 +1097,7 @@ luabind::scope lua_register_npc() {
|
||||
.def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType)
|
||||
.def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro)
|
||||
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
|
||||
.def("SetNPCTintIndex", &Lua_NPC::SetNPCTintIndex)
|
||||
.def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID)
|
||||
.def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum)
|
||||
.def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill)
|
||||
|
||||
@@ -199,6 +199,8 @@ public:
|
||||
void ReturnHandinItems(Lua_Client c);
|
||||
Lua_Spawn GetSpawn(lua_State* L);
|
||||
bool IsResumedFromZoneSuspend();
|
||||
void SetNPCTintIndex(uint32 id);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -353,6 +353,7 @@ LuaParser::LuaParser() {
|
||||
PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss;
|
||||
PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked;
|
||||
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item;
|
||||
PlayerArgumentDispatch[EVENT_CONNECT] = handle_player_connect;
|
||||
|
||||
ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click;
|
||||
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;
|
||||
|
||||
@@ -1809,6 +1809,26 @@ void handle_player_read_item(
|
||||
}
|
||||
}
|
||||
|
||||
void handle_player_connect(
|
||||
QuestInterface *parse,
|
||||
lua_State* L,
|
||||
Client* client,
|
||||
std::string data,
|
||||
uint32 extra_data,
|
||||
std::vector<std::any> *extra_pointers
|
||||
)
|
||||
{
|
||||
Seperator sep(data.c_str());
|
||||
lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0]));
|
||||
lua_setfield(L, -2, "last_login");
|
||||
|
||||
lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1]));
|
||||
lua_setfield(L, -2, "seconds_since_last_login");
|
||||
|
||||
lua_pushboolean(L, Strings::ToBool(sep.arg[2]));
|
||||
lua_setfield(L, -2, "is_first_login");
|
||||
}
|
||||
|
||||
// Item
|
||||
void handle_item_click(
|
||||
QuestInterface *parse,
|
||||
|
||||
@@ -865,6 +865,15 @@ void handle_player_read_item(
|
||||
std::vector<std::any> *extra_pointers
|
||||
);
|
||||
|
||||
void handle_player_connect(
|
||||
QuestInterface *parse,
|
||||
lua_State* L,
|
||||
Client* client,
|
||||
std::string data,
|
||||
uint32 extra_data,
|
||||
std::vector<std::any> *extra_pointers
|
||||
);
|
||||
|
||||
// Item
|
||||
void handle_item_click(
|
||||
QuestInterface *parse,
|
||||
|
||||
+3
-3
@@ -727,10 +727,10 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
|
||||
return self->GetBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Lua_Zone::ClearVariables()
|
||||
bool Lua_Zone::ClearVariables()
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->ClearVariables();
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->ClearVariables();
|
||||
}
|
||||
|
||||
bool Lua_Zone::DeleteVariable(const std::string& variable_name)
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ public:
|
||||
void SetInstanceTimeRemaining(uint32 time_remaining);
|
||||
void SetIsHotzone(bool is_hotzone);
|
||||
void ShowZoneGlobalLoot(Lua_Client c);
|
||||
void ClearVariables();
|
||||
bool ClearVariables();
|
||||
bool DeleteVariable(const std::string& variable_name);
|
||||
std::string GetVariable(const std::string& variable_name);
|
||||
luabind::object GetVariables(lua_State* L);
|
||||
|
||||
+23
-19
@@ -101,7 +101,8 @@ Mob::Mob(
|
||||
bool in_always_aggro,
|
||||
int32 in_heroic_strikethrough,
|
||||
bool in_keeps_sold_items,
|
||||
int64 in_hp_regen_per_second
|
||||
int64 in_hp_regen_per_second,
|
||||
uint32 npc_tint_id
|
||||
) :
|
||||
attack_timer(2000),
|
||||
attack_dw_timer(2000),
|
||||
@@ -289,6 +290,7 @@ Mob::Mob(
|
||||
always_aggro = in_always_aggro;
|
||||
heroic_strikethrough = in_heroic_strikethrough;
|
||||
keeps_sold_items = in_keeps_sold_items;
|
||||
m_npc_tint_id = npc_tint_id;
|
||||
|
||||
InitializeBuffSlots();
|
||||
|
||||
@@ -1285,23 +1287,24 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
|
||||
}
|
||||
|
||||
ns->spawn.heading = FloatToEQ12(m_Position.w);
|
||||
ns->spawn.x = FloatToEQ19(m_Position.x);//((int32)x_pos)<<3;
|
||||
ns->spawn.y = FloatToEQ19(m_Position.y);//((int32)y_pos)<<3;
|
||||
ns->spawn.z = FloatToEQ19(m_Position.z);//((int32)z_pos)<<3;
|
||||
ns->spawn.spawnId = GetID();
|
||||
ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
|
||||
ns->spawn.max_hp = 100; //this field needs a better name
|
||||
ns->spawn.race = (use_model) ? use_model : race;
|
||||
ns->spawn.runspeed = runspeed;
|
||||
ns->spawn.walkspeed = walkspeed;
|
||||
ns->spawn.class_ = class_;
|
||||
ns->spawn.gender = gender;
|
||||
ns->spawn.level = level;
|
||||
ns->spawn.PlayerState = GetPlayerState();
|
||||
ns->spawn.deity = deity;
|
||||
ns->spawn.animation = 0;
|
||||
ns->spawn.findable = findable?1:0;
|
||||
ns->spawn.heading = FloatToEQ12(m_Position.w);
|
||||
ns->spawn.x = FloatToEQ19(m_Position.x); //((int32)x_pos)<<3;
|
||||
ns->spawn.y = FloatToEQ19(m_Position.y); //((int32)y_pos)<<3;
|
||||
ns->spawn.z = FloatToEQ19(m_Position.z); //((int32)z_pos)<<3;
|
||||
ns->spawn.spawnId = GetID();
|
||||
ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
|
||||
ns->spawn.max_hp = 100; // this field needs a better name
|
||||
ns->spawn.race = (use_model) ? use_model : race;
|
||||
ns->spawn.runspeed = runspeed;
|
||||
ns->spawn.walkspeed = walkspeed;
|
||||
ns->spawn.class_ = class_;
|
||||
ns->spawn.gender = gender;
|
||||
ns->spawn.level = level;
|
||||
ns->spawn.PlayerState = GetPlayerState();
|
||||
ns->spawn.deity = deity;
|
||||
ns->spawn.animation = 0;
|
||||
ns->spawn.findable = findable ? 1 : 0;
|
||||
ns->spawn.npc_tint_id = GetNpcTintId();
|
||||
|
||||
UpdateActiveLight();
|
||||
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];
|
||||
@@ -1312,7 +1315,8 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
ns->spawn.NPC = IsClient() ? 0 : 1;
|
||||
ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
|
||||
ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic!
|
||||
|
||||
ns->spawn.untargetable = IsTargetable();
|
||||
|
||||
ns->spawn.petOwnerId = ownerid;
|
||||
|
||||
ns->spawn.haircolor = haircolor;
|
||||
|
||||
+4
-1
@@ -192,7 +192,8 @@ public:
|
||||
bool in_always_aggros_foes,
|
||||
int32 in_heroic_strikethrough,
|
||||
bool keeps_sold_items,
|
||||
int64 in_hp_regen_per_second = 0
|
||||
int64 in_hp_regen_per_second = 0,
|
||||
uint32 npc_tint_id = 0
|
||||
);
|
||||
virtual ~Mob();
|
||||
|
||||
@@ -1066,6 +1067,7 @@ public:
|
||||
void SendWearChangeAndLighting(int8 last_texture);
|
||||
inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; }
|
||||
bool UpdateActiveLight(); // returns true if change, false if no change
|
||||
uint32 GetNpcTintId() { return m_npc_tint_id; }
|
||||
|
||||
EQ::LightSourceProfile* GetLightProfile() { return &m_Light; }
|
||||
|
||||
@@ -1597,6 +1599,7 @@ protected:
|
||||
bool rare_spawn;
|
||||
int32 heroic_strikethrough;
|
||||
bool keeps_sold_items;
|
||||
uint32 m_npc_tint_id;
|
||||
|
||||
uint32 m_PlayerState;
|
||||
uint32 GetPlayerState() { return m_PlayerState; }
|
||||
|
||||
+14
-8
@@ -128,7 +128,8 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
npc_type_data->always_aggro,
|
||||
npc_type_data->heroic_strikethrough,
|
||||
npc_type_data->keeps_sold_items,
|
||||
npc_type_data->hp_regen_per_second
|
||||
npc_type_data->hp_regen_per_second,
|
||||
npc_type_data->m_npc_tint_id
|
||||
),
|
||||
attacked_timer(CombatEventTimer_expire),
|
||||
swarm_timer(100),
|
||||
@@ -451,6 +452,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
raid_target = npc_type_data->raid_target;
|
||||
ignore_despawn = npc_type_data->ignore_despawn;
|
||||
m_targetable = !npc_type_data->untargetable;
|
||||
m_npc_tint_id = npc_type_data->m_npc_tint_id;
|
||||
|
||||
npc_scale_manager->ScaleNPC(this);
|
||||
|
||||
@@ -1256,10 +1258,11 @@ uint32 ZoneDatabase::CreateNewNPCCommand(
|
||||
e.Avoidance = n->GetAvoidanceRating();
|
||||
e.heroic_strikethrough = n->GetHeroicStrikethrough();
|
||||
|
||||
e.see_hide = n->SeeHide();
|
||||
e.see_improved_hide = n->SeeImprovedHide();
|
||||
e.see_invis = n->SeeInvisible();
|
||||
e.see_invis_undead = n->SeeInvisibleUndead();
|
||||
e.see_hide = n->SeeHide();
|
||||
e.see_improved_hide = n->SeeImprovedHide();
|
||||
e.see_invis = n->SeeInvisible();
|
||||
e.see_invis_undead = n->SeeInvisibleUndead();
|
||||
e.npc_tint_id = n->GetNpcTintId();
|
||||
|
||||
|
||||
e = NpcTypesRepository::InsertOne(*this, e);
|
||||
@@ -1399,6 +1402,7 @@ uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client* c, NPC* n)
|
||||
e.loottable_id = n->GetLoottableID();
|
||||
e.merchant_id = n->MerchantType;
|
||||
e.face = n->GetLuclinFace();
|
||||
e.npc_tint_id = n->GetNpcTintId();
|
||||
|
||||
const int updated = NpcTypesRepository::UpdateOne(*this, e);
|
||||
|
||||
@@ -1539,6 +1543,7 @@ uint32 ZoneDatabase::AddNPCTypes(
|
||||
e.runspeed = n->GetRunspeed();
|
||||
e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
||||
e.sec_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
||||
e.npc_tint_id = n->GetNpcTintId();
|
||||
|
||||
e = NpcTypesRepository::InsertOne(*this, e);
|
||||
|
||||
@@ -2169,9 +2174,10 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
PetOnSpawn(ns);
|
||||
ns->spawn.is_npc = 1;
|
||||
UpdateActiveLight();
|
||||
ns->spawn.light = GetActiveLightType();
|
||||
ns->spawn.show_name = NPCTypedata->show_name;
|
||||
ns->spawn.trader = false;
|
||||
ns->spawn.light = GetActiveLightType();
|
||||
ns->spawn.show_name = NPCTypedata->show_name;
|
||||
ns->spawn.trader = false;
|
||||
ns->spawn.npc_tint_id = GetNpcTintId();
|
||||
}
|
||||
|
||||
void NPC::PetOnSpawn(NewSpawn_Struct* ns)
|
||||
|
||||
@@ -75,6 +75,8 @@ decay_timer(300000)
|
||||
decay_timer.Disable();
|
||||
}
|
||||
|
||||
memset(m_display_name, 0, sizeof(m_display_name));
|
||||
|
||||
respawn_timer.Disable();
|
||||
|
||||
// Set drop_id to zero - it will be set when added to zone with SetID()
|
||||
@@ -122,6 +124,8 @@ decay_timer(300000)
|
||||
// Set as much struct data as we can
|
||||
memset(&m_data, 0, sizeof(Object_Struct));
|
||||
|
||||
memset(m_display_name, 0, sizeof(m_display_name));
|
||||
|
||||
m_data.heading = heading;
|
||||
m_data.z = z;
|
||||
m_data.zone_id = zone->GetZoneID();
|
||||
@@ -164,6 +168,8 @@ decay_timer(300000)
|
||||
// Set as much struct data as we can
|
||||
memset(&m_data, 0, sizeof(Object_Struct));
|
||||
|
||||
memset(m_display_name, 0, sizeof(m_display_name));
|
||||
|
||||
m_data.heading = client->GetHeading();
|
||||
m_data.x = client->GetX();
|
||||
m_data.y = client->GetY();
|
||||
@@ -236,6 +242,8 @@ decay_timer(decay_time)
|
||||
// Set as much struct data as we can
|
||||
memset(&m_data, 0, sizeof(Object_Struct));
|
||||
|
||||
memset(m_display_name, 0, sizeof(m_display_name));
|
||||
|
||||
m_data.heading = heading;
|
||||
m_data.x = x;
|
||||
m_data.y = y;
|
||||
@@ -312,6 +320,8 @@ decay_timer(decay_time)
|
||||
m_data.z = z;
|
||||
m_data.zone_id = zone->GetZoneID();
|
||||
|
||||
memset(m_display_name, 0, sizeof(m_display_name));
|
||||
|
||||
if (!IsFixZEnabled()) {
|
||||
FixZ();
|
||||
}
|
||||
@@ -353,6 +363,8 @@ void Object::SetID(uint16 set_id)
|
||||
// Reset state of object back to zero
|
||||
void Object::ResetState()
|
||||
{
|
||||
Close();
|
||||
|
||||
safe_delete(m_inst);
|
||||
|
||||
m_id = 0;
|
||||
@@ -440,6 +452,12 @@ void Object::Close() {
|
||||
}
|
||||
}
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_ClearObject, sizeof(ClearObject_Struct));
|
||||
ClearObject_Struct *cos = (ClearObject_Struct *)outapp->pBuffer;
|
||||
cos->Clear = 1;
|
||||
user->QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
|
||||
user->SetTradeskillObject(nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -409,6 +409,13 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
parcel_out.aug_slot_6 = augs.at(5);
|
||||
}
|
||||
|
||||
if (!inst->IsDroppable(true)) {
|
||||
Message(Chat::Yellow, "Unable to send a parcel that is NO-DROP or contains a NO-DROP item.");
|
||||
SendParcelAck();
|
||||
DoParcelCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
|
||||
if (!result.id) {
|
||||
LogError(
|
||||
|
||||
@@ -885,6 +885,11 @@ Spawn2* Perl_NPC_GetSpawn(NPC* self)
|
||||
return self->GetSpawn();
|
||||
}
|
||||
|
||||
void Perl_NPC_SetNPCTintIndex(NPC* self, uint32 id)
|
||||
{
|
||||
return self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
|
||||
}
|
||||
|
||||
void perl_register_npc()
|
||||
{
|
||||
perl::interpreter perl(PERL_GET_THX);
|
||||
@@ -1034,6 +1039,7 @@ void perl_register_npc()
|
||||
package.add("SetGold", &Perl_NPC_SetGold);
|
||||
package.add("SetGrid", &Perl_NPC_SetGrid);
|
||||
package.add("SetNPCFactionID", &Perl_NPC_SetNPCFactionID);
|
||||
package.add("SetNPCTintIndex", &Perl_NPC_SetNPCTintIndex);
|
||||
package.add("SetPetSpellID", &Perl_NPC_SetPetSpellID);
|
||||
package.add("SetPlatinum", &Perl_NPC_SetPlatinum);
|
||||
package.add("SetPrimSkill", &Perl_NPC_SetPrimSkill);
|
||||
|
||||
+2
-2
@@ -561,9 +561,9 @@ std::string Perl_Zone_GetBucketRemaining(Zone* self, const std::string bucket_na
|
||||
return self->GetBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Perl_Zone_ClearVariables(Zone* self)
|
||||
bool Perl_Zone_ClearVariables(Zone* self)
|
||||
{
|
||||
self->ClearVariables();
|
||||
return self->ClearVariables();
|
||||
}
|
||||
|
||||
bool Perl_Zone_DeleteVariable(Zone* self, const std::string variable_name)
|
||||
|
||||
+1
-1
@@ -2786,7 +2786,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level
|
||||
|
||||
std::string test_name = name;
|
||||
bool available_flag = false;
|
||||
if (!database.botdb.QueryNameAvailablity(test_name, available_flag)) {
|
||||
if (!database.botdb.QueryNameAvailability(test_name, available_flag)) {
|
||||
initiator->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
|
||||
+23
-11
@@ -308,7 +308,7 @@ void Client::SetTitleSuffix(std::string suffix)
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void Client::EnableTitle(int title_set)
|
||||
void Client::EnableTitle(int title_set, bool insert)
|
||||
{
|
||||
if (CheckTitle(title_set)) {
|
||||
return;
|
||||
@@ -319,22 +319,26 @@ void Client::EnableTitle(int title_set)
|
||||
e.char_id = CharacterID();
|
||||
e.title_set = title_set;
|
||||
|
||||
if (!PlayerTitlesetsRepository::InsertOne(database, e).id) {
|
||||
LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID());
|
||||
if (insert) {
|
||||
e = PlayerTitlesetsRepository::InsertOne(database, e);
|
||||
if (!e.id) {
|
||||
LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_player_title_sets.emplace_back(e);
|
||||
}
|
||||
|
||||
bool Client::CheckTitle(int title_set)
|
||||
{
|
||||
return !PlayerTitlesetsRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_id` = {} AND `title_set` = {}",
|
||||
CharacterID(),
|
||||
title_set
|
||||
)
|
||||
).empty();
|
||||
for (const auto& e : m_player_title_sets) {
|
||||
if (e.title_set == title_set) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::RemoveTitle(int title_set)
|
||||
@@ -357,6 +361,14 @@ void Client::RemoveTitle(int title_set)
|
||||
}
|
||||
}
|
||||
|
||||
auto& titles = m_player_title_sets;
|
||||
for (auto e = titles.begin(); e != titles.end(); e++) {
|
||||
if (e->title_set == title_set) {
|
||||
titles.erase(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerTitlesetsRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "../common/global_define.h"
|
||||
#include "../common/events/player_event_logs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
|
||||
#ifndef WIN32
|
||||
@@ -1234,15 +1235,18 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
|
||||
return; //not allowed to go higher.
|
||||
uint16 maxskill = MaxSkill(tradeskill);
|
||||
|
||||
float min_skill_up_chance = RuleR(Character, TradeskillUpMinChance);
|
||||
min_skill_up_chance = std::max(min_skill_up_chance, 2.5f);
|
||||
|
||||
float chance_stage2 = 0;
|
||||
|
||||
//A successfull combine doubles the stage1 chance for an skillup
|
||||
//Some tradeskill are harder than others. See above for more.
|
||||
float chance_stage1 = (bonusstat - stat_modifier) / (skillup_modifier * success_modifier);
|
||||
chance_stage1 = std::max(min_skill_up_chance, chance_stage1);
|
||||
|
||||
//In stage2 the only thing that matters is your current unmodified skill.
|
||||
//If you want to customize here you probbably need to implement your own
|
||||
//formula instead of tweaking the below one.
|
||||
//In stage2 the only thing that matters is your current unmodified skill
|
||||
//and the Character:TradeskillUpMinChance rule.
|
||||
if (chance_stage1 > zone->random.Real(0, 99)) {
|
||||
if (current_raw_skill < 15) {
|
||||
//Always succeed
|
||||
@@ -1254,6 +1258,7 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
|
||||
//At skill 175, your chance of success falls linearly from 12.5% to 2.5% at skill 300.
|
||||
chance_stage2 = 12.5 - (.08 * (current_raw_skill - 175));
|
||||
}
|
||||
chance_stage2 = std::max(min_skill_up_chance, chance_stage2);
|
||||
}
|
||||
|
||||
if (chance_stage2 > zone->random.Real(0, 99)) {
|
||||
|
||||
+43
-27
@@ -1351,12 +1351,12 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic
|
||||
return;
|
||||
}
|
||||
|
||||
auto in = (TraderBuy_Struct *) app->pBuffer;
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, sizeof(TraderBuy_Struct));
|
||||
auto outtbs = (TraderBuy_Struct *) outapp->pBuffer;
|
||||
outtbs->item_id = tbs->item_id;
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, static_cast<uint32>(sizeof(TraderBuy_Struct)));
|
||||
auto outtbs = (TraderBuy_Struct *) outapp->pBuffer;
|
||||
outtbs->item_id = tbs->item_id;
|
||||
|
||||
const EQ::ItemInstance *buy_item = nullptr;
|
||||
uint32 item_id = 0;
|
||||
uint32 item_id = 0;
|
||||
|
||||
if (ClientVersion() >= EQ::versions::ClientVersion::RoF) {
|
||||
tbs->item_id = Strings::ToUnsignedBigInt(tbs->serial_number);
|
||||
@@ -1557,15 +1557,15 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic
|
||||
|
||||
void Client::SendBazaarWelcome()
|
||||
{
|
||||
const auto results = TraderRepository::GetWelcomeData(database);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BazaarSearch, sizeof(BazaarWelcome_Struct));
|
||||
auto data = (BazaarWelcome_Struct *) outapp->pBuffer;
|
||||
const auto results = TraderRepository::GetWelcomeData(database);
|
||||
EQApplicationPacket outapp(OP_BazaarSearch, static_cast<uint32>(sizeof(BazaarWelcome_Struct)));
|
||||
auto data = (BazaarWelcome_Struct *) outapp.pBuffer;
|
||||
|
||||
data->action = BazaarWelcome;
|
||||
data->traders = results.count_of_traders;
|
||||
data->items = results.count_of_items;
|
||||
data->action = BazaarWelcome;
|
||||
data->traders = results.count_of_traders;
|
||||
data->items = results.count_of_items;
|
||||
|
||||
QueuePacket(outapp.get());
|
||||
QueuePacket(&outapp);
|
||||
}
|
||||
|
||||
void Client::SendBarterWelcome()
|
||||
@@ -1798,7 +1798,10 @@ void Client::SendBuyerResults(BarterSearchRequest_Struct& bsr)
|
||||
|
||||
{ ar(results); }
|
||||
|
||||
auto packet = std::make_unique<EQApplicationPacket>(OP_BuyerItems, ss.str().length() + sizeof(BuyerGeneric_Struct));
|
||||
auto packet = std::make_unique<EQApplicationPacket>(
|
||||
OP_BuyerItems,
|
||||
static_cast<uint32>(ss.str().length()) + static_cast<uint32>(sizeof(BuyerGeneric_Struct))
|
||||
);
|
||||
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
||||
|
||||
emu->action = Barter_BuyerSearch;
|
||||
@@ -1851,7 +1854,10 @@ void Client::ShowBuyLines(const EQApplicationPacket *app)
|
||||
|
||||
{ ar(l); }
|
||||
|
||||
auto packet = std::make_unique<EQApplicationPacket>(OP_BuyerItems, ss.str().length() + sizeof(BuyerGeneric_Struct));
|
||||
auto packet = std::make_unique<EQApplicationPacket>(
|
||||
OP_BuyerItems,
|
||||
static_cast<uint32>(ss.str().length()) + static_cast<uint32>(sizeof(BuyerGeneric_Struct))
|
||||
);
|
||||
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
||||
|
||||
emu->action = Barter_BuyerInspectBegin;
|
||||
@@ -2075,7 +2081,7 @@ void Client::SellToBuyer(const EQApplicationPacket *app)
|
||||
|
||||
auto server_packet = std::make_unique<ServerPacket>(
|
||||
ServerOP_BuyerMessaging,
|
||||
sizeof(BuyerMessaging_Struct)
|
||||
static_cast<uint32>(sizeof(BuyerMessaging_Struct))
|
||||
);
|
||||
|
||||
auto data = (BuyerMessaging_Struct *) server_packet->pBuffer;
|
||||
@@ -2123,7 +2129,10 @@ void Client::SendBuyerPacket(Client* Buyer) {
|
||||
|
||||
void Client::ToggleBuyerMode(bool status)
|
||||
{
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, sizeof(BuyerSetAppearance_Struct));
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_Barter,
|
||||
static_cast<uint32>(sizeof(BuyerSetAppearance_Struct))
|
||||
);
|
||||
auto data = (BuyerSetAppearance_Struct *) outapp->pBuffer;
|
||||
|
||||
data->action = Barter_BuyerAppearance;
|
||||
@@ -2319,8 +2328,7 @@ void Client::ModifyBuyLine(const EQApplicationPacket *app)
|
||||
|
||||
auto packet = std::make_unique<EQApplicationPacket>(
|
||||
OP_BuyerItems,
|
||||
ss_customer.str().length() +
|
||||
sizeof(BuyerGeneric_Struct)
|
||||
static_cast<uint32>(ss_customer.str().length()) + static_cast<uint32>(sizeof(BuyerGeneric_Struct))
|
||||
);
|
||||
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
||||
|
||||
@@ -2813,7 +2821,10 @@ void Client::DoBazaarInspect(BazaarInspect_Struct &in)
|
||||
|
||||
void Client::SendBazaarDeliveryCosts()
|
||||
{
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BazaarSearch, sizeof(BazaarDeliveryCost_Struct));
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_BazaarSearch,
|
||||
static_cast<uint32>(sizeof(BazaarDeliveryCost_Struct))
|
||||
);
|
||||
auto data = (BazaarDeliveryCost_Struct *) outapp->pBuffer;
|
||||
|
||||
data->action = DeliveryCostUpdate;
|
||||
@@ -3074,7 +3085,9 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati
|
||||
BazaarAuditTrail(tbs->seller_name, GetName(), buy_item->GetItem()->Name, tbs->quantity, tbs->price, 0);
|
||||
}
|
||||
|
||||
auto out_server = std::make_unique<ServerPacket>(ServerOP_BazaarPurchase, sizeof(BazaarPurchaseMessaging_Struct));
|
||||
auto out_server = std::make_unique<ServerPacket>(
|
||||
ServerOP_BazaarPurchase, static_cast<uint32>(sizeof(BazaarPurchaseMessaging_Struct))
|
||||
);
|
||||
auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer;
|
||||
|
||||
out_data->trader_buy_struct = *tbs;
|
||||
@@ -3111,7 +3124,7 @@ void Client::SendBuyerGreeting(uint32 buyer_id)
|
||||
|
||||
void Client::SendSellerBrowsing(const std::string &browser)
|
||||
{
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, sizeof(BuyerBrowsing_Struct));
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, static_cast<uint32>(sizeof(BuyerBrowsing_Struct)));
|
||||
auto eq = (BuyerBrowsing_Struct *) outapp->pBuffer;
|
||||
|
||||
eq->action = Barter_SellerBrowsing;
|
||||
@@ -3309,7 +3322,7 @@ void Client::SendWindowUpdatesToSellerAndBuyer(BuyerLineSellItem_Struct &blsi)
|
||||
if (blsi.item_quantity - blsi.seller_quantity <= 0) {
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_BuyerItems,
|
||||
sizeof(BuyerRemoveItemFromMerchantWindow_Struct)
|
||||
static_cast<uint32>(sizeof(BuyerRemoveItemFromMerchantWindow_Struct))
|
||||
);
|
||||
auto data = (BuyerRemoveItemFromMerchantWindow_Struct *) outapp->pBuffer;
|
||||
|
||||
@@ -3399,7 +3412,7 @@ void Client::SendBuyerToBarterWindow(Client *buyer, uint32 action)
|
||||
{
|
||||
auto server_packet = std::make_unique<ServerPacket>(
|
||||
ServerOP_BuyerMessaging,
|
||||
sizeof(BuyerMessaging_Struct)
|
||||
static_cast<uint32>(sizeof(BuyerMessaging_Struct))
|
||||
);
|
||||
auto data = (BuyerMessaging_Struct *) server_packet->pBuffer;
|
||||
|
||||
@@ -3420,7 +3433,10 @@ void Client::SendBulkBazaarBuyers()
|
||||
return;
|
||||
}
|
||||
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, sizeof(BuyerAddBuyertoBarterWindow_Struct));
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_Barter,
|
||||
static_cast<uint32>(sizeof(BuyerAddBuyertoBarterWindow_Struct))
|
||||
);
|
||||
auto emu = (BuyerAddBuyertoBarterWindow_Struct *) outapp->pBuffer;
|
||||
|
||||
for (auto const &b: results) {
|
||||
@@ -3663,11 +3679,11 @@ bool Client::ValidateBuyLineItems(std::map<uint32, BuylineItemDetails_Struct> &i
|
||||
|
||||
int64 Client::ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct> &item_map)
|
||||
{
|
||||
int64 proposed_total_cost = std::accumulate(
|
||||
uint64 proposed_total_cost = std::accumulate(
|
||||
item_map.cbegin(),
|
||||
item_map.cend(),
|
||||
0,
|
||||
[](auto prev_sum, const std::pair<uint32, BuylineItemDetails_Struct> &x) {
|
||||
static_cast<uint64>(0),
|
||||
[](uint64 prev_sum, const std::pair<uint32, BuylineItemDetails_Struct> &x) {
|
||||
return prev_sum + x.second.item_cost;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3794,7 +3794,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
}
|
||||
|
||||
auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, sizeof(TraderBuy_Struct));
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, static_cast<uint32>(sizeof(TraderBuy_Struct)));
|
||||
auto data = (TraderBuy_Struct *) outapp->pBuffer;
|
||||
|
||||
memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct));
|
||||
@@ -3841,7 +3841,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
case Barter_AddToBarterWindow: {
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_Barter,
|
||||
sizeof(BuyerAddBuyertoBarterWindow_Struct)
|
||||
static_cast<uint32>(sizeof(BuyerAddBuyertoBarterWindow_Struct))
|
||||
);
|
||||
auto emu = (BuyerAddBuyertoBarterWindow_Struct *) outapp->pBuffer;
|
||||
|
||||
@@ -3858,7 +3858,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
case Barter_RemoveFromBarterWindow: {
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_Barter,
|
||||
sizeof(BuyerRemoveBuyerFromBarterWindow_Struct)
|
||||
static_cast<uint32>(sizeof(BuyerRemoveBuyerFromBarterWindow_Struct))
|
||||
);
|
||||
auto emu = (BuyerRemoveBuyerFromBarterWindow_Struct *) outapp->pBuffer;
|
||||
|
||||
|
||||
+6
-1
@@ -3225,9 +3225,14 @@ void Zone::DisableRespawnTimers()
|
||||
}
|
||||
}
|
||||
|
||||
void Zone::ClearVariables()
|
||||
bool Zone::ClearVariables()
|
||||
{
|
||||
if (m_zone_variables.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_zone_variables.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Zone::DeleteVariable(const std::string& variable_name)
|
||||
|
||||
+2
-1
@@ -49,6 +49,7 @@
|
||||
#include "../common/repositories/skill_caps_repository.h"
|
||||
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||
#include "../common/repositories/spawn2_disabled_repository.h"
|
||||
#include "../common/repositories/player_titlesets_repository.h"
|
||||
|
||||
struct EXPModifier
|
||||
{
|
||||
@@ -197,7 +198,7 @@ public:
|
||||
int32 MobsAggroCount() { return aggroedmobs; }
|
||||
DynamicZone *GetDynamicZone();
|
||||
|
||||
void ClearVariables();
|
||||
bool ClearVariables();
|
||||
bool DeleteVariable(const std::string& variable_name);
|
||||
std::string GetVariable(const std::string& variable_name);
|
||||
std::vector<std::string> GetVariables();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "zone.h"
|
||||
#include "zone_save_state.h"
|
||||
#include "../common/repositories/spawn2_repository.h"
|
||||
#include "../common/repositories/criteria/content_filter_criteria.h"
|
||||
|
||||
// IsZoneStateValid checks if the zone state is valid
|
||||
// if these fields are all empty or zero value for an entire zone state, it's considered invalid
|
||||
@@ -517,7 +518,7 @@ bool Zone::LoadZoneState(
|
||||
|
||||
new_spawn->SetStoredLocation(glm::vec4(s.x, s.y, s.z, s.heading));
|
||||
|
||||
if (spawn_time_left == 0) {
|
||||
if (spawn_time_left == 0 && s.npc_id > 0) {
|
||||
new_spawn->SetResumedNPCID(s.npc_id);
|
||||
new_spawn->SetResumedFromZoneSuspend(true);
|
||||
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
|
||||
@@ -531,6 +532,69 @@ bool Zone::LoadZoneState(
|
||||
}
|
||||
}
|
||||
|
||||
// compare state spawns to spawn2 list, if there are any missing, we need to add them
|
||||
// this is to cover the rare case where a spawn2 is created in the database but not in the zone state
|
||||
auto zone_spawns = Spawn2Repository::GetWhere(
|
||||
content_db, fmt::format(
|
||||
"TRUE {} AND zone = '{}' AND (version = {} OR version = -1) ",
|
||||
ContentFilterCriteria::apply(),
|
||||
zone->GetShortName(),
|
||||
zone->GetInstanceVersion()
|
||||
)
|
||||
);
|
||||
|
||||
for (auto &s: zone_spawns) {
|
||||
bool found = false;
|
||||
for (auto &ss: spawn_states) {
|
||||
if (ss.spawn2_id == 0 || ss.spawngroup_id == 0 || ss.is_corpse || ss.is_zone) {
|
||||
continue;
|
||||
}
|
||||
if (s.id == ss.spawn2_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
bool spawn_enabled = true;
|
||||
|
||||
for (auto &ds: disabled_spawns) {
|
||||
if (ds.spawn2_id == s.id) {
|
||||
spawn_enabled = !ds.disabled;
|
||||
}
|
||||
}
|
||||
|
||||
LogZoneState("Missing spawn2 [{}] in zone state, this NPC spawn was newly created", s.id);
|
||||
uint32 spawn_time_left = 0;
|
||||
if (spawn_times.count(s.id) != 0) {
|
||||
spawn_time_left = spawn_times[s.id];
|
||||
LogInfo("Spawn2 [{}] Respawn time left [{}]", s.id, spawn_time_left);
|
||||
}
|
||||
|
||||
auto new_spawn = new Spawn2(
|
||||
s.id,
|
||||
s.spawngroupID,
|
||||
s.x,
|
||||
s.y,
|
||||
s.z,
|
||||
s.heading,
|
||||
s.respawntime,
|
||||
s.variance,
|
||||
spawn_time_left,
|
||||
s.pathgrid,
|
||||
(bool) s.path_when_zone_idle,
|
||||
s._condition,
|
||||
(int16) s.cond_value,
|
||||
spawn_enabled,
|
||||
(EmuAppearance) s.animation
|
||||
);
|
||||
|
||||
new_spawn->SetStoredLocation(glm::vec4(s.x, s.y, s.z, s.heading));
|
||||
spawn2_list.Insert(new_spawn);
|
||||
new_spawn->Process();
|
||||
}
|
||||
}
|
||||
|
||||
// dynamic spawns, quest spawns, triggers etc.
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.spawngroup_id > 0 || s.is_zone) {
|
||||
|
||||
@@ -1732,6 +1732,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
|
||||
t->attack_count = n.attack_count;
|
||||
t->is_parcel_merchant = n.is_parcel_merchant ? true : false;
|
||||
t->greed = n.greed;
|
||||
t->m_npc_tint_id = n.npc_tint_id;
|
||||
|
||||
if (!n.special_abilities.empty()) {
|
||||
strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512);
|
||||
@@ -4271,3 +4272,28 @@ void ZoneDatabase::SaveCharacterEXPModifier(Client* c)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void ZoneDatabase::LoadCharacterTitleSets(Client* c)
|
||||
{
|
||||
if (!zone || !c) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& l = PlayerTitlesetsRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`char_id` = {}",
|
||||
c->CharacterID()
|
||||
)
|
||||
);
|
||||
|
||||
if (l.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32 character_id = c->CharacterID();
|
||||
|
||||
for (const auto& e : l) {
|
||||
c->EnableTitle(e.title_set, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,9 @@ public:
|
||||
void LoadCharacterEXPModifier(Client* c);
|
||||
void SaveCharacterEXPModifier(Client *c);
|
||||
|
||||
/* Player Title Sets */
|
||||
void LoadCharacterTitleSets(Client* c);
|
||||
|
||||
float GetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id, int16 instance_version = -1);
|
||||
float GetEXPModifierByCharID(uint32 character_id, uint32 zone_id, int16 instance_version = -1);
|
||||
void SetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id, float aa_modifier, int16 instance_version = -1);
|
||||
|
||||
@@ -157,6 +157,7 @@ struct NPCType
|
||||
bool is_parcel_merchant;
|
||||
uint8 greed;
|
||||
bool multiquest_enabled;
|
||||
uint32 m_npc_tint_id;
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
+12
-1
@@ -681,9 +681,20 @@ void Client::MoveZoneInstanceRaid(uint16 instance_id, const glm::vec4 &location)
|
||||
|
||||
void Client::ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions, ZoneMode zm)
|
||||
{
|
||||
// From what I have read, dragged corpses should stay with the player for Intra-zone summons etc, but we can implement that later.
|
||||
// From what I have read, dragged corpses should stay with the player for Intra-zone summons etc, but we can
|
||||
// implement that later.
|
||||
ClearDraggedCorpses();
|
||||
|
||||
// Added to ensure that if a player is moved (ported, gmmove, etc) and they are an active trader or buyer, they will
|
||||
// be removed from future transactions.
|
||||
if (IsTrader()) {
|
||||
TraderEndTrader();
|
||||
}
|
||||
|
||||
if (IsBuyer()) {
|
||||
ToggleBuyerMode(false);
|
||||
}
|
||||
|
||||
if(zoneID == 0)
|
||||
zoneID = zone->GetZoneID();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user