Compare commits

..

2 Commits

Author SHA1 Message Date
KayenEQ d18a30b047 root no break variable update
root no break variable update
2025-04-06 14:34:52 -04:00
KayenEQ 359f90a987 Root break update
Roots will not break from damage spells if spell skill is set to 200. Live mechanic.
2025-04-03 18:42:53 -04:00
121 changed files with 1575 additions and 2955 deletions
-108
View File
@@ -1,111 +1,3 @@
## [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
* World API Optimizations ([#4850](https://github.com/EQEmu/Server/pull/4850)) @Akkadius 2025-04-10
### Bots
* Add valid state checks to ^clickitem ([#4830](https://github.com/EQEmu/Server/pull/4830)) @nytmyr 2025-04-10
* Flag all buffs with SE_DamageShield as Damage Shield ([#4833](https://github.com/EQEmu/Server/pull/4833)) @nytmyr 2025-04-10
* Positioning rewrite ([#4856](https://github.com/EQEmu/Server/pull/4856)) @nytmyr 2025-04-10
* Restore old buff overwrite blocking ([#4832](https://github.com/EQEmu/Server/pull/4832)) @nytmyr 2025-04-10
### Bugfix
* Load zone variables before encounter_load. ([#4846](https://github.com/EQEmu/Server/pull/4846)) @zimp-wow 2025-04-10
* Prevent depops from blocking new spawns. ([#4841](https://github.com/EQEmu/Server/pull/4841)) @zimp-wow 2025-04-10
* Prevent final shutdown from persisting incomplete state. ([#4849](https://github.com/EQEmu/Server/pull/4849)) @zimp-wow 2025-04-10
### Code
* Remove queryserv dump flag ([#4842](https://github.com/EQEmu/Server/pull/4842)) @joligario 2025-04-10
* Update link for legacy EQEmu loginserver account setup ([#4826](https://github.com/EQEmu/Server/pull/4826)) @joligario 2025-04-10
### Crash
* Fix rarer exception crash issue in PlayerEventLogs::ProcessBatchQueue ([#4835](https://github.com/EQEmu/Server/pull/4835)) @Akkadius 2025-04-03
### Database
* Fix manifest for `helmtexture` in `horses` table ([#4852](https://github.com/EQEmu/Server/pull/4852)) @joligario 2025-04-10
### Feature
* Add rule to consume command text from any channel ([#4839](https://github.com/EQEmu/Server/pull/4839)) @catapultam-habeo 2025-04-10
### Fixes
* Add the bazaar search limit to query ([#4829](https://github.com/EQEmu/Server/pull/4829)) @neckkola 2025-04-10
* Backfill expire_at (not sure why this didn't make it in there to begin with) @Akkadius 2025-03-31
* Bazaar Search window not working in a DZ ([#4828](https://github.com/EQEmu/Server/pull/4828)) @neckkola 2025-04-10
* Databuckets Account Cache Loading ([#4855](https://github.com/EQEmu/Server/pull/4855)) @Akkadius 2025-04-10
* Fix missing timer_name check on Mob::StopTimer ([#4840](https://github.com/EQEmu/Server/pull/4840)) @zimp-wow 2025-04-04
* FixHeading Infinite Loop Fix ([#4854](https://github.com/EQEmu/Server/pull/4854)) @KimLS 2025-04-10
* Make sure we don't expire default value instances @Akkadius 2025-03-31
* Regression in World SendEmoteMessageRaw ([#4837](https://github.com/EQEmu/Server/pull/4837)) @Akkadius 2025-04-03
* Remove QS Tables From Export @Akkadius 2025-04-10
* Zone State Spawn2 Location Restore ([#4844](https://github.com/EQEmu/Server/pull/4844)) @Akkadius 2025-04-10
### Netcode
* Fix Stale Client Edge Case ([#4853](https://github.com/EQEmu/Server/pull/4853)) @Akkadius 2025-04-10
### Performance
* Character Save Optimizations ([#4851](https://github.com/EQEmu/Server/pull/4851)) @Akkadius 2025-04-10
* Network Ring Buffers ([#4857](https://github.com/EQEmu/Server/pull/4857)) @Akkadius 2025-04-10
* Pre-Compute CLE Server Lists ([#4838](https://github.com/EQEmu/Server/pull/4838)) @Akkadius 2025-04-10
### Spells
* Fear resistance effects edge case fixes and support for SPA 102 as an AA ([#4848](https://github.com/EQEmu/Server/pull/4848)) @KayenEQ 2025-04-10
* Update to SPA 180 SE_ResistSpellChance to not block unresistable spells. ([#4847](https://github.com/EQEmu/Server/pull/4847)) @KayenEQ 2025-04-10
* Update to SPA 378 SE_SpellEffectResistChance ([#4845](https://github.com/EQEmu/Server/pull/4845)) @KayenEQ 2025-04-10
## [23.4.0] 3/30/2025 ## [23.4.0] 3/30/2025
### API ### API
+55 -123
View File
@@ -1,147 +1,79 @@
<h1 align="center">EQEmulator Server Platform</h1> # EQEmulator Core Server
| Drone (Linux x64) | Drone (Windows x64) |
<p align="center"> |:---:|:---:|
<img src="https://github.com/user-attachments/assets/11942e15-b512-402d-a619-0543c7f1151e" style="border-radius: 10px"> |[![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) |[![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) |
</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&amp;logo=discord&amp;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>
*** ***
<p align="center"> **EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++**
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>. * MySQL/MariaDB is used as the database engine (over 200+ tables)
</p> * 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"> ## Server Installs
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 EverQuests 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. | |Windows|Linux|
</p> |:---:|:---:|:---:|
|**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)|
### > Windows
<p align="center"> * [Install Guide](https://docs.eqemu.io/server/installation/server-installation-windows/)
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>
<p align="center"> ### > Debian/Ubuntu/CentOS/Fedora
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/)
<h3 align="center"> * You can use curl or wget to kick off the installer (whichever your OS has)
Technical Overview & Reverse Engineering Effort > curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh
</h1>
<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> > 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
<h1 align="center"> ## Supported Clients
💡 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 ProjectEQs 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| |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">| |<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">|
## 📚 Resources ## 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.
| Resource | Badges | Link | ## Contributions <img src="http://image.flaticon.com/icons/png/512/25/25231.png" width="20">
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| **EQEmulator Docs** | [![Docs](https://img.shields.io/badge/docs-MkDocs%20Powered-blueviolet)](https://docs.eqemu.io) | [docs.eqemu.io](https://docs.eqemu.io/) |
| **Discord Community**| [![Discord](https://img.shields.io/discord/212663220849213441?label=Discord&logo=discord&color=7289DA)](https://discord.gg/QHsm7CD) | [Join Discord](https://discord.gg/QHsm7CD) |
| **Latest Release** | [![Latest Release](https://img.shields.io/github/v/release/eqemu/server)](https://github.com/eqemu/server/releases) <br> [![Release Date](https://img.shields.io/github/release-date/EQEmu/Server)](https://github.com/EQEmu/Server/releases) <br> [![All Releases](https://img.shields.io/github/downloads/eqemu/server/total.svg)](https://github.com/eqemu/server/releases) | [View Releases](https://github.com/eqemu/server/releases) |
| **License** | [![License](https://img.shields.io/github/license/EQEmu/Server)](./LICENSE) | [View License](./LICENSE) |
| **Build Status** | [![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) | [View Build Status](http://drone.akkadius.com/EQEmu/Server) |
| **Docker Pulls** | [![Docker Pulls](https://img.shields.io/docker/pulls/akkadius/eqemu-server)](https://hub.docker.com/r/akkadius/eqemu-server) | [Docker Hub](https://hub.docker.com/r/akkadius/eqemu-server) |
| **Contributions** | [![GitHub PRs](https://img.shields.io/github/issues-pr-closed/eqemu/server)](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) |
## 🛠️ Getting Started * 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.
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! ## Contact <img src="http://gamerescape.com/wp-content/uploads/2015/06/discord.png" height="20">
## 🗂️ Related Repositories - Discord Channel: https://discord.gg/QHsm7CD
- **User Discord Channel**: `#general`
- **Developer Discord Channel**: `#eqemucoders`
| Repository | Description | ## Resources
|--------------------|----------------------------------------------------------------------------------| - [EQEmulator Forums](http://www.eqemulator.org/forums)
| [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) | Official quests and event scripts for ProjectEQ | - [EQEmulator Wiki](https://docs.eqemu.io/)
| [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 ## Contributors
-5
View File
@@ -17,7 +17,6 @@ SET(common_sources
database.cpp database.cpp
database_instances.cpp database_instances.cpp
database/database_update_manifest.cpp database/database_update_manifest.cpp
database/database_update_manifest_custom.cpp
database/database_update_manifest_bots.cpp database/database_update_manifest_bots.cpp
database/database_update.cpp database/database_update.cpp
dbcore.cpp dbcore.cpp
@@ -672,7 +671,6 @@ SET(common_headers
net/console_server_connection.h net/console_server_connection.h
net/crc32.h net/crc32.h
net/daybreak_connection.h net/daybreak_connection.h
net/daybreak_pooling.h
net/daybreak_structs.h net/daybreak_structs.h
net/dns.h net/dns.h
net/endian.h net/endian.h
@@ -684,7 +682,6 @@ SET(common_headers
net/servertalk_server.h net/servertalk_server.h
net/servertalk_server_connection.h net/servertalk_server_connection.h
net/tcp_connection.h net/tcp_connection.h
net/tcp_connection_pooling.h
net/tcp_server.h net/tcp_server.h
net/websocket_server.h net/websocket_server.h
net/websocket_server_connection.h net/websocket_server_connection.h
@@ -745,7 +742,6 @@ SOURCE_GROUP(Net FILES
net/crc32.h net/crc32.h
net/daybreak_connection.cpp net/daybreak_connection.cpp
net/daybreak_connection.h net/daybreak_connection.h
net/daybreak_pooling.h
net/daybreak_structs.h net/daybreak_structs.h
net/dns.h net/dns.h
net/endian.h net/endian.h
@@ -766,7 +762,6 @@ SOURCE_GROUP(Net FILES
net/servertalk_server_connection.h net/servertalk_server_connection.h
net/tcp_connection.cpp net/tcp_connection.cpp
net/tcp_connection.h net/tcp_connection.h
net/tcp_connection_pooling.h
net/tcp_server.cpp net/tcp_server.cpp
net/tcp_server.h net/tcp_server.h
net/websocket_server.cpp net/websocket_server.cpp
+1 -2
View File
@@ -279,8 +279,7 @@ Bazaar::GetSearchResults(
trader_items_ids, trader_items_ids,
std::string(search.item_name), std::string(search.item_name),
field_criteria_items, field_criteria_items,
where_criteria_items, where_criteria_items
search.max_results
); );
if (item_results.empty()) { if (item_results.empty()) {
+5 -23
View File
@@ -1095,11 +1095,11 @@ void Database::SetLFP(uint32 character_id, bool is_lfp)
CharacterDataRepository::UpdateOne(*this, e); CharacterDataRepository::UpdateOne(*this, e);
} }
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 ingame) void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon)
{ {
auto e = CharacterDataRepository::FindOne(*this, character_id); auto e = CharacterDataRepository::FindOne(*this, character_id);
e.ingame = ingame; e.firstlogon = first_logon;
e.lfg = is_lfg ? 1 : 0; e.lfg = is_lfg ? 1 : 0;
e.lfp = is_lfp ? 1 : 0; e.lfp = is_lfp ? 1 : 0;
@@ -1115,11 +1115,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg)
CharacterDataRepository::UpdateOne(*this, e); CharacterDataRepository::UpdateOne(*this, e);
} }
void Database::SetIngame(uint32 character_id, uint8 ingame) void Database::SetFirstLogon(uint32 character_id, uint8 first_logon)
{ {
auto e = CharacterDataRepository::FindOne(*this, character_id); auto e = CharacterDataRepository::FindOne(*this, character_id);
e.ingame = ingame; e.firstlogon = first_logon;
CharacterDataRepository::UpdateOne(*this, e); CharacterDataRepository::UpdateOne(*this, e);
} }
@@ -1920,7 +1920,6 @@ bool Database::CopyCharacter(
std::vector<std::string> tables_to_zero_id = { std::vector<std::string> tables_to_zero_id = {
"keyring", "keyring",
"data_buckets", "data_buckets",
"character_evolving_items",
"character_instance_safereturns", "character_instance_safereturns",
"character_expedition_lockouts", "character_expedition_lockouts",
"character_instance_lockouts", "character_instance_lockouts",
@@ -1952,12 +1951,6 @@ bool Database::CopyCharacter(
) )
); );
if (!results.Success()) {
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
TransactionRollback();
return false;
}
std::vector<std::string> columns = {}; std::vector<std::string> columns = {};
int column_count = 0; int column_count = 0;
@@ -1976,12 +1969,6 @@ bool Database::CopyCharacter(
) )
); );
if (!results.Success()) {
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
TransactionRollback();
return false;
}
std::vector<std::vector<std::string>> new_rows; std::vector<std::vector<std::string>> new_rows;
for (auto row : results) { for (auto row : results) {
@@ -2049,18 +2036,13 @@ bool Database::CopyCharacter(
LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied)); LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied));
if (!insert.ErrorMessage().empty()) { if (!insert.ErrorMessage().empty()) {
LogError("Error copying table [{}] [{}]", table_name, insert.ErrorMessage());
TransactionRollback(); TransactionRollback();
return false; return false;
} }
} }
} }
auto r = TransactionCommit(); TransactionCommit();
if (!r.Success()) {
LogError("Transaction failed [{}] rolling back", r.ErrorMessage());
return false;
}
LogInfo( LogInfo(
"Character [{}] copied to [{}] total rows [{}]", "Character [{}] copied to [{}] total rows [{}]",
+1 -1
View File
@@ -263,7 +263,7 @@ public:
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year); bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
void ClearMerchantTemp(); void ClearMerchantTemp();
void ClearPTimers(uint32 character_id); void ClearPTimers(uint32 character_id);
void SetIngame(uint32 character_id, uint8 ingame); void SetFirstLogon(uint32 character_id, uint8 first_logon);
void SetLFG(uint32 character_id, bool is_lfg); void SetLFG(uint32 character_id, bool is_lfg);
void SetLFP(uint32 character_id, bool is_lfp); void SetLFP(uint32 character_id, bool is_lfp);
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon); void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
+1 -1
View File
@@ -50,7 +50,7 @@ bool DatabaseDumpService::IsMySQLInstalled()
{ {
std::string version_output = GetMySQLVersion(); std::string version_output = GetMySQLVersion();
return version_output.find("mysql") != std::string::npos && (version_output.find("Ver") != std::string::npos || version_output.find("from") != std::string::npos); return version_output.find("mysql") != std::string::npos && version_output.find("Ver") != std::string::npos;
} }
/** /**
+1
View File
@@ -65,6 +65,7 @@ private:
bool dump_system_tables = false; bool dump_system_tables = false;
bool dump_content_tables = false; bool dump_content_tables = false;
bool dump_player_tables = false; bool dump_player_tables = false;
bool dump_query_server_tables = false;
bool dump_login_server_tables = false; bool dump_login_server_tables = false;
bool dump_with_no_data = false; bool dump_with_no_data = false;
bool dump_table_lock = false; bool dump_table_lock = false;
+2 -37
View File
@@ -7,7 +7,6 @@
#include "../http/httplib.h" #include "../http/httplib.h"
#include "database_update_manifest.cpp" #include "database_update_manifest.cpp"
#include "database_update_manifest_custom.cpp"
#include "database_update_manifest_bots.cpp" #include "database_update_manifest_bots.cpp"
#include "database_dump_service.h" #include "database_dump_service.h"
@@ -15,7 +14,7 @@ constexpr int BREAK_LENGTH = 70;
DatabaseVersion DatabaseUpdate::GetDatabaseVersions() DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
{ {
auto results = m_database->QueryDatabase("SELECT `version`, `bots_version`, `custom_version` FROM `db_version` LIMIT 1"); auto results = m_database->QueryDatabase("SELECT `version`, `bots_version` FROM `db_version` LIMIT 1");
if (!results.Success() || !results.RowCount()) { if (!results.Success() || !results.RowCount()) {
LogError("Failed to read from [db_version] table!"); LogError("Failed to read from [db_version] table!");
return DatabaseVersion{}; return DatabaseVersion{};
@@ -26,7 +25,6 @@ DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
return DatabaseVersion{ return DatabaseVersion{
.server_database_version = Strings::ToInt(r[0]), .server_database_version = Strings::ToInt(r[0]),
.bots_database_version = Strings::ToInt(r[1]), .bots_database_version = Strings::ToInt(r[1]),
.custom_database_version = Strings::ToInt(r[2]),
}; };
} }
@@ -35,7 +33,6 @@ DatabaseVersion DatabaseUpdate::GetBinaryDatabaseVersions()
return DatabaseVersion{ return DatabaseVersion{
.server_database_version = CURRENT_BINARY_DATABASE_VERSION, .server_database_version = CURRENT_BINARY_DATABASE_VERSION,
.bots_database_version = (RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0), .bots_database_version = (RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0),
.custom_database_version = CUSTOM_BINARY_DATABASE_VERSION,
}; };
} }
@@ -46,7 +43,6 @@ constexpr int LOOK_BACK_AMOUNT = 10;
// this check will take action // this check will take action
void DatabaseUpdate::CheckDbUpdates() void DatabaseUpdate::CheckDbUpdates()
{ {
InjectCustomVersionColumn();
InjectBotsVersionColumn(); InjectBotsVersionColumn();
auto v = GetDatabaseVersions(); auto v = GetDatabaseVersions();
auto b = GetBinaryDatabaseVersions(); auto b = GetBinaryDatabaseVersions();
@@ -63,15 +59,6 @@ void DatabaseUpdate::CheckDbUpdates()
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", b.server_database_version)); 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 (b.bots_database_version > 0) {
if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) { if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) {
LogInfo( LogInfo(
@@ -357,16 +344,6 @@ 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("{:>8} | [server.auto_database_updates] [<green>true]", "Config");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
@@ -376,10 +353,7 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b)
// bots database version is optional, if not enabled then it is always up-to-date // 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; bool bots_up_to_date = RuleB(Bots, Enabled) ? v.bots_database_version >= b.bots_database_version : true;
// custom database version is optional, if not enabled then it is always up-to-date return server_up_to_date && bots_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 // checks to see if there are pending updates
@@ -399,12 +373,3 @@ void DatabaseUpdate::InjectBotsVersionColumn()
m_database->QueryDatabase("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version"); 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");
}
}
-2
View File
@@ -17,7 +17,6 @@ struct ManifestEntry {
struct DatabaseVersion { struct DatabaseVersion {
int server_database_version; int server_database_version;
int bots_database_version; int bots_database_version;
int custom_database_version;
}; };
class DatabaseUpdate { class DatabaseUpdate {
@@ -39,7 +38,6 @@ private:
Database *m_content_database; Database *m_content_database;
static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b); static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b);
void InjectBotsVersionColumn(); void InjectBotsVersionColumn();
void InjectCustomVersionColumn();
}; };
+2 -38
View File
@@ -6942,8 +6942,8 @@ CREATE TABLE `character_pet_name` (
.version = 9310, .version = 9310,
.description = "2025_03_7_expand_horse_def.sql", .description = "2025_03_7_expand_horse_def.sql",
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'", .check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
.condition = "empty", .condition = "missing",
.match = "", .match = "TINYINT(2)",
.sql = R"( .sql = R"(
ALTER TABLE `horses` ALTER TABLE `horses`
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`; ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
@@ -7084,42 +7084,6 @@ 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 UPDATE instance_list set expire_at = `start_time` + `duration`; -- backfill existing data
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`); 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
},
ManifestEntry{
.version = 9324,
.description = "2025_05_17_keyring_index.sql",
.check = "SHOW CREATE TABLE keyring",
.condition = "missing",
.match = "idx_charid_itemid",
.sql = R"(
ALTER TABLE keyring ADD INDEX idx_charid_itemid (char_id, item_id);
)", )",
.content_schema_update = false .content_schema_update = false
}, },
@@ -1,55 +0,0 @@
#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
// };
+1 -1
View File
@@ -80,7 +80,7 @@ namespace DatabaseSchema {
{"guild_members", "char_id"}, {"guild_members", "char_id"},
{"guilds", "id"}, {"guilds", "id"},
{"instance_list_player", "id"}, {"instance_list_player", "id"},
{"inventory", "character_id"}, {"inventory", "charid"},
{"inventory_snapshots", "charid"}, {"inventory_snapshots", "charid"},
{"keyring", "char_id"}, {"keyring", "char_id"},
{"mail", "charid"}, {"mail", "charid"},
+2 -2
View File
@@ -189,9 +189,9 @@ void DBcore::TransactionBegin()
QueryDatabase("START TRANSACTION"); QueryDatabase("START TRANSACTION");
} }
MySQLRequestResult DBcore::TransactionCommit() void DBcore::TransactionCommit()
{ {
return QueryDatabase("COMMIT"); QueryDatabase("COMMIT");
} }
void DBcore::TransactionRollback() void DBcore::TransactionRollback()
+1 -1
View File
@@ -32,7 +32,7 @@ public:
MySQLRequestResult QueryDatabase(const std::string& query, bool retryOnFailureOnce = true); MySQLRequestResult QueryDatabase(const std::string& query, bool retryOnFailureOnce = true);
MySQLRequestResult QueryDatabaseMulti(const std::string &query); MySQLRequestResult QueryDatabaseMulti(const std::string &query);
void TransactionBegin(); void TransactionBegin();
MySQLRequestResult TransactionCommit(); void TransactionCommit();
void TransactionRollback(); void TransactionRollback();
std::string Escape(const std::string& s); std::string Escape(const std::string& s);
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen); uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
-2
View File
@@ -324,8 +324,6 @@ union
bool guild_show; bool guild_show;
bool trader; bool trader;
bool buyer; bool buyer;
bool untargetable;
uint32 npc_tint_id;
}; };
struct PlayerState_Struct { struct PlayerState_Struct {
+8 -27
View File
@@ -682,33 +682,14 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings(bool silent_load)
if (is_missing_in_database && !is_deprecated_category) { if (is_missing_in_database && !is_deprecated_category) {
LogInfo("Automatically adding new log category [{}] ({})", Logs::LogCategoryName[i], i); LogInfo("Automatically adding new log category [{}] ({})", Logs::LogCategoryName[i], i);
auto e = LogsysCategoriesRepository::NewEntity(); auto new_category = LogsysCategoriesRepository::NewEntity();
e.log_category_id = i; new_category.log_category_id = i;
e.log_category_description = Strings::Escape(Logs::LogCategoryName[i]); new_category.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
e.log_to_console = log_settings[i].log_to_console; new_category.log_to_console = log_settings[i].log_to_console;
e.log_to_gmsay = log_settings[i].log_to_gmsay; new_category.log_to_gmsay = log_settings[i].log_to_gmsay;
e.log_to_file = log_settings[i].log_to_file; new_category.log_to_file = log_settings[i].log_to_file;
e.log_to_discord = log_settings[i].log_to_discord; new_category.log_to_discord = log_settings[i].log_to_discord;
db_categories_to_add.emplace_back(e); db_categories_to_add.emplace_back(new_category);
}
// 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);
}
} }
} }
+8 -12
View File
@@ -74,7 +74,7 @@ namespace Logs {
Spawns, Spawns,
Spells, Spells,
Status, // deprecated Status, // deprecated
TCPConnection, // deprecated TCPConnection,
Tasks, Tasks,
Tradeskills, Tradeskills,
Trading, Trading,
@@ -150,8 +150,6 @@ namespace Logs {
BotSpellTypeChecks, BotSpellTypeChecks,
NpcHandin, NpcHandin,
ZoneState, ZoneState,
NetClient,
NetTCP,
MaxCategoryID /* Don't Remove this */ MaxCategoryID /* Don't Remove this */
}; };
@@ -185,7 +183,7 @@ namespace Logs {
"Spawns", "Spawns",
"Spells", "Spells",
"Status (Deprecated)", "Status (Deprecated)",
"TCP Connection (Deprecated)", "TCP Connection",
"Tasks", "Tasks",
"Tradeskills", "Tradeskills",
"Trading", "Trading",
@@ -194,8 +192,8 @@ namespace Logs {
"Web Interface (Deprecated)", "Web Interface (Deprecated)",
"World Server (Deprecated)", "World Server (Deprecated)",
"Zone Server (Deprecated)", "Zone Server (Deprecated)",
"MySQL Error", "QueryErr",
"MySQL Query", "Query",
"Mercenaries", "Mercenaries",
"Quest Debug", "Quest Debug",
"Legacy Packet Logging (Deprecated)", "Legacy Packet Logging (Deprecated)",
@@ -218,8 +216,8 @@ namespace Logs {
"Emergency (Deprecated)", "Emergency (Deprecated)",
"Alert (Deprecated)", "Alert (Deprecated)",
"Notice (Deprecated)", "Notice (Deprecated)",
"AI Scan Close", "AI Scan",
"AI Yell For Help", "AI Yell",
"AI CastBeneficial", "AI CastBeneficial",
"AOE Cast", "AOE Cast",
"Entity Management", "Entity Management",
@@ -237,7 +235,7 @@ namespace Logs {
"DialogueWindow", "DialogueWindow",
"HTTP", "HTTP",
"Saylink", "Saylink",
"Checksum Verification", "ChecksumVer",
"CombatRecord", "CombatRecord",
"Hate", "Hate",
"Discord", "Discord",
@@ -260,9 +258,7 @@ namespace Logs {
"Bot Spell Checks", "Bot Spell Checks",
"Bot Spell Type Checks", "Bot Spell Type Checks",
"NpcHandin", "NpcHandin",
"ZoneState", "ZoneState"
"Net Server <-> Client",
"Net TCP"
}; };
} }
+20 -20
View File
@@ -261,6 +261,26 @@
OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0) } while (0)
#define LogStatus(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Status))\
OutF(LogSys, Logs::General, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogStatusDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Status))\
OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTCPConnection(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::TCPConnection))\
OutF(LogSys, Logs::General, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTCPConnectionDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::TCPConnection))\
OutF(LogSys, Logs::Detail, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTasks(message, ...) do {\ #define LogTasks(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\ if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@@ -904,26 +924,6 @@
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0) } while (0)
#define LogNetClient(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::NetClient))\
OutF(LogSys, Logs::General, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetClientDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetClient))\
OutF(LogSys, Logs::Detail, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetTCP(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::NetTCP))\
OutF(LogSys, Logs::General, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetTCPDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetTCP))\
OutF(LogSys, Logs::Detail, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\ #define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\ if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
-23
View File
@@ -54,10 +54,6 @@ double EvolvingItemsManager::CalculateProgression(const uint64 current_amount, c
void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const
{ {
if (!inst) {
return;
}
inst.SetEvolveEquipped(false); inst.SetEvolveEquipped(false);
if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) { if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
inst.SetEvolveEquipped(true); inst.SetEvolveEquipped(true);
@@ -91,10 +87,6 @@ void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_
uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
{ {
if (!inst) {
return 0;
}
const auto start_iterator = std::ranges::find_if( const auto start_iterator = std::ranges::find_if(
evolving_items_manager.GetEvolvingItemsCache().cbegin(), evolving_items_manager.GetEvolvingItemsCache().cbegin(),
evolving_items_manager.GetEvolvingItemsCache().cend(), evolving_items_manager.GetEvolvingItemsCache().cend(),
@@ -124,10 +116,6 @@ uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const
{ {
if (!inst) {
return 0;
}
int8 const current_level = inst.GetEvolveLvl(); int8 const current_level = inst.GetEvolveLvl();
const auto iterator = std::ranges::find_if( const auto iterator = std::ranges::find_if(
@@ -203,10 +191,6 @@ uint64 EvolvingItemsManager::GetTotalEarnedXP(const EQ::ItemInstance &inst)
EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp) EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp)
{ {
EvolveGetNextItem ets{}; EvolveGetNextItem ets{};
if (!inst_in) {
return ets;
}
const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID()); const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID());
uint32 max_transfer_level = 0; uint32 max_transfer_level = 0;
int64 xp = in_xp; int64 xp = in_xp;
@@ -251,9 +235,6 @@ EvolveTransfer EvolvingItemsManager::DetermineTransferResults(
) )
{ {
EvolveTransfer ets{}; 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_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID());
auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID()); auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID());
@@ -314,10 +295,6 @@ uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_i
void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e) void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e)
{ {
if (!inst) {
return;
}
e.item_id = inst.GetID(); e.item_id = inst.GetID();
e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string(); e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string();
e.level = inst.GetEvolveLvl(); e.level = inst.GetEvolveLvl();
+2 -2
View File
@@ -53,11 +53,11 @@ public:
ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id); ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id);
EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to); EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to);
EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp); EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp);
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return m_evolving_items_cache; } std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return evolving_items_cache; }
std::vector<ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> GetEvolveIDItems(uint32 evolve_id); std::vector<ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> GetEvolveIDItems(uint32 evolve_id);
private: private:
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> m_evolving_items_cache; std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> evolving_items_cache;
Database * m_db; Database * m_db;
Database * m_content_db; Database * m_content_db;
}; };
+93 -83
View File
@@ -1,16 +1,12 @@
#include "daybreak_connection.h" #include "daybreak_connection.h"
#include "../event/event_loop.h" #include "../event/event_loop.h"
#include "../event/task.h"
#include "../data_verification.h" #include "../data_verification.h"
#include "crc32.h" #include "crc32.h"
#include "../eqemu_logsys.h"
#include <zlib.h> #include <zlib.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <sstream>
// observed client receive window is 300 packets, 140KB
constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300;
constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024;
// buffer pools
SendBufferPool send_buffer_pool;
EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager() EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager()
{ {
@@ -57,22 +53,16 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
uv_ip4_addr("0.0.0.0", m_options.port, &recv_addr); uv_ip4_addr("0.0.0.0", m_options.port, &recv_addr);
int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR); int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
rc = uv_udp_recv_start( rc = uv_udp_recv_start(&m_socket,
&m_socket,
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size]; buf->base = new char[suggested_size];
memset(buf->base, 0, suggested_size);
buf->len = suggested_size; buf->len = suggested_size;
return;
}
static thread_local char temp_buf[65536];
buf->base = temp_buf;
buf->len = 65536;
}, },
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { [](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data; DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
if (nread < 0 || addr == nullptr) { if (nread < 0 || addr == nullptr) {
delete[] buf->base;
return; return;
} }
@@ -80,10 +70,7 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16); uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
auto port = ntohs(((const sockaddr_in*)addr)->sin_port); auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
c->ProcessPacket(endpoint, port, buf->base, nread); c->ProcessPacket(endpoint, port, buf->base, nread);
if (buf->len > 65536) {
delete[] buf->base; delete[] buf->base;
}
}); });
m_attached = loop; m_attached = loop;
@@ -323,7 +310,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
m_last_session_stats = Clock::now(); m_last_session_stats = Clock::now();
m_outgoing_budget = owner->m_options.outgoing_data_rate; m_outgoing_budget = owner->m_options.outgoing_data_rate;
LogNetClient("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key)); LogNetcode("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
} }
//new connection made as client //new connection made as client
@@ -355,17 +342,17 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
void EQ::Net::DaybreakConnection::Close() void EQ::Net::DaybreakConnection::Close()
{ {
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) { if (m_status == StatusConnected) {
FlushBuffer(); FlushBuffer();
SendDisconnect(); SendDisconnect();
}
if (m_status != StatusDisconnecting) {
m_close_time = Clock::now(); m_close_time = Clock::now();
}
ChangeStatus(StatusDisconnecting); ChangeStatus(StatusDisconnecting);
} }
else {
ChangeStatus(StatusDisconnecting);
}
}
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p) void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
{ {
@@ -647,7 +634,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
p.PutSerialize(0, reply); p.PutSerialize(0, reply);
InternalSend(p); InternalSend(p);
LogNetClient("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key)); LogNetcode("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
} }
break; break;
@@ -666,7 +653,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
m_max_packet_size = reply.max_packet_size; m_max_packet_size = reply.max_packet_size;
ChangeStatus(StatusConnected); ChangeStatus(StatusConnected);
LogNetClient( LogNetcode(
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]", "[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
m_connect_code, m_connect_code,
HostToNetwork(m_encode_key) HostToNetwork(m_encode_key)
@@ -795,7 +782,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
SendDisconnect(); SendDisconnect();
} }
LogNetClient( LogNetcode(
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]", "[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
m_connect_code, m_connect_code,
HostToNetwork(m_encode_key) HostToNetwork(m_encode_key)
@@ -865,7 +852,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
} }
if (p.Length() < (size_t)m_crc_bytes) { if (p.Length() < (size_t)m_crc_bytes) {
LogNetClient("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code); LogNetcode("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
return false; return false;
} }
@@ -1056,7 +1043,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
return; return;
} }
static thread_local uint8_t new_buffer[4096]; static uint8_t new_buffer[4096];
uint8_t *buffer = (uint8_t*)p.Data() + offset; uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0; uint32_t new_length = 0;
@@ -1077,7 +1064,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t length) void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t length)
{ {
static thread_local uint8_t new_buffer[2048] = { 0 }; uint8_t new_buffer[2048] = { 0 };
uint8_t *buffer = (uint8_t*)p.Data() + offset; uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0; uint32_t new_length = 0;
bool send_uncompressed = true; bool send_uncompressed = true;
@@ -1104,6 +1091,10 @@ void EQ::Net::DaybreakConnection::ProcessResend()
} }
} }
// observed client receive window is 300 packets, 140KB
constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300;
constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024;
void EQ::Net::DaybreakConnection::ProcessResend(int stream) void EQ::Net::DaybreakConnection::ProcessResend(int stream)
{ {
if (m_status == DbProtocolStatus::StatusDisconnected) { if (m_status == DbProtocolStatus::StatusDisconnected) {
@@ -1126,6 +1117,19 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
auto &first_packet = s->sent_packets.begin()->second; auto &first_packet = s->sent_packets.begin()->second;
auto time_since_first_sent = std::chrono::duration_cast<std::chrono::milliseconds>(now - first_packet.first_sent).count(); auto time_since_first_sent = std::chrono::duration_cast<std::chrono::milliseconds>(now - first_packet.first_sent).count();
// 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) {
LogNetcodeDetail(
"Not resending packets for stream [{}] time since first sent [{}] resend delay [{}] m_acked_since_last_resend [{}]",
stream,
time_since_first_sent,
first_packet.resend_delay,
m_acked_since_last_resend
);
return;
}
if (time_since_first_sent >= m_owner->m_options.resend_timeout) { if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
Close(); Close();
return; return;
@@ -1138,18 +1142,19 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
total_size += e.second.packet.Length(); total_size += e.second.packet.Length();
} }
LogNetClient( LogNetcodeDetail(
"Resending packets for stream [{}] packet count [{}] total packet size [{}]", "Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
stream, stream,
s->sent_packets.size(), s->sent_packets.size(),
total_size total_size,
m_acked_since_last_resend
); );
} }
for (auto &e: s->sent_packets) { for (auto &e: s->sent_packets) {
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW || if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) { m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
LogNetClient( LogNetcodeDetail(
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]", "Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
m_resend_packets_sent, m_resend_packets_sent,
MAX_CLIENT_RECV_PACKETS_PER_WINDOW, MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
@@ -1187,6 +1192,8 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
m_owner->m_options.resend_delay_max m_owner->m_options.resend_delay_max
); );
} }
m_acked_since_last_resend = false;
} }
void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq) void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
@@ -1207,6 +1214,7 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
m_rolling_ping = (m_rolling_ping * 2 + round_time) / 3; m_rolling_ping = (m_rolling_ping * 2 + round_time) / 3;
iter = s->sent_packets.erase(iter); iter = s->sent_packets.erase(iter);
m_acked_since_last_resend = true;
} }
else { else {
++iter; ++iter;
@@ -1325,53 +1333,46 @@ void EQ::Net::DaybreakConnection::SendKeepAlive()
InternalSend(p); InternalSend(p);
} }
void EQ::Net::DaybreakConnection::InternalSend(Packet &p) { void EQ::Net::DaybreakConnection::InternalSend(Packet &p)
{
if (m_owner->m_options.outgoing_data_rate > 0.0) { if (m_owner->m_options.outgoing_data_rate > 0.0) {
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0); auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
if (new_budget <= 0.0) { if (new_budget <= 0.0) {
m_stats.dropped_datarate_packets++; m_stats.dropped_datarate_packets++;
return; return;
} else { }
else {
m_outgoing_budget = new_budget; m_outgoing_budget = new_budget;
} }
} }
m_last_send = Clock::now(); m_last_send = Clock::now();
auto pooled_opt = send_buffer_pool.acquire(); auto send_func = [](uv_udp_send_t* req, int status) {
if (!pooled_opt) { delete[](char*)req->data;
m_stats.dropped_datarate_packets++; delete req;
return; };
}
auto [send_req, data, ctx] = *pooled_opt;
ctx->pool = &send_buffer_pool; // set pool pointer
sockaddr_in send_addr{};
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
if (PacketCanBeEncoded(p)) { if (PacketCanBeEncoded(p)) {
m_stats.bytes_before_encode += p.Length(); m_stats.bytes_before_encode += p.Length();
DynamicPacket out; DynamicPacket out;
out.PutPacket(0, p); out.PutPacket(0, p);
for (auto &m_encode_passe: m_encode_passes) { for (int i = 0; i < 2; ++i) {
switch (m_encode_passe) { switch (m_encode_passes[i]) {
case EncodeCompression: case EncodeCompression:
if (out.GetInt8(0) == 0) { if (out.GetInt8(0) == 0)
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
} else { else
Compress(out, 1, out.Length() - 1); Compress(out, 1, out.Length() - 1);
}
break; break;
case EncodeXOR: case EncodeXOR:
if (out.GetInt8(0) == 0) { if (out.GetInt8(0) == 0)
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
} else { else
Encode(out, 1, out.Length() - 1); Encode(out, 1, out.Length() - 1);
}
break; break;
default: default:
break; break;
@@ -1379,43 +1380,52 @@ void EQ::Net::DaybreakConnection::InternalSend(Packet &p) {
} }
AppendCRC(out); AppendCRC(out);
uv_udp_send_t *send_req = new uv_udp_send_t;
memset(send_req, 0, sizeof(*send_req));
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[out.Length()];
memcpy(data, out.Data(), out.Length()); memcpy(data, out.Data(), out.Length());
send_buffers[0] = uv_buf_init(data, out.Length()); send_buffers[0] = uv_buf_init(data, out.Length());
} else { send_req->data = send_buffers[0].base;
m_stats.sent_bytes += out.Length();
m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
delete[](char*)send_req->data;
delete send_req;
return;
}
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
return;
}
m_stats.bytes_before_encode += p.Length();
uv_udp_send_t *send_req = new uv_udp_send_t;
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[p.Length()];
memcpy(data, p.Data(), p.Length()); memcpy(data, p.Data(), p.Length());
send_buffers[0] = uv_buf_init(data, p.Length()); send_buffers[0] = uv_buf_init(data, p.Length());
} send_req->data = send_buffers[0].base;
m_stats.sent_bytes += p.Length(); m_stats.sent_bytes += p.Length();
m_stats.sent_packets++; m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss && if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) { delete[](char*)send_req->data;
send_buffer_pool.release(ctx); delete send_req;
return; return;
} }
int send_result = uv_udp_send( uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr *)&send_addr,
[](uv_udp_send_t *req, int status) {
auto *ctx = reinterpret_cast<EmbeddedContext *>(req->data);
if (!ctx) {
std::cerr << "Error: send_req->data is null in callback!" << std::endl;
return;
}
if (status < 0) {
std::cerr << "uv_udp_send failed: " << uv_strerror(status) << std::endl;
}
ctx->pool->release(ctx);
}
);
if (send_result < 0) {
std::cerr << "uv_udp_send() failed: " << uv_strerror(send_result) << std::endl;
send_buffer_pool.release(ctx);
}
} }
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable) void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
+1 -1
View File
@@ -3,7 +3,6 @@
#include "../random.h" #include "../random.h"
#include "packet.h" #include "packet.h"
#include "daybreak_structs.h" #include "daybreak_structs.h"
#include "daybreak_pooling.h"
#include <uv.h> #include <uv.h>
#include <chrono> #include <chrono>
#include <functional> #include <functional>
@@ -185,6 +184,7 @@ namespace EQ
// resend tracking // resend tracking
size_t m_resend_packets_sent = 0; size_t m_resend_packets_sent = 0;
size_t m_resend_bytes_sent = 0; size_t m_resend_bytes_sent = 0;
bool m_acked_since_last_resend = false;
struct DaybreakSentPacket struct DaybreakSentPacket
{ {
-123
View File
@@ -1,123 +0,0 @@
#pragma once
#include <optional>
#include <atomic>
#include <memory>
#include <array>
#include <vector>
#include <mutex>
#include <iostream>
#include "../eqemu_logsys.h"
#include <uv.h>
constexpr size_t UDP_BUFFER_SIZE = 512;
struct EmbeddedContext {
size_t pool_index;
class SendBufferPool* pool;
};
class SendBufferPool {
public:
explicit SendBufferPool(size_t initial_capacity = 64)
: m_capacity(initial_capacity), m_head(0)
{
LogNetClient("[SendBufferPool] Initializing with capacity [{}]", (int)m_capacity);
m_pool.reserve(m_capacity);
m_locks = std::make_unique<std::atomic_bool[]>(m_capacity);
for (size_t i = 0; i < m_capacity; ++i) {
auto* req = new PooledUdpSend();
req->context.pool_index = i;
req->context.pool = this;
req->uv_req.data = &req->context;
m_pool.emplace_back(std::unique_ptr<PooledUdpSend>(req));
m_locks[i].store(false, std::memory_order_relaxed);
}
}
std::optional<std::tuple<uv_udp_send_t*, char*, EmbeddedContext*>> acquire() {
size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
bool expected = false;
if (m_locks[index].compare_exchange_strong(expected, true)) {
auto* req = m_pool[index].get();
LogNetClientDetail("[SendBufferPool] Acquired [{}]", index);
return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context);
}
}
LogNetClient("[SendBufferPool] Growing from [{}] to [{}]", cap, cap * 2);
grow();
return acquireAfterGrowth();
}
void release(EmbeddedContext* ctx) {
if (!ctx || ctx->pool != this || ctx->pool_index >= m_capacity.load(std::memory_order_acquire)) {
LogNetClient("[SendBufferPool] Invalid context release [{}]", ctx ? ctx->pool_index : -1);
return;
}
m_locks[ctx->pool_index].store(false, std::memory_order_release);
LogNetClientDetail("[SendBufferPool] Released [{}]", ctx->pool_index);
}
private:
struct PooledUdpSend {
uv_udp_send_t uv_req;
std::array<char, UDP_BUFFER_SIZE> buffer;
EmbeddedContext context;
};
std::vector<std::unique_ptr<PooledUdpSend>> m_pool;
std::unique_ptr<std::atomic_bool[]> m_locks;
std::atomic<size_t> m_capacity;
std::atomic<size_t> m_head;
std::mutex m_grow_mutex;
void grow() {
std::lock_guard<std::mutex> lock(m_grow_mutex);
size_t old_cap = m_capacity.load(std::memory_order_acquire);
size_t new_cap = old_cap * 2;
m_pool.reserve(new_cap);
for (size_t i = old_cap; i < new_cap; ++i) {
auto* req = new PooledUdpSend();
req->context.pool_index = i;
req->context.pool = this;
req->uv_req.data = &req->context;
m_pool.emplace_back(std::unique_ptr<PooledUdpSend>(req));
}
auto new_locks = std::make_unique<std::atomic_bool[]>(new_cap);
for (size_t i = 0; i < old_cap; ++i) {
new_locks[i].store(m_locks[i].load(std::memory_order_acquire));
}
for (size_t i = old_cap; i < new_cap; ++i) {
new_locks[i].store(false, std::memory_order_relaxed);
}
m_locks = std::move(new_locks);
m_capacity.store(new_cap, std::memory_order_release);
LogNetClient("[SendBufferPool] Grew to [{}] from [{}]", new_cap, old_cap);
}
std::optional<std::tuple<uv_udp_send_t*, char*, EmbeddedContext*>> acquireAfterGrowth() {
size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
bool expected = false;
if (m_locks[index].compare_exchange_strong(expected, true)) {
auto* req = m_pool[index].get();
LogNetClient("[SendBufferPool] Acquired after grow [{}]", index);
return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context);
}
}
return std::nullopt;
}
};
-1
View File
@@ -171,4 +171,3 @@ namespace EQ
}; };
} }
} }
+3 -3
View File
@@ -62,15 +62,15 @@ void EQ::Net::ServertalkClient::Connect()
m_connecting = true; m_connecting = true;
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) { EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
if (connection == nullptr) { if (connection == nullptr) {
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port); LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connecting = false; m_connecting = false;
return; return;
} }
LogNetTCP("Connected to {0}:{1}", m_addr, m_port); LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
m_connection = connection; m_connection = connection;
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) { m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connection.reset(); m_connection.reset();
}); });
@@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect()
m_connecting = true; m_connecting = true;
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) { EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
if (connection == nullptr) { if (connection == nullptr) {
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port); LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connecting = false; m_connecting = false;
return; return;
} }
LogNetTCP("Connected to {0}:{1}", m_addr, m_port); LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
m_connection = connection; m_connection = connection;
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) { m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connection.reset(); m_connection.reset();
}); });
+43 -96
View File
@@ -1,8 +1,5 @@
#include "tcp_connection.h" #include "tcp_connection.h"
#include "../event/event_loop.h" #include "../event/event_loop.h"
#include <iostream>
WriteReqPool tcp_write_pool;
void on_close_handle(uv_handle_t* handle) { void on_close_handle(uv_handle_t* handle) {
delete (uv_tcp_t *)handle; delete (uv_tcp_t *)handle;
@@ -67,37 +64,36 @@ void EQ::Net::TCPConnection::Connect(const std::string &addr, int port, bool ipv
}); });
} }
void EQ::Net::TCPConnection::Start() void EQ::Net::TCPConnection::Start() {
{ uv_read_start((uv_stream_t*)m_socket, [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
uv_read_start(
(uv_stream_t *) m_socket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size]; buf->base = new char[suggested_size];
buf->len = suggested_size; buf->len = suggested_size;
return;
}
static thread_local char temp_buf[65536];
buf->base = temp_buf;
buf->len = 65536;
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { }, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
auto *connection = (TCPConnection *) stream->data;
TCPConnection *connection = (TCPConnection*)stream->data;
if (nread > 0) { if (nread > 0) {
connection->Read(buf->base, nread); connection->Read(buf->base, nread);
}
else if (nread == UV_EOF) {
connection->Disconnect();
}
else if (nread < 0) {
connection->Disconnect();
}
if (buf->len > 65536) { if (buf->base) {
delete[] buf->base; delete[] buf->base;
} }
} }
); else if (nread == UV_EOF) {
connection->Disconnect();
if (buf->base) {
delete[] buf->base;
}
}
else if (nread < 0) {
connection->Disconnect();
if (buf->base) {
delete[] buf->base;
}
}
});
} }
void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb) void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb)
@@ -134,91 +130,42 @@ void EQ::Net::TCPConnection::Read(const char *data, size_t count)
} }
} }
void EQ::Net::TCPConnection::Write(const char* data, size_t count) { void EQ::Net::TCPConnection::Write(const char *data, size_t count)
if (!m_socket || !data || count == 0) { {
std::cerr << "TCPConnection::Write - Invalid socket or data\n"; if (!m_socket) {
return; return;
} }
if (count <= TCP_BUFFER_SIZE) { struct WriteBaton
// Fast path: use pooled request with embedded buffer {
auto req_opt = tcp_write_pool.acquire(); TCPConnection *connection;
if (!req_opt) { char *buffer;
std::cerr << "TCPConnection::Write - Out of write requests\n"; };
return;
}
TCPWriteReq* write_req = *req_opt; WriteBaton *baton = new WriteBaton;
baton->connection = this;
// Fill buffer and set context baton->buffer = new char[count];
memcpy(write_req->buffer.data(), data, count);
write_req->connection = this;
write_req->magic = 0xC0FFEE;
uv_buf_t buf = uv_buf_init(write_req->buffer.data(), static_cast<unsigned int>(count));
int result = uv_write(
&write_req->req,
reinterpret_cast<uv_stream_t*>(m_socket),
&buf,
1,
[](uv_write_t* req, int status) {
auto* full_req = reinterpret_cast<TCPWriteReq*>(req);
if (full_req->magic != 0xC0FFEE) {
std::cerr << "uv_write callback - invalid magic, skipping release\n";
return;
}
tcp_write_pool.release(full_req);
if (status < 0 && full_req->connection) {
std::cerr << "uv_write failed: " << uv_strerror(status) << std::endl;
full_req->connection->Disconnect();
}
}
);
if (result < 0) {
std::cerr << "uv_write() failed immediately: " << uv_strerror(result) << std::endl;
tcp_write_pool.release(write_req);
}
} else {
// Slow path: allocate heap buffer for large write
LogNetTCP("[TCPConnection] Large write of [{}] bytes, using heap buffer", count);
char* heap_buffer = new char[count];
memcpy(heap_buffer, data, count);
uv_write_t *write_req = new uv_write_t; uv_write_t *write_req = new uv_write_t;
write_req->data = heap_buffer; memset(write_req, 0, sizeof(uv_write_t));
write_req->data = baton;
uv_buf_t send_buffers[1];
uv_buf_t buf = uv_buf_init(heap_buffer, static_cast<unsigned int>(count)); memcpy(baton->buffer, data, count);
send_buffers[0] = uv_buf_init(baton->buffer, count);
int result = uv_write( uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) {
write_req, WriteBaton *baton = (WriteBaton*)req->data;
reinterpret_cast<uv_stream_t*>(m_socket), delete[] baton->buffer;
&buf,
1,
[](uv_write_t* req, int status) {
char* data = static_cast<char*>(req->data);
delete[] data;
delete req; delete req;
if (status < 0) { if (status < 0) {
std::cerr << "uv_write (large) failed: " << uv_strerror(status) << std::endl; baton->connection->Disconnect();
}
}
);
if (result < 0) {
std::cerr << "uv_write() (large) failed immediately: " << uv_strerror(result) << std::endl;
delete[] heap_buffer;
delete write_req;
}
}
} }
delete baton;
});
}
std::string EQ::Net::TCPConnection::LocalIP() const std::string EQ::Net::TCPConnection::LocalIP() const
{ {
-1
View File
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "tcp_connection_pooling.h"
#include <functional> #include <functional>
#include <string> #include <string>
#include <memory> #include <memory>
-125
View File
@@ -1,125 +0,0 @@
#pragma once
#include "../eqemu_logsys.h"
#include <vector>
#include <array>
#include <atomic>
#include <memory>
#include <optional>
#include <mutex>
#include <uv.h>
#include <iostream>
namespace EQ { namespace Net { class TCPConnection; } }
constexpr size_t TCP_BUFFER_SIZE = 8192;
struct TCPWriteReq {
uv_write_t req{};
std::array<char, TCP_BUFFER_SIZE> buffer{};
size_t buffer_index{};
EQ::Net::TCPConnection* connection{};
uint32_t magic = 0xC0FFEE;
};
class WriteReqPool {
public:
explicit WriteReqPool(size_t initial_capacity = 512)
: m_capacity(initial_capacity), m_head(0) {
initialize_pool(m_capacity);
}
std::optional<TCPWriteReq*> acquire() {
size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
bool expected = false;
if (m_locks[index].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
LogNetTCPDetail("[WriteReqPool] Acquired buffer index [{}]", index);
return m_reqs[index].get();
}
}
LogNetTCP("[WriteReqPool] Growing from [{}] to [{}]", cap, cap * 2);
grow();
return acquireAfterGrow();
}
void release(TCPWriteReq* req) {
if (!req) return;
const size_t index = req->buffer_index;
const size_t cap = m_capacity.load(std::memory_order_acquire);
if (index >= cap || m_reqs[index].get() != req) {
std::cerr << "WriteReqPool::release - Invalid or stale pointer (index=" << index << ")\n";
return;
}
m_locks[index].store(false, std::memory_order_release);
LogNetTCPDetail("[WriteReqPool] Released buffer index [{}]", index);
}
private:
std::vector<std::unique_ptr<TCPWriteReq>> m_reqs;
std::unique_ptr<std::atomic_bool[]> m_locks;
std::atomic<size_t> m_capacity;
std::atomic<size_t> m_head;
std::mutex m_grow_mutex;
void initialize_pool(size_t count) {
m_reqs.reserve(count);
m_locks = std::make_unique<std::atomic_bool[]>(count);
for (size_t i = 0; i < count; ++i) {
auto req = std::make_unique<TCPWriteReq>();
req->buffer_index = i;
req->req.data = req.get(); // optional: for use in libuv callbacks
m_locks[i].store(false, std::memory_order_relaxed);
m_reqs.emplace_back(std::move(req));
}
m_capacity.store(count, std::memory_order_release);
}
void grow() {
std::lock_guard<std::mutex> lock(m_grow_mutex);
const size_t old_cap = m_capacity.load(std::memory_order_acquire);
const size_t new_cap = old_cap * 2;
m_reqs.reserve(new_cap);
for (size_t i = old_cap; i < new_cap; ++i) {
auto req = std::make_unique<TCPWriteReq>();
req->buffer_index = i;
req->req.data = req.get(); // optional
m_reqs.emplace_back(std::move(req));
}
auto new_locks = std::make_unique<std::atomic_bool[]>(new_cap);
for (size_t i = 0; i < old_cap; ++i) {
new_locks[i].store(m_locks[i].load(std::memory_order_acquire));
}
for (size_t i = old_cap; i < new_cap; ++i) {
new_locks[i].store(false, std::memory_order_relaxed);
}
m_locks = std::move(new_locks);
m_capacity.store(new_cap, std::memory_order_release);
}
std::optional<TCPWriteReq*> acquireAfterGrow() {
const size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
bool expected = false;
if (m_locks[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
LogNetTCP("[WriteReqPool] Acquired buffer index [{}] after grow", i);
return m_reqs[i].get();
}
}
return std::nullopt;
}
};
+2 -2
View File
@@ -4688,7 +4688,7 @@ namespace RoF2
Bitfields->linkdead = 0; Bitfields->linkdead = 0;
Bitfields->showhelm = emu->showhelm; Bitfields->showhelm = emu->showhelm;
Bitfields->trader = emu->trader ? 1 : 0; Bitfields->trader = emu->trader ? 1 : 0;
Bitfields->targetable = emu->NPC ? emu->untargetable : 1; Bitfields->targetable = 1;
Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0; Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0;
Bitfields->showname = ShowName; Bitfields->showname = ShowName;
@@ -4841,7 +4841,7 @@ namespace RoF2
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, emu->PlayerState);
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex 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); // These do something with OP_WeaponEquip1
+11 -11
View File
@@ -4908,12 +4908,12 @@ namespace UF
UFSlot = serverSlot - 2; UFSlot = serverSlot - 2;
} }
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) { else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_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; UFSlot = serverSlot + 11;
} }
else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) { else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) {
UFSlot = serverSlot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // - 9; UFSlot = serverSlot - 9;
} }
else if (serverSlot <= EQ::invslot::TRIBUTE_END && serverSlot >= EQ::invslot::TRIBUTE_BEGIN) { 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) { else if (serverSlot <= EQ::invbag::BANK_BAGS_END && serverSlot >= EQ::invbag::BANK_BAGS_BEGIN) {
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; UFSlot = serverSlot + 1;
} }
else if (serverSlot <= EQ::invslot::SHARED_BANK_END && serverSlot >= EQ::invslot::SHARED_BANK_BEGIN) { 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) { else if (serverSlot <= EQ::invbag::SHARED_BANK_BAGS_END && serverSlot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) {
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; UFSlot = serverSlot + 1;
} }
else if (serverSlot <= EQ::invslot::TRADE_END && serverSlot >= EQ::invslot::TRADE_BEGIN) { 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) { else if (serverSlot <= EQ::invbag::TRADE_BAGS_END && serverSlot >= EQ::invbag::TRADE_BAGS_BEGIN) {
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; UFSlot = serverSlot;
} }
else if (serverSlot <= EQ::invslot::WORLD_END && serverSlot >= EQ::invslot::WORLD_BEGIN) { 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) { else if (ufSlot <= invbag::GENERAL_BAGS_END && ufSlot >= invbag::GENERAL_BAGS_BEGIN) {
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; ServerSlot = ufSlot - 11;
} }
else if (ufSlot <= invbag::CURSOR_BAG_END && ufSlot >= invbag::CURSOR_BAG_BEGIN) { else if (ufSlot <= invbag::CURSOR_BAG_END && ufSlot >= invbag::CURSOR_BAG_BEGIN) {
ServerSlot = ufSlot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // + 9; ServerSlot = ufSlot + 9;
} }
else if (ufSlot <= invslot::TRIBUTE_END && ufSlot >= invslot::TRIBUTE_BEGIN) { 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) { else if (ufSlot <= invbag::BANK_BAGS_END && ufSlot >= invbag::BANK_BAGS_BEGIN) {
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; ServerSlot = ufSlot - 1;
} }
else if (ufSlot <= invslot::SHARED_BANK_END && ufSlot >= invslot::SHARED_BANK_BEGIN) { 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) { else if (ufSlot <= invbag::SHARED_BANK_BAGS_END && ufSlot >= invbag::SHARED_BANK_BAGS_BEGIN) {
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; ServerSlot = ufSlot - 1;
} }
else if (ufSlot <= invslot::TRADE_END && ufSlot >= invslot::TRADE_BEGIN) { 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) { else if (ufSlot <= invbag::TRADE_BAGS_END && ufSlot >= invbag::TRADE_BAGS_BEGIN) {
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; ServerSlot = ufSlot;
} }
else if (ufSlot <= invslot::WORLD_END && ufSlot >= invslot::WORLD_BEGIN) { else if (ufSlot <= invslot::WORLD_END && ufSlot >= invslot::WORLD_BEGIN) {
+37 -24
View File
@@ -22,7 +22,6 @@
#include "ptimer.h" #include "ptimer.h"
#include "database.h" #include "database.h"
#include "strings.h" #include "strings.h"
#include "repositories/timers_repository.h"
#ifdef _WINDOWS #ifdef _WINDOWS
#include <winsock2.h> #include <winsock2.h>
@@ -150,6 +149,27 @@ bool PersistentTimer::Load(Database *db) {
return true; return true;
} }
bool PersistentTimer::Store(Database *db) {
if(Expired(db, false)) //dont need to store expired timers.
return true;
std::string query = StringFormat("REPLACE INTO timers "
" (char_id, type, start, duration, enable) "
" VALUES (%lu, %u, %lu, %lu, %d)",
(unsigned long)_char_id, _type, (unsigned long)start_time,
(unsigned long)timer_time, enabled ? 1: 0);
#ifdef DEBUG_PTIMERS
printf("Storing timer: char %lu of type %u: '%s'\n", (unsigned long)_char_id, _type, query.c_str());
#endif
auto results = db->QueryDatabase(query);
if (!results.Success()) {
return false;
}
return true;
}
bool PersistentTimer::Clear(Database *db) { bool PersistentTimer::Clear(Database *db) {
std::string query = StringFormat("DELETE FROM timers " std::string query = StringFormat("DELETE FROM timers "
@@ -284,34 +304,27 @@ bool PTimerList::Load(Database *db) {
return true; return true;
} }
bool PTimerList::Store(Database *db) bool PTimerList::Store(Database *db) {
{ #ifdef DEBUG_PTIMERS
auto e = TimersRepository::NewEntity(); printf("Storing all timers for char %lu\n", (unsigned long)_char_id);
std::vector<TimersRepository::Timers> entries; #endif
for (auto &[type, timer] : _list) { std::map<pTimerType, PersistentTimer *>::iterator s;
if (!timer) { s = _list.begin();
continue; bool res = true;
while(s != _list.end()) {
if(s->second != nullptr) {
#ifdef DEBUG_PTIMERS
printf("Storing timer %u for char %lu\n", s->first, (unsigned long)_char_id);
#endif
if(!s->second->Store(db))
res = false;
} }
++s;
e.char_id = _char_id;
e.type = type;
e.start = timer->GetStartTime();
e.duration = timer->GetTimerTime();
e.enable = timer->Enabled() ? 1 : 0;
entries.emplace_back(e);
} }
return(res);
if (!entries.empty()) {
Expired(db, false);
TimersRepository::ReplaceMany(*db, entries);
} }
return true;
}
bool PTimerList::Clear(Database *db) { bool PTimerList::Clear(Database *db) {
_list.clear(); _list.clear();
+1
View File
@@ -91,6 +91,7 @@ public:
inline bool Enabled() { return enabled; } inline bool Enabled() { return enabled; }
bool Load(Database *db); bool Load(Database *db);
bool Store(Database *db);
bool Clear(Database *db); bool Clear(Database *db);
protected: protected:
@@ -115,8 +115,7 @@ public:
uint8_t lfg; uint8_t lfg;
std::string mailkey; std::string mailkey;
uint8_t xtargets; uint8_t xtargets;
uint32_t first_login; int8_t firstlogon;
uint8_t ingame;
uint32_t e_aa_effects; uint32_t e_aa_effects;
uint32_t e_percent_to_aa; uint32_t e_percent_to_aa;
uint32_t e_expended_aa_spent; uint32_t e_expended_aa_spent;
@@ -231,8 +230,7 @@ public:
"lfg", "lfg",
"mailkey", "mailkey",
"xtargets", "xtargets",
"first_login", "firstlogon",
"ingame",
"e_aa_effects", "e_aa_effects",
"e_percent_to_aa", "e_percent_to_aa",
"e_expended_aa_spent", "e_expended_aa_spent",
@@ -343,8 +341,7 @@ public:
"lfg", "lfg",
"mailkey", "mailkey",
"xtargets", "xtargets",
"first_login", "firstlogon",
"ingame",
"e_aa_effects", "e_aa_effects",
"e_percent_to_aa", "e_percent_to_aa",
"e_expended_aa_spent", "e_expended_aa_spent",
@@ -489,8 +486,7 @@ public:
e.lfg = 0; e.lfg = 0;
e.mailkey = ""; e.mailkey = "";
e.xtargets = 5; e.xtargets = 5;
e.first_login = 0; e.firstlogon = 0;
e.ingame = 0;
e.e_aa_effects = 0; e.e_aa_effects = 0;
e.e_percent_to_aa = 0; e.e_percent_to_aa = 0;
e.e_expended_aa_spent = 0; e.e_expended_aa_spent = 0;
@@ -631,16 +627,15 @@ public:
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0; e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
e.mailkey = row[94] ? row[94] : ""; e.mailkey = row[94] ? row[94] : "";
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5; e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
e.first_login = row[96] ? static_cast<uint32_t>(strtoul(row[96], nullptr, 10)) : 0; e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
e.ingame = row[97] ? static_cast<uint8_t>(strtoul(row[97], nullptr, 10)) : 0; e.e_aa_effects = 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[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[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[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[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[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[103] ? row[103] : "-1", nullptr, 10);
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
return e; return e;
} }
@@ -769,16 +764,15 @@ public:
v.push_back(columns[93] + " = " + std::to_string(e.lfg)); v.push_back(columns[93] + " = " + std::to_string(e.lfg));
v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'"); v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'");
v.push_back(columns[95] + " = " + std::to_string(e.xtargets)); v.push_back(columns[95] + " = " + std::to_string(e.xtargets));
v.push_back(columns[96] + " = " + std::to_string(e.first_login)); v.push_back(columns[96] + " = " + std::to_string(e.firstlogon));
v.push_back(columns[97] + " = " + std::to_string(e.ingame)); v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects));
v.push_back(columns[98] + " = " + 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_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.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_spent_old)); v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old));
v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old)); v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot));
v.push_back(columns[103] + " = " + 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] + " = 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[105] + " = " + std::to_string(e.illusion_block));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -896,8 +890,7 @@ public:
v.push_back(std::to_string(e.lfg)); v.push_back(std::to_string(e.lfg));
v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back("'" + Strings::Escape(e.mailkey) + "'");
v.push_back(std::to_string(e.xtargets)); v.push_back(std::to_string(e.xtargets));
v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.firstlogon));
v.push_back(std::to_string(e.ingame));
v.push_back(std::to_string(e.e_aa_effects)); 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_percent_to_aa));
v.push_back(std::to_string(e.e_expended_aa_spent)); v.push_back(std::to_string(e.e_expended_aa_spent));
@@ -1031,8 +1024,7 @@ public:
v.push_back(std::to_string(e.lfg)); v.push_back(std::to_string(e.lfg));
v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back("'" + Strings::Escape(e.mailkey) + "'");
v.push_back(std::to_string(e.xtargets)); v.push_back(std::to_string(e.xtargets));
v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.firstlogon));
v.push_back(std::to_string(e.ingame));
v.push_back(std::to_string(e.e_aa_effects)); 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_percent_to_aa));
v.push_back(std::to_string(e.e_expended_aa_spent)); v.push_back(std::to_string(e.e_expended_aa_spent));
@@ -1170,16 +1162,15 @@ public:
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0; e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
e.mailkey = row[94] ? row[94] : ""; e.mailkey = row[94] ? row[94] : "";
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5; e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
e.first_login = row[96] ? static_cast<uint32_t>(strtoul(row[96], nullptr, 10)) : 0; e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
e.ingame = row[97] ? static_cast<uint8_t>(strtoul(row[97], nullptr, 10)) : 0; e.e_aa_effects = 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[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[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[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[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[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[103] ? row[103] : "-1", nullptr, 10);
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@@ -1300,16 +1291,15 @@ public:
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0; e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
e.mailkey = row[94] ? row[94] : ""; e.mailkey = row[94] ? row[94] : "";
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5; e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
e.first_login = row[96] ? static_cast<uint32_t>(strtoul(row[96], nullptr, 10)) : 0; e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
e.ingame = row[97] ? static_cast<uint8_t>(strtoul(row[97], nullptr, 10)) : 0; e.e_aa_effects = 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[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[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[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[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[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[103] ? row[103] : "-1", nullptr, 10);
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@@ -1480,8 +1470,7 @@ public:
v.push_back(std::to_string(e.lfg)); v.push_back(std::to_string(e.lfg));
v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back("'" + Strings::Escape(e.mailkey) + "'");
v.push_back(std::to_string(e.xtargets)); v.push_back(std::to_string(e.xtargets));
v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.firstlogon));
v.push_back(std::to_string(e.ingame));
v.push_back(std::to_string(e.e_aa_effects)); 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_percent_to_aa));
v.push_back(std::to_string(e.e_expended_aa_spent)); v.push_back(std::to_string(e.e_expended_aa_spent));
@@ -1608,8 +1597,7 @@ public:
v.push_back(std::to_string(e.lfg)); v.push_back(std::to_string(e.lfg));
v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back("'" + Strings::Escape(e.mailkey) + "'");
v.push_back(std::to_string(e.xtargets)); v.push_back(std::to_string(e.xtargets));
v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.firstlogon));
v.push_back(std::to_string(e.ingame));
v.push_back(std::to_string(e.e_aa_effects)); 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_percent_to_aa));
v.push_back(std::to_string(e.e_expended_aa_spent)); v.push_back(std::to_string(e.e_expended_aa_spent));
@@ -149,7 +149,6 @@ public:
uint8_t keeps_sold_items; uint8_t keeps_sold_items;
uint8_t is_parcel_merchant; uint8_t is_parcel_merchant;
uint8_t multiquest_enabled; uint8_t multiquest_enabled;
uint16_t npc_tint_id;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@@ -290,7 +289,6 @@ public:
"keeps_sold_items", "keeps_sold_items",
"is_parcel_merchant", "is_parcel_merchant",
"multiquest_enabled", "multiquest_enabled",
"npc_tint_id",
}; };
} }
@@ -427,7 +425,6 @@ public:
"keeps_sold_items", "keeps_sold_items",
"is_parcel_merchant", "is_parcel_merchant",
"multiquest_enabled", "multiquest_enabled",
"npc_tint_id",
}; };
} }
@@ -598,7 +595,6 @@ public:
e.keeps_sold_items = 1; e.keeps_sold_items = 1;
e.is_parcel_merchant = 0; e.is_parcel_merchant = 0;
e.multiquest_enabled = 0; e.multiquest_enabled = 0;
e.npc_tint_id = 0;
return e; return e;
} }
@@ -765,7 +761,6 @@ public:
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1; 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.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.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; return e;
} }
@@ -928,7 +923,6 @@ public:
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items)); 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[128] + " = " + std::to_string(e.is_parcel_merchant));
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled)); 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( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -1080,7 +1074,6 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); 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.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -1240,7 +1233,6 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); 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.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); 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) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@@ -1404,7 +1396,6 @@ public:
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1; 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.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.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); all_entries.push_back(e);
} }
@@ -1559,7 +1550,6 @@ public:
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1; 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.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.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); all_entries.push_back(e);
} }
@@ -1764,7 +1754,6 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); 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.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -1917,7 +1906,6 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); 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.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); 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) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
+11 -48
View File
@@ -106,8 +106,13 @@ public:
return false; return false;
} }
auto buy_lines = auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id)); db,
fmt::format("`buyer_id` = '{}'", buyer.front().id)
);
if (buy_lines.empty()) {
return false;
}
std::vector<std::string> buy_line_ids{}; std::vector<std::string> buy_line_ids{};
for (auto const &bl: buy_lines) { for (auto const &bl: buy_lines) {
@@ -116,62 +121,20 @@ public:
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id)); DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
if (buy_line_ids.empty()) { 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;
}
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; 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( BaseBuyerBuyLinesRepository::DeleteWhere(
db, db,
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids)) fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
); );
BaseBuyerTradeItemsRepository::DeleteWhere( BaseBuyerTradeItemsRepository::DeleteWhere(
db, db,
fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids)) fmt::format(
"`buyer_buy_lines_id` IN({})",
Strings::Implode(", ", buy_line_ids))
); );
}
return true; return true;
} }
+1 -14
View File
@@ -54,10 +54,8 @@ public:
{ {
BulkTraders_Struct all_entries{}; BulkTraders_Struct all_entries{};
std::vector<DistinctTraders_Struct> distinct_traders; std::vector<DistinctTraders_Struct> distinct_traders;
MySQLRequestResult results;
if (RuleB(Bazaar, UseAlternateBazaarSearch)) { auto results = db.QueryDatabase(fmt::format(
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t " "FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id " "JOIN character_data AS c ON t.char_id = c.id "
@@ -67,17 +65,6 @@ public:
char_zone_instance_id, char_zone_instance_id,
max_results) max_results)
); );
}
else {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
max_results)
);
}
distinct_traders.reserve(results.RowCount()); distinct_traders.reserve(results.RowCount());
-13
View File
@@ -502,19 +502,6 @@ 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 (injected_rule_entries.size()) {
if (!RuleValuesRepository::InjectRules(*db, injected_rule_entries)) { if (!RuleValuesRepository::InjectRules(*db, injected_rule_entries)) {
return false; return false;
+6 -5
View File
@@ -156,7 +156,6 @@ 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, 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, 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, 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_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, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars")
RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres") RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres")
@@ -348,7 +347,6 @@ RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to
RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1") RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1")
RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files") RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files")
RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified") RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified")
RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.")
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(Zone) RULE_CATEGORY(Zone)
@@ -859,6 +857,12 @@ RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery A
RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption")
RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).")
RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.")
RULE_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
RULE_REAL(Bots, LowerTauntingMeleeDistanceMultiplier, 0.25, "Closest % of the hit box a taunting melee bot will get to the target. Default 0.25")
RULE_REAL(Bots, LowerMaxMeleeRangeDistanceMultiplier, 0.80, "Closest % of the hit box a max melee range melee bot will get to the target. Default 0.80")
RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.") RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.") RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.") RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
@@ -879,7 +883,6 @@ 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, 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, 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, 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, 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_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.") RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.")
@@ -925,7 +928,6 @@ RULE_BOOL(Chat, AutoInjectSaylinksToSay, true, "Automatically injects saylinks i
RULE_BOOL(Chat, AutoInjectSaylinksToClientMessage, true, "Automatically injects saylinks into dialogue that has [brackets in them]") RULE_BOOL(Chat, AutoInjectSaylinksToClientMessage, true, "Automatically injects saylinks into dialogue that has [brackets in them]")
RULE_BOOL(Chat, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window") RULE_BOOL(Chat, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window")
RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute") RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute")
RULE_BOOL(Chat, AlwaysCaptureCommandText, false, "Consume command text (# and ^ by default), regardless of which channel it is sent to")
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(Merchant) RULE_CATEGORY(Merchant)
@@ -1073,7 +1075,6 @@ RULE_CATEGORY(Logging)
RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...") RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...")
RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output") 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_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, 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_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() RULE_CATEGORY_END()
+2
View File
@@ -246,6 +246,8 @@
#define MAX_INVISIBILTY_LEVEL 254 #define MAX_INVISIBILTY_LEVEL 254
#define NO_ROOT_BREAK_SKILL_ID 200 //Live mechanic where if skill id in a spell is set to 200 it will prevent that spell from having a chance to break roots.
//instrument item id's used as song components //instrument item id's used as song components
#define INSTRUMENT_HAND_DRUM 13000 #define INSTRUMENT_HAND_DRUM 13000
#define INSTRUMENT_WOODEN_FLUTE 13001 #define INSTRUMENT_WOODEN_FLUTE 13001
+2 -3
View File
@@ -25,7 +25,7 @@
// Build variables // Build variables
// these get injected during the build pipeline // these get injected during the build pipeline
#define CURRENT_VERSION "23.6.0-dev" // always append -dev to the current version for custom-builds #define CURRENT_VERSION "23.4.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0" #define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__ #define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__ #define COMPILE_TIME __TIME__
@@ -42,9 +42,8 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9324 #define CURRENT_BINARY_DATABASE_VERSION 9321
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#define CUSTOM_BINARY_DATABASE_VERSION 0
#endif #endif
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "eqemu-server", "name": "eqemu-server",
"version": "23.6.0", "version": "23.4.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EQEmu/Server.git" "url": "https://github.com/EQEmu/Server.git"
+2 -2
View File
@@ -10,7 +10,7 @@ require (
require ( require (
github.com/golang/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.36.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
) )
+4 -4
View File
@@ -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= 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-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.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 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/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= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+2
View File
@@ -33,6 +33,7 @@ bash -c "${world_bin} database:dump --login-tables --drop-table-syntax-only --du
bash -c "${world_bin} database:dump --player-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_player.sql" bash -c "${world_bin} database:dump --player-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_player.sql"
bash -c "${world_bin} database:dump --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql" bash -c "${world_bin} database:dump --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql"
bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql" bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql"
bash -c "${world_bin} database:dump --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql"
############################################# #############################################
# generate "create_" table files # generate "create_" table files
@@ -44,6 +45,7 @@ bash -c "${world_bin} database:dump --login-tables --table-structure-only --dump
bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql" bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql"
bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql" bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql"
bash -c "${world_bin} database:dump --static-instance-data --dump-output-to-console >> ${dump_path}create_tables_state.sql" bash -c "${world_bin} database:dump --static-instance-data --dump-output-to-console >> ${dump_path}create_tables_state.sql"
bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql"
# with content # with content
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql" bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
+1 -1
View File
@@ -16,7 +16,7 @@ void WorldserverCLI::EtlGetSettings(int argc, char **argv, argh::parser &cmd, st
auto event_settings = player_event_logs.GetSettings(); auto event_settings = player_event_logs.GetSettings();
auto etl_details = player_event_logs.GetEtlSettings(); auto etl_details = player_event_logs.GetEtlSettings();
for (int i = PlayerEvent::GM_COMMAND; i < PlayerEvent::EventType::MAX; i++) { for (auto i = 0; i < PlayerEvent::EventType::MAX; i++) {
player_events["event_id"] = event_settings[i].id; player_events["event_id"] = event_settings[i].id;
player_events["enabled"] = event_settings[i].event_enabled ? true : false; player_events["enabled"] = event_settings[i].event_enabled ? true : false;
player_events["retention"] = event_settings[i].retention_days; player_events["retention"] = event_settings[i].retention_days;
+56 -99
View File
@@ -42,16 +42,11 @@ extern ZSList zoneserver_list;
uint32 numplayers = 0; //this really wants to be a member variable of ClientList... uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
ClientList::ClientList() ClientList::ClientList()
: CLStale_timer(10000), : CLStale_timer(10000)
m_poll_cache_timer(6000)
{ {
NextCLEID = 1; NextCLEID = 1;
m_tick = std::make_unique<EQ::Timer>(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1)); m_tick = std::make_unique<EQ::Timer>(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1));
// pre-allocate / pin memory for the zone server caches
m_gm_zone_server_ids.reserve(512);
m_guild_zone_server_ids.reserve(1024);
} }
ClientList::~ClientList() { ClientList::~ClientList() {
@@ -62,10 +57,6 @@ void ClientList::Process() {
if (CLStale_timer.Check()) if (CLStale_timer.Check())
CLCheckStale(); CLCheckStale();
if (m_poll_cache_timer.Check()) {
RebuildZoneServerCaches();
}
LinkedListIterator<Client*> iterator(list); LinkedListIterator<Client*> iterator(list);
iterator.Reset(); iterator.Reset();
@@ -393,7 +384,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
} }
else { else {
cle->Update(zoneserver, scl); cle->Update(zoneserver, scl);
AddToZoneServerCaches(cle);
} }
return; return;
} }
@@ -468,7 +458,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
); );
clientlist.Insert(cle); clientlist.Insert(cle);
AddToZoneServerCaches(cle);
zoneserver->ChangeWID(scl->charid, cle->GetID()); zoneserver->ChangeWID(scl->charid, cle->GetID());
} }
@@ -1619,7 +1608,7 @@ void ClientList::OnTick(EQ::Timer *t)
/** /**
* @param response * @param response
*/ */
void ClientList::GetClientList(Json::Value &response, bool full_list) void ClientList::GetClientList(Json::Value &response)
{ {
LinkedListIterator<ClientListEntry *> Iterator(clientlist); LinkedListIterator<ClientListEntry *> Iterator(clientlist);
@@ -1630,53 +1619,24 @@ void ClientList::GetClientList(Json::Value &response, bool full_list)
Json::Value row; Json::Value row;
row["id"] = cle->GetID();
row["name"] = cle->name();
row["level"] = cle->level();
row["ip"] = cle->GetIP();
row["gm"] = cle->GetGM();
row["race"] = cle->race();
row["class"] = cle->class_();
row["client_version"] = cle->GetClientVersion();
row["admin"] = cle->Admin();
row["account_id"] = cle->AccountID(); row["account_id"] = cle->AccountID();
row["account_name"] = cle->AccountName(); row["account_name"] = cle->AccountName();
row["character_id"] = cle->CharID(); row["admin"] = cle->Admin();
row["anon"] = cle->Anon(); row["id"] = cle->GetID();
row["guild_id"] = cle->GuildID(); row["ip"] = cle->GetIP();
if (full_list) {
row["loginserver_account_id"] = cle->LSAccountID(); row["loginserver_account_id"] = cle->LSAccountID();
row["loginserver_id"] = cle->LSID(); row["loginserver_id"] = cle->LSID();
row["loginserver_name"] = cle->LSName(); row["loginserver_name"] = cle->LSName();
row["online"] = cle->Online(); row["online"] = cle->Online();
row["world_admin"] = cle->WorldAdmin(); row["world_admin"] = cle->WorldAdmin();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
}
auto server = cle->Server(); auto server = cle->Server();
if (server) { if (server) {
row["server"]["zone_id"] = server->GetZoneID();
row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["zone_name"] = server->GetZoneName();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
row["server"]["id"] = server->GetID();
if (full_list) {
row["server"]["client_address"] = server->GetCAddress(); row["server"]["client_address"] = server->GetCAddress();
row["server"]["client_local_address"] = server->GetCLocalAddress(); row["server"]["client_local_address"] = server->GetCLocalAddress();
row["server"]["client_port"] = server->GetCPort(); row["server"]["client_port"] = server->GetCPort();
row["server"]["compile_time"] = server->GetCompileTime(); row["server"]["compile_time"] = server->GetCompileTime();
row["server"]["id"] = server->GetID();
row["server"]["instance_id"] = server->GetInstanceID(); row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["ip"] = server->GetIP(); row["server"]["ip"] = server->GetIP();
row["server"]["is_booting"] = server->IsBootingUp(); row["server"]["is_booting"] = server->IsBootingUp();
@@ -1687,11 +1647,34 @@ void ClientList::GetClientList(Json::Value &response, bool full_list)
row["server"]["previous_zone_id"] = server->GetPrevZoneID(); row["server"]["previous_zone_id"] = server->GetPrevZoneID();
row["server"]["static_zone"] = server->IsStaticZone(); row["server"]["static_zone"] = server->IsStaticZone();
row["server"]["uui"] = server->GetUUID(); row["server"]["uui"] = server->GetUUID();
} row["server"]["zone_id"] = server->GetZoneID();
row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["zone_name"] = server->GetZoneName();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
} }
else { else {
row["server"] = Json::Value(); row["server"] = Json::Value();
} }
row["anon"] = cle->Anon();
row["character_id"] = cle->CharID();
row["class"] = cle->class_();
row["client_version"] = cle->GetClientVersion();
row["gm"] = cle->GetGM();
row["guild_id"] = cle->GuildID();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["level"] = cle->level();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["name"] = cle->name();
row["race"] = cle->race();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
response.append(row); response.append(row);
@@ -1868,43 +1851,10 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
return guild_members; return guild_members;
} }
void ClientList::RebuildZoneServerCaches() #include <unordered_set>
{
// Clear without freeing memory (buckets stay allocated)
m_gm_zone_server_ids.clear();
m_guild_zone_server_ids.clear();
LinkedListIterator<ClientListEntry*> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry* cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone || !cle->Server()) {
iterator.Advance();
continue;
}
uint32_t server_id = cle->Server()->GetID();
// Track GM zone server
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
}
// Track guild zone servers
if (cle->GuildID() > 0) {
auto& guild_set = m_guild_zone_server_ids[cle->GuildID()];
guild_set.insert(server_id);
}
iterator.Advance();
}
}
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id) std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
{ {
if (RuleB(World, RealTimeCalculateGuilds)) {
std::vector<uint32_t> zone_server_ids; std::vector<uint32_t> zone_server_ids;
std::unordered_set<uint32_t> seen_ids; std::unordered_set<uint32_t> seen_ids;
@@ -1937,28 +1887,35 @@ std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
return zone_server_ids; return zone_server_ids;
} }
auto it = m_guild_zone_server_ids.find(guild_id); std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
if (it == m_guild_zone_server_ids.end()) {
return {};
}
return {it->second.begin(), it->second.end()};
}
void ClientList::AddToZoneServerCaches(ClientListEntry* cle)
{ {
if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) { std::vector<uint32_t> zone_server_ids;
return; std::unordered_set<uint32_t> seen_ids;
LinkedListIterator<ClientListEntry *> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone) {
iterator.Advance();
continue;
} }
uint32_t server_id = cle->Server()->GetID(); if (!cle->Server()) {
iterator.Advance();
// Add GM zone server if applicable continue;
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
} }
// Add guild zone server if applicable if (cle->Admin() > 0) {
if (cle->GuildID() > 0) { uint32_t id = cle->Server()->GetID();
m_guild_zone_server_ids[cle->GuildID()].insert(server_id); if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
} }
} }
iterator.Advance();
}
return zone_server_ids;
}
+3 -15
View File
@@ -60,13 +60,15 @@ public:
void CLCheckStale(); void CLCheckStale();
void CLEKeepAlive(uint32 numupdates, uint32* wid); void CLEKeepAlive(uint32 numupdates, uint32* wid);
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0); void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
std::vector<uint32_t> GetZoneServersWithGMs();
void UpdateClientGuild(uint32 char_id, uint32 guild_id); void UpdateClientGuild(uint32 char_id, uint32 guild_id);
bool IsAccountInGame(uint32 iLSID); bool IsAccountInGame(uint32 iLSID);
int GetClientCount(); int GetClientCount();
void GetClients(const char *zone_name, std::vector<ClientListEntry *> &into); void GetClients(const char *zone_name, std::vector<ClientListEntry *> &into);
void GetClientList(Json::Value &response, bool full_list = false); void GetClientList(Json::Value &response);
void GetGuildClientList(Json::Value& response, uint32 guild_id); void GetGuildClientList(Json::Value& response, uint32 guild_id);
void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message); void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
@@ -76,15 +78,6 @@ public:
void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {}); void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {}); void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
void AddToZoneServerCaches(ClientListEntry* cle);
void RebuildZoneServerCaches();
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
inline std::vector<uint32_t> GetZoneServersWithGMs()
{
return {m_gm_zone_server_ids.begin(), m_gm_zone_server_ids.end()};
}
private: private:
void OnTick(EQ::Timer *t); void OnTick(EQ::Timer *t);
inline uint32 GetNextCLEID() { return NextCLEID++; } inline uint32 GetNextCLEID() { return NextCLEID++; }
@@ -99,11 +92,6 @@ private:
std::unique_ptr<EQ::Timer> m_tick; std::unique_ptr<EQ::Timer> m_tick;
// Zone server routing caches
Timer m_poll_cache_timer;
std::unordered_set<uint32_t> m_gm_zone_server_ids;
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> m_guild_zone_server_ids;
}; };
#endif /*CLIENTLIST_H_*/ #endif /*CLIENTLIST_H_*/
+5 -22
View File
@@ -111,17 +111,9 @@ void callGetDatabaseSchema(Json::Value &response)
response.append(schema); response.append(schema);
} }
void callGetClientList(Json::Value &response, const std::vector<std::string> &args) void callGetClientList(Json::Value &response)
{ {
// if args has "full" client_list.GetClientList(response);
bool full_list = false;
if (args.size() > 1) {
if (args[1] == "full") {
full_list = true;
}
}
client_list.GetClientList(response, full_list);
} }
void getReloadTypes(Json::Value &response) void getReloadTypes(Json::Value &response)
@@ -135,12 +127,6 @@ void getReloadTypes(Json::Value &response)
} }
} }
void getServerCounts(Json::Value &response, const std::vector<std::string> &args)
{
response["zone_count"] = zoneserver_list.GetServerListCount();
response["client_count"] = client_list.GetClientCount();
}
void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::string> &args) void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::string> &args)
{ {
std::vector<std::string> commands{}; std::vector<std::string> commands{};
@@ -162,8 +148,7 @@ void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::str
for (auto &t: ServerReload::GetTypes()) { for (auto &t: ServerReload::GetTypes()) {
if (std::to_string(t) == command || Strings::ToLower(ServerReload::GetName(t)) == command) { if (std::to_string(t) == command || Strings::ToLower(ServerReload::GetName(t)) == command) {
message(r, fmt::format("Reloading [{}] globally", ServerReload::GetName(t))); message(r, fmt::format("Reloading [{}] globally", ServerReload::GetName(t)));
LogInfo("Queueing reload of type [{}] to zones", ServerReload::GetName(t)); zoneserver_list.SendServerReload(t, nullptr);
zoneserver_list.QueueServerReload(t);
} }
found_command = true; found_command = true;
} }
@@ -189,7 +174,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
callGetDatabaseSchema(r); callGetDatabaseSchema(r);
} }
if (m == "get_client_list") { if (m == "get_client_list") {
callGetClientList(r, args); callGetClientList(r);
} }
if (m == "get_reload_types") { if (m == "get_reload_types") {
getReloadTypes(r); getReloadTypes(r);
@@ -200,9 +185,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
if (m == "get_guild_details") { if (m == "get_guild_details") {
callGetGuildDetails(r, args); callGetGuildDetails(r, args);
} }
if (m == "get_server_counts") {
getServerCounts(r, args);
}
if (m == "lock_status") { if (m == "lock_status") {
r["locked"] = WorldConfig::get()->Locked; r["locked"] = WorldConfig::get()->Locked;
} }
@@ -210,6 +192,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args) void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args)
{ {
std::string command = !args[1].empty() ? args[1] : ""; std::string command = !args[1].empty() ? args[1] : "";
if (command.empty()) { if (command.empty()) {
return; return;
+1 -1
View File
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) { if (error.find("Worldserver Account / Password INVALID") != std::string::npos) {
reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. "; reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. ";
if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) { if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) {
reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt"; reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS";
} }
} }
+1 -1
View File
@@ -91,7 +91,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
} }
//broadcast this packet to all zones. //broadcast this packet to all zones.
zoneserver_list.SendPacketToBootedZones(pack); zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break; break;
} }
-21
View File
@@ -37,8 +37,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "dynamic_zone_manager.h" #include "dynamic_zone_manager.h"
#include "ucs.h" #include "ucs.h"
#include "clientlist.h" #include "clientlist.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/buyer_repository.h"
extern uint32 numzones; extern uint32 numzones;
extern EQ::Random emu_random; extern EQ::Random emu_random;
@@ -86,8 +84,6 @@ void ZSList::Remove(const std::string &uuid)
while (iter != zone_server_list.end()) { while (iter != zone_server_list.end()) {
if ((*iter)->GetUUID().compare(uuid) == 0) { if ((*iter)->GetUUID().compare(uuid) == 0) {
auto port = (*iter)->GetCPort(); auto port = (*iter)->GetCPort();
(*iter)->CheckToClearTraderAndBuyerTables();
zone_server_list.erase(iter); zone_server_list.erase(iter);
if (port != 0) { if (port != 0) {
@@ -132,16 +128,6 @@ void ZSList::Process() {
).c_str() ).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) { bool ZSList::SendPacket(ServerPacket* pack) {
@@ -1013,10 +999,3 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
++counter; ++counter;
} }
} }
void ZSList::QueueServerReload(ServerReload::Type &type)
{
m_queued_reloads_mutex.lock();
m_queued_reloads.emplace_back(type);
m_queued_reloads_mutex.unlock();
}
-5
View File
@@ -9,7 +9,6 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <deque> #include <deque>
#include <mutex>
class WorldTCPConnection; class WorldTCPConnection;
class ServerPacket; class ServerPacket;
@@ -73,12 +72,8 @@ public:
ZoneServer* FindByZoneID(uint32 ZoneID); ZoneServer* FindByZoneID(uint32 ZoneID);
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const; 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); 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: private:
void OnTick(EQ::Timer *t); void OnTick(EQ::Timer *t);
uint32 NextID; uint32 NextID;
-18
View File
@@ -50,8 +50,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/guild_tributes_repository.h" #include "../common/repositories/guild_tributes_repository.h"
#include "../common/skill_caps.h" #include "../common/skill_caps.h"
#include "../common/server_reload_types.h" #include "../common/server_reload_types.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/buyer_repository.h"
extern ClientList client_list; extern ClientList client_list;
extern GroupLFPList LFPGroupList; extern GroupLFPList LFPGroupList;
@@ -1862,19 +1860,3 @@ void ZoneServer::IncomingClient(Client* client) {
SendPacket(pack); SendPacket(pack);
delete 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()
);
}
}
-1
View File
@@ -54,7 +54,6 @@ public:
inline const char* GetZoneName() const { return zone_name; } inline const char* GetZoneName() const { return zone_name; }
inline const char* GetZoneLongName() const { return long_name; } inline const char* GetZoneLongName() const { return long_name; }
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; } inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
void CheckToClearTraderAndBuyerTables();
inline std::string GetCompileDate() const { return COMPILE_DATE; } inline std::string GetCompileDate() const { return COMPILE_DATE; }
const char* GetCompileTime() const{ return compiled; } const char* GetCompileTime() const{ return compiled; }
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); } void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
+29 -42
View File
@@ -3068,11 +3068,11 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
else else
SetAssistAggro(true); SetAssistAggro(true);
bool was_engaged = IsEngaged(); bool wasengaged = IsEngaged();
Mob* owner = other->GetOwner(); Mob* owner = other->GetOwner();
Mob* my_pet = GetPet(); Mob* mypet = GetPet();
Mob* my_owner = GetOwner(); Mob* myowner = GetOwner();
Mob* target_mob = GetTarget(); Mob* targetmob = GetTarget();
bool on_hatelist = CheckAggro(other); bool on_hatelist = CheckAggro(other);
AddRampage(other); AddRampage(other);
@@ -3101,7 +3101,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (IsPet()) { if (IsPet()) {
if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list
return; return;
if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !was_engaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold" if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !wasengaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold"
return; return;
} }
} }
@@ -3134,7 +3134,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
return; return;
} }
if (other == my_owner) { if (other == myowner) {
return; return;
} }
@@ -3236,39 +3236,26 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
} }
} }
if (my_pet) { if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it
bool aggro_immunity = my_pet->GetSpecialAbility(SpecialAbility::AggroImmunity); if (
bool bot_aggro_immunity = IsBot() && my_pet->GetSpecialAbility(SpecialAbility::BotAggroImmunity); !mypet->IsFamiliar() &&
bool client_aggro_immunity = IsClient() && my_pet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity); !mypet->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
bool npc_aggro_immunity = IsNPC() && my_pet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity); !(IsBot() && mypet->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
bool can_add_to_hatelist = !my_pet->IsFamiliar() && !(IsClient() && mypet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
!aggro_immunity && !(IsNPC() && mypet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
!bot_aggro_immunity && ) {
!client_aggro_immunity && mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
!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
else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD if (
if (my_owner->IsAIControlled()) { myowner->IsAIControlled() &&
bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity); !myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity); !(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity); !(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity); !(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
bool can_add_to_hatelist = !aggro_immunity && ) {
!bot_aggro_immunity && myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
!client_aggro_immunity &&
!npc_aggro_immunity;
if (can_add_to_hatelist) {
my_owner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
} }
} }
@@ -3277,7 +3264,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
entity_list.AddTempPetsToHateList(this, other, bFrenzy); entity_list.AddTempPetsToHateList(this, other, bFrenzy);
} }
if (!was_engaged) { if (!wasengaged) {
if (IsNPC() && other->IsClient() && other->CastToClient()) { if (IsNPC() && other->IsClient() && other->CastToClient()) {
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) { if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) {
parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0); parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0);
@@ -4452,9 +4439,10 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
if (IsValidSpell(spell_id) && !iBuffTic) { if (IsValidSpell(spell_id) && !iBuffTic) {
//see if root will break //see if root will break
if (IsRooted() && !FromDamageShield) // neotoyko: only spells cancel root if (IsRooted() && !FromDamageShield && spells[spell_id].skill != NO_ROOT_BREAK_SKILL_ID) { // neotoyko: only spells cancel root
TryRootFadeByDamage(buffslot, attacker); TryRootFadeByDamage(buffslot, attacker);
} }
}
else if (!IsValidSpell(spell_id)) else if (!IsValidSpell(spell_id))
{ {
//increment chances of interrupting //increment chances of interrupting
@@ -6236,6 +6224,7 @@ bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) {
- If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root.
- Only roots on determental spells can be broken by damage. - Only roots on determental spells can be broken by damage.
- Root break chance values obtained from live parses. - Root break chance values obtained from live parses.
- If casting skill is set to 200 then spell can not break root
*/ */
if (!attacker || !spellbonuses.Root[SBIndex::ROOT_EXISTS] || spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] < 0) { if (!attacker || !spellbonuses.Root[SBIndex::ROOT_EXISTS] || spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] < 0) {
@@ -6690,9 +6679,7 @@ void Client::SetAttackTimer()
else else
speed = static_cast<int>(speed + ((hhe / 100.0f) * delay)); 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) { if (i == EQ::invslot::slotPrimary) {
primary_speed = speed; primary_speed = speed;
+12 -9
View File
@@ -1165,6 +1165,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
} }
case SE_ResistFearChance: { case SE_ResistFearChance: {
if (base_value == 100) // If we reach 100% in a single spell/item then we should be immune to
// negative fear resist effects until our immunity is over
newbon->Fearless = true;
newbon->ResistFearChance += base_value; // these should stack newbon->ResistFearChance += base_value; // these should stack
break; break;
} }
@@ -2470,6 +2474,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_ResistFearChance: case SE_ResistFearChance:
{ {
if(effect_value == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over
new_bonus->Fearless = true;
new_bonus->ResistFearChance += effect_value; // these should stack new_bonus->ResistFearChance += effect_value; // these should stack
break; break;
} }
@@ -4682,7 +4689,11 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
break; break;
case SE_ResistFearChance: case SE_ResistFearChance:
if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; } if (negate_spellbonus) {
spellbonuses.Fearless = false;
spellbonuses.ResistFearChance = effect_value;
}
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; } if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; } if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
break; break;
@@ -5320,14 +5331,6 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
spellbonuses.SEResist[e] = effect_value; spellbonuses.SEResist[e] = effect_value;
spellbonuses.SEResist[e + 1] = effect_value; spellbonuses.SEResist[e + 1] = effect_value;
} }
if (negate_itembonus) {
itembonuses.SEResist[e] = effect_value;
itembonuses.SEResist[e + 1] = effect_value;
}
if (negate_aabonus) {
aabonuses.SEResist[e] = effect_value;
aabonuses.SEResist[e + 1] = effect_value;
}
} }
break; break;
} }
+256 -316
View File
@@ -245,8 +245,6 @@ Bot::Bot(
EquipBot(); EquipBot();
m_combat_jitter_timer.Start();
if (GetClass() == Class::Rogue) { if (GetClass() == Class::Rogue) {
m_rogue_evade_timer.Start(); m_rogue_evade_timer.Start();
} }
@@ -2102,6 +2100,10 @@ void Bot::SetGuardMode() {
StopMoving(); StopMoving();
m_GuardPoint = GetPosition(); m_GuardPoint = GetPosition();
SetGuardFlag(); SetGuardFlag();
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
GetPet()->StopMoving();
}
} }
void Bot::SetHoldMode() { void Bot::SetHoldMode() {
@@ -2184,7 +2186,8 @@ void Bot::AI_Process()
} }
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) { if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
TryNonCombatMovementChecks(bot_owner, follow_mob); glm::vec3 Goal(0, 0, 0);
TryNonCombatMovementChecks(bot_owner, follow_mob, Goal);
return; return;
} }
@@ -2214,6 +2217,8 @@ void Bot::AI_Process()
} }
//ALT COMBAT (ACQUIRE HATE) //ALT COMBAT (ACQUIRE HATE)
glm::vec3 Goal(0, 0, 0);
// We have aggro to choose from // We have aggro to choose from
if (IsEngaged()) { if (IsEngaged()) {
if (rest_timer.Enabled()) { if (rest_timer.Enabled()) {
@@ -2266,7 +2271,7 @@ void Bot::AI_Process()
} }
// This causes conflicts with default pet handler (bounces between targets) // This causes conflicts with default pet handler (bounces between targets)
if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasControllablePet(BotAnimEmpathy::Attack)) { if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
// We don't add to hate list here because it's assumed to already be on the list // We don't add to hate list here because it's assumed to already be on the list
GetPet()->SetTarget(tar); GetPet()->SetTarget(tar);
} }
@@ -2283,12 +2288,12 @@ void Bot::AI_Process()
bool front_mob = InFrontMob(tar, GetX(), GetY()); bool front_mob = InFrontMob(tar, GetX(), GetY());
bool behind_mob = BehindMob(tar, GetX(), GetY()); bool behind_mob = BehindMob(tar, GetX(), GetY());
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
tar_distance = sqrt(tar_distance); // sqrt this for future calculations tar_distance = sqrt(tar_distance); // sqrt this for future calculations
// Item variables
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary); const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary); const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
CombatRangeInput i = { CombatRangeInput input = {
.target = tar, .target = tar,
.target_distance = tar_distance, .target_distance = tar_distance,
.stop_melee_level = stop_melee_level, .stop_melee_level = stop_melee_level,
@@ -2296,7 +2301,7 @@ void Bot::AI_Process()
.s_item = s_item .s_item = s_item
}; };
CombatRangeOutput o = EvaluateCombatRange(i); CombatRangeOutput o = EvaluateCombatRange(input);
// Combat range variables // Combat range variables
bool at_combat_range = o.at_combat_range; bool at_combat_range = o.at_combat_range;
@@ -2306,36 +2311,20 @@ void Bot::AI_Process()
// PULLING FLAG (ACTIONABLE RANGE) // PULLING FLAG (ACTIONABLE RANGE)
if (PULLING_BOT) { if (PULLING_BOT || RETURNING_BOT) {
if (!TargetValidation(tar)) { if (!TargetValidation(tar)) { return; }
SetPullFlag(false);
SetPullingFlag(false);
bot_owner->SetBotPulling(false);
if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow);
GetPet()->CastToNPC()->SaveGuardSpot(true);
}
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
return; return;
} }
if (!at_combat_range && RuleB(Bots, UseSpellPulling)) { if (at_combat_range) {
uint16 pull_spell_id = RuleI(Bots, PullSpellID); if (
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
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) && RuleB(Bots, AllowRangedPulling) &&
IsBotRanged() && IsBotRanged() &&
ranged_timer.Check(false); ranged_timer.Check(false)
) {
if (can_range_attack) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) {
@@ -2347,36 +2336,33 @@ void Bot::AI_Process()
return; return;
} }
bool can_ai_spell_pull = RuleB(Bots, AllowAISpellPulling) && if (
RuleB(Bots, AllowAISpellPulling) &&
!IsBotNonSpellFighter() && !IsBotNonSpellFighter() &&
AI_HasSpells(); AI_HasSpells()
) {
if (can_ai_spell_pull) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
SetPullingSpell(true); SetPullingSpell(true);
ai_cast_successful = AI_EngagedCastCheck(); AI_EngagedCastCheck();
SetPullingSpell(false); SetPullingSpell(false);
if (ai_cast_successful) {
return; return;
} }
} }
if (RuleB(Bots, UseSpellPulling)) { if (RuleB(Bots, UseSpellPulling)) {
uint16 pull_spell_id = RuleI(Bots, PullSpellID); uint16 spell_id = RuleI(Bots, PullSpellID);
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) { if (tar_distance <= spells[spell_id].range) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); StopMoving();
SetPullingSpell(true); SetPullingSpell(true);
CastSpell(pull_spell_id, tar->GetID()); CastSpell(spell_id, tar->GetID());
SetPullingSpell(false); SetPullingSpell(false);
}
}
return; return;
} }
}
TryPursueTarget(leash_distance); TryPursueTarget(leash_distance, Goal);
return; return;
} }
@@ -2399,30 +2385,52 @@ void Bot::AI_Process()
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT); (bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
if (!other_bot_pulling && at_combat_range) { if (!other_bot_pulling && at_combat_range) {
CombatPositioningInput cpi { bool jitter_cooldown = false;
.tar = tar,
.stop_melee_level = stop_melee_level, if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) {
.tar_distance = tar_distance, jitter_cooldown = true;
.melee_distance_min = melee_distance_min, }
.melee_distance = melee_distance,
.melee_distance_max = melee_distance_max, if (
.behind_mob = behind_mob, IsMoving() ||
.front_mob = front_mob GetCombatJitterFlag() ||
}; GetCombatOutOfRangeJitterFlag()
) {
if (
!GetCombatJitterFlag() ||
!IsMoving() ||
GetCombatOutOfRangeJitterFlag()
) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
}
if (DoCombatPositioning(cpi) && IsMoving()) {
return; return;
} }
if (
!jitter_cooldown &&
AI_movement_timer->Check() &&
(!spellend_timer.Enabled() || GetClass() == Class::Bard)
) {
DoCombatPositioning(tar, Goal, stop_melee_level, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behind_mob, front_mob);
return;
}
else {
if (!IsSitting() && !IsFacingMob(tar)) { if (!IsSitting() && !IsFacingMob(tar)) {
FaceTarget(tar); FaceTarget(tar);
return; return;
} }
}
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) { if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
return; return;
} }
if (IsMoving()) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
return;
}
if ( if (
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) && !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
IsBotRanged() && IsBotRanged() &&
@@ -2471,7 +2479,7 @@ void Bot::AI_Process()
// ENGAGED NOT AT COMBAT RANGE // ENGAGED NOT AT COMBAT RANGE
else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) { else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
return; return;
} }
@@ -2484,7 +2492,7 @@ void Bot::AI_Process()
TryMeditate(); TryMeditate();
} }
else { // Out-of-combat behavior else { // Out-of-combat behavior
DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance); DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance);
} }
} }
@@ -2501,10 +2509,8 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i
return false; return false;
} }
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) { if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
glm::vec3 Goal(0, 0, 0);
if (GUARDING) { if (GUARDING) {
Goal = GetGuardPoint(); Goal = GetGuardPoint();
} }
@@ -2558,7 +2564,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
return false; return false;
} }
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) { void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) {
SetAttackFlag(false); SetAttackFlag(false);
SetCombatRoundForAlerts(false); SetCombatRoundForAlerts(false);
SetAttackingFlag(false); SetAttackingFlag(false);
@@ -2566,12 +2572,6 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) { if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) {
SetPullingFlag(false); SetPullingFlag(false);
SetReturningFlag(false); SetReturningFlag(false);
bot_owner->SetBotPulling(false);
if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow);
GetPet()->CastToNPC()->SaveGuardSpot(true);
}
} }
if (TryAutoDefend(bot_owner, leash_distance) ) { if (TryAutoDefend(bot_owner, leash_distance) ) {
@@ -2580,7 +2580,14 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
SetTarget(nullptr); SetTarget(nullptr);
if (HasControllablePet(BotAnimEmpathy::BackOff)) { if (
HasPet() &&
(
GetClass() != Class::Enchanter ||
GetPet()->GetPetType() != petAnimation ||
GetAA(aaAnimationEmpathy) >= 1
)
) {
GetPet()->WipeHateList(); GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr); GetPet()->SetTarget(nullptr);
} }
@@ -2590,7 +2597,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
} }
// Ok to idle // Ok to idle
if (TryNonCombatMovementChecks(bot_owner, follow_mob)) { if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
return; return;
} }
@@ -2743,7 +2750,7 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) {
SetTarget(hater); SetTarget(hater);
SetAttackingFlag(); SetAttackingFlag();
if (HasControllablePet(BotAnimEmpathy::Attack)) { if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
GetPet()->AddToHateList(hater, 1); GetPet()->AddToHateList(hater, 1);
GetPet()->SetTarget(hater); GetPet()->SetTarget(hater);
} }
@@ -2801,19 +2808,27 @@ bool Bot::TryMeditate() {
} }
// This code actually gets processed when we are too far away from target and have not engaged yet // This code actually gets processed when we are too far away from target and have not engaged yet
bool Bot::TryPursueTarget(float leash_distance) { bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) {
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
if (GetTarget() && !IsRooted()) { if (GetTarget() && !IsRooted()) {
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName()); LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
glm::vec3 Goal = GetTarget()->GetPosition(); Goal = GetTarget()->GetPosition();
if (DistanceSquared(m_Position, Goal) <= leash_distance) { if (DistanceSquared(m_Position, Goal) <= leash_distance) {
RunTo(Goal.x, Goal.y, Goal.z); RunTo(Goal.x, Goal.y, Goal.z);
SetCombatOutOfRangeJitter();
} else { } else {
WipeHateList(); WipeHateList();
SetTarget(nullptr); SetTarget(nullptr);
if (HasControllablePet(BotAnimEmpathy::BackOff)) { if (
HasPet() &&
(
GetClass() != Class::Enchanter ||
GetPet()->GetPetType() != petAnimation ||
GetAA(aaAnimationEmpathy) >= 2
)
) {
GetPet()->WipeHateList(); GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr); GetPet()->SetTarget(nullptr);
} }
@@ -3115,8 +3130,8 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage(); bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
if (IsTaunting()) { // Taunting bots if (IsTaunting()) { // Taunting bots
o.melee_distance_min = o.melee_distance_max * 0.25f; o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * 0.45f; o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
} }
else if (IsBotRanged()) { // Archers/Throwers else if (IsBotRanged()) { // Archers/Throwers
float min_distance = RuleI(Combat, MinRangedAttackDist); float min_distance = RuleI(Combat, MinRangedAttackDist);
@@ -3124,22 +3139,22 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
float desired_range = GetBotDistanceRanged(); float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
o.melee_distance_min = std::max(min_distance, (desired_range * 0.75f)); o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range); o.melee_distance = std::min(max_distance, desired_range);
} }
else if (input.stop_melee_level) { // Casters else if (input.stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged(); float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range * 0.75f)); o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range); o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
} }
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
o.melee_distance_min = o.melee_distance_max * 0.80f; o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * 0.95f; o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
} }
else { // Regular melee else { // Regular melee
o.melee_distance_min = o.melee_distance_max * 0.30f; o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * 0.65f; o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
} }
o.at_combat_range = (input.target_distance <= o.melee_distance); o.at_combat_range = (input.target_distance <= o.melee_distance);
@@ -3198,8 +3213,7 @@ bool Bot::IsValidTarget(
bot_owner->SetBotPulling(false); bot_owner->SetBotPulling(false);
if (GetPet()) { if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow); GetPet()->SetPetOrder(m_previous_pet_order);
GetPet()->CastToNPC()->SaveGuardSpot(true);
} }
} }
@@ -3233,8 +3247,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner)
bot_owner->SetBotPulling(false); bot_owner->SetBotPulling(false);
if (GetPet()) { if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow); GetPet()->SetPetOrder(m_previous_pet_order);
GetPet()->CastToNPC()->SaveGuardSpot(true);
} }
} }
@@ -3257,11 +3270,15 @@ bool Bot::TargetValidation(Mob* other) {
} }
bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) { bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) {
bool target_check = !GetTarget() || Distance(GetPosition(), GetTarget()->GetPosition()) <= 75.0f; auto engage_range = (GetBotDistanceRanged() < 30 ? 30 : GetBotDistanceRanged());
bool returned_check = (NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance());
if (target_check && returned_check) { // Once we're back, clear blocking flags so everyone else can join in 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
WipeHateList(); WipeHateList();
SetTarget(nullptr); SetTarget(nullptr);
SetPullingFlag(false); SetPullingFlag(false);
@@ -3269,10 +3286,9 @@ bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_dist
bot_owner->SetBotPulling(false); bot_owner->SetBotPulling(false);
if (GetPet()) { if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow); GetPet()->SetPetOrder(m_previous_pet_order);
GetPet()->CastToNPC()->SaveGuardSpot(true);
if (HasControllablePet(BotAnimEmpathy::BackOff)) { if (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1) {
GetPet()->WipeHateList(); GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr); GetPet()->SetTarget(nullptr);
} }
@@ -3313,8 +3329,7 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
bot_owner->SetBotPulling(false); bot_owner->SetBotPulling(false);
if (GetPet()) { if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow); GetPet()->SetPetOrder(m_previous_pet_order);
GetPet()->CastToNPC()->SaveGuardSpot(true);
} }
return false; return false;
@@ -3323,16 +3338,11 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
SetPullingFlag(false); SetPullingFlag(false);
SetReturningFlag(); SetReturningFlag();
Mob* my_pet = GetPet(); if (HasPet() &&
(GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
if (my_pet) { GetPet()->WipeHateList();
if (HasControllablePet(BotAnimEmpathy::BackOff)) { GetPet()->SetTarget(nullptr);
my_pet->WipeHateList();
my_pet->SetTarget(nullptr);
} else {
my_pet->AddToHateList(GetTarget(), 1);
my_pet->SetTarget(GetTarget());
}
} }
if (GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive)) { if (GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive)) {
@@ -3494,7 +3504,7 @@ Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint
void Bot::SetOwnerTarget(Client* bot_owner) { void Bot::SetOwnerTarget(Client* bot_owner) {
if (GetPet() && (PULLING_BOT || RETURNING_BOT)) { if (GetPet() && (PULLING_BOT || RETURNING_BOT)) {
GetPet()->SetPetOrder(SPO_Follow); GetPet()->SetPetOrder(m_previous_pet_order);
} }
SetAttackFlag(false); SetAttackFlag(false);
@@ -3514,7 +3524,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) {
SetTarget(attack_target); SetTarget(attack_target);
SetAttackingFlag(); SetAttackingFlag();
if (HasControllablePet(BotAnimEmpathy::Attack)) { if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
GetPet()->WipeHateList(); GetPet()->WipeHateList();
GetPet()->AddToHateList(attack_target, 1); GetPet()->AddToHateList(attack_target, 1);
GetPet()->SetTarget(attack_target); GetPet()->SetTarget(attack_target);
@@ -3532,21 +3542,20 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
SetReturningFlag(false); SetReturningFlag(false);
bot_owner->SetBotPulling(false); bot_owner->SetBotPulling(false);
if (GetPet()) {
GetPet()->SetPetOrder(SPO_Follow);
GetPet()->CastToNPC()->SaveGuardSpot(true);
}
if (NOT_HOLDING && NOT_PASSIVE) { if (NOT_HOLDING && NOT_PASSIVE) {
auto pull_target = bot_owner->GetTarget(); auto pull_target = bot_owner->GetTarget();
if (pull_target) { if (pull_target) {
if (raid) {
const auto msg = fmt::format("Pulling {}.", pull_target->GetCleanName());
raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100);
} else {
RaidGroupSay( RaidGroupSay(
fmt::format( fmt::format(
"Pulling {}.", "Pulling {}.",
pull_target->GetCleanName() pull_target->GetCleanName()
).c_str() ).c_str()
); );
}
InterruptSpell(); InterruptSpell();
WipeHateList(); WipeHateList();
@@ -3555,11 +3564,9 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
SetPullingFlag(); SetPullingFlag();
bot_owner->SetBotPulling(); bot_owner->SetBotPulling();
if (GetPet()) { if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
GetPet()->WipeHateList(); GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr); GetPet()->SetTarget(nullptr);
if (HasControllablePet(BotAnimEmpathy::Guard)) {
m_previous_pet_order = GetPet()->GetPetOrder(); m_previous_pet_order = GetPet()->GetPetOrder();
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition()); GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
GetPet()->SetPetOrder(SPO_Guard); GetPet()->SetPetOrder(SPO_Guard);
@@ -3567,7 +3574,6 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
} }
} }
} }
}
void Bot::Depop() { void Bot::Depop() {
WipeHateList(); WipeHateList();
@@ -8731,86 +8737,6 @@ bool Bot::CheckCampSpawnConditions(Client* c) {
return true; 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) void Bot::AddBotStartingItems(uint16 race_id, uint8 class_id)
{ {
if (!IsPlayerRace(race_id) || !IsPlayerClass(class_id)) { if (!IsPlayerRace(race_id) || !IsPlayerClass(class_id)) {
@@ -9812,7 +9738,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
) )
) )
&& &&
tar->CanBuffStack(spell_id, GetLevel(), true) < 0 tar->CanBuffStack(spell_id, GetLevel(), false) < 0
) { ) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
return false; return false;
@@ -11916,7 +11842,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false; return false;
case BotSpellTypes::ResistBuffs: case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs: case BotSpellTypes::PetResistBuffs:
if (IsResistanceBuffSpell(spell_id) && !IsEffectInSpell(spell_id, SE_DamageShield)) { if (IsResistanceBuffSpell(spell_id)) {
return true; return true;
} }
@@ -12040,171 +11966,193 @@ void Bot::SetCastedSpellType(uint16 spell_type) {
_castedSpellType = spell_type; _castedSpellType = spell_type;
} }
void Bot::DoFaceCheckWithJitter(Mob* tar) {
if (!tar) {
return;
}
if (IsMoving()) {
return;
}
SetCombatJitter();
if (!IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
return;
}
void Bot::DoFaceCheckNoJitter(Mob* tar) {
if (!tar) {
return;
}
if (IsMoving()) {
return;
}
if (!IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
return;
}
void Bot::RunToGoalWithJitter(glm::vec3 Goal) { void Bot::RunToGoalWithJitter(glm::vec3 Goal) {
RunTo(Goal.x, Goal.y, Goal.z); RunTo(Goal.x, Goal.y, Goal.z);
SetCombatJitter(); SetCombatJitter();
} }
void Bot::SetCombatJitter() { void Bot::SetCombatOutOfRangeJitter() {
SetCombatOutOfRangeJitterFlag();
if (RuleI(Bots, MaxJitterTimer) > 0) { if (RuleI(Bots, MaxJitterTimer) > 0) {
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true); m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
} }
} }
bool Bot::DoCombatPositioning(const CombatPositioningInput& input) void Bot::SetCombatJitter() {
{ SetCombatJitterFlag();
bool adjustment_needed = false;
bool is_too_close = input.tar_distance < input.melee_distance_min;
bool los_adjust = !HasRequiredLoSForPositioning(input.tar);
bool behind_mob_set = !input.stop_melee_level &&
!IsBotRanged() &&
GetBehindMob(); // Don't want casters or ranged to find positions behind the target.
bool adjustment_allowed = !IsMoving() &&
m_combat_jitter_timer.Check() &&
(!spellend_timer.Enabled() || GetClass() == Class::Bard);
if (RuleI(Bots, MaxJitterTimer) > 0) {
if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) { m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
FaceTarget(input.tar);
}
FindPositionInput find_position_input = {
.tar = input.tar,
.distance_min = input.melee_distance_min,
.distance_max = input.melee_distance_max,
.behind_only = behind_mob_set,
.front_only = IsTaunting(),
.bypass_los = false,
};
bool is_melee = (!input.stop_melee_level && !IsBotRanged());
if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
adjustment_needed =
(input.tar_distance <= input.melee_distance_max) &&
HasTargetReflection();
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_max + 1;
find_position_input.distance_max = input.melee_distance_max * 1.25f;
PlotBotPositionAroundTarget(find_position_input);
}
} else {
if (input.tar->IsFeared()) {
adjustment_needed = los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
} }
} }
else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
adjustment_needed =
(IsTaunting() && is_too_close) ||
los_adjust ||
(is_melee && !input.front_mob);
if (adjustment_needed && adjustment_allowed) { void Bot::DoCombatPositioning(
find_position_input.distance_min = input.melee_distance_min; Mob* tar,
find_position_input.distance_max = input.melee_distance; glm::vec3 Goal,
find_position_input.behind_only = false; bool stop_melee_level,
find_position_input.front_only = true; float tar_distance,
float melee_distance_min,
float melee_distance,
float melee_distance_max,
bool behind_mob,
bool front_mob
) {
if (!tar->IsFeared()) {
bool is_too_close = tar_distance < melee_distance_min;
bool los_adjust = !HasRequiredLoSForPositioning(tar);
PlotBotPositionAroundTarget(find_position_input); if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
if (rooted_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal);
return;
} }
} else {
if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage
adjustment_needed =
!behind_mob_set ||
is_too_close ||
los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = true;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
} }
} else { // Regular adjustments }
adjustment_needed = else {
if (IsTaunting()) { // Taunting adjustments
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
if (taunting_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else {
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
if (enraged_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else { // Regular adjustments
bool regular_adjust =
is_too_close || is_too_close ||
los_adjust || los_adjust ||
(behind_mob_set && !input.behind_mob); (!GetBehindMob() && !front_mob) ||
(GetBehindMob() && !behind_mob);
if (adjustment_needed && adjustment_allowed) { if (regular_adjust) {
find_position_input.distance_min = input.melee_distance_min; if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
find_position_input.distance_max = input.melee_distance; RunToGoalWithJitter(Goal);
PlotBotPositionAroundTarget(find_position_input); return;
}
}
} }
} }
} }
} }
if (!adjustment_needed && IsMoving()) { DoFaceCheckNoJitter(tar);
StopMoving();
} }
return adjustment_needed; bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) {
}
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
bool Result = false; bool Result = false;
if (input.tar) { if (target) {
glm::vec3 temp_goal(0, 0, input.tar->GetZ());
glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ());
float look_heading = 0; float look_heading = 0;
min_distance = min_distance;
max_distance = max_distance;
float temp_x = 0;
float temp_y = 0;
float temp_z = target->GetZ();
float best_z = 0; float best_z = 0;
auto offset = GetZOffset();
const float tar_x = target->GetX();
const float tar_y = target->GetY();
float tar_distance = 0; float tar_distance = 0;
float desired_angle = 0;
const float offset = GetZOffset(); glm::vec3 temp_z_Position;
glm::vec4 temp_m_Position;
const uint16 max_iterations_allowed = 50; const uint16 max_iterations_allowed = 50;
uint16 counter = 0; uint16 counter = 0;
while (counter < max_iterations_allowed) { while (counter < max_iterations_allowed) {
temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max); temp_x = tar_x + zone->random.Real(-max_distance, max_distance);
temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max); temp_y = tar_y + zone->random.Real(-max_distance, max_distance);
best_z = GetFixedZ(temp_goal);
temp_z_Position.x = temp_x;
temp_z_Position.y = temp_y;
temp_z_Position.z = temp_z;
best_z = GetFixedZ(temp_z_Position);
if (best_z != BEST_Z_INVALID) { if (best_z != BEST_Z_INVALID) {
temp_goal.z = best_z; temp_z = best_z;
} }
else { else {
counter++; counter++;
continue; continue;
} }
tar_distance = Distance(input.tar->GetPosition(), temp_goal); temp_m_Position.x = temp_x;
temp_m_Position.y = temp_y;
temp_m_Position.z = temp_z;
if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) { tar_distance = Distance(target->GetPosition(), temp_m_Position);
if (tar_distance > max_distance || tar_distance < min_distance) {
counter++; counter++;
continue; continue;
} }
if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) { if (front_only && !InFrontMob(target, temp_x, temp_y)) {
counter++; counter++;
continue; continue;
} }
else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) { else if (behind_only && !BehindMob(target, temp_x, temp_y)) {
counter++; counter++;
continue; continue;
} }
if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) { if (!bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, temp_x, temp_y, temp_z)) {
counter++; counter++;
continue; continue;
} }
@@ -12213,7 +12161,9 @@ bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
} }
if (Result) { if (Result) {
RunToGoalWithJitter(temp_goal); x_dest = temp_x;
y_dest = temp_y;
z_dest = temp_z;
} }
} }
@@ -13454,13 +13404,3 @@ bool Bot::IsValidBotStance(uint8 stance) {
return false; return false;
} }
bool Bot::HasControllablePet(uint8 ranks_required) {
if (!GetPet()) {
return false;
}
return GetClass() != Class::Enchanter ||
GetPet()->GetPetType() != petAnimation ||
GetAA(aaAnimationEmpathy) >= ranks_required;
}
+31 -14
View File
@@ -39,6 +39,9 @@
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 3000; // 3 seconds
constexpr uint32 MAG_EPIC_1_0 = 28034; constexpr uint32 MAG_EPIC_1_0 = 28034;
extern WorldServer worldserver; extern WorldServer worldserver;
@@ -229,10 +232,19 @@ static std::map<uint16, std::string> botSubType_names = {
{ CommandedSubTypes::Selo, "Selo" } { CommandedSubTypes::Selo, "Selo" }
}; };
namespace BotAnimEmpathy { struct CombatRangeInput {
constexpr uint8 Guard = 1; Mob* target;
constexpr uint8 Attack = 2; float target_distance;
constexpr uint8 BackOff = 3; bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
}; };
class Bot : public NPC { class Bot : public NPC {
@@ -565,7 +577,7 @@ public:
uint16 GetPetBotSpellType(uint16 spell_type); uint16 GetPetBotSpellType(uint16 spell_type);
// Movement checks // Movement checks
bool PlotBotPositionAroundTarget(const FindPositionInput& input); bool PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only = false, bool front_only = false, bool bypass_los = false);
std::vector<Mob*> GetSpellTargetList(bool entire_raid = false); std::vector<Mob*> GetSpellTargetList(bool entire_raid = false);
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; } void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; } std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
@@ -753,7 +765,7 @@ public:
static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); 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 BotSpell GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
static Mob* GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE); static Mob* GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_type, bool AE);
static BotSpell GetBestBotSpellForMez(Bot* caster, uint16 spell_type = BotSpellTypes::Mez); static BotSpell GetBestBotSpellForMez(Bot* caster, uint16 spell_type = BotSpellTypes::Mez);
static BotSpell GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet); static BotSpell GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet);
static std::string GetBotMagicianPetType(Bot* caster); static std::string GetBotMagicianPetType(Bot* caster);
@@ -792,7 +804,6 @@ public:
EQ::ItemInstance* GetBotItem(uint16 slot_id); EQ::ItemInstance* GetBotItem(uint16 slot_id);
bool GetSpawnStatus() { return _spawnStatus; } bool GetSpawnStatus() { return _spawnStatus; }
uint8 GetPetChooserID() { return _petChooserID; } uint8 GetPetChooserID() { return _petChooserID; }
bool HasControllablePet(uint8 ranks_required = 0);
bool IsBotRanged() { return _botRangedSetting; } bool IsBotRanged() { return _botRangedSetting; }
bool IsBotCharmer() { return _botCharmer; } bool IsBotCharmer() { return _botCharmer; }
bool IsBot() const override { return true; } bool IsBot() const override { return true; }
@@ -1091,8 +1102,15 @@ public:
bool CheckIfCasting(float fm_distance); bool CheckIfCasting(float fm_distance);
void HealRotationChecks(); void HealRotationChecks();
bool GetCombatJitterFlag() { return m_combat_jitter_flag; }
void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; }
bool GetCombatOutOfRangeJitterFlag() { return m_combat_out_of_range_jitter_flag; }
void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; }
void SetCombatJitter(); void SetCombatJitter();
bool DoCombatPositioning(const CombatPositioningInput& input); void SetCombatOutOfRangeJitter();
void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stop_melee_level, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behind_mob, bool front_mob);
void DoFaceCheckWithJitter(Mob* tar);
void DoFaceCheckNoJitter(Mob* tar);
void RunToGoalWithJitter(glm::vec3 Goal); void RunToGoalWithJitter(glm::vec3 Goal);
bool RequiresLoSForPositioning(); bool RequiresLoSForPositioning();
bool HasRequiredLoSForPositioning(Mob* tar); bool HasRequiredLoSForPositioning(Mob* tar);
@@ -1100,21 +1118,18 @@ public:
// Try Combat Methods // Try Combat Methods
bool TryEvade(Mob* tar); bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar); bool TryFacingTarget(Mob* tar);
bool TryPursueTarget(float leash_distance); bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
bool TryMeditate(); bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance); bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance); bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob); bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance); void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
bool TryBardMovementCasts(); bool TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false); bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack(); bool CheckDoubleRangedAttack();
// Public "Refactor" Methods // Public "Refactor" Methods
static bool CheckCampSpawnConditions(Client* c); 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: protected:
void BotMeditate(bool is_sitting); void BotMeditate(bool is_sitting);
@@ -1174,6 +1189,8 @@ private:
Timer m_auto_save_timer; Timer m_auto_save_timer;
Timer m_combat_jitter_timer; Timer m_combat_jitter_timer;
bool m_combat_jitter_flag;
bool m_combat_out_of_range_jitter_flag;
bool m_dirtyautohaters; bool m_dirtyautohaters;
bool m_guard_flag; bool m_guard_flag;
+69 -12
View File
@@ -468,7 +468,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
bool available_flag = false; bool available_flag = false;
!database.botdb.QueryNameAvailability(bot_name, available_flag); !database.botdb.QueryNameAvailablity(bot_name, available_flag);
if (!available_flag) { if (!available_flag) {
bot_owner->Message( bot_owner->Message(
@@ -517,31 +517,88 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
return bot_id; return bot_id;
} }
if (!Bot::CheckHighEnoughLevelForBots(bot_owner)) { auto bot_creation_limit = bot_owner->GetBotCreationLimit();
return bot_id; auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class);
}
if (!Bot::CheckHighEnoughLevelForBots(bot_owner, bot_class)) {
return bot_id;
}
uint32 bot_count = 0; uint32 bot_count = 0;
uint32 bot_class_count = 0; uint32 bot_class_count = 0;
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) { if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
bot_owner->Message(Chat::Yellow, "Failed to query bot count."); bot_owner->Message(Chat::Yellow, "Failed to query bot count.");
return bot_id; return bot_id;
} }
if (!Bot::CheckCreateLimit(bot_owner, bot_count)) { 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());
return bot_id; return bot_id;
} }
if (!Bot::CheckCreateLimit(bot_owner, bot_class_count, bot_class)) { 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());
return bot_id; 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); auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
if (!my_bot->Save()) { if (!my_bot->Save()) {
+119 -27
View File
@@ -130,7 +130,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
bool available_flag = false; bool available_flag = false;
!database.botdb.QueryNameAvailability(bot_name, available_flag); !database.botdb.QueryNameAvailablity(bot_name, available_flag);
if (!available_flag) { if (!available_flag) {
c->Message( c->Message(
@@ -144,25 +144,55 @@ void bot_command_clone(Client *c, const Seperator *sep)
return; return;
} }
auto bot_creation_limit = c->GetBotCreationLimit();
auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass());
uint32 bot_count = 0; uint32 bot_count = 0;
uint32 bot_class_count = 0; uint32 bot_class_count = 0;
if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) { if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) {
c->Message(Chat::Yellow, "Failed to query bot count."); c->Message(Chat::White, "Failed to query bot count.");
return; return;
} }
if (!Bot::CheckCreateLimit(c, bot_count)) { 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());
return; return;
} }
if (!Bot::CheckCreateLimit(c, bot_class_count, my_bot->GetClass())) { 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());
return; return;
} }
uint32 clone_id = 0; uint32 clone_id = 0;
if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) { if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
c->Message( c->Message(
Chat::White, Chat::White,
@@ -175,7 +205,6 @@ void bot_command_clone(Client *c, const Seperator *sep)
} }
int clone_stance = Stance::Passive; int clone_stance = Stance::Passive;
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) { if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
c->Message( c->Message(
Chat::White, Chat::White,
@@ -700,7 +729,6 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
return; return;
} }
int NO_BOT_LIMIT = -1;
bool Account = false; bool Account = false;
int seps = 1; int seps = 1;
uint32 filter_value[FilterCount]; uint32 filter_value[FilterCount];
@@ -839,7 +867,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) { for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) {
auto class_creation_limit = c->GetBotCreationLimit(class_id); auto class_creation_limit = c->GetBotCreationLimit(class_id);
if (class_creation_limit != NO_BOT_LIMIT && class_creation_limit != overall_bot_creation_limit) { if (class_creation_limit != overall_bot_creation_limit) {
c->Message( c->Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
@@ -910,7 +938,20 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return; return;
} }
if (!Bot::CheckHighEnoughLevelForBots(c)) { 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()
);
return; return;
} }
@@ -918,7 +959,27 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return; return;
} }
if (!Bot::CheckSpawnLimit(c)) { 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());
return; return;
} }
@@ -943,6 +1004,52 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return; 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) { if (!bot_id) {
c->Message( c->Message(
Chat::White, Chat::White,
@@ -954,14 +1061,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return; return;
} }
if (!Bot::CheckHighEnoughLevelForBots(c, bot_class)) {
return;
}
if (!Bot::CheckSpawnLimit(c, bot_class)) {
return;
}
if (entity_list.GetMobByBotID(bot_id)) { if (entity_list.GetMobByBotID(bot_id)) {
c->Message( c->Message(
Chat::White, Chat::White,
@@ -970,7 +1069,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
bot_name bot_name
).c_str() ).c_str()
); );
return; return;
} }
@@ -985,7 +1083,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
bot_id bot_id
).c_str() ).c_str()
); );
return; return;
} }
@@ -1000,7 +1097,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
); );
safe_delete(my_bot); safe_delete(my_bot);
return; return;
} }
@@ -1025,7 +1121,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
}; };
uint8 message_index = 0; uint8 message_index = 0;
if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) { if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) {
message_index = VALIDATECLASSID(my_bot->GetClass()); message_index = VALIDATECLASSID(my_bot->GetClass());
} }
@@ -1465,11 +1560,8 @@ void bot_command_summon(Client *c, const Seperator *sep)
continue; continue;
} }
if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
bot_iter->GetPet()->WipeHateList(); bot_iter->GetPet()->WipeHateList();
bot_iter->GetPet()->SetTarget(nullptr); bot_iter->GetPet()->SetTarget(nullptr);
}
bot_iter->GetPet()->Teleport(c->GetPosition()); bot_iter->GetPet()->Teleport(c->GetPosition());
} }
+1 -7
View File
@@ -48,10 +48,9 @@ void bot_command_click_item(Client* c, const Seperator* sep)
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
Mob* tar = c->GetTarget(); Mob* tar = c->GetTarget();
bool is_success = false;
for (auto my_bot : sbl) { for (auto my_bot : sbl) {
if (!my_bot->ValidStateCheck(c)) { if (my_bot->BotPassiveCheck()) {
continue; continue;
} }
@@ -69,11 +68,6 @@ void bot_command_click_item(Client* c, const Seperator* sep)
continue; continue;
} }
is_success = true;
my_bot->TryItemClick(slot_id); my_bot->TryItemClick(slot_id);
} }
if (!is_success) {
c->Message(Chat::Yellow, "None of your bots are capable of doing that currently.");
}
} }
+58 -27
View File
@@ -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")) { if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) {
return; 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]); 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; return;
} }
@@ -40,7 +40,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
if ( if (
!target_mob || !target_mob ||
target_mob->IsOfClientBotMerc() || target_mob == c ||
!c->IsAttackAllowed(target_mob) !c->IsAttackAllowed(target_mob)
) { ) {
c->Message(Chat::White, "Your current target is not attackable!"); c->Message(Chat::White, "Your current target is not attackable!");
@@ -55,16 +55,12 @@ void bot_command_pull(Client *c, const Seperator *sep)
} }
if (target_mob->IsNPC() && target_mob->GetHateList().size()) { if (target_mob->IsNPC() && target_mob->GetHateList().size()) {
c->Message(Chat::White, "Your current target is already engaged!");
c->Message(Chat::White, "Your current target is already engaged!");
return; return;
} }
Bot* bot_puller = nullptr; 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) { for (auto bot_iter : sbl) {
if (!bot_iter->ValidStateCheck(c)) { if (!bot_iter->ValidStateCheck(c)) {
@@ -76,36 +72,71 @@ void bot_command_pull(Client *c, const Seperator *sep)
case Class::Monk: case Class::Monk:
case Class::Bard: case Class::Bard:
case Class::Ranger: case Class::Ranger:
bot_iter->SetPullFlag(); bot_puller = bot_iter;
return;
default:
break; break;
}
if (!backup_puller_found) {
switch (bot_iter->GetClass()) {
case Class::Warrior: case Class::Warrior:
case Class::ShadowKnight: case Class::ShadowKnight:
case Class::Paladin: case Class::Paladin:
case Class::Berserker: case Class::Berserker:
case Class::Beastlord: case Class::Beastlord:
backup_bot_puller = bot_iter; if (!bot_puller) {
backup_puller_found = true;
break; 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: 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;
default:
continue;
}
bot_puller = bot_iter;
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) { if (bot_puller) {
bot_puller->SetPullFlag(); bot_puller->SetPullFlag();
-6
View File
@@ -17,12 +17,6 @@ void bot_command_release(Client *c, const Seperator *sep)
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
for (auto bot_iter : sbl) { for (auto bot_iter : sbl) {
bot_iter->WipeHateList(); 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); bot_iter->SetPauseAI(false);
} }
+3 -3
View File
@@ -182,7 +182,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
return true; return true;
} }
bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag) bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag)
{ {
if ( if (
bot_name.empty() || bot_name.empty() ||
@@ -207,7 +207,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
bot_count = BotDataRepository::Count( bot_count = BotDataRepository::Count(
database, database,
fmt::format( fmt::format(
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'", "`owner_id` = {}",
owner_id owner_id
) )
); );
@@ -216,7 +216,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
bot_class_count = BotDataRepository::Count( bot_class_count = BotDataRepository::Count(
database, database,
fmt::format( fmt::format(
"`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'", "`owner_id` = {} AND `class` = {}",
owner_id, owner_id,
class_id class_id
) )
+1 -1
View File
@@ -48,7 +48,7 @@ public:
/* Bot functions */ /* Bot functions */
bool QueryNameAvailability(const std::string& bot_name, bool& available_flag); bool QueryNameAvailablity(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 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); bool LoadBotsList(const uint32 owner_id, std::list<BotsAvailableList>& bots_list, bool by_account = false);
+2 -38
View File
@@ -21,7 +21,6 @@
#include "../common/types.h" #include "../common/types.h"
#include "../common/timer.h" #include "../common/timer.h"
#include "mob.h"
#include <sstream> #include <sstream>
@@ -68,7 +67,7 @@ struct BotSpellSetting {
struct BotSpells { struct BotSpells {
uint32 type; // 0 = never, must be one (and only one) of the defined values uint32 type; // 0 = never, must be one (and only one) of the defined values
uint16 spellid; // <= 0 = no spell int16 spellid; // <= 0 = no spell
int16 manacost; // -1 = use spdat, -2 = no cast time int16 manacost; // -1 = use spdat, -2 = no cast time
uint32 time_cancast; // when we can cast this spell next uint32 time_cancast; // when we can cast this spell next
int32 recast_delay; int32 recast_delay;
@@ -86,7 +85,7 @@ struct BotSpells {
struct BotSpells_wIndex { struct BotSpells_wIndex {
uint32 index; //index of AIBot_spells uint32 index; //index of AIBot_spells
uint32 type; // 0 = never, must be one (and only one) of the defined values uint32 type; // 0 = never, must be one (and only one) of the defined values
uint16 spellid; // <= 0 = no spell int16 spellid; // <= 0 = no spell
int16 manacost; // -1 = use spdat, -2 = no cast time int16 manacost; // -1 = use spdat, -2 = no cast time
uint32 time_cancast; // when we can cast this spell next uint32 time_cancast; // when we can cast this spell next
int32 recast_delay; int32 recast_delay;
@@ -152,39 +151,4 @@ struct BotSpellTypesByClass {
std::string description; std::string description;
}; };
struct CombatRangeInput {
Mob* target;
float target_distance;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
};
struct CombatPositioningInput {
Mob* tar;
bool stop_melee_level;
float tar_distance;
float melee_distance_min;
float melee_distance;
float melee_distance_max;
bool behind_mob;
bool front_mob;
};
struct FindPositionInput {
Mob* tar;
float distance_min;
float distance_max;
bool behind_only;
bool front_only;
bool bypass_los;
};
#endif // BOT_STRUCTS #endif // BOT_STRUCTS
+1 -1
View File
@@ -1459,7 +1459,7 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
return result; return result;
} }
Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE) { Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_type, bool AE) {
Mob* result = nullptr; Mob* result = nullptr;
if (caster && caster->GetOwner()) { if (caster && caster->GetOwner()) {
+16 -56
View File
@@ -38,11 +38,7 @@ inline void SetupStateZone()
SetupZone("soldungb"); SetupZone("soldungb");
zone->Process(); zone->Process();
// depop the zone controller // depop the zone controller
auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID); entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->Depop();
if (controller != nullptr) {
controller->Depop();
}
entity_list.MobProcess(); // process the depop entity_list.MobProcess(); // process the depop
} }
@@ -356,12 +352,8 @@ inline void TestSpawns()
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand); npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
} }
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries; bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
RunTest( RunTest("Spawns > All NPC's killed (0 NPCs) (115 Corpses)", true, condition);
fmt::format("Spawns > All NPC's killed (0 NPCs) ([{}] Corpses)", entries),
true,
condition
);
std::vector<uint32_t> spawn2_ids = {}; std::vector<uint32_t> spawn2_ids = {};
@@ -385,15 +377,11 @@ inline void TestSpawns()
zone->Shutdown(); zone->Shutdown();
SetupStateZone(); SetupStateZone();
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries; condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
if (!condition) { if (!condition) {
PrintEntityCounts(); PrintEntityCounts();
} }
RunTest( RunTest("Spawns > After restore (0 NPCs) (115 Corpses)", true, condition);
fmt::format("Spawns > After restore (0 NPCs) ([{}] Corpses)", entries),
true,
condition
);
for (auto &e: entity_list.GetCorpseList()) { for (auto &e: entity_list.GetCorpseList()) {
auto c = e.second; auto c = e.second;
@@ -417,15 +405,11 @@ inline void TestSpawns()
zone->Process(); zone->Process();
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == entries; condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 115;
if (!condition) { if (!condition) {
PrintEntityCounts(); PrintEntityCounts();
} }
RunTest( RunTest("Spawns > After respawn (115 NPCs) (115 Corpses)", true, condition);
fmt::format("Spawns > After respawn ([{}] NPCs) ([{}] Corpses)", entries, entries),
true,
condition
);
for (auto &c: entity_list.GetCorpseList()) { for (auto &c: entity_list.GetCorpseList()) {
c.second->DepopNPCCorpse(); c.second->DepopNPCCorpse();
@@ -433,15 +417,11 @@ inline void TestSpawns()
entity_list.CorpseProcess(); entity_list.CorpseProcess();
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 0; condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 0;
if (!condition) { if (!condition) {
PrintEntityCounts(); PrintEntityCounts();
} }
RunTest( RunTest("Spawns > After respawn (115 NPCs) (0 Corpses)", true, condition);
fmt::format("Spawns > After respawn ([{}] NPCs) (0 Corpses)", entries),
true,
condition
);
// lets set NPC's up with a predictable loottable for testing // lets set NPC's up with a predictable loottable for testing
uint32_t loottable_id = SeedLootTable(); uint32_t loottable_id = SeedLootTable();
@@ -482,28 +462,20 @@ inline void TestSpawns()
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand); npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
} }
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10; condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
if (!condition) { if (!condition) {
PrintEntityCounts(); PrintEntityCounts();
} }
RunTest( RunTest("Spawns > Kill 10 NPC's before save/restore (105 NPCs) (10 Corpses)", true, condition);
fmt::format("Spawns > Kill 10 NPC's before save/restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
true,
condition
);
zone->Shutdown(); zone->Shutdown();
SetupStateZone(); SetupStateZone();
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10; condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
if (!condition) { if (!condition) {
PrintEntityCounts(); PrintEntityCounts();
} }
RunTest( RunTest("Spawns > After restore (105 NPCs) (10 Corpses)", true, condition);
fmt::format("Spawns > After restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
true,
condition
);
// validate that all corpses and npc's have cloak of flames // validate that all corpses and npc's have cloak of flames
bool test_failed = false; bool test_failed = false;
@@ -600,14 +572,6 @@ inline void TestSpawns()
false false
); );
int max_respawn = 0;
const auto& l = RespawnTimesRepository::All(database);
for (const auto& e : l) {
if (e.duration > max_respawn) {
max_respawn = e.duration;
}
}
entity_list.MobProcess(); entity_list.MobProcess();
zone->Process(); zone->Process();
@@ -623,20 +587,16 @@ inline void TestSpawns()
npc->SetEntityVariable("previously_spawned", "true"); npc->SetEntityVariable("previously_spawned", "true");
} }
Timer::RollForward(max_respawn); // longest respawn time in zone Timer::RollForward(302401); // longest respawn time in zone
zone->Process(); zone->Process();
entity_list.MobProcess(); // processing depops entity_list.MobProcess(); // processing depops
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 10; condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 10;
if (!condition) { if (!condition) {
PrintEntityCounts(); PrintEntityCounts();
PrintZoneNpcs(); PrintZoneNpcs();
} }
RunTest( RunTest("Spawns > After respawn, ensure we have expected entity counts (115 NPCs) (10 Corpses)", true, condition);
fmt::format("Spawns > After respawn, ensure we have expected entity counts ([{}] NPCs) (10 Corpses)", entries),
true,
condition
);
entity_list.MobProcess(); // processing depops entity_list.MobProcess(); // processing depops
+1 -56
View File
@@ -995,8 +995,6 @@ bool Client::Save(uint8 iCommitNow) {
if(!ClientDataLoaded()) if(!ClientDataLoaded())
return false; return false;
BenchTimer timer;
/* Wrote current basics to PP for saves */ /* Wrote current basics to PP for saves */
if (!m_lock_save_position) { if (!m_lock_save_position) {
m_pp.x = m_Position.x; m_pp.x = m_Position.x;
@@ -1107,8 +1105,6 @@ bool Client::Save(uint8 iCommitNow) {
database.botdb.SaveBotSettings(this); database.botdb.SaveBotSettings(this);
} }
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
return true; return true;
} }
@@ -1213,58 +1209,6 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message); LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message);
if (RuleB(Chat, AlwaysCaptureCommandText)) {
if (message[0] == COMMAND_CHAR) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
else {
if (!RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
}
return;
}
if (message[0] == BOT_COMMAND_CHAR) {
if (RuleB(Bots, Enabled)) {
if (bot_command_dispatch(this, message) == -2) {
if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) {
int i = parse->EventPlayer(EVENT_BOT_COMMAND, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
else {
if (!RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
}
} else {
Message(Chat::Red, "Bots are disabled on this server.");
}
return;
}
}
if (targetname == nullptr) { if (targetname == nullptr) {
targetname = (!GetTarget()) ? "" : GetTarget()->GetName(); targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
} }
@@ -11479,6 +11423,7 @@ void Client::SaveSpells()
} }
CharacterSpellsRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID())); CharacterSpellsRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID()));
if (!character_spells.empty()) { if (!character_spells.empty()) {
CharacterSpellsRepository::InsertMany(database, character_spells); CharacterSpellsRepository::InsertMany(database, character_spells);
} }
+4 -11
View File
@@ -73,7 +73,6 @@ namespace EQ
#include "../common/guild_base.h" #include "../common/guild_base.h"
#include "../common/repositories/buyer_buy_lines_repository.h" #include "../common/repositories/buyer_buy_lines_repository.h"
#include "../common/repositories/character_evolving_items_repository.h" #include "../common/repositories/character_evolving_items_repository.h"
#include "../common/repositories/player_titlesets_repository.h"
#include "bot_structs.h" #include "bot_structs.h"
@@ -484,9 +483,6 @@ public:
virtual bool Save() { return Save(0); } virtual bool Save() { return Save(0); }
bool Save(uint8 iCommitNow); // 0 = delayed, 1=async now, 2=sync now bool Save(uint8 iCommitNow); // 0 = delayed, 1=async now, 2=sync now
inline void SaveCharacterData() {
database.SaveCharacterData(this, &m_pp, &m_epp);
};
/* New PP Save Functions */ /* New PP Save Functions */
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); } bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
@@ -1256,10 +1252,9 @@ public:
void ResetAllCastbarCooldowns(); void ResetAllCastbarCooldowns();
void ResetCastbarCooldownBySpellID(uint32 spell_id); void ResetCastbarCooldownBySpellID(uint32 spell_id);
bool CheckTitle(int title_set); bool CheckTitle(int titleset);
void EnableTitle(int title_set, bool insert = true); void EnableTitle(int titleset);
const std::vector<PlayerTitlesetsRepository::PlayerTitlesets>& GetTitles() { return m_player_title_sets; }; void RemoveTitle(int titleset);
void RemoveTitle(int title_set);
void EnteringMessages(Client* client); void EnteringMessages(Client* client);
void SendRules(); void SendRules();
@@ -2082,8 +2077,7 @@ private:
uint16 trader_id; uint16 trader_id;
uint16 customer_id; uint16 customer_id;
uint32 account_creation; uint32 account_creation;
bool first_login; uint8 firstlogon;
bool ingame;
uint32 mercid; // current merc uint32 mercid; // current merc
uint8 mercSlot; // selected merc slot uint8 mercSlot; // selected merc slot
time_t m_trader_transaction_date; time_t m_trader_transaction_date;
@@ -2260,7 +2254,6 @@ private:
bool m_exp_enabled; bool m_exp_enabled;
std::vector<EXPModifier> m_exp_modifiers; std::vector<EXPModifier> m_exp_modifiers;
std::vector<PlayerTitlesetsRepository::PlayerTitlesets> m_player_title_sets;
//Anti Spam Stuff //Anti Spam Stuff
Timer *KarmaUpdateTimer; Timer *KarmaUpdateTimer;
+36 -30
View File
@@ -1,8 +1,6 @@
#include "bot.h" #include "bot.h"
#include "client.h" #include "client.h"
#define NO_BOT_LIMIT -1;
bool Client::GetBotOption(BotOwnerOption boo) const { bool Client::GetBotOption(BotOwnerOption boo) const {
if (boo < _booCount) { if (boo < _booCount) {
return bot_owner_options[boo]; return bot_owner_options[boo];
@@ -20,25 +18,25 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) {
uint32 Client::GetBotCreationLimit(uint8 class_id) { uint32 Client::GetBotCreationLimit(uint8 class_id) {
uint32 bot_creation_limit = RuleI(Bots, CreationLimit); uint32 bot_creation_limit = RuleI(Bots, CreationLimit);
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) { if (Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
return RuleI(Bots, MinStatusBypassCreateLimit); return RuleI(Bots, MinStatusBypassCreateLimit);
} }
const auto bucket_name = fmt::format( const auto bucket_name = fmt::format(
"bot_creation_limit{}", "bot_creation_limit{}",
(
class_id && IsPlayerClass(class_id) ? class_id && IsPlayerClass(class_id) ?
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) : fmt::format(
"_{}",
Strings::ToLower(GetClassIDName(class_id))
) :
"" ""
)
); );
auto bucket_value = GetBucket(bucket_name); auto bucket_value = GetBucket(bucket_name);
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
bot_creation_limit = Strings::ToInt(bucket_value); bot_creation_limit = Strings::ToUnsignedInt(bucket_value);
}
if (class_id && bucket_value.empty()) {
bot_creation_limit = NO_BOT_LIMIT;
} }
return bot_creation_limit; return bot_creation_limit;
@@ -47,53 +45,61 @@ uint32 Client::GetBotCreationLimit(uint8 class_id) {
int Client::GetBotRequiredLevel(uint8 class_id) { int Client::GetBotRequiredLevel(uint8 class_id) {
int bot_character_level = RuleI(Bots, BotCharacterLevel); int bot_character_level = RuleI(Bots, BotCharacterLevel);
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassBotLevelRequirement)) {
return 0;
}
const auto bucket_name = fmt::format( const auto bucket_name = fmt::format(
"bot_required_level{}", "bot_required_level{}",
(
class_id && IsPlayerClass(class_id) ? class_id && IsPlayerClass(class_id) ?
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) : fmt::format(
"_{}",
Strings::ToLower(GetClassIDName(class_id))
) :
"" ""
)
); );
auto bucket_value = GetBucket(bucket_name); auto bucket_value = GetBucket(bucket_name);
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
bot_character_level = Strings::ToInt(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; return bot_character_level;
} }
int Client::GetBotSpawnLimit(uint8 class_id) int Client::GetBotSpawnLimit(uint8 class_id) {
{
int bot_spawn_limit = RuleI(Bots, SpawnLimit); int bot_spawn_limit = RuleI(Bots, SpawnLimit);
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) { if (Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
return RuleI(Bots, MinStatusBypassSpawnLimit); return RuleI(Bots, MinStatusBypassSpawnLimit);
} }
const auto bucket_name = fmt::format( const auto bucket_name = fmt::format(
"bot_spawn_limit{}", "bot_spawn_limit{}",
(
class_id && IsPlayerClass(class_id) ? class_id && IsPlayerClass(class_id) ?
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) : fmt::format(
"_{}",
Strings::ToLower(GetClassIDName(class_id))
) :
"" ""
)
); );
auto bucket_value = GetBucket(bucket_name); 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)) { if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
bot_spawn_limit = Strings::ToInt(bucket_value); bot_spawn_limit = Strings::ToInt(bucket_value);
return bot_spawn_limit;
}
} }
if (class_id && bucket_value.empty()) { if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
return NO_BOT_LIMIT; bot_spawn_limit = Strings::ToInt(bucket_value);
} }
if (RuleB(Bots, QuestableSpawnLimit)) { if (RuleB(Bots, QuestableSpawnLimit)) {
@@ -105,13 +111,14 @@ int Client::GetBotSpawnLimit(uint8 class_id)
auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls
if (results.Success() && results.RowCount()) { if (!results.Success() || !results.RowCount()) {
return bot_spawn_limit;
}
auto row = results.begin(); auto row = results.begin();
bot_spawn_limit = Strings::ToInt(row[0]); bot_spawn_limit = Strings::ToInt(row[0]);
} }
}
if (!class_id) {
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ","); const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
if (!zones_list.empty()) { if (!zones_list.empty()) {
@@ -155,7 +162,6 @@ int Client::GetBotSpawnLimit(uint8 class_id)
} }
} }
} }
}
return bot_spawn_limit; return bot_spawn_limit;
} }
+4 -21
View File
@@ -92,16 +92,6 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
continue; 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 type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type;
auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type; auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type;
@@ -293,31 +283,24 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app)
} }
std::unique_ptr<EQ::ItemInstance> const inst(database.CreateItem(item_id)); std::unique_ptr<EQ::ItemInstance> const inst(database.CreateItem(item_id));
if (!inst) {
return;
}
LogEvolveItemDetail( LogEvolveItemDetail(
"Character ID <green>[{}] requested to view final evolve item id <yellow>[{}] for evolve item id <yellow>[{}]", "Character ID <green>[{}] requested to view final evolve item id <yellow>[{}] for evolve item id <yellow>[{}]",
CharacterID(), CharacterID(),
item_id, item_id,
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id) evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id));
);
inst->SetEvolveProgression(100); inst->SetEvolveProgression(100);
if (inst) {
LogEvolveItemDetail( LogEvolveItemDetail(
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID() "Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID());
);
SendItemPacket(0, inst.get(), ItemPacketViewLink); SendItemPacket(0, inst.get(), ItemPacketViewLink);
} }
}
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
{ {
if (!inst) {
return false;
}
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) { if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
return false; return false;
} }
+40 -72
View File
@@ -807,44 +807,22 @@ void Client::CompleteConnect()
m_last_position_before_bulk_update = GetPosition(); m_last_position_before_bulk_update = GetPosition();
/* This sub event is for if a player logs in for the first time since entering world. */ /* This sub event is for if a player logs in for the first time since entering world. */
if (ingame) { if (firstlogon == 1) {
auto e = CharacterDataRepository::FindOne(
database,
CharacterID()
);
bool is_first_login = e.first_login == 0;
RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{}); RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{});
if (parse->PlayerHasQuestSub(EVENT_CONNECT)) { if (parse->PlayerHasQuestSub(EVENT_CONNECT)) {
const std::string& export_string = fmt::format( parse->EventPlayer(EVENT_CONNECT, this, "", 0);
"{} {} {}",
e.last_login,
time(nullptr) - e.last_login,
is_first_login ? 1 : 0
);
parse->EventPlayer(EVENT_CONNECT, this, export_string, 0);
} }
RecordStats(); /**
* Update last login since this doesn't get updated until a late save later so we can update online status
if (is_first_login) { */
e.first_login = time(nullptr); database.QueryDatabase(
TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); StringFormat(
BuyerRepository::DeleteBuyer(database, CharacterID()); "UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u",
LogTradingDetail(
"Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.",
CharacterID() 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)) { if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
InvokeChangePetName(false); InvokeChangePetName(false);
@@ -893,7 +871,7 @@ void Client::CompleteConnect()
entity_list.SendFindableNPCList(this); entity_list.SendFindableNPCList(this);
if (IsInAGuild()) { if (IsInAGuild()) {
if (ingame) { if (firstlogon == 1) {
guild_mgr.UpdateDbMemberOnline(CharacterID(), true); guild_mgr.UpdateDbMemberOnline(CharacterID(), true);
SendGuildMembersList(); SendGuildMembersList();
} }
@@ -1004,6 +982,7 @@ void Client::CompleteConnect()
safe_delete(p); safe_delete(p);
} }
RecordStats();
AutoGrantAAPoints(); AutoGrantAAPoints();
// set initial position for mob tracking // set initial position for mob tracking
@@ -1328,7 +1307,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
/* Load Character Data */ /* Load Character Data */
query = fmt::format( query = fmt::format(
"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` = {}", "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` = {}",
cid cid
); );
auto results = database.QueryDatabase(query); auto results = database.QueryDatabase(query);
@@ -1343,21 +1322,10 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
SetExtraHaste(Strings::ToInt(row[8]), false); SetExtraHaste(Strings::ToInt(row[8]), false);
SetIllusionBlock(Strings::ToBool(row[9])); SetIllusionBlock(Strings::ToBool(row[9]));
if (LFP) { if (LFP) { LFP = Strings::ToInt(row[0]); }
LFP = Strings::ToInt(row[0]); if (LFG) { LFG = Strings::ToInt(row[1]); }
} if (row[3])
firstlogon = Strings::ToInt(row[3]);
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)) if (RuleB(Character, SharedBankPlat))
@@ -1367,22 +1335,21 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
// set to full support in case they're a gm with items in disabled expansion slots...but, have their gm flag off... // set to full support in case they're a gm with items in disabled expansion slots...but, have their gm flag off...
// item loss will occur when they use the 'empty' slots, if this is not done // item loss will occur when they use the 'empty' slots, if this is not done
m_inv.SetGMInventory(true); m_inv.SetGMInventory(true);
loaditems = database.GetInventory(this); loaditems = database.GetInventory(this); /* Load Character Inventory */
database.LoadCharacterData(cid, &m_pp, &m_epp); database.LoadCharacterBandolier(cid, &m_pp); /* Load Character Bandolier */
database.LoadCharacterBandolier(cid, &m_pp); database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */
database.LoadCharacterBindPoint(cid, &m_pp); database.LoadCharacterMaterialColor(cid, &m_pp); /* Load Character Material */
database.LoadCharacterMaterialColor(cid, &m_pp); database.LoadCharacterPotionBelt(cid, &m_pp); /* Load Character Potion Belt */
database.LoadCharacterPotionBelt(cid, &m_pp); database.LoadCharacterCurrency(cid, &m_pp); /* Load Character Currency into PP */
database.LoadCharacterCurrency(cid, &m_pp); database.LoadCharacterData(cid, &m_pp, &m_epp); /* Load Character Data from DB into PP as well as E_PP */
database.LoadCharacterSkills(cid, &m_pp); database.LoadCharacterSkills(cid, &m_pp); /* Load Character Skills */
database.LoadCharacterInspectMessage(cid, &m_inspect_message); database.LoadCharacterInspectMessage(cid, &m_inspect_message); /* Load Character Inspect Message */
database.LoadCharacterSpellBook(cid, &m_pp); database.LoadCharacterSpellBook(cid, &m_pp); /* Load Character Spell Book */
database.LoadCharacterMemmedSpells(cid, &m_pp); database.LoadCharacterMemmedSpells(cid, &m_pp); /* Load Character Memorized Spells */
database.LoadCharacterLanguages(cid, &m_pp); database.LoadCharacterLanguages(cid, &m_pp); /* Load Character Languages */
database.LoadCharacterLeadershipAbilities(cid, &m_pp); database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */
database.LoadCharacterTribute(this); database.LoadCharacterTribute(this); /* Load CharacterTribute */
database.LoadCharacterEXPModifier(this); database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */
database.LoadCharacterTitleSets(this);
// this pattern is strange // this pattern is strange
// this is remnants of the old way of doing things // this is remnants of the old way of doing things
@@ -1554,7 +1521,10 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
} }
if (SPDAT_RECORDS > 0) { if (SPDAT_RECORDS > 0) {
UnmemSpellAll(false); for (uint32 z = 0; z < EQ::spells::SPELL_GEM_COUNT; z++) {
if (m_pp.mem_spells[z] >= (uint32)SPDAT_RECORDS)
UnmemSpell(z, false);
}
database.LoadBuffs(this); database.LoadBuffs(this);
uint32 max_slots = GetMaxBuffSlots(); uint32 max_slots = GetMaxBuffSlots();
@@ -1743,7 +1713,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
// Taunt persists when zoning on newer clients, overwrite default. // Taunt persists when zoning on newer clients, overwrite default.
if (m_ClientVersionBit & EQ::versions::maskUFAndLater) { if (m_ClientVersionBit & EQ::versions::maskUFAndLater) {
if (!ingame) { if (!firstlogon) {
pet->SetTaunting(m_petinfo.taunting); pet->SetTaunting(m_petinfo.taunting);
} }
} }
@@ -10239,13 +10209,13 @@ void Client::Handle_OP_LoadSpellSet(const EQApplicationPacket *app)
printf("Wrong size of LoadSpellSet_Struct! Expected: %zu, Got: %i\n", sizeof(LoadSpellSet_Struct), app->size); printf("Wrong size of LoadSpellSet_Struct! Expected: %zu, Got: %i\n", sizeof(LoadSpellSet_Struct), app->size);
return; return;
} }
int i;
LoadSpellSet_Struct* ss = (LoadSpellSet_Struct*)app->pBuffer; LoadSpellSet_Struct* ss = (LoadSpellSet_Struct*)app->pBuffer;
for (int i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) { for (i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) {
if (ss->spell[i] != 0xFFFFFFFF) { if (ss->spell[i] != 0xFFFFFFFF)
UnmemSpell(i, true); UnmemSpell(i, true);
} }
} }
}
void Client::Handle_OP_Logout(const EQApplicationPacket *app) void Client::Handle_OP_Logout(const EQApplicationPacket *app)
{ {
@@ -15597,9 +15567,7 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
switch (in->Code) { switch (in->Code) {
case ClickTrader: { case ClickTrader: {
LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code); LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code);
auto outapp = auto outapp = std::make_unique<EQApplicationPacket>(OP_TraderShop, sizeof(TraderClick_Struct));
std::make_unique<EQApplicationPacket>(OP_TraderShop, static_cast<uint32>(sizeof(TraderClick_Struct))
);
auto data = (TraderClick_Struct *) outapp->pBuffer; auto data = (TraderClick_Struct *) outapp->pBuffer;
auto trader_client = entity_list.GetClientByID(in->TraderID); auto trader_client = entity_list.GetClientByID(in->TraderID);
+1 -3
View File
@@ -217,8 +217,6 @@ bool Client::Process() {
GetMerc()->Depop(); GetMerc()->Depop();
} }
instalog = true; instalog = true;
camp_timer.Disable();
} }
if (IsStunned() && stunned_timer.Check()) if (IsStunned() && stunned_timer.Check())
@@ -727,7 +725,7 @@ void Client::OnDisconnect(bool hard_disconnect) {
o->trade->Reset(); o->trade->Reset();
} }
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. 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.
/* Remove from all proximities */ /* Remove from all proximities */
ClearAllProximities(); ClearAllProximities();
+1 -11
View File
@@ -246,7 +246,6 @@ 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("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("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("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_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
) { ) {
command_deinit(); command_deinit();
@@ -514,15 +513,7 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status)
parse->EventPlayer(EVENT_GM_COMMAND, c, message, 0); parse->EventPlayer(EVENT_GM_COMMAND, c, message, 0);
} }
bool log_command = true; if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && message != "#help") {
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{ auto e = PlayerEvent::GMCommandEvent{
.message = message, .message = message,
.target = c->GetTarget() ? c->GetTarget()->GetName() : "NONE" .target = c->GetTarget() ? c->GetTarget()->GetName() : "NONE"
@@ -937,7 +928,6 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/zone.cpp" #include "gm_commands/zone.cpp"
#include "gm_commands/zonebootup.cpp" #include "gm_commands/zonebootup.cpp"
#include "gm_commands/zoneshutdown.cpp" #include "gm_commands/zoneshutdown.cpp"
#include "gm_commands/zonevariable.cpp"
#include "gm_commands/zone_instance.cpp" #include "gm_commands/zone_instance.cpp"
#include "gm_commands/zone_shard.cpp" #include "gm_commands/zone_shard.cpp"
#include "gm_commands/zsave.cpp" #include "gm_commands/zsave.cpp"
-1
View File
@@ -198,7 +198,6 @@ void command_zone_instance(Client *c, const Seperator *sep);
void command_zone_shard(Client *c, const Seperator *sep); void command_zone_shard(Client *c, const Seperator *sep);
void command_zonebootup(Client *c, const Seperator *sep); void command_zonebootup(Client *c, const Seperator *sep);
void command_zoneshutdown(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); void command_zsave(Client *c, const Seperator *sep);
#include "bot.h" #include "bot.h"
+1 -3
View File
@@ -1,7 +1,6 @@
#include "data_bucket.h" #include "data_bucket.h"
#include "zonedb.h" #include "zonedb.h"
#include "mob.h" #include "mob.h"
#include "client.h"
#include "worldserver.h" #include "worldserver.h"
#include <ctime> #include <ctime>
#include <cctype> #include <cctype>
@@ -360,8 +359,7 @@ bool DataBucket::GetDataBuckets(Mob *mob)
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id}); BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
} }
else if (mob->IsClient()) { else if (mob->IsClient()) {
uint32 account_id = mob->CastToClient()->AccountID(); BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id});
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id}); BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
} }
+2 -2
View File
@@ -1125,8 +1125,8 @@ void EntityList::AESpell(
RuleI(Range, MobCloseScanDistance), RuleI(Range, MobCloseScanDistance),
distance distance
); );
auto list = caster_mob->GetCloseMobList(distance);
for (auto& it: list) { for (auto& it: caster_mob->GetCloseMobList(distance)) {
current_mob = it.second; current_mob = it.second;
if (!current_mob) { if (!current_mob) {
continue; continue;
-8
View File
@@ -2543,14 +2543,6 @@ void PerlembParser::ExportEventVariables(
break; 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: { default: {
break; break;
} }
-29
View File
@@ -505,9 +505,6 @@ void EntityList::MobProcess()
zone->GetSecondsBeforeIdle(), zone->GetSecondsBeforeIdle(),
zone->GetSecondsBeforeIdle() != 1 ? "s" : "" zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
); );
CheckToClearTraderAndBuyerTables();
mob_settle_timer->Disable(); mob_settle_timer->Disable();
} }
@@ -3162,13 +3159,6 @@ void EntityList::Depop(bool StartSpawnTimer)
UpdateFindableNPCState(pnpc, true); UpdateFindableNPCState(pnpc, true);
} }
// Depop below will eventually remove this npc from the entity list
// but that can happen AFTER we've already tried to spawn its replacement.
// So go ahead and remove it from the limits so it doesn't count.
if (npc_limit_list.count(pnpc->GetID())) {
npc_limit_list.erase(pnpc->GetID());
}
pnpc->WipeHateList(); pnpc->WipeHateList();
pnpc->Depop(StartSpawnTimer); pnpc->Depop(StartSpawnTimer);
} }
@@ -6012,22 +6002,3 @@ void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
c->SetDecayTimer(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()
);
}
}
-1
View File
@@ -581,7 +581,6 @@ public:
void SendMerchantEnd(Mob* merchant); void SendMerchantEnd(Mob* merchant);
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false); void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
void RestoreCorpse(NPC* npc, uint32_t decay_time); void RestoreCorpse(NPC* npc, uint32_t decay_time);
void CheckToClearTraderAndBuyerTables();
protected: protected:
friend class Zone; friend class Zone;
-18
View File
@@ -1693,24 +1693,6 @@ void command_npcedit(Client *c, const Seperator *sep)
c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID"); c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID");
return; 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 { } else {
SendNPCEditSubCommands(c); SendNPCEditSubCommands(c);
return; return;
-5
View File
@@ -8,11 +8,6 @@ extern WorldServer worldserver;
void command_task(Client *c, const Seperator *sep) 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; const int arguments = sep->argnum;
if (!arguments) { if (!arguments) {
c->Message(Chat::White, "Syntax: #task [subcommand]"); c->Message(Chat::White, "Syntax: #task [subcommand]");
-100
View File
@@ -1,100 +0,0 @@
#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());
}
}
-1
View File
@@ -406,7 +406,6 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack)
c.second->SendGuildDeletePacket(s->guild_id); c.second->SendGuildDeletePacket(s->guild_id);
c.second->RefreshGuildInfo(); c.second->RefreshGuildInfo();
c.second->MessageString(Chat::Guild, GUILD_DISBANDED); c.second->MessageString(Chat::Guild, GUILD_DISBANDED);
c.second->SendGuildList();
} }
} }
+2 -2
View File
@@ -1094,8 +1094,8 @@ int lua_faction_value() {
return quest_manager.FactionValue(); return quest_manager.FactionValue();
} }
bool lua_check_title(uint32 title_set) { void lua_check_title(uint32 title_set) {
return quest_manager.checktitle(title_set); quest_manager.checktitle(title_set);
} }
void lua_enable_title(uint32 title_set) { void lua_enable_title(uint32 title_set) {
-7
View File
@@ -945,12 +945,6 @@ bool Lua_NPC::IsResumedFromZoneSuspend()
return self->IsResumedFromZoneSuspend(); return self->IsResumedFromZoneSuspend();
} }
void Lua_NPC::SetNPCTintIndex(uint32 id)
{
Lua_Safe_Call_Void();
self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
}
luabind::scope lua_register_npc() { luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC") return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
@@ -1097,7 +1091,6 @@ luabind::scope lua_register_npc() {
.def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType) .def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType)
.def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro) .def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro)
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID) .def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
.def("SetNPCTintIndex", &Lua_NPC::SetNPCTintIndex)
.def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID) .def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID)
.def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum) .def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum)
.def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill) .def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill)
-2
View File
@@ -199,8 +199,6 @@ public:
void ReturnHandinItems(Lua_Client c); void ReturnHandinItems(Lua_Client c);
Lua_Spawn GetSpawn(lua_State* L); Lua_Spawn GetSpawn(lua_State* L);
bool IsResumedFromZoneSuspend(); bool IsResumedFromZoneSuspend();
void SetNPCTintIndex(uint32 id);
}; };
#endif #endif
-1
View File
@@ -353,7 +353,6 @@ LuaParser::LuaParser() {
PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss; PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss;
PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked; PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked;
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item; 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] = handle_item_click;
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;
-20
View File
@@ -1809,26 +1809,6 @@ 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 // Item
void handle_item_click( void handle_item_click(
QuestInterface *parse, QuestInterface *parse,
-9
View File
@@ -865,15 +865,6 @@ void handle_player_read_item(
std::vector<std::any> *extra_pointers 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 // Item
void handle_item_click( void handle_item_click(
QuestInterface *parse, QuestInterface *parse,
+3 -3
View File
@@ -727,10 +727,10 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
return self->GetBucketRemaining(bucket_name); return self->GetBucketRemaining(bucket_name);
} }
bool Lua_Zone::ClearVariables() void Lua_Zone::ClearVariables()
{ {
Lua_Safe_Call_Bool(); Lua_Safe_Call_Void();
return self->ClearVariables(); self->ClearVariables();
} }
bool Lua_Zone::DeleteVariable(const std::string& variable_name) bool Lua_Zone::DeleteVariable(const std::string& variable_name)
+1 -1
View File
@@ -141,7 +141,7 @@ public:
void SetInstanceTimeRemaining(uint32 time_remaining); void SetInstanceTimeRemaining(uint32 time_remaining);
void SetIsHotzone(bool is_hotzone); void SetIsHotzone(bool is_hotzone);
void ShowZoneGlobalLoot(Lua_Client c); void ShowZoneGlobalLoot(Lua_Client c);
bool ClearVariables(); void ClearVariables();
bool DeleteVariable(const std::string& variable_name); bool DeleteVariable(const std::string& variable_name);
std::string GetVariable(const std::string& variable_name); std::string GetVariable(const std::string& variable_name);
luabind::object GetVariables(lua_State* L); luabind::object GetVariables(lua_State* L);
-1
View File
@@ -678,7 +678,6 @@ int main(int argc, char **argv)
safe_delete(Config); safe_delete(Config);
if (zone != 0) { if (zone != 0) {
zone->SetSaveZoneState(false);
zone->Shutdown(true); zone->Shutdown(true);
} }
//Fix for Linux world server problem. //Fix for Linux world server problem.
+9 -10
View File
@@ -101,8 +101,7 @@ Mob::Mob(
bool in_always_aggro, bool in_always_aggro,
int32 in_heroic_strikethrough, int32 in_heroic_strikethrough,
bool in_keeps_sold_items, 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_timer(2000),
attack_dw_timer(2000), attack_dw_timer(2000),
@@ -290,7 +289,6 @@ Mob::Mob(
always_aggro = in_always_aggro; always_aggro = in_always_aggro;
heroic_strikethrough = in_heroic_strikethrough; heroic_strikethrough = in_heroic_strikethrough;
keeps_sold_items = in_keeps_sold_items; keeps_sold_items = in_keeps_sold_items;
m_npc_tint_id = npc_tint_id;
InitializeBuffSlots(); InitializeBuffSlots();
@@ -1304,7 +1302,6 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
ns->spawn.deity = deity; ns->spawn.deity = deity;
ns->spawn.animation = 0; ns->spawn.animation = 0;
ns->spawn.findable = findable?1:0; ns->spawn.findable = findable?1:0;
ns->spawn.npc_tint_id = GetNpcTintId();
UpdateActiveLight(); UpdateActiveLight();
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive]; ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];
@@ -1315,7 +1312,6 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
ns->spawn.NPC = IsClient() ? 0 : 1; ns->spawn.NPC = IsClient() ? 0 : 1;
ns->spawn.IsMercenary = IsMerc() ? 1 : 0; ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic! ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic!
ns->spawn.untargetable = IsTargetable();
ns->spawn.petOwnerId = ownerid; ns->spawn.petOwnerId = ownerid;
@@ -1702,6 +1698,13 @@ void Mob::StopMoving()
void Mob::StopMoving(float new_heading) void Mob::StopMoving(float new_heading)
{ {
if (IsBot()) {
auto bot = CastToBot();
bot->SetCombatJitterFlag(false);
bot->SetCombatOutOfRangeJitterFlag(false);
}
StopNavigation(); StopNavigation();
RotateTo(new_heading); RotateTo(new_heading);
@@ -4618,12 +4621,8 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id)
{ {
CastToClient()->GetPP().zone_id = zone_id; CastToClient()->GetPP().zone_id = zone_id;
CastToClient()->GetPP().zoneInstance = instance_id; CastToClient()->GetPP().zoneInstance = instance_id;
CastToClient()->SaveCharacterData();
}
if (!IsClient()) {
Save(); // bots or other things might be covered here for some reason
} }
Save();
} }
void Mob::Kill() { void Mob::Kill() {
+2 -5
View File
@@ -192,8 +192,7 @@ public:
bool in_always_aggros_foes, bool in_always_aggros_foes,
int32 in_heroic_strikethrough, int32 in_heroic_strikethrough,
bool keeps_sold_items, 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(); virtual ~Mob();
@@ -963,7 +962,7 @@ public:
uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id); uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id);
bool TryFadeEffect(int slot); bool TryFadeEffect(int slot);
void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value); void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value);
bool TrySpellEffectResist(uint16 spell_id); uint16 GetSpellEffectResistChance(uint16 spell_id);
int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false); int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false);
int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false); int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false);
int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated **** int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated ****
@@ -1067,7 +1066,6 @@ public:
void SendWearChangeAndLighting(int8 last_texture); void SendWearChangeAndLighting(int8 last_texture);
inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; } inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; }
bool UpdateActiveLight(); // returns true if change, false if no change bool UpdateActiveLight(); // returns true if change, false if no change
uint32 GetNpcTintId() { return m_npc_tint_id; }
EQ::LightSourceProfile* GetLightProfile() { return &m_Light; } EQ::LightSourceProfile* GetLightProfile() { return &m_Light; }
@@ -1599,7 +1597,6 @@ protected:
bool rare_spawn; bool rare_spawn;
int32 heroic_strikethrough; int32 heroic_strikethrough;
bool keeps_sold_items; bool keeps_sold_items;
uint32 m_npc_tint_id;
uint32 m_PlayerState; uint32 m_PlayerState;
uint32 GetPlayerState() { return m_PlayerState; } uint32 GetPlayerState() { return m_PlayerState; }
+9 -4
View File
@@ -933,11 +933,16 @@ void MobMovementManager::SendCommandToClients(
float MobMovementManager::FixHeading(float in) float MobMovementManager::FixHeading(float in)
{ {
int h = static_cast<int>(in) % 512; auto h = in;
if (h < 0) { while (h > 512.0) {
h += 512; h -= 512.0;
} }
return static_cast<float>(h);
while (h < 0.0) {
h += 512.0;
}
return h;
} }
void MobMovementManager::DumpStats(Client *client) void MobMovementManager::DumpStats(Client *client)
+1 -7
View File
@@ -128,8 +128,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
npc_type_data->always_aggro, npc_type_data->always_aggro,
npc_type_data->heroic_strikethrough, npc_type_data->heroic_strikethrough,
npc_type_data->keeps_sold_items, 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), attacked_timer(CombatEventTimer_expire),
swarm_timer(100), swarm_timer(100),
@@ -452,7 +451,6 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
raid_target = npc_type_data->raid_target; raid_target = npc_type_data->raid_target;
ignore_despawn = npc_type_data->ignore_despawn; ignore_despawn = npc_type_data->ignore_despawn;
m_targetable = !npc_type_data->untargetable; m_targetable = !npc_type_data->untargetable;
m_npc_tint_id = npc_type_data->m_npc_tint_id;
npc_scale_manager->ScaleNPC(this); npc_scale_manager->ScaleNPC(this);
@@ -1262,7 +1260,6 @@ uint32 ZoneDatabase::CreateNewNPCCommand(
e.see_improved_hide = n->SeeImprovedHide(); e.see_improved_hide = n->SeeImprovedHide();
e.see_invis = n->SeeInvisible(); e.see_invis = n->SeeInvisible();
e.see_invis_undead = n->SeeInvisibleUndead(); e.see_invis_undead = n->SeeInvisibleUndead();
e.npc_tint_id = n->GetNpcTintId();
e = NpcTypesRepository::InsertOne(*this, e); e = NpcTypesRepository::InsertOne(*this, e);
@@ -1402,7 +1399,6 @@ uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client* c, NPC* n)
e.loottable_id = n->GetLoottableID(); e.loottable_id = n->GetLoottableID();
e.merchant_id = n->MerchantType; e.merchant_id = n->MerchantType;
e.face = n->GetLuclinFace(); e.face = n->GetLuclinFace();
e.npc_tint_id = n->GetNpcTintId();
const int updated = NpcTypesRepository::UpdateOne(*this, e); const int updated = NpcTypesRepository::UpdateOne(*this, e);
@@ -1543,7 +1539,6 @@ uint32 ZoneDatabase::AddNPCTypes(
e.runspeed = n->GetRunspeed(); e.runspeed = n->GetRunspeed();
e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand); e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
e.sec_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); e = NpcTypesRepository::InsertOne(*this, e);
@@ -2177,7 +2172,6 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
ns->spawn.light = GetActiveLightType(); ns->spawn.light = GetActiveLightType();
ns->spawn.show_name = NPCTypedata->show_name; ns->spawn.show_name = NPCTypedata->show_name;
ns->spawn.trader = false; ns->spawn.trader = false;
ns->spawn.npc_tint_id = GetNpcTintId();
} }
void NPC::PetOnSpawn(NewSpawn_Struct* ns) void NPC::PetOnSpawn(NewSpawn_Struct* ns)

Some files were not shown because too many files have changed in this diff Show More