mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-22 16:28:28 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d18a30b047 | |||
| 359f90a987 |
-108
@@ -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
|
||||
|
||||
### API
|
||||
|
||||
@@ -1,147 +1,79 @@
|
||||
<h1 align="center">EQEmulator Server Platform</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/11942e15-b512-402d-a619-0543c7f1151e" style="border-radius: 10px">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<b>EverQuest Emulator (EQEmu) - A Fan-Made Project Honoring the Legendary MMORPG</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/eqemu/server/graphs/contributors"><img src="https://img.shields.io/github/contributors/eqemu/server" alt="Contributors"></a>
|
||||
<a href="https://discord.gg/QHsm7CD"><img src="https://img.shields.io/discord/212663220849213441?label=Discord&logo=discord&color=7289DA" alt="Discord"></a>
|
||||
<a href="https://docs.eqemu.io"><img src="https://img.shields.io/badge/docs-MkDocs%20Powered-blueviolet" alt="Docs"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/github/license/EQEmu/Server" alt="License"></a>
|
||||
<a href="https://github.com/eqemu/server/releases"><img src="https://img.shields.io/github/v/release/eqemu/server" alt="Latest Release"></a>
|
||||
<a href="https://github.com/EQEmu/Server/releases"><img src="https://img.shields.io/github/release-date/EQEmu/Server" alt="Release Date"></a>
|
||||
<img src="https://img.shields.io/github/downloads/eqemu/server/total.svg" alt="Github All Releases"></a>
|
||||
<a href="http://drone.akkadius.com/EQEmu/Server"><img src="http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg" alt="Build Status"></a>
|
||||
<img src="https://img.shields.io/github/issues-pr-closed/eqemu/server" alt="GitHub Issues or Pull Requests">
|
||||
<img src="https://img.shields.io/docker/pulls/akkadius/eqemu-server" alt="Docker Pulls">
|
||||
<a href="http://drone.akkadius.com/EQEmu/Server"><img src="http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg" alt="Build Status"></a> <img src="https://jb.gg/badges/official-plastic.svg" alt="Official">
|
||||
|
||||
</p>
|
||||
# EQEmulator Core Server
|
||||
| Drone (Linux x64) | Drone (Windows x64) |
|
||||
|:---:|:---:|
|
||||
|[](http://drone.akkadius.com/EQEmu/Server) |[](http://drone.akkadius.com/EQEmu/Server) |
|
||||
|
||||
***
|
||||
|
||||
<p align="center">
|
||||
EQEmulator is a <b>passion-driven</b>, <b>open source server emulator</b> project dedicated to preserving and celebrating the groundbreaking world of <b>EverQuest</b>, the massively multiplayer online role-playing game originally developed by <b>Verant Interactive</b> and <b>Sony Online Entertainment (now Daybreak Game Company)</b>.
|
||||
</p>
|
||||
**EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++**
|
||||
* MySQL/MariaDB is used as the database engine (over 200+ tables)
|
||||
* Perl and LUA are both supported scripting languages for NPC/Player/Quest oriented events
|
||||
* Open source database (Project EQ) has content up to expansion OoW (included in server installs)
|
||||
* Game server environments and databases can be heavily customized to create all new experiences
|
||||
* Hundreds of Quests/events created and maintained by Project EQ
|
||||
|
||||
<p align="center">
|
||||
For over two decades and continuing, EQEmulator has served as a <strong>fan tribute</strong>, providing tools and technology that allow players to explore, customize, and experience EverQuest’s iconic gameplay in new ways. This project exists solely out of <strong>deep admiration</strong> for the original developers, artists, designers, and visionaries who created one of the most influential online worlds of all time.
|
||||
</p>
|
||||
## Server Installs
|
||||
| |Windows|Linux|
|
||||
|:---:|:---:|:---:|
|
||||
|**Install Count**|||
|
||||
### > Windows
|
||||
|
||||
<p align="center">
|
||||
We do not claim ownership of EverQuest or its assets. <strong>All credit and respect belong to the original creators and Daybreak Game Company</strong>, whose work continues to inspire generations of players and developers alike.
|
||||
</p>
|
||||
* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-windows/)
|
||||
|
||||
<p align="center">
|
||||
EQEmulator has for over 20 years and always will be a <strong>fan-based, non-commercial open-source effort</strong> made by players, for players—preserving the legacy of EverQuest while empowering community-driven creativity, learning and joy that the game and its creators has so strongly inspired in us all.
|
||||
</p>
|
||||
### > Debian/Ubuntu/CentOS/Fedora
|
||||
|
||||
***
|
||||
* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-linux/)
|
||||
|
||||
<h3 align="center">
|
||||
Technical Overview & Reverse Engineering Effort
|
||||
</h1>
|
||||
* You can use curl or wget to kick off the installer (whichever your OS has)
|
||||
> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh
|
||||
|
||||
<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">
|
||||
💡 How We Did It
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/b6b48cf7-f64a-4497-9750-71f442a3d132" height="300px">
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<strong>Reverse Engineering</strong>
|
||||
Every system, packet, opcode, and game mechanic has been reconstructed through countless hours of live packet sniffing, client disassembly, and in-game experimentation by dedicated contributors over the years.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
No proprietary code or server sources were ever used.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
All implementations are the result of clean-room engineering.
|
||||
</p>
|
||||
|
||||
|
||||
<h1 align="center">
|
||||
🛠️ Technology Stack
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/df5ea809-86c5-439d-a8fa-651fb04ba477" style="border-radius: 10px">
|
||||
</p>
|
||||
|
||||
**C++ Core Engine**
|
||||
|
||||
* High-performance networking and gameplay logic built in C++
|
||||
* Cross-platform support for Linux and Windows
|
||||
|
||||
**MySQL / MariaDB Backend**
|
||||
|
||||
* Fully structured schema with over 200+ tables
|
||||
* Supports content customization, expansions, and custom worlds
|
||||
|
||||
**Scripting Engine**
|
||||
|
||||
* Native support for **Perl** and **Lua** scripting
|
||||
* Powerfully extendable for quests, NPC behaviors, and custom events
|
||||
|
||||
**Open Source Content Database**
|
||||
|
||||
* Includes ProjectEQ’s world data up through *Dragons of Norrath*
|
||||
* 100% customizable to create entirely new game worlds
|
||||
|
||||
<h1 align="center">
|
||||
🚀 Why It Matters
|
||||
</h1>
|
||||
|
||||
<p align="center">🧬 EQEmulator stands as a <strong>technical preservation project</strong>, ensuring that the magic of classic and custom EverQuest servers lives on for future generations of players, tinkerers, and game designers.
|
||||
</p>
|
||||
|
||||
> We humbly acknowledge and thank the original developers at **Verant Interactive** and **Sony Online Entertainment (now Daybreak Game Company)** for creating one of the most influential online experiences in gaming history.
|
||||
|
||||
<h1 align="center">
|
||||
🧑💻🖥️ Supported Clients
|
||||
</h1>
|
||||
## Supported Clients
|
||||
|
||||
|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">|
|
||||
|
||||
## 📚 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 |
|
||||
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
||||
| **EQEmulator Docs** | [](https://docs.eqemu.io) | [docs.eqemu.io](https://docs.eqemu.io/) |
|
||||
| **Discord Community**| [](https://discord.gg/QHsm7CD) | [Join Discord](https://discord.gg/QHsm7CD) |
|
||||
| **Latest Release** | [](https://github.com/eqemu/server/releases) <br> [](https://github.com/EQEmu/Server/releases) <br> [](https://github.com/eqemu/server/releases) | [View Releases](https://github.com/eqemu/server/releases) |
|
||||
| **License** | [](./LICENSE) | [View License](./LICENSE) |
|
||||
| **Build Status** | [](http://drone.akkadius.com/EQEmu/Server) | [View Build Status](http://drone.akkadius.com/EQEmu/Server) |
|
||||
| **Docker Pulls** | [](https://hub.docker.com/r/akkadius/eqemu-server) | [Docker Hub](https://hub.docker.com/r/akkadius/eqemu-server) |
|
||||
| **Contributions** | [](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) | [Closed PRs & Issues](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) |
|
||||
## Contributions <img src="http://image.flaticon.com/icons/png/512/25/25231.png" width="20">
|
||||
|
||||
## 🛠️ 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 |
|
||||
|--------------------|----------------------------------------------------------------------------------|
|
||||
| [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) | Official quests and event scripts for ProjectEQ |
|
||||
| [Maps](https://github.com/Akkadius/EQEmuMaps) | EQEmu-compatible zone maps |
|
||||
| [Installer Resources](https://github.com/Akkadius/EQEmuInstall) | Scripts and assets for setting up EQEmu servers |
|
||||
| [Zone Utilities](https://github.com/EQEmu/zone-utilities) | Utilities for parsing, rendering, and manipulating EQ zone files |
|
||||
## Resources
|
||||
- [EQEmulator Forums](http://www.eqemulator.org/forums)
|
||||
- [EQEmulator Wiki](https://docs.eqemu.io/)
|
||||
|
||||
## Related Repositories
|
||||
* [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests)
|
||||
* [Maps](https://github.com/Akkadius/EQEmuMaps)
|
||||
* [Installer Resources](https://github.com/Akkadius/EQEmuInstall)
|
||||
* [Zone Utilities](https://github.com/EQEmu/zone-utilities) - Various utilities and libraries for parsing, rendering and manipulating EQ Zone files.
|
||||
|
||||
## Other License Info
|
||||
|
||||
* The server code and utilities are released under **GPLv3**
|
||||
* We also include some small libraries for convienence that may be under different licensing
|
||||
* SocketLib - GPL LibXML
|
||||
* zlib - zlib license
|
||||
* MariaDB/MySQL - GPL
|
||||
* GPL Perl - GPL / ActiveState (under the assumption that this is a free project)
|
||||
* CPPUnit - GLP StringUtilities - Apache
|
||||
* LUA - MIT
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ SET(common_sources
|
||||
database.cpp
|
||||
database_instances.cpp
|
||||
database/database_update_manifest.cpp
|
||||
database/database_update_manifest_custom.cpp
|
||||
database/database_update_manifest_bots.cpp
|
||||
database/database_update.cpp
|
||||
dbcore.cpp
|
||||
@@ -672,7 +671,6 @@ SET(common_headers
|
||||
net/console_server_connection.h
|
||||
net/crc32.h
|
||||
net/daybreak_connection.h
|
||||
net/daybreak_pooling.h
|
||||
net/daybreak_structs.h
|
||||
net/dns.h
|
||||
net/endian.h
|
||||
@@ -684,7 +682,6 @@ SET(common_headers
|
||||
net/servertalk_server.h
|
||||
net/servertalk_server_connection.h
|
||||
net/tcp_connection.h
|
||||
net/tcp_connection_pooling.h
|
||||
net/tcp_server.h
|
||||
net/websocket_server.h
|
||||
net/websocket_server_connection.h
|
||||
@@ -745,7 +742,6 @@ SOURCE_GROUP(Net FILES
|
||||
net/crc32.h
|
||||
net/daybreak_connection.cpp
|
||||
net/daybreak_connection.h
|
||||
net/daybreak_pooling.h
|
||||
net/daybreak_structs.h
|
||||
net/dns.h
|
||||
net/endian.h
|
||||
@@ -766,7 +762,6 @@ SOURCE_GROUP(Net FILES
|
||||
net/servertalk_server_connection.h
|
||||
net/tcp_connection.cpp
|
||||
net/tcp_connection.h
|
||||
net/tcp_connection_pooling.h
|
||||
net/tcp_server.cpp
|
||||
net/tcp_server.h
|
||||
net/websocket_server.cpp
|
||||
|
||||
+1
-2
@@ -279,8 +279,7 @@ Bazaar::GetSearchResults(
|
||||
trader_items_ids,
|
||||
std::string(search.item_name),
|
||||
field_criteria_items,
|
||||
where_criteria_items,
|
||||
search.max_results
|
||||
where_criteria_items
|
||||
);
|
||||
|
||||
if (item_results.empty()) {
|
||||
|
||||
+7
-25
@@ -1095,13 +1095,13 @@ void Database::SetLFP(uint32 character_id, bool is_lfp)
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 ingame)
|
||||
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon)
|
||||
{
|
||||
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
||||
|
||||
e.ingame = ingame;
|
||||
e.lfg = is_lfg ? 1 : 0;
|
||||
e.lfp = is_lfp ? 1 : 0;
|
||||
e.firstlogon = first_logon;
|
||||
e.lfg = is_lfg ? 1 : 0;
|
||||
e.lfp = is_lfp ? 1 : 0;
|
||||
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
@@ -1115,11 +1115,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg)
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
void Database::SetIngame(uint32 character_id, uint8 ingame)
|
||||
void Database::SetFirstLogon(uint32 character_id, uint8 first_logon)
|
||||
{
|
||||
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
||||
|
||||
e.ingame = ingame;
|
||||
e.firstlogon = first_logon;
|
||||
|
||||
CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
@@ -1920,7 +1920,6 @@ bool Database::CopyCharacter(
|
||||
std::vector<std::string> tables_to_zero_id = {
|
||||
"keyring",
|
||||
"data_buckets",
|
||||
"character_evolving_items",
|
||||
"character_instance_safereturns",
|
||||
"character_expedition_lockouts",
|
||||
"character_instance_lockouts",
|
||||
@@ -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 = {};
|
||||
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;
|
||||
|
||||
for (auto row : results) {
|
||||
@@ -2049,18 +2036,13 @@ bool Database::CopyCharacter(
|
||||
LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied));
|
||||
|
||||
if (!insert.ErrorMessage().empty()) {
|
||||
LogError("Error copying table [{}] [{}]", table_name, insert.ErrorMessage());
|
||||
TransactionRollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto r = TransactionCommit();
|
||||
if (!r.Success()) {
|
||||
LogError("Transaction failed [{}] rolling back", r.ErrorMessage());
|
||||
return false;
|
||||
}
|
||||
TransactionCommit();
|
||||
|
||||
LogInfo(
|
||||
"Character [{}] copied to [{}] total rows [{}]",
|
||||
|
||||
+1
-1
@@ -263,7 +263,7 @@ public:
|
||||
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
|
||||
void ClearMerchantTemp();
|
||||
void ClearPTimers(uint32 character_id);
|
||||
void SetIngame(uint32 character_id, uint8 ingame);
|
||||
void SetFirstLogon(uint32 character_id, uint8 first_logon);
|
||||
void SetLFG(uint32 character_id, bool is_lfg);
|
||||
void SetLFP(uint32 character_id, bool is_lfp);
|
||||
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
|
||||
|
||||
@@ -50,7 +50,7 @@ bool DatabaseDumpService::IsMySQLInstalled()
|
||||
{
|
||||
std::string version_output = GetMySQLVersion();
|
||||
|
||||
return version_output.find("mysql") != std::string::npos && (version_output.find("Ver") != std::string::npos || version_output.find("from") != std::string::npos);
|
||||
return version_output.find("mysql") != std::string::npos && version_output.find("Ver") != std::string::npos;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,7 @@ private:
|
||||
bool dump_system_tables = false;
|
||||
bool dump_content_tables = false;
|
||||
bool dump_player_tables = false;
|
||||
bool dump_query_server_tables = false;
|
||||
bool dump_login_server_tables = false;
|
||||
bool dump_with_no_data = false;
|
||||
bool dump_table_lock = false;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../http/httplib.h"
|
||||
|
||||
#include "database_update_manifest.cpp"
|
||||
#include "database_update_manifest_custom.cpp"
|
||||
#include "database_update_manifest_bots.cpp"
|
||||
#include "database_dump_service.h"
|
||||
|
||||
@@ -15,7 +14,7 @@ constexpr int BREAK_LENGTH = 70;
|
||||
|
||||
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()) {
|
||||
LogError("Failed to read from [db_version] table!");
|
||||
return DatabaseVersion{};
|
||||
@@ -26,7 +25,6 @@ DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
|
||||
return DatabaseVersion{
|
||||
.server_database_version = Strings::ToInt(r[0]),
|
||||
.bots_database_version = Strings::ToInt(r[1]),
|
||||
.custom_database_version = Strings::ToInt(r[2]),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +33,6 @@ DatabaseVersion DatabaseUpdate::GetBinaryDatabaseVersions()
|
||||
return DatabaseVersion{
|
||||
.server_database_version = CURRENT_BINARY_DATABASE_VERSION,
|
||||
.bots_database_version = (RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0),
|
||||
.custom_database_version = CUSTOM_BINARY_DATABASE_VERSION,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +43,6 @@ constexpr int LOOK_BACK_AMOUNT = 10;
|
||||
// this check will take action
|
||||
void DatabaseUpdate::CheckDbUpdates()
|
||||
{
|
||||
InjectCustomVersionColumn();
|
||||
InjectBotsVersionColumn();
|
||||
auto v = GetDatabaseVersions();
|
||||
auto b = GetBinaryDatabaseVersions();
|
||||
@@ -63,15 +59,6 @@ void DatabaseUpdate::CheckDbUpdates()
|
||||
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", b.server_database_version));
|
||||
}
|
||||
|
||||
if (UpdateManifest(manifest_entries_custom, v.custom_database_version, b.custom_database_version)) {
|
||||
LogInfo(
|
||||
"Updates ran successfully, setting database version to [{}] from [{}]",
|
||||
b.custom_database_version,
|
||||
v.custom_database_version
|
||||
);
|
||||
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `custom_version` = {}", b.custom_database_version));
|
||||
}
|
||||
|
||||
if (b.bots_database_version > 0) {
|
||||
if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) {
|
||||
LogInfo(
|
||||
@@ -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("{}", 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
|
||||
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
|
||||
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;
|
||||
return server_up_to_date && bots_up_to_date;
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseUpdate::InjectCustomVersionColumn()
|
||||
{
|
||||
auto results = m_database->QueryDatabase("SHOW COLUMNS FROM `db_version` LIKE 'custom_version'");
|
||||
if (!results.Success() || results.RowCount() == 0) {
|
||||
LogInfo("Adding custom_version column to db_version table");
|
||||
m_database->QueryDatabase("ALTER TABLE `db_version` ADD COLUMN `custom_version` INT(11) UNSIGNED NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ struct ManifestEntry {
|
||||
struct DatabaseVersion {
|
||||
int server_database_version;
|
||||
int bots_database_version;
|
||||
int custom_database_version;
|
||||
};
|
||||
|
||||
class DatabaseUpdate {
|
||||
@@ -39,7 +38,6 @@ private:
|
||||
Database *m_content_database;
|
||||
static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b);
|
||||
void InjectBotsVersionColumn();
|
||||
void InjectCustomVersionColumn();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -6942,8 +6942,8 @@ CREATE TABLE `character_pet_name` (
|
||||
.version = 9310,
|
||||
.description = "2025_03_7_expand_horse_def.sql",
|
||||
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.condition = "missing",
|
||||
.match = "TINYINT(2)",
|
||||
.sql = R"(
|
||||
ALTER TABLE `horses`
|
||||
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
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
@@ -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
|
||||
// };
|
||||
@@ -80,7 +80,7 @@ namespace DatabaseSchema {
|
||||
{"guild_members", "char_id"},
|
||||
{"guilds", "id"},
|
||||
{"instance_list_player", "id"},
|
||||
{"inventory", "character_id"},
|
||||
{"inventory", "charid"},
|
||||
{"inventory_snapshots", "charid"},
|
||||
{"keyring", "char_id"},
|
||||
{"mail", "charid"},
|
||||
|
||||
+2
-2
@@ -189,9 +189,9 @@ void DBcore::TransactionBegin()
|
||||
QueryDatabase("START TRANSACTION");
|
||||
}
|
||||
|
||||
MySQLRequestResult DBcore::TransactionCommit()
|
||||
void DBcore::TransactionCommit()
|
||||
{
|
||||
return QueryDatabase("COMMIT");
|
||||
QueryDatabase("COMMIT");
|
||||
}
|
||||
|
||||
void DBcore::TransactionRollback()
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ public:
|
||||
MySQLRequestResult QueryDatabase(const std::string& query, bool retryOnFailureOnce = true);
|
||||
MySQLRequestResult QueryDatabaseMulti(const std::string &query);
|
||||
void TransactionBegin();
|
||||
MySQLRequestResult TransactionCommit();
|
||||
void TransactionCommit();
|
||||
void TransactionRollback();
|
||||
std::string Escape(const std::string& s);
|
||||
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
|
||||
|
||||
@@ -324,8 +324,6 @@ union
|
||||
bool guild_show;
|
||||
bool trader;
|
||||
bool buyer;
|
||||
bool untargetable;
|
||||
uint32 npc_tint_id;
|
||||
};
|
||||
|
||||
struct PlayerState_Struct {
|
||||
|
||||
+8
-27
@@ -682,33 +682,14 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings(bool silent_load)
|
||||
if (is_missing_in_database && !is_deprecated_category) {
|
||||
LogInfo("Automatically adding new log category [{}] ({})", Logs::LogCategoryName[i], i);
|
||||
|
||||
auto e = LogsysCategoriesRepository::NewEntity();
|
||||
e.log_category_id = i;
|
||||
e.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
|
||||
e.log_to_console = log_settings[i].log_to_console;
|
||||
e.log_to_gmsay = log_settings[i].log_to_gmsay;
|
||||
e.log_to_file = log_settings[i].log_to_file;
|
||||
e.log_to_discord = log_settings[i].log_to_discord;
|
||||
db_categories_to_add.emplace_back(e);
|
||||
}
|
||||
|
||||
// look to see if the category name is different in the database
|
||||
auto it = std::find_if(
|
||||
categories.begin(),
|
||||
categories.end(),
|
||||
[i](const auto &c) { return c.log_category_id == i; }
|
||||
);
|
||||
if (it != categories.end()) {
|
||||
if (it->log_category_description != Logs::LogCategoryName[i]) {
|
||||
LogInfo(
|
||||
"Updating log category [{}] ({}) to new name [{}]",
|
||||
it->log_category_description,
|
||||
i,
|
||||
Logs::LogCategoryName[i]
|
||||
);
|
||||
it->log_category_description = Logs::LogCategoryName[i];
|
||||
LogsysCategoriesRepository::ReplaceOne(*m_database, *it);
|
||||
}
|
||||
auto new_category = LogsysCategoriesRepository::NewEntity();
|
||||
new_category.log_category_id = i;
|
||||
new_category.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
|
||||
new_category.log_to_console = log_settings[i].log_to_console;
|
||||
new_category.log_to_gmsay = log_settings[i].log_to_gmsay;
|
||||
new_category.log_to_file = log_settings[i].log_to_file;
|
||||
new_category.log_to_discord = log_settings[i].log_to_discord;
|
||||
db_categories_to_add.emplace_back(new_category);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-13
@@ -74,7 +74,7 @@ namespace Logs {
|
||||
Spawns,
|
||||
Spells,
|
||||
Status, // deprecated
|
||||
TCPConnection, // deprecated
|
||||
TCPConnection,
|
||||
Tasks,
|
||||
Tradeskills,
|
||||
Trading,
|
||||
@@ -150,8 +150,6 @@ namespace Logs {
|
||||
BotSpellTypeChecks,
|
||||
NpcHandin,
|
||||
ZoneState,
|
||||
NetClient,
|
||||
NetTCP,
|
||||
MaxCategoryID /* Don't Remove this */
|
||||
};
|
||||
|
||||
@@ -185,7 +183,7 @@ namespace Logs {
|
||||
"Spawns",
|
||||
"Spells",
|
||||
"Status (Deprecated)",
|
||||
"TCP Connection (Deprecated)",
|
||||
"TCP Connection",
|
||||
"Tasks",
|
||||
"Tradeskills",
|
||||
"Trading",
|
||||
@@ -194,8 +192,8 @@ namespace Logs {
|
||||
"Web Interface (Deprecated)",
|
||||
"World Server (Deprecated)",
|
||||
"Zone Server (Deprecated)",
|
||||
"MySQL Error",
|
||||
"MySQL Query",
|
||||
"QueryErr",
|
||||
"Query",
|
||||
"Mercenaries",
|
||||
"Quest Debug",
|
||||
"Legacy Packet Logging (Deprecated)",
|
||||
@@ -211,15 +209,15 @@ namespace Logs {
|
||||
"Traps",
|
||||
"NPC Roam Box",
|
||||
"NPC Scaling",
|
||||
"Mob Appearance",
|
||||
"MobAppearance",
|
||||
"Info",
|
||||
"Warning",
|
||||
"Critical (Deprecated)",
|
||||
"Emergency (Deprecated)",
|
||||
"Alert (Deprecated)",
|
||||
"Notice (Deprecated)",
|
||||
"AI Scan Close",
|
||||
"AI Yell For Help",
|
||||
"AI Scan",
|
||||
"AI Yell",
|
||||
"AI CastBeneficial",
|
||||
"AOE Cast",
|
||||
"Entity Management",
|
||||
@@ -237,7 +235,7 @@ namespace Logs {
|
||||
"DialogueWindow",
|
||||
"HTTP",
|
||||
"Saylink",
|
||||
"Checksum Verification",
|
||||
"ChecksumVer",
|
||||
"CombatRecord",
|
||||
"Hate",
|
||||
"Discord",
|
||||
@@ -260,9 +258,7 @@ namespace Logs {
|
||||
"Bot Spell Checks",
|
||||
"Bot Spell Type Checks",
|
||||
"NpcHandin",
|
||||
"ZoneState",
|
||||
"Net Server <-> Client",
|
||||
"Net TCP"
|
||||
"ZoneState"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -261,6 +261,26 @@
|
||||
OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} 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 {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
|
||||
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__);\
|
||||
} 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 {\
|
||||
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
|
||||
@@ -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
|
||||
{
|
||||
if (!inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
inst.SetEvolveEquipped(false);
|
||||
if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
|
||||
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
|
||||
{
|
||||
if (!inst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto start_iterator = std::ranges::find_if(
|
||||
evolving_items_manager.GetEvolvingItemsCache().cbegin(),
|
||||
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
|
||||
{
|
||||
if (!inst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8 const current_level = inst.GetEvolveLvl();
|
||||
|
||||
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 ets{};
|
||||
if (!inst_in) {
|
||||
return ets;
|
||||
}
|
||||
|
||||
const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID());
|
||||
uint32 max_transfer_level = 0;
|
||||
int64 xp = in_xp;
|
||||
@@ -251,9 +235,6 @@ EvolveTransfer EvolvingItemsManager::DetermineTransferResults(
|
||||
)
|
||||
{
|
||||
EvolveTransfer ets{};
|
||||
if (!inst_from || !inst_to) {
|
||||
return ets;
|
||||
}
|
||||
|
||||
auto evolving_details_inst_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID());
|
||||
auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID());
|
||||
@@ -314,10 +295,6 @@ uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_i
|
||||
|
||||
void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e)
|
||||
{
|
||||
if (!inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.item_id = inst.GetID();
|
||||
e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string();
|
||||
e.level = inst.GetEvolveLvl();
|
||||
|
||||
@@ -53,11 +53,11 @@ public:
|
||||
ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id);
|
||||
EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to);
|
||||
EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp);
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return m_evolving_items_cache; }
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return evolving_items_cache; }
|
||||
std::vector<ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> GetEvolveIDItems(uint32 evolve_id);
|
||||
|
||||
private:
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> m_evolving_items_cache;
|
||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> evolving_items_cache;
|
||||
Database * m_db;
|
||||
Database * m_content_db;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
#include "daybreak_connection.h"
|
||||
#include "../event/event_loop.h"
|
||||
#include "../event/task.h"
|
||||
#include "../data_verification.h"
|
||||
#include "crc32.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
#include <zlib.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
// 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;
|
||||
#include <sstream>
|
||||
|
||||
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);
|
||||
int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
|
||||
|
||||
rc = uv_udp_recv_start(
|
||||
&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->len = suggested_size;
|
||||
return;
|
||||
}
|
||||
|
||||
static thread_local char temp_buf[65536];
|
||||
buf->base = temp_buf;
|
||||
buf->len = 65536;
|
||||
},
|
||||
rc = uv_udp_recv_start(&m_socket,
|
||||
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
|
||||
buf->base = new char[suggested_size];
|
||||
memset(buf->base, 0, suggested_size);
|
||||
buf->len = suggested_size;
|
||||
},
|
||||
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
|
||||
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
|
||||
if (nread < 0 || addr == nullptr) {
|
||||
delete[] buf->base;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,10 +70,7 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
|
||||
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
|
||||
auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
|
||||
c->ProcessPacket(endpoint, port, buf->base, nread);
|
||||
|
||||
if (buf->len > 65536) {
|
||||
delete[] buf->base;
|
||||
}
|
||||
delete[] buf->base;
|
||||
});
|
||||
|
||||
m_attached = loop;
|
||||
@@ -323,7 +310,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
|
||||
m_last_session_stats = Clock::now();
|
||||
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
|
||||
@@ -355,16 +342,16 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
|
||||
|
||||
void EQ::Net::DaybreakConnection::Close()
|
||||
{
|
||||
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) {
|
||||
if (m_status == StatusConnected) {
|
||||
FlushBuffer();
|
||||
SendDisconnect();
|
||||
}
|
||||
|
||||
if (m_status != StatusDisconnecting) {
|
||||
m_close_time = Clock::now();
|
||||
ChangeStatus(StatusDisconnecting);
|
||||
}
|
||||
else {
|
||||
ChangeStatus(StatusDisconnecting);
|
||||
}
|
||||
|
||||
ChangeStatus(StatusDisconnecting);
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
|
||||
@@ -647,7 +634,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
p.PutSerialize(0, reply);
|
||||
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;
|
||||
@@ -666,7 +653,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
m_max_packet_size = reply.max_packet_size;
|
||||
ChangeStatus(StatusConnected);
|
||||
|
||||
LogNetClient(
|
||||
LogNetcode(
|
||||
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
|
||||
m_connect_code,
|
||||
HostToNetwork(m_encode_key)
|
||||
@@ -795,7 +782,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
SendDisconnect();
|
||||
}
|
||||
|
||||
LogNetClient(
|
||||
LogNetcode(
|
||||
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
|
||||
m_connect_code,
|
||||
HostToNetwork(m_encode_key)
|
||||
@@ -865,7 +852,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1056,7 +1043,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
|
||||
return;
|
||||
}
|
||||
|
||||
static thread_local uint8_t new_buffer[4096];
|
||||
static uint8_t new_buffer[4096];
|
||||
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
||||
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)
|
||||
{
|
||||
static thread_local uint8_t new_buffer[2048] = { 0 };
|
||||
uint8_t new_buffer[2048] = { 0 };
|
||||
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
||||
uint32_t new_length = 0;
|
||||
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)
|
||||
{
|
||||
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 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) {
|
||||
Close();
|
||||
return;
|
||||
@@ -1138,18 +1142,19 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
total_size += e.second.packet.Length();
|
||||
}
|
||||
|
||||
LogNetClient(
|
||||
"Resending packets for stream [{}] packet count [{}] total packet size [{}]",
|
||||
LogNetcodeDetail(
|
||||
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
|
||||
stream,
|
||||
s->sent_packets.size(),
|
||||
total_size
|
||||
total_size,
|
||||
m_acked_since_last_resend
|
||||
);
|
||||
}
|
||||
|
||||
for (auto &e: s->sent_packets) {
|
||||
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_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 [{}]",
|
||||
m_resend_packets_sent,
|
||||
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_acked_since_last_resend = false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
iter = s->sent_packets.erase(iter);
|
||||
m_acked_since_last_resend = true;
|
||||
}
|
||||
else {
|
||||
++iter;
|
||||
@@ -1325,97 +1333,99 @@ void EQ::Net::DaybreakConnection::SendKeepAlive()
|
||||
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) {
|
||||
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
|
||||
if (new_budget <= 0.0) {
|
||||
m_stats.dropped_datarate_packets++;
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
m_outgoing_budget = new_budget;
|
||||
}
|
||||
}
|
||||
|
||||
m_last_send = Clock::now();
|
||||
|
||||
auto pooled_opt = send_buffer_pool.acquire();
|
||||
if (!pooled_opt) {
|
||||
m_stats.dropped_datarate_packets++;
|
||||
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];
|
||||
auto send_func = [](uv_udp_send_t* req, int status) {
|
||||
delete[](char*)req->data;
|
||||
delete req;
|
||||
};
|
||||
|
||||
if (PacketCanBeEncoded(p)) {
|
||||
|
||||
m_stats.bytes_before_encode += p.Length();
|
||||
|
||||
DynamicPacket out;
|
||||
out.PutPacket(0, p);
|
||||
|
||||
for (auto &m_encode_passe: m_encode_passes) {
|
||||
switch (m_encode_passe) {
|
||||
case EncodeCompression:
|
||||
if (out.GetInt8(0) == 0) {
|
||||
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
} else {
|
||||
Compress(out, 1, out.Length() - 1);
|
||||
}
|
||||
break;
|
||||
case EncodeXOR:
|
||||
if (out.GetInt8(0) == 0) {
|
||||
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
} else {
|
||||
Encode(out, 1, out.Length() - 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
switch (m_encode_passes[i]) {
|
||||
case EncodeCompression:
|
||||
if (out.GetInt8(0) == 0)
|
||||
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
else
|
||||
Compress(out, 1, out.Length() - 1);
|
||||
break;
|
||||
case EncodeXOR:
|
||||
if (out.GetInt8(0) == 0)
|
||||
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||
else
|
||||
Encode(out, 1, out.Length() - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
send_buffers[0] = uv_buf_init(data, out.Length());
|
||||
} else {
|
||||
memcpy(data, p.Data(), p.Length());
|
||||
send_buffers[0] = uv_buf_init(data, p.Length());
|
||||
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());
|
||||
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_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)) {
|
||||
send_buffer_pool.release(ctx);
|
||||
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;
|
||||
}
|
||||
|
||||
int send_result = uv_udp_send(
|
||||
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);
|
||||
}
|
||||
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "../random.h"
|
||||
#include "packet.h"
|
||||
#include "daybreak_structs.h"
|
||||
#include "daybreak_pooling.h"
|
||||
#include <uv.h>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
@@ -185,6 +184,7 @@ namespace EQ
|
||||
// resend tracking
|
||||
size_t m_resend_packets_sent = 0;
|
||||
size_t m_resend_bytes_sent = 0;
|
||||
bool m_acked_since_last_resend = false;
|
||||
|
||||
struct DaybreakSentPacket
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -171,4 +171,3 @@ namespace EQ
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,15 +62,15 @@ void EQ::Net::ServertalkClient::Connect()
|
||||
m_connecting = true;
|
||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
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;
|
||||
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->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();
|
||||
});
|
||||
|
||||
|
||||
@@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect()
|
||||
m_connecting = true;
|
||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||
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;
|
||||
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->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();
|
||||
});
|
||||
|
||||
@@ -131,7 +131,7 @@ void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer()
|
||||
}
|
||||
else {
|
||||
EQ::Net::StaticPacket p(&m_buffer[current + 4], length);
|
||||
|
||||
|
||||
auto cb = m_message_callbacks.find(opcode);
|
||||
if (cb != m_message_callbacks.end()) {
|
||||
cb->second(opcode, p);
|
||||
|
||||
+55
-108
@@ -1,8 +1,5 @@
|
||||
#include "tcp_connection.h"
|
||||
#include "../event/event_loop.h"
|
||||
#include <iostream>
|
||||
|
||||
WriteReqPool tcp_write_pool;
|
||||
|
||||
void on_close_handle(uv_handle_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()
|
||||
{
|
||||
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->len = suggested_size;
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
buf->base = new char[suggested_size];
|
||||
buf->len = suggested_size;
|
||||
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
|
||||
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) {
|
||||
auto *connection = (TCPConnection *) stream->data;
|
||||
TCPConnection *connection = (TCPConnection*)stream->data;
|
||||
|
||||
if (nread > 0) {
|
||||
connection->Read(buf->base, nread);
|
||||
}
|
||||
else if (nread == UV_EOF) {
|
||||
connection->Disconnect();
|
||||
}
|
||||
else if (nread < 0) {
|
||||
connection->Disconnect();
|
||||
}
|
||||
if (nread > 0) {
|
||||
connection->Read(buf->base, nread);
|
||||
|
||||
if (buf->len > 65536) {
|
||||
delete [] buf->base;
|
||||
if (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)
|
||||
@@ -134,92 +130,43 @@ void EQ::Net::TCPConnection::Read(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";
|
||||
void EQ::Net::TCPConnection::Write(const char *data, size_t count)
|
||||
{
|
||||
if (!m_socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count <= TCP_BUFFER_SIZE) {
|
||||
// Fast path: use pooled request with embedded buffer
|
||||
auto req_opt = tcp_write_pool.acquire();
|
||||
if (!req_opt) {
|
||||
std::cerr << "TCPConnection::Write - Out of write requests\n";
|
||||
return;
|
||||
struct WriteBaton
|
||||
{
|
||||
TCPConnection *connection;
|
||||
char *buffer;
|
||||
};
|
||||
|
||||
WriteBaton *baton = new WriteBaton;
|
||||
baton->connection = this;
|
||||
baton->buffer = new char[count];
|
||||
|
||||
uv_write_t *write_req = new uv_write_t;
|
||||
memset(write_req, 0, sizeof(uv_write_t));
|
||||
write_req->data = baton;
|
||||
uv_buf_t send_buffers[1];
|
||||
|
||||
memcpy(baton->buffer, data, count);
|
||||
send_buffers[0] = uv_buf_init(baton->buffer, count);
|
||||
|
||||
uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) {
|
||||
WriteBaton *baton = (WriteBaton*)req->data;
|
||||
delete[] baton->buffer;
|
||||
delete req;
|
||||
|
||||
if (status < 0) {
|
||||
baton->connection->Disconnect();
|
||||
}
|
||||
|
||||
TCPWriteReq* write_req = *req_opt;
|
||||
|
||||
// Fill buffer and set context
|
||||
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;
|
||||
write_req->data = heap_buffer;
|
||||
|
||||
uv_buf_t buf = uv_buf_init(heap_buffer, static_cast<unsigned int>(count));
|
||||
|
||||
int result = uv_write(
|
||||
write_req,
|
||||
reinterpret_cast<uv_stream_t*>(m_socket),
|
||||
&buf,
|
||||
1,
|
||||
[](uv_write_t* req, int status) {
|
||||
char* data = static_cast<char*>(req->data);
|
||||
delete[] data;
|
||||
delete req;
|
||||
|
||||
if (status < 0) {
|
||||
std::cerr << "uv_write (large) failed: " << uv_strerror(status) << std::endl;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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
|
||||
{
|
||||
sockaddr_storage addr;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_connection_pooling.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -17,7 +16,7 @@ namespace EQ
|
||||
~TCPConnection();
|
||||
|
||||
static void Connect(const std::string &addr, int port, bool ipv6, std::function<void(std::shared_ptr<TCPConnection>)> cb);
|
||||
|
||||
|
||||
void Start();
|
||||
void OnRead(std::function<void(TCPConnection*, const unsigned char *, size_t)> cb);
|
||||
void OnDisconnect(std::function<void(TCPConnection*)> cb);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -4688,7 +4688,7 @@ namespace RoF2
|
||||
Bitfields->linkdead = 0;
|
||||
Bitfields->showhelm = emu->showhelm;
|
||||
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->showname = ShowName;
|
||||
|
||||
@@ -4839,13 +4839,13 @@ namespace RoF2
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId);
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^
|
||||
|
||||
if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) ||
|
||||
(emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin)
|
||||
|
||||
+11
-11
@@ -4908,12 +4908,12 @@ namespace UF
|
||||
UFSlot = serverSlot - 2;
|
||||
}
|
||||
|
||||
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 11;
|
||||
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
||||
UFSlot = serverSlot + 11;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -4933,7 +4933,7 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -4941,7 +4941,7 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -4949,7 +4949,7 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -4991,11 +4991,11 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
@@ -5015,7 +5015,7 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -5023,7 +5023,7 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -5031,7 +5031,7 @@ namespace UF
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
+37
-24
@@ -22,7 +22,6 @@
|
||||
#include "ptimer.h"
|
||||
#include "database.h"
|
||||
#include "strings.h"
|
||||
#include "repositories/timers_repository.h"
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#include <winsock2.h>
|
||||
@@ -150,6 +149,27 @@ bool PersistentTimer::Load(Database *db) {
|
||||
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) {
|
||||
|
||||
std::string query = StringFormat("DELETE FROM timers "
|
||||
@@ -284,34 +304,27 @@ bool PTimerList::Load(Database *db) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PTimerList::Store(Database *db)
|
||||
{
|
||||
auto e = TimersRepository::NewEntity();
|
||||
std::vector<TimersRepository::Timers> entries;
|
||||
bool PTimerList::Store(Database *db) {
|
||||
#ifdef DEBUG_PTIMERS
|
||||
printf("Storing all timers for char %lu\n", (unsigned long)_char_id);
|
||||
#endif
|
||||
|
||||
for (auto &[type, timer] : _list) {
|
||||
if (!timer) {
|
||||
continue;
|
||||
std::map<pTimerType, PersistentTimer *>::iterator s;
|
||||
s = _list.begin();
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
++s;
|
||||
}
|
||||
|
||||
if (!entries.empty()) {
|
||||
Expired(db, false);
|
||||
TimersRepository::ReplaceMany(*db, entries);
|
||||
}
|
||||
|
||||
return true;
|
||||
return(res);
|
||||
}
|
||||
|
||||
|
||||
bool PTimerList::Clear(Database *db) {
|
||||
_list.clear();
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
inline bool Enabled() { return enabled; }
|
||||
|
||||
bool Load(Database *db);
|
||||
bool Store(Database *db);
|
||||
bool Clear(Database *db);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -115,8 +115,7 @@ public:
|
||||
uint8_t lfg;
|
||||
std::string mailkey;
|
||||
uint8_t xtargets;
|
||||
uint32_t first_login;
|
||||
uint8_t ingame;
|
||||
int8_t firstlogon;
|
||||
uint32_t e_aa_effects;
|
||||
uint32_t e_percent_to_aa;
|
||||
uint32_t e_expended_aa_spent;
|
||||
@@ -231,8 +230,7 @@ public:
|
||||
"lfg",
|
||||
"mailkey",
|
||||
"xtargets",
|
||||
"first_login",
|
||||
"ingame",
|
||||
"firstlogon",
|
||||
"e_aa_effects",
|
||||
"e_percent_to_aa",
|
||||
"e_expended_aa_spent",
|
||||
@@ -343,8 +341,7 @@ public:
|
||||
"lfg",
|
||||
"mailkey",
|
||||
"xtargets",
|
||||
"first_login",
|
||||
"ingame",
|
||||
"firstlogon",
|
||||
"e_aa_effects",
|
||||
"e_percent_to_aa",
|
||||
"e_expended_aa_spent",
|
||||
@@ -489,8 +486,7 @@ public:
|
||||
e.lfg = 0;
|
||||
e.mailkey = "";
|
||||
e.xtargets = 5;
|
||||
e.first_login = 0;
|
||||
e.ingame = 0;
|
||||
e.firstlogon = 0;
|
||||
e.e_aa_effects = 0;
|
||||
e.e_percent_to_aa = 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.mailkey = row[94] ? row[94] : "";
|
||||
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.ingame = row[97] ? static_cast<uint8_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -769,16 +764,15 @@ public:
|
||||
v.push_back(columns[93] + " = " + std::to_string(e.lfg));
|
||||
v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(columns[95] + " = " + std::to_string(e.xtargets));
|
||||
v.push_back(columns[96] + " = " + std::to_string(e.first_login));
|
||||
v.push_back(columns[97] + " = " + std::to_string(e.ingame));
|
||||
v.push_back(columns[98] + " = " + std::to_string(e.e_aa_effects));
|
||||
v.push_back(columns[99] + " = " + std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(columns[100] + " = " + std::to_string(e.e_expended_aa_spent));
|
||||
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_spent_old));
|
||||
v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old));
|
||||
v.push_back(columns[103] + " = " + std::to_string(e.e_last_invsnapshot));
|
||||
v.push_back(columns[104] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
|
||||
v.push_back(columns[105] + " = " + std::to_string(e.illusion_block));
|
||||
v.push_back(columns[96] + " = " + std::to_string(e.firstlogon));
|
||||
v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects));
|
||||
v.push_back(columns[98] + " = " + std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(columns[99] + " = " + std::to_string(e.e_expended_aa_spent));
|
||||
v.push_back(columns[100] + " = " + std::to_string(e.aa_points_spent_old));
|
||||
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old));
|
||||
v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot));
|
||||
v.push_back(columns[103] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
|
||||
v.push_back(columns[104] + " = " + std::to_string(e.illusion_block));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -896,8 +890,7 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
@@ -1031,8 +1024,7 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
@@ -1170,16 +1162,15 @@ public:
|
||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||
e.mailkey = row[94] ? row[94] : "";
|
||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||
e.first_login = row[96] ? static_cast<uint32_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||
e.ingame = row[97] ? static_cast<uint8_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
||||
|
||||
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.mailkey = row[94] ? row[94] : "";
|
||||
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.ingame = row[97] ? static_cast<uint8_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1480,8 +1470,7 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
@@ -1608,8 +1597,7 @@ public:
|
||||
v.push_back(std::to_string(e.lfg));
|
||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||
v.push_back(std::to_string(e.xtargets));
|
||||
v.push_back(std::to_string(e.first_login));
|
||||
v.push_back(std::to_string(e.ingame));
|
||||
v.push_back(std::to_string(e.firstlogon));
|
||||
v.push_back(std::to_string(e.e_aa_effects));
|
||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||
|
||||
@@ -149,7 +149,6 @@ public:
|
||||
uint8_t keeps_sold_items;
|
||||
uint8_t is_parcel_merchant;
|
||||
uint8_t multiquest_enabled;
|
||||
uint16_t npc_tint_id;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -290,7 +289,6 @@ public:
|
||||
"keeps_sold_items",
|
||||
"is_parcel_merchant",
|
||||
"multiquest_enabled",
|
||||
"npc_tint_id",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -427,7 +425,6 @@ public:
|
||||
"keeps_sold_items",
|
||||
"is_parcel_merchant",
|
||||
"multiquest_enabled",
|
||||
"npc_tint_id",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -598,7 +595,6 @@ public:
|
||||
e.keeps_sold_items = 1;
|
||||
e.is_parcel_merchant = 0;
|
||||
e.multiquest_enabled = 0;
|
||||
e.npc_tint_id = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -765,7 +761,6 @@ public:
|
||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -928,7 +923,6 @@ public:
|
||||
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
|
||||
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled));
|
||||
v.push_back(columns[130] + " = " + std::to_string(e.npc_tint_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -1080,7 +1074,6 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -1240,7 +1233,6 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
@@ -1404,7 +1396,6 @@ public:
|
||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1559,7 +1550,6 @@ public:
|
||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -1764,7 +1754,6 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -1917,7 +1906,6 @@ public:
|
||||
v.push_back(std::to_string(e.keeps_sold_items));
|
||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||
v.push_back(std::to_string(e.multiquest_enabled));
|
||||
v.push_back(std::to_string(e.npc_tint_id));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
@@ -106,8 +106,13 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buy_lines =
|
||||
BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id));
|
||||
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
||||
db,
|
||||
fmt::format("`buyer_id` = '{}'", buyer.front().id)
|
||||
);
|
||||
if (buy_lines.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> buy_line_ids{};
|
||||
for (auto const &bl: buy_lines) {
|
||||
@@ -116,65 +121,23 @@ public:
|
||||
|
||||
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
||||
if (buy_line_ids.empty()) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
BaseBuyerBuyLinesRepository::DeleteWhere(
|
||||
db, fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
db,
|
||||
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
BaseBuyerTradeItemsRepository::DeleteWhere(
|
||||
db, fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
db,
|
||||
fmt::format(
|
||||
"`buyer_buy_lines_id` IN({})",
|
||||
Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DeleteBuyers(Database &db, uint32 char_zone_id, uint32 char_zone_instance_id)
|
||||
{
|
||||
auto buyers = GetWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", char_zone_id, char_zone_instance_id
|
||||
)
|
||||
);
|
||||
if (buyers.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> buyer_ids{};
|
||||
std::vector<std::string> buy_line_ids{};
|
||||
|
||||
for (auto const &b: buyers) {
|
||||
buyer_ids.push_back(std::to_string(b.id));
|
||||
}
|
||||
|
||||
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
||||
db, fmt::format("`buyer_id` IN({})", Strings::Implode(", ", buyer_ids))
|
||||
);
|
||||
|
||||
if (!buy_lines.empty()) {
|
||||
for (auto const &bl: buy_lines) {
|
||||
buy_line_ids.push_back(std::to_string(bl.id));
|
||||
}
|
||||
}
|
||||
|
||||
DeleteWhere(db, fmt::format("`id` IN({});", Strings::Implode(", ", buyer_ids)));
|
||||
if (buy_line_ids.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseBuyerBuyLinesRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
BaseBuyerTradeItemsRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BUYER_REPOSITORY_H
|
||||
|
||||
@@ -54,30 +54,17 @@ public:
|
||||
{
|
||||
BulkTraders_Struct all_entries{};
|
||||
std::vector<DistinctTraders_Struct> distinct_traders;
|
||||
MySQLRequestResult results;
|
||||
|
||||
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
|
||||
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 "
|
||||
"WHERE t.char_zone_instance_id = {} "
|
||||
"ORDER BY t.char_zone_instance_id ASC "
|
||||
"LIMIT {}",
|
||||
char_zone_instance_id,
|
||||
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)
|
||||
);
|
||||
}
|
||||
auto 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 "
|
||||
"WHERE t.char_zone_instance_id = {} "
|
||||
"ORDER BY t.char_zone_instance_id ASC "
|
||||
"LIMIT {}",
|
||||
char_zone_instance_id,
|
||||
max_results)
|
||||
);
|
||||
|
||||
distinct_traders.reserve(results.RowCount());
|
||||
|
||||
|
||||
@@ -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 (!RuleValuesRepository::InjectRules(*db, injected_rule_entries)) {
|
||||
return false;
|
||||
|
||||
+6
-5
@@ -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, TradeskillUpTinkering, 2.0, "Tinkering skillup rate adjustment. Lower is faster")
|
||||
RULE_REAL(Character, TradeskillUpTailoring, 2.0, "Tailoring skillup rate adjustment. Lower is faster")
|
||||
RULE_REAL(Character, TradeskillUpMinChance, 2.5, "Determines the minimum percentage chance to gain a skill increase from a tradeskill. Cannot go below 2.5")
|
||||
RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%")
|
||||
RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars")
|
||||
RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres")
|
||||
@@ -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, 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_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(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_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_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_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.")
|
||||
@@ -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, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass create limit. Default 100.")
|
||||
RULE_INT(Bots, MinStatusBypassCreateLimit, 120, "Create limit with status bypass. Default 120.")
|
||||
RULE_INT(Bots, MinStatusToBypassBotLevelRequirement, 100, "Minimum status to bypass level requirement for bots. Default 100.")
|
||||
RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.")
|
||||
RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.")
|
||||
RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.")
|
||||
@@ -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, 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, AlwaysCaptureCommandText, false, "Consume command text (# and ^ by default), regardless of which channel it is sent to")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
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, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output")
|
||||
RULE_BOOL(Logging, PlayerEventsQSProcess, false, "Have query server process player events instead of world. Useful when wanting to use a dedicated server and database for processing player events on separate disk")
|
||||
RULE_STRING(Logging, PlayerEventsIgnoreGMCommands, "help,show", "This is a comma delimited list of commands to ignore when recording GM command player events.")
|
||||
RULE_INT(Logging, BatchPlayerEventProcessIntervalSeconds, 5, "This is the interval in which player events are processed in world or qs")
|
||||
RULE_INT(Logging, BatchPlayerEventProcessChunkSize, 10000, "This is the cap of events that can be inserted into the queue before a force flush. This is to keep from hitting MySQL max_allowed_packet and killing the connection")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
@@ -246,6 +246,8 @@
|
||||
|
||||
#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
|
||||
#define INSTRUMENT_HAND_DRUM 13000
|
||||
#define INSTRUMENT_WOODEN_FLUTE 13001
|
||||
|
||||
+2
-3
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// 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 COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,9 +42,8 @@
|
||||
* 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 CUSTOM_BINARY_DATABASE_VERSION 0
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "23.6.0",
|
||||
"version": "23.4.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/net v0.36.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
)
|
||||
|
||||
@@ -10,12 +10,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -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 --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 --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql"
|
||||
|
||||
#############################################
|
||||
# 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 --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 --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
|
||||
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
|
||||
|
||||
@@ -16,7 +16,7 @@ void WorldserverCLI::EtlGetSettings(int argc, char **argv, argh::parser &cmd, st
|
||||
auto event_settings = player_event_logs.GetSettings();
|
||||
auto etl_details = player_event_logs.GetEtlSettings();
|
||||
|
||||
for (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["enabled"] = event_settings[i].event_enabled ? true : false;
|
||||
player_events["retention"] = event_settings[i].retention_days;
|
||||
|
||||
+93
-136
@@ -42,16 +42,11 @@ extern ZSList zoneserver_list;
|
||||
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
|
||||
|
||||
ClientList::ClientList()
|
||||
: CLStale_timer(10000),
|
||||
m_poll_cache_timer(6000)
|
||||
: CLStale_timer(10000)
|
||||
{
|
||||
NextCLEID = 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() {
|
||||
@@ -62,10 +57,6 @@ void ClientList::Process() {
|
||||
if (CLStale_timer.Check())
|
||||
CLCheckStale();
|
||||
|
||||
if (m_poll_cache_timer.Check()) {
|
||||
RebuildZoneServerCaches();
|
||||
}
|
||||
|
||||
LinkedListIterator<Client*> iterator(list);
|
||||
|
||||
iterator.Reset();
|
||||
@@ -393,7 +384,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
|
||||
}
|
||||
else {
|
||||
cle->Update(zoneserver, scl);
|
||||
AddToZoneServerCaches(cle);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -468,7 +458,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
|
||||
);
|
||||
|
||||
clientlist.Insert(cle);
|
||||
AddToZoneServerCaches(cle);
|
||||
zoneserver->ChangeWID(scl->charid, cle->GetID());
|
||||
}
|
||||
|
||||
@@ -1619,7 +1608,7 @@ void ClientList::OnTick(EQ::Timer *t)
|
||||
/**
|
||||
* @param response
|
||||
*/
|
||||
void ClientList::GetClientList(Json::Value &response, bool full_list)
|
||||
void ClientList::GetClientList(Json::Value &response)
|
||||
{
|
||||
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
|
||||
|
||||
@@ -1630,68 +1619,62 @@ void ClientList::GetClientList(Json::Value &response, bool full_list)
|
||||
|
||||
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_name"] = cle->AccountName();
|
||||
row["character_id"] = cle->CharID();
|
||||
row["anon"] = cle->Anon();
|
||||
row["guild_id"] = cle->GuildID();
|
||||
|
||||
if (full_list) {
|
||||
row["loginserver_account_id"] = cle->LSAccountID();
|
||||
row["loginserver_id"] = cle->LSID();
|
||||
row["loginserver_name"] = cle->LSName();
|
||||
row["online"] = cle->Online();
|
||||
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();
|
||||
}
|
||||
row["account_id"] = cle->AccountID();
|
||||
row["account_name"] = cle->AccountName();
|
||||
row["admin"] = cle->Admin();
|
||||
row["id"] = cle->GetID();
|
||||
row["ip"] = cle->GetIP();
|
||||
row["loginserver_account_id"] = cle->LSAccountID();
|
||||
row["loginserver_id"] = cle->LSID();
|
||||
row["loginserver_name"] = cle->LSName();
|
||||
row["online"] = cle->Online();
|
||||
row["world_admin"] = cle->WorldAdmin();
|
||||
|
||||
auto server = cle->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_local_address"] = server->GetCLocalAddress();
|
||||
row["server"]["client_port"] = server->GetCPort();
|
||||
row["server"]["compile_time"] = server->GetCompileTime();
|
||||
row["server"]["instance_id"] = server->GetInstanceID();
|
||||
row["server"]["ip"] = server->GetIP();
|
||||
row["server"]["is_booting"] = server->IsBootingUp();
|
||||
row["server"]["launch_name"] = server->GetLaunchName();
|
||||
row["server"]["launched_name"] = server->GetLaunchedName();
|
||||
row["server"]["number_players"] = server->NumPlayers();
|
||||
row["server"]["port"] = server->GetPort();
|
||||
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
|
||||
row["server"]["static_zone"] = server->IsStaticZone();
|
||||
row["server"]["uui"] = server->GetUUID();
|
||||
}
|
||||
row["server"]["client_address"] = server->GetCAddress();
|
||||
row["server"]["client_local_address"] = server->GetCLocalAddress();
|
||||
row["server"]["client_port"] = server->GetCPort();
|
||||
row["server"]["compile_time"] = server->GetCompileTime();
|
||||
row["server"]["id"] = server->GetID();
|
||||
row["server"]["instance_id"] = server->GetInstanceID();
|
||||
row["server"]["ip"] = server->GetIP();
|
||||
row["server"]["is_booting"] = server->IsBootingUp();
|
||||
row["server"]["launch_name"] = server->GetLaunchName();
|
||||
row["server"]["launched_name"] = server->GetLaunchedName();
|
||||
row["server"]["number_players"] = server->NumPlayers();
|
||||
row["server"]["port"] = server->GetPort();
|
||||
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
|
||||
row["server"]["static_zone"] = server->IsStaticZone();
|
||||
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 {
|
||||
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);
|
||||
|
||||
@@ -1868,97 +1851,71 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
|
||||
return guild_members;
|
||||
}
|
||||
|
||||
void ClientList::RebuildZoneServerCaches()
|
||||
#include <unordered_set>
|
||||
|
||||
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
|
||||
{
|
||||
// Clear without freeing memory (buckets stay allocated)
|
||||
m_gm_zone_server_ids.clear();
|
||||
m_guild_zone_server_ids.clear();
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
LinkedListIterator<ClientListEntry*> iterator(clientlist);
|
||||
iterator.Reset();
|
||||
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry* cle = iterator.GetData();
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone || !cle->Server()) {
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
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);
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track guild zone servers
|
||||
if (cle->GuildID() > 0) {
|
||||
auto& guild_set = m_guild_zone_server_ids[cle->GuildID()];
|
||||
guild_set.insert(server_id);
|
||||
if (cle->GuildID() == guild_id) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
}
|
||||
|
||||
return zone_server_ids;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
|
||||
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
|
||||
{
|
||||
if (RuleB(World, RealTimeCalculateGuilds)) {
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->GuildID() == guild_id) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
return zone_server_ids;
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->Admin() > 0) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
}
|
||||
|
||||
auto it = m_guild_zone_server_ids.find(guild_id);
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t server_id = cle->Server()->GetID();
|
||||
|
||||
// Add GM zone server if applicable
|
||||
if (cle->GetGM()) {
|
||||
m_gm_zone_server_ids.insert(server_id);
|
||||
}
|
||||
|
||||
// Add guild zone server if applicable
|
||||
if (cle->GuildID() > 0) {
|
||||
m_guild_zone_server_ids[cle->GuildID()].insert(server_id);
|
||||
}
|
||||
return zone_server_ids;
|
||||
}
|
||||
|
||||
+3
-15
@@ -60,13 +60,15 @@ public:
|
||||
void CLCheckStale();
|
||||
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);
|
||||
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
|
||||
std::vector<uint32_t> GetZoneServersWithGMs();
|
||||
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
|
||||
bool IsAccountInGame(uint32 iLSID);
|
||||
|
||||
int GetClientCount();
|
||||
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 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(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:
|
||||
void OnTick(EQ::Timer *t);
|
||||
inline uint32 GetNextCLEID() { return NextCLEID++; }
|
||||
@@ -99,11 +92,6 @@ private:
|
||||
|
||||
|
||||
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_*/
|
||||
|
||||
@@ -111,17 +111,9 @@ void callGetDatabaseSchema(Json::Value &response)
|
||||
response.append(schema);
|
||||
}
|
||||
|
||||
void callGetClientList(Json::Value &response, const std::vector<std::string> &args)
|
||||
void callGetClientList(Json::Value &response)
|
||||
{
|
||||
// if args has "full"
|
||||
bool full_list = false;
|
||||
if (args.size() > 1) {
|
||||
if (args[1] == "full") {
|
||||
full_list = true;
|
||||
}
|
||||
}
|
||||
|
||||
client_list.GetClientList(response, full_list);
|
||||
client_list.GetClientList(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)
|
||||
{
|
||||
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()) {
|
||||
if (std::to_string(t) == command || Strings::ToLower(ServerReload::GetName(t)) == command) {
|
||||
message(r, fmt::format("Reloading [{}] globally", ServerReload::GetName(t)));
|
||||
LogInfo("Queueing reload of type [{}] to zones", ServerReload::GetName(t));
|
||||
zoneserver_list.QueueServerReload(t);
|
||||
zoneserver_list.SendServerReload(t, nullptr);
|
||||
}
|
||||
found_command = true;
|
||||
}
|
||||
@@ -189,7 +174,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
||||
callGetDatabaseSchema(r);
|
||||
}
|
||||
if (m == "get_client_list") {
|
||||
callGetClientList(r, args);
|
||||
callGetClientList(r);
|
||||
}
|
||||
if (m == "get_reload_types") {
|
||||
getReloadTypes(r);
|
||||
@@ -200,9 +185,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
||||
if (m == "get_guild_details") {
|
||||
callGetGuildDetails(r, args);
|
||||
}
|
||||
if (m == "get_server_counts") {
|
||||
getServerCounts(r, args);
|
||||
}
|
||||
if (m == "lock_status") {
|
||||
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)
|
||||
{
|
||||
|
||||
std::string command = !args[1].empty() ? args[1] : "";
|
||||
if (command.empty()) {
|
||||
return;
|
||||
|
||||
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
|
||||
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. ";
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
}
|
||||
|
||||
//broadcast this packet to all zones.
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "dynamic_zone_manager.h"
|
||||
#include "ucs.h"
|
||||
#include "clientlist.h"
|
||||
#include "../common/repositories/trader_repository.h"
|
||||
#include "../common/repositories/buyer_repository.h"
|
||||
|
||||
extern uint32 numzones;
|
||||
extern EQ::Random emu_random;
|
||||
@@ -86,8 +84,6 @@ void ZSList::Remove(const std::string &uuid)
|
||||
while (iter != zone_server_list.end()) {
|
||||
if ((*iter)->GetUUID().compare(uuid) == 0) {
|
||||
auto port = (*iter)->GetCPort();
|
||||
(*iter)->CheckToClearTraderAndBuyerTables();
|
||||
|
||||
zone_server_list.erase(iter);
|
||||
|
||||
if (port != 0) {
|
||||
@@ -132,16 +128,6 @@ void ZSList::Process() {
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (!m_queued_reloads.empty()) {
|
||||
m_queued_reloads_mutex.lock();
|
||||
for (auto &type : m_queued_reloads) {
|
||||
LogInfo("Sending reload of type [{}] to zones", ServerReload::GetName(type));
|
||||
SendServerReload(type, nullptr);
|
||||
}
|
||||
m_queued_reloads.clear();
|
||||
m_queued_reloads_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool ZSList::SendPacket(ServerPacket* pack) {
|
||||
@@ -1013,10 +999,3 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
void ZSList::QueueServerReload(ServerReload::Type &type)
|
||||
{
|
||||
m_queued_reloads_mutex.lock();
|
||||
m_queued_reloads.emplace_back(type);
|
||||
m_queued_reloads_mutex.unlock();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
class WorldTCPConnection;
|
||||
class ServerPacket;
|
||||
@@ -73,12 +72,8 @@ public:
|
||||
ZoneServer* FindByZoneID(uint32 ZoneID);
|
||||
|
||||
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const;
|
||||
inline uint32_t GetServerListCount() { return zone_server_list.size(); }
|
||||
void SendServerReload(ServerReload::Type type, uchar *packet = nullptr);
|
||||
std::mutex m_queued_reloads_mutex;
|
||||
std::vector<ServerReload::Type> m_queued_reloads = {};
|
||||
|
||||
void QueueServerReload(ServerReload::Type &type);
|
||||
private:
|
||||
void OnTick(EQ::Timer *t);
|
||||
uint32 NextID;
|
||||
|
||||
@@ -50,8 +50,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "../common/repositories/guild_tributes_repository.h"
|
||||
#include "../common/skill_caps.h"
|
||||
#include "../common/server_reload_types.h"
|
||||
#include "../common/repositories/trader_repository.h"
|
||||
#include "../common/repositories/buyer_repository.h"
|
||||
|
||||
extern ClientList client_list;
|
||||
extern GroupLFPList LFPGroupList;
|
||||
@@ -1862,19 +1860,3 @@ void ZoneServer::IncomingClient(Client* client) {
|
||||
SendPacket(pack);
|
||||
delete pack;
|
||||
}
|
||||
|
||||
void ZoneServer::CheckToClearTraderAndBuyerTables()
|
||||
{
|
||||
if (GetZoneID() == Zones::BAZAAR) {
|
||||
TraderRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format("`char_zone_id` = {} AND `char_zone_instance_id` = {}", GetZoneID(), GetInstanceID()
|
||||
)
|
||||
);
|
||||
BuyerRepository::DeleteBuyers(database, GetZoneID(), GetInstanceID());
|
||||
|
||||
LogTradingDetail(
|
||||
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]", GetZoneID(), GetInstanceID()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ public:
|
||||
inline const char* GetZoneName() const { return zone_name; }
|
||||
inline const char* GetZoneLongName() const { return long_name; }
|
||||
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
|
||||
void CheckToClearTraderAndBuyerTables();
|
||||
inline std::string GetCompileDate() const { return COMPILE_DATE; }
|
||||
const char* GetCompileTime() const{ return compiled; }
|
||||
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
|
||||
|
||||
+29
-42
@@ -3068,11 +3068,11 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
else
|
||||
SetAssistAggro(true);
|
||||
|
||||
bool was_engaged = IsEngaged();
|
||||
bool wasengaged = IsEngaged();
|
||||
Mob* owner = other->GetOwner();
|
||||
Mob* my_pet = GetPet();
|
||||
Mob* my_owner = GetOwner();
|
||||
Mob* target_mob = GetTarget();
|
||||
Mob* mypet = GetPet();
|
||||
Mob* myowner = GetOwner();
|
||||
Mob* targetmob = GetTarget();
|
||||
bool on_hatelist = CheckAggro(other);
|
||||
|
||||
AddRampage(other);
|
||||
@@ -3101,7 +3101,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
if (IsPet()) {
|
||||
if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list
|
||||
return;
|
||||
if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !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;
|
||||
}
|
||||
}
|
||||
@@ -3134,7 +3134,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
return;
|
||||
}
|
||||
|
||||
if (other == my_owner) {
|
||||
if (other == myowner) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3236,39 +3236,26 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
}
|
||||
}
|
||||
|
||||
if (my_pet) {
|
||||
bool aggro_immunity = my_pet->GetSpecialAbility(SpecialAbility::AggroImmunity);
|
||||
bool bot_aggro_immunity = IsBot() && my_pet->GetSpecialAbility(SpecialAbility::BotAggroImmunity);
|
||||
bool client_aggro_immunity = IsClient() && my_pet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
|
||||
bool npc_aggro_immunity = IsNPC() && my_pet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
|
||||
bool can_add_to_hatelist = !my_pet->IsFamiliar() &&
|
||||
!aggro_immunity &&
|
||||
!bot_aggro_immunity &&
|
||||
!client_aggro_immunity &&
|
||||
!npc_aggro_immunity;
|
||||
|
||||
if (can_add_to_hatelist) {
|
||||
bool bot_with_controllable_pet = IsBot() && CastToBot()->HasControllablePet(BotAnimEmpathy::Attack);
|
||||
|
||||
if (!IsBot() || bot_with_controllable_pet) {
|
||||
my_pet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
}
|
||||
if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it
|
||||
if (
|
||||
!mypet->IsFamiliar() &&
|
||||
!mypet->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
|
||||
!(IsBot() && mypet->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
|
||||
!(IsClient() && mypet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
|
||||
!(IsNPC() && mypet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
|
||||
) {
|
||||
mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
}
|
||||
}
|
||||
else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD
|
||||
if (my_owner->IsAIControlled()) {
|
||||
bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity);
|
||||
bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity);
|
||||
bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
|
||||
bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
|
||||
bool can_add_to_hatelist = !aggro_immunity &&
|
||||
!bot_aggro_immunity &&
|
||||
!client_aggro_immunity &&
|
||||
!npc_aggro_immunity;
|
||||
|
||||
if (can_add_to_hatelist) {
|
||||
my_owner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
}
|
||||
else if (myowner) { // I am a pet, add other to owner if it's NPC/LD
|
||||
if (
|
||||
myowner->IsAIControlled() &&
|
||||
!myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
|
||||
!(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
|
||||
!(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
|
||||
!(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
|
||||
) {
|
||||
myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3277,7 +3264,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
entity_list.AddTempPetsToHateList(this, other, bFrenzy);
|
||||
}
|
||||
|
||||
if (!was_engaged) {
|
||||
if (!wasengaged) {
|
||||
if (IsNPC() && other->IsClient() && other->CastToClient()) {
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) {
|
||||
parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0);
|
||||
@@ -4452,8 +4439,9 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
|
||||
if (IsValidSpell(spell_id) && !iBuffTic) {
|
||||
//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);
|
||||
}
|
||||
}
|
||||
else if (!IsValidSpell(spell_id))
|
||||
{
|
||||
@@ -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.
|
||||
- Only roots on determental spells can be broken by damage.
|
||||
- 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) {
|
||||
@@ -6690,9 +6679,7 @@ void Client::SetAttackTimer()
|
||||
else
|
||||
speed = static_cast<int>(speed + ((hhe / 100.0f) * delay));
|
||||
}
|
||||
|
||||
bool reinit = !TimerToUse->Enabled();
|
||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), reinit, reinit);
|
||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true);
|
||||
|
||||
if (i == EQ::invslot::slotPrimary) {
|
||||
primary_speed = speed;
|
||||
|
||||
+12
-9
@@ -1165,6 +1165,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
||||
}
|
||||
|
||||
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
|
||||
break;
|
||||
}
|
||||
@@ -2470,6 +2474,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
||||
|
||||
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
|
||||
break;
|
||||
}
|
||||
@@ -4682,7 +4689,11 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
|
||||
break;
|
||||
|
||||
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_itembonus) { itembonuses.ResistFearChance = effect_value; }
|
||||
break;
|
||||
@@ -5320,14 +5331,6 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
|
||||
spellbonuses.SEResist[e] = 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;
|
||||
}
|
||||
|
||||
+303
-363
@@ -245,8 +245,6 @@ Bot::Bot(
|
||||
|
||||
EquipBot();
|
||||
|
||||
m_combat_jitter_timer.Start();
|
||||
|
||||
if (GetClass() == Class::Rogue) {
|
||||
m_rogue_evade_timer.Start();
|
||||
}
|
||||
@@ -2102,6 +2100,10 @@ void Bot::SetGuardMode() {
|
||||
StopMoving();
|
||||
m_GuardPoint = GetPosition();
|
||||
SetGuardFlag();
|
||||
|
||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
GetPet()->StopMoving();
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::SetHoldMode() {
|
||||
@@ -2184,7 +2186,8 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -2214,6 +2217,8 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
//ALT COMBAT (ACQUIRE HATE)
|
||||
glm::vec3 Goal(0, 0, 0);
|
||||
|
||||
// We have aggro to choose from
|
||||
if (IsEngaged()) {
|
||||
if (rest_timer.Enabled()) {
|
||||
@@ -2266,7 +2271,7 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
// 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
|
||||
GetPet()->SetTarget(tar);
|
||||
}
|
||||
@@ -2280,23 +2285,23 @@ void Bot::AI_Process()
|
||||
}
|
||||
|
||||
// COMBAT RANGE CALCS
|
||||
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
||||
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||
|
||||
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
||||
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
|
||||
// Item variables
|
||||
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
|
||||
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
|
||||
|
||||
CombatRangeInput i = {
|
||||
.target = tar,
|
||||
.target_distance = tar_distance,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.p_item = p_item,
|
||||
.s_item = s_item
|
||||
CombatRangeInput input = {
|
||||
.target = tar,
|
||||
.target_distance = tar_distance,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.p_item = p_item,
|
||||
.s_item = s_item
|
||||
};
|
||||
|
||||
CombatRangeOutput o = EvaluateCombatRange(i);
|
||||
CombatRangeOutput o = EvaluateCombatRange(input);
|
||||
|
||||
// Combat range variables
|
||||
bool at_combat_range = o.at_combat_range;
|
||||
@@ -2306,36 +2311,20 @@ void Bot::AI_Process()
|
||||
|
||||
// PULLING FLAG (ACTIONABLE RANGE)
|
||||
|
||||
if (PULLING_BOT) {
|
||||
if (!TargetValidation(tar)) {
|
||||
SetPullFlag(false);
|
||||
SetPullingFlag(false);
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
if (PULLING_BOT || RETURNING_BOT) {
|
||||
if (!TargetValidation(tar)) { return; }
|
||||
|
||||
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!at_combat_range && RuleB(Bots, UseSpellPulling)) {
|
||||
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
|
||||
|
||||
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
|
||||
at_combat_range = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (at_combat_range && DoLosChecks(tar)) {
|
||||
bool ai_cast_successful = false;
|
||||
bool can_range_attack = !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||
if (at_combat_range) {
|
||||
if (
|
||||
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||
RuleB(Bots, AllowRangedPulling) &&
|
||||
IsBotRanged() &&
|
||||
ranged_timer.Check(false);
|
||||
|
||||
if (can_range_attack) {
|
||||
ranged_timer.Check(false)
|
||||
) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
|
||||
if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) {
|
||||
@@ -2347,36 +2336,33 @@ void Bot::AI_Process()
|
||||
return;
|
||||
}
|
||||
|
||||
bool can_ai_spell_pull = RuleB(Bots, AllowAISpellPulling) &&
|
||||
if (
|
||||
RuleB(Bots, AllowAISpellPulling) &&
|
||||
!IsBotNonSpellFighter() &&
|
||||
AI_HasSpells();
|
||||
|
||||
if (can_ai_spell_pull) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
AI_HasSpells()
|
||||
) {
|
||||
SetPullingSpell(true);
|
||||
ai_cast_successful = AI_EngagedCastCheck();
|
||||
AI_EngagedCastCheck();
|
||||
SetPullingSpell(false);
|
||||
|
||||
if (ai_cast_successful) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuleB(Bots, UseSpellPulling)) {
|
||||
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
|
||||
|
||||
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
SetPullingSpell(true);
|
||||
CastSpell(pull_spell_id, tar->GetID());
|
||||
SetPullingSpell(false);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TryPursueTarget(leash_distance);
|
||||
if (RuleB(Bots, UseSpellPulling)) {
|
||||
uint16 spell_id = RuleI(Bots, PullSpellID);
|
||||
|
||||
if (tar_distance <= spells[spell_id].range) {
|
||||
StopMoving();
|
||||
SetPullingSpell(true);
|
||||
CastSpell(spell_id, tar->GetID());
|
||||
SetPullingSpell(false);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TryPursueTarget(leash_distance, Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -2399,30 +2385,52 @@ void Bot::AI_Process()
|
||||
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
|
||||
|
||||
if (!other_bot_pulling && at_combat_range) {
|
||||
CombatPositioningInput cpi {
|
||||
.tar = tar,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.tar_distance = tar_distance,
|
||||
.melee_distance_min = melee_distance_min,
|
||||
.melee_distance = melee_distance,
|
||||
.melee_distance_max = melee_distance_max,
|
||||
.behind_mob = behind_mob,
|
||||
.front_mob = front_mob
|
||||
};
|
||||
bool jitter_cooldown = false;
|
||||
|
||||
if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) {
|
||||
jitter_cooldown = true;
|
||||
}
|
||||
|
||||
if (
|
||||
IsMoving() ||
|
||||
GetCombatJitterFlag() ||
|
||||
GetCombatOutOfRangeJitterFlag()
|
||||
) {
|
||||
if (
|
||||
!GetCombatJitterFlag() ||
|
||||
!IsMoving() ||
|
||||
GetCombatOutOfRangeJitterFlag()
|
||||
) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
}
|
||||
|
||||
if (DoCombatPositioning(cpi) && IsMoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsSitting() && !IsFacingMob(tar)) {
|
||||
FaceTarget(tar);
|
||||
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)) {
|
||||
FaceTarget(tar);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMoving()) {
|
||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||
IsBotRanged() &&
|
||||
@@ -2471,7 +2479,7 @@ void Bot::AI_Process()
|
||||
|
||||
// ENGAGED NOT AT COMBAT RANGE
|
||||
|
||||
else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) {
|
||||
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2484,7 +2492,7 @@ void Bot::AI_Process()
|
||||
TryMeditate();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
glm::vec3 Goal(0, 0, 0);
|
||||
|
||||
if (GUARDING) {
|
||||
Goal = GetGuardPoint();
|
||||
}
|
||||
@@ -2558,7 +2564,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
|
||||
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);
|
||||
SetCombatRoundForAlerts(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()) {
|
||||
SetPullingFlag(false);
|
||||
SetReturningFlag(false);
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (TryAutoDefend(bot_owner, leash_distance) ) {
|
||||
@@ -2580,7 +2580,14 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
|
||||
|
||||
SetTarget(nullptr);
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
if (
|
||||
HasPet() &&
|
||||
(
|
||||
GetClass() != Class::Enchanter ||
|
||||
GetPet()->GetPetType() != petAnimation ||
|
||||
GetAA(aaAnimationEmpathy) >= 1
|
||||
)
|
||||
) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
@@ -2590,7 +2597,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
|
||||
}
|
||||
|
||||
// Ok to idle
|
||||
if (TryNonCombatMovementChecks(bot_owner, follow_mob)) {
|
||||
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2743,7 +2750,7 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) {
|
||||
SetTarget(hater);
|
||||
SetAttackingFlag();
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
GetPet()->AddToHateList(hater, 1);
|
||||
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
|
||||
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 (GetTarget() && !IsRooted()) {
|
||||
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
|
||||
glm::vec3 Goal = GetTarget()->GetPosition();
|
||||
Goal = GetTarget()->GetPosition();
|
||||
|
||||
if (DistanceSquared(m_Position, Goal) <= leash_distance) {
|
||||
RunTo(Goal.x, Goal.y, Goal.z);
|
||||
SetCombatOutOfRangeJitter();
|
||||
} else {
|
||||
WipeHateList();
|
||||
SetTarget(nullptr);
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
if (
|
||||
HasPet() &&
|
||||
(
|
||||
GetClass() != Class::Enchanter ||
|
||||
GetPet()->GetPetType() != petAnimation ||
|
||||
GetAA(aaAnimationEmpathy) >= 2
|
||||
)
|
||||
) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
@@ -3115,8 +3130,8 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
||||
|
||||
if (IsTaunting()) { // Taunting bots
|
||||
o.melee_distance_min = o.melee_distance_max * 0.25f;
|
||||
o.melee_distance = o.melee_distance_max * 0.45f;
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
|
||||
}
|
||||
else if (IsBotRanged()) { // Archers/Throwers
|
||||
float min_distance = RuleI(Combat, MinRangedAttackDist);
|
||||
@@ -3124,22 +3139,22 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
|
||||
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);
|
||||
}
|
||||
else if (input.stop_melee_level) { // Casters
|
||||
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);
|
||||
}
|
||||
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
|
||||
o.melee_distance_min = o.melee_distance_max * 0.80f;
|
||||
o.melee_distance = o.melee_distance_max * 0.95f;
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
|
||||
}
|
||||
else { // Regular melee
|
||||
o.melee_distance_min = o.melee_distance_max * 0.30f;
|
||||
o.melee_distance = o.melee_distance_max * 0.65f;
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
|
||||
}
|
||||
|
||||
o.at_combat_range = (input.target_distance <= o.melee_distance);
|
||||
@@ -3198,8 +3213,7 @@ bool Bot::IsValidTarget(
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3233,8 +3247,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner)
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3257,11 +3270,15 @@ bool Bot::TargetValidation(Mob* other) {
|
||||
}
|
||||
|
||||
bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) {
|
||||
bool target_check = !GetTarget() || Distance(GetPosition(), GetTarget()->GetPosition()) <= 75.0f;
|
||||
bool returned_check = (NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
|
||||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance());
|
||||
auto engage_range = (GetBotDistanceRanged() < 30 ? 30 : GetBotDistanceRanged());
|
||||
|
||||
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();
|
||||
SetTarget(nullptr);
|
||||
SetPullingFlag(false);
|
||||
@@ -3269,10 +3286,9 @@ bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_dist
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
if (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
@@ -3313,8 +3329,7 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -3323,16 +3338,11 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
|
||||
SetPullingFlag(false);
|
||||
SetReturningFlag();
|
||||
|
||||
Mob* my_pet = GetPet();
|
||||
if (HasPet() &&
|
||||
(GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
|
||||
|
||||
if (my_pet) {
|
||||
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
my_pet->WipeHateList();
|
||||
my_pet->SetTarget(nullptr);
|
||||
} else {
|
||||
my_pet->AddToHateList(GetTarget(), 1);
|
||||
my_pet->SetTarget(GetTarget());
|
||||
}
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (GetPet() && (PULLING_BOT || RETURNING_BOT)) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
||||
}
|
||||
|
||||
SetAttackFlag(false);
|
||||
@@ -3514,7 +3524,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) {
|
||||
SetTarget(attack_target);
|
||||
SetAttackingFlag();
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||
if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->AddToHateList(attack_target, 1);
|
||||
GetPet()->SetTarget(attack_target);
|
||||
@@ -3532,21 +3542,20 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
||||
SetReturningFlag(false);
|
||||
bot_owner->SetBotPulling(false);
|
||||
|
||||
if (GetPet()) {
|
||||
GetPet()->SetPetOrder(SPO_Follow);
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||
}
|
||||
|
||||
if (NOT_HOLDING && NOT_PASSIVE) {
|
||||
auto pull_target = bot_owner->GetTarget();
|
||||
|
||||
if (pull_target) {
|
||||
RaidGroupSay(
|
||||
fmt::format(
|
||||
"Pulling {}.",
|
||||
pull_target->GetCleanName()
|
||||
).c_str()
|
||||
);
|
||||
if (raid) {
|
||||
const auto msg = fmt::format("Pulling {}.", pull_target->GetCleanName());
|
||||
raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100);
|
||||
} else {
|
||||
RaidGroupSay(
|
||||
fmt::format(
|
||||
"Pulling {}.",
|
||||
pull_target->GetCleanName()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
InterruptSpell();
|
||||
WipeHateList();
|
||||
@@ -3555,15 +3564,12 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
||||
SetPullingFlag();
|
||||
bot_owner->SetBotPulling();
|
||||
|
||||
if (GetPet()) {
|
||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
|
||||
GetPet()->WipeHateList();
|
||||
GetPet()->SetTarget(nullptr);
|
||||
|
||||
if (HasControllablePet(BotAnimEmpathy::Guard)) {
|
||||
m_previous_pet_order = GetPet()->GetPetOrder();
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
|
||||
GetPet()->SetPetOrder(SPO_Guard);
|
||||
}
|
||||
m_previous_pet_order = GetPet()->GetPetOrder();
|
||||
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
|
||||
GetPet()->SetPetOrder(SPO_Guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8731,86 +8737,6 @@ bool Bot::CheckCampSpawnConditions(Client* c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::CheckHighEnoughLevelForBots(Client* c, uint8 bot_class) {
|
||||
auto bot_character_level = c->GetBotRequiredLevel(bot_class);
|
||||
bool not_high_enough_level = bot_character_level >= 0 && c->GetLevel() < bot_character_level;
|
||||
|
||||
if (not_high_enough_level) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"You must be level {} to spawn {}bots.",
|
||||
bot_character_level,
|
||||
bot_class ? GetClassIDName(bot_class) : ""
|
||||
).c_str()
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class) {
|
||||
auto bot_creation_limit = c->GetBotCreationLimit(bot_class);
|
||||
bool is_beyond_spawn_limit = bot_creation_limit >= 0 && bot_count >= bot_creation_limit;
|
||||
|
||||
if (is_beyond_spawn_limit) {
|
||||
std::string message;
|
||||
|
||||
if (bot_creation_limit) {
|
||||
message = fmt::format(
|
||||
"You cannot create anymore than {} {}bot{}.",
|
||||
bot_creation_limit,
|
||||
bot_class ? GetClassIDName(bot_class) : "",
|
||||
bot_creation_limit != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = fmt::format(
|
||||
"You cannot create any {}bots.",
|
||||
bot_class ? GetClassIDName(bot_class) : ""
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::Yellow, message.c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::CheckSpawnLimit(Client* c, uint8 bot_class) {
|
||||
auto bot_spawn_limit = c->GetBotSpawnLimit(bot_class);
|
||||
auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID(), bot_class);
|
||||
bool is_beyond_spawn_limit = bot_spawn_limit >= 0 && spawned_bot_count >= bot_spawn_limit;
|
||||
|
||||
if (is_beyond_spawn_limit) {
|
||||
std::string message;
|
||||
|
||||
if (bot_spawn_limit) {
|
||||
message = fmt::format(
|
||||
"You cannot have more than {} spawned {}bot{}.",
|
||||
bot_spawn_limit,
|
||||
bot_class ? GetClassIDName(bot_class) : "",
|
||||
bot_spawn_limit != 1 ? "s" : ""
|
||||
);
|
||||
}
|
||||
else {
|
||||
message = fmt::format(
|
||||
"You are not currently allowed to spawn any {}bots.",
|
||||
bot_class ? GetClassIDName(bot_class) : ""
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bot::AddBotStartingItems(uint16 race_id, uint8 class_id)
|
||||
{
|
||||
if (!IsPlayerRace(race_id) || !IsPlayerClass(class_id)) {
|
||||
@@ -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());
|
||||
return false;
|
||||
@@ -11916,7 +11842,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
return false;
|
||||
case BotSpellTypes::ResistBuffs:
|
||||
case BotSpellTypes::PetResistBuffs:
|
||||
if (IsResistanceBuffSpell(spell_id) && !IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||
if (IsResistanceBuffSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12040,171 +11966,193 @@ void Bot::SetCastedSpellType(uint16 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) {
|
||||
RunTo(Goal.x, Goal.y, Goal.z);
|
||||
SetCombatJitter();
|
||||
}
|
||||
|
||||
void Bot::SetCombatJitter() {
|
||||
void Bot::SetCombatOutOfRangeJitter() {
|
||||
SetCombatOutOfRangeJitterFlag();
|
||||
|
||||
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
||||
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
||||
}
|
||||
}
|
||||
|
||||
bool Bot::DoCombatPositioning(const CombatPositioningInput& input)
|
||||
{
|
||||
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);
|
||||
void Bot::SetCombatJitter() {
|
||||
SetCombatJitterFlag();
|
||||
|
||||
|
||||
if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) {
|
||||
FaceTarget(input.tar);
|
||||
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
||||
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
||||
}
|
||||
|
||||
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) {
|
||||
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 = true;
|
||||
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
} 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 =
|
||||
is_too_close ||
|
||||
los_adjust ||
|
||||
(behind_mob_set && !input.behind_mob);
|
||||
|
||||
if (adjustment_needed && adjustment_allowed) {
|
||||
find_position_input.distance_min = input.melee_distance_min;
|
||||
find_position_input.distance_max = input.melee_distance;
|
||||
|
||||
PlotBotPositionAroundTarget(find_position_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!adjustment_needed && IsMoving()) {
|
||||
StopMoving();
|
||||
}
|
||||
|
||||
return adjustment_needed;
|
||||
}
|
||||
|
||||
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
|
||||
void Bot::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
|
||||
) {
|
||||
if (!tar->IsFeared()) {
|
||||
bool is_too_close = tar_distance < melee_distance_min;
|
||||
bool los_adjust = !HasRequiredLoSForPositioning(tar);
|
||||
|
||||
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 (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 ||
|
||||
los_adjust ||
|
||||
(!GetBehindMob() && !front_mob) ||
|
||||
(GetBehindMob() && !behind_mob);
|
||||
|
||||
if (regular_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoFaceCheckNoJitter(tar);
|
||||
}
|
||||
|
||||
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 Result = false;
|
||||
|
||||
if (input.tar) {
|
||||
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 best_z = 0;
|
||||
float tar_distance = 0;
|
||||
float desired_angle = 0;
|
||||
const float offset = GetZOffset();
|
||||
const uint16 max_iterations_allowed = 50;
|
||||
uint16 counter = 0;
|
||||
if (target) {
|
||||
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;
|
||||
auto offset = GetZOffset();
|
||||
const float tar_x = target->GetX();
|
||||
const float tar_y = target->GetY();
|
||||
float tar_distance = 0;
|
||||
|
||||
glm::vec3 temp_z_Position;
|
||||
glm::vec4 temp_m_Position;
|
||||
|
||||
const uint16 max_iterations_allowed = 50;
|
||||
uint16 counter = 0;
|
||||
|
||||
while (counter < max_iterations_allowed) {
|
||||
temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max);
|
||||
temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max);
|
||||
best_z = GetFixedZ(temp_goal);
|
||||
temp_x = tar_x + zone->random.Real(-max_distance, max_distance);
|
||||
temp_y = tar_y + zone->random.Real(-max_distance, max_distance);
|
||||
|
||||
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) {
|
||||
temp_goal.z = best_z;
|
||||
temp_z = best_z;
|
||||
}
|
||||
else {
|
||||
counter++;
|
||||
|
||||
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++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) {
|
||||
if (front_only && !InFrontMob(target, temp_x, temp_y)) {
|
||||
counter++;
|
||||
|
||||
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++;
|
||||
|
||||
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++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12213,7 +12161,9 @@ bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool Bot::HasControllablePet(uint8 ranks_required) {
|
||||
if (!GetPet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetClass() != Class::Enchanter ||
|
||||
GetPet()->GetPetType() != petAnimation ||
|
||||
GetAA(aaAnimationEmpathy) >= ranks_required;
|
||||
}
|
||||
+31
-14
@@ -39,6 +39,9 @@
|
||||
|
||||
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;
|
||||
|
||||
extern WorldServer worldserver;
|
||||
@@ -229,10 +232,19 @@ static std::map<uint16, std::string> botSubType_names = {
|
||||
{ CommandedSubTypes::Selo, "Selo" }
|
||||
};
|
||||
|
||||
namespace BotAnimEmpathy {
|
||||
constexpr uint8 Guard = 1;
|
||||
constexpr uint8 Attack = 2;
|
||||
constexpr uint8 BackOff = 3;
|
||||
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;
|
||||
};
|
||||
|
||||
class Bot : public NPC {
|
||||
@@ -565,7 +577,7 @@ public:
|
||||
uint16 GetPetBotSpellType(uint16 spell_type);
|
||||
|
||||
// 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);
|
||||
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = 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 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 GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet);
|
||||
static std::string GetBotMagicianPetType(Bot* caster);
|
||||
@@ -792,7 +804,6 @@ public:
|
||||
EQ::ItemInstance* GetBotItem(uint16 slot_id);
|
||||
bool GetSpawnStatus() { return _spawnStatus; }
|
||||
uint8 GetPetChooserID() { return _petChooserID; }
|
||||
bool HasControllablePet(uint8 ranks_required = 0);
|
||||
bool IsBotRanged() { return _botRangedSetting; }
|
||||
bool IsBotCharmer() { return _botCharmer; }
|
||||
bool IsBot() const override { return true; }
|
||||
@@ -1091,8 +1102,15 @@ public:
|
||||
bool CheckIfCasting(float fm_distance);
|
||||
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();
|
||||
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);
|
||||
bool RequiresLoSForPositioning();
|
||||
bool HasRequiredLoSForPositioning(Mob* tar);
|
||||
@@ -1100,21 +1118,18 @@ public:
|
||||
// Try Combat Methods
|
||||
bool TryEvade(Mob* tar);
|
||||
bool TryFacingTarget(Mob* tar);
|
||||
bool TryPursueTarget(float leash_distance);
|
||||
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
|
||||
bool TryMeditate();
|
||||
bool TryAutoDefend(Client* bot_owner, float leash_distance);
|
||||
bool TryIdleChecks(float fm_distance);
|
||||
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
|
||||
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
|
||||
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
|
||||
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
|
||||
bool TryBardMovementCasts();
|
||||
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
|
||||
bool CheckDoubleRangedAttack();
|
||||
|
||||
// Public "Refactor" Methods
|
||||
static bool CheckCampSpawnConditions(Client* c);
|
||||
static bool CheckHighEnoughLevelForBots(Client* c, uint8 bot_class = Class::None);
|
||||
static bool CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class = Class::None);
|
||||
static bool CheckSpawnLimit(Client* c, uint8 bot_class = Class::None);
|
||||
|
||||
protected:
|
||||
void BotMeditate(bool is_sitting);
|
||||
@@ -1174,6 +1189,8 @@ private:
|
||||
Timer m_auto_save_timer;
|
||||
|
||||
Timer m_combat_jitter_timer;
|
||||
bool m_combat_jitter_flag;
|
||||
bool m_combat_out_of_range_jitter_flag;
|
||||
|
||||
bool m_dirtyautohaters;
|
||||
bool m_guard_flag;
|
||||
|
||||
+69
-12
@@ -468,7 +468,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
|
||||
|
||||
bool available_flag = false;
|
||||
|
||||
!database.botdb.QueryNameAvailability(bot_name, available_flag);
|
||||
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
|
||||
|
||||
if (!available_flag) {
|
||||
bot_owner->Message(
|
||||
@@ -517,31 +517,88 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
if (!Bot::CheckHighEnoughLevelForBots(bot_owner)) {
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
if (!Bot::CheckHighEnoughLevelForBots(bot_owner, bot_class)) {
|
||||
return bot_id;
|
||||
}
|
||||
auto bot_creation_limit = bot_owner->GetBotCreationLimit();
|
||||
auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class);
|
||||
|
||||
uint32 bot_count = 0;
|
||||
uint32 bot_class_count = 0;
|
||||
|
||||
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
|
||||
bot_owner->Message(Chat::Yellow, "Failed to query bot count.");
|
||||
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
if (!Bot::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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto bot_character_level = bot_owner->GetBotRequiredLevel();
|
||||
|
||||
if (
|
||||
bot_character_level >= 0 &&
|
||||
bot_owner->GetLevel() < bot_character_level
|
||||
) {
|
||||
bot_owner->Message(
|
||||
Chat::Yellow,
|
||||
fmt::format(
|
||||
"You must be level {} to use bots.",
|
||||
bot_character_level
|
||||
).c_str()
|
||||
);
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
auto bot_character_level_class = bot_owner->GetBotRequiredLevel(bot_class);
|
||||
|
||||
if (
|
||||
bot_character_level_class >= 0 &&
|
||||
bot_owner->GetLevel() < bot_character_level_class
|
||||
) {
|
||||
bot_owner->Message(
|
||||
Chat::Yellow,
|
||||
fmt::format(
|
||||
"You must be level {} to use {} bots.",
|
||||
bot_character_level_class,
|
||||
GetClassIDName(bot_class)
|
||||
).c_str()
|
||||
);
|
||||
return bot_id;
|
||||
}
|
||||
|
||||
|
||||
auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
|
||||
|
||||
if (!my_bot->Save()) {
|
||||
|
||||
+121
-29
@@ -130,7 +130,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
|
||||
bool available_flag = false;
|
||||
|
||||
!database.botdb.QueryNameAvailability(bot_name, available_flag);
|
||||
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
|
||||
|
||||
if (!available_flag) {
|
||||
c->Message(
|
||||
@@ -144,25 +144,55 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_creation_limit = c->GetBotCreationLimit();
|
||||
auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass());
|
||||
|
||||
uint32 bot_count = 0;
|
||||
uint32 bot_class_count = 0;
|
||||
|
||||
if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) {
|
||||
c->Message(Chat::Yellow, "Failed to query bot count.");
|
||||
|
||||
c->Message(Chat::White, "Failed to query bot count.");
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint32 clone_id = 0;
|
||||
|
||||
if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -175,7 +205,6 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
}
|
||||
|
||||
int clone_stance = Stance::Passive;
|
||||
|
||||
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -700,7 +729,6 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
int NO_BOT_LIMIT = -1;
|
||||
bool Account = false;
|
||||
int seps = 1;
|
||||
uint32 filter_value[FilterCount];
|
||||
@@ -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++) {
|
||||
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(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
@@ -910,7 +938,20 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -918,7 +959,27 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -943,6 +1004,52 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class);
|
||||
auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class);
|
||||
|
||||
if (
|
||||
bot_spawn_limit_class >= 0 &&
|
||||
spawned_bot_count_class >= bot_spawn_limit_class &&
|
||||
!c->GetGM()
|
||||
) {
|
||||
std::string message;
|
||||
|
||||
if (bot_spawn_limit_class) {
|
||||
message = fmt::format(
|
||||
"You cannot have more than {} spawned {} bot{}.",
|
||||
bot_spawn_limit_class,
|
||||
GetClassIDName(bot_class),
|
||||
bot_spawn_limit_class != 1 ? "s" : ""
|
||||
);
|
||||
} else {
|
||||
message = fmt::format(
|
||||
"You are not currently allowed to spawn any {} bots.",
|
||||
GetClassIDName(bot_class)
|
||||
);
|
||||
}
|
||||
|
||||
c->Message(Chat::White, message.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto bot_character_level_class = c->GetBotRequiredLevel(bot_class);
|
||||
|
||||
if (
|
||||
bot_character_level_class >= 0 &&
|
||||
c->GetLevel() < bot_character_level_class &&
|
||||
!c->GetGM()
|
||||
) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"You must be level {} to spawn {} bots.",
|
||||
bot_character_level_class,
|
||||
GetClassIDName(bot_class)
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bot_id) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -954,14 +1061,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Bot::CheckHighEnoughLevelForBots(c, bot_class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Bot::CheckSpawnLimit(c, bot_class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity_list.GetMobByBotID(bot_id)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -970,7 +1069,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
bot_name
|
||||
).c_str()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -985,7 +1083,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
bot_id
|
||||
).c_str()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1000,7 +1097,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
);
|
||||
|
||||
safe_delete(my_bot);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1025,7 +1121,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
};
|
||||
|
||||
uint8 message_index = 0;
|
||||
|
||||
if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) {
|
||||
message_index = VALIDATECLASSID(my_bot->GetClass());
|
||||
}
|
||||
@@ -1465,11 +1560,8 @@ void bot_command_summon(Client *c, const Seperator *sep)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
bot_iter->GetPet()->WipeHateList();
|
||||
bot_iter->GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
|
||||
bot_iter->GetPet()->WipeHateList();
|
||||
bot_iter->GetPet()->SetTarget(nullptr);
|
||||
bot_iter->GetPet()->Teleport(c->GetPosition());
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
Mob* tar = c->GetTarget();
|
||||
bool is_success = false;
|
||||
|
||||
for (auto my_bot : sbl) {
|
||||
if (!my_bot->ValidStateCheck(c)) {
|
||||
if (my_bot->BotPassiveCheck()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -69,11 +68,6 @@ void bot_command_click_item(Client* c, const Seperator* sep)
|
||||
continue;
|
||||
}
|
||||
|
||||
is_success = true;
|
||||
my_bot->TryItemClick(slot_id);
|
||||
}
|
||||
|
||||
if (!is_success) {
|
||||
c->Message(Chat::Yellow, "None of your bots are capable of doing that currently.");
|
||||
}
|
||||
}
|
||||
|
||||
+62
-31
@@ -5,8 +5,8 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (helper_is_help_or_usage(sep->arg[1])) {
|
||||
|
||||
c->Message(Chat::White, "usage: <enemy_target> %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]);
|
||||
return;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
|
||||
if (
|
||||
!target_mob ||
|
||||
target_mob->IsOfClientBotMerc() ||
|
||||
target_mob == c ||
|
||||
!c->IsAttackAllowed(target_mob)
|
||||
) {
|
||||
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()) {
|
||||
c->Message(Chat::White, "Your current target is already engaged!");
|
||||
|
||||
c->Message(Chat::White, "Your current target is already engaged!");
|
||||
return;
|
||||
}
|
||||
|
||||
Bot* bot_puller = nullptr;
|
||||
Bot* backup_bot_puller = nullptr;
|
||||
Bot* alternate_bot_puller = nullptr;
|
||||
bool backup_puller_found = false;
|
||||
bool alternate_puller_found = false;
|
||||
|
||||
for (auto bot_iter : sbl) {
|
||||
if (!bot_iter->ValidStateCheck(c)) {
|
||||
@@ -76,37 +72,72 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
case Class::Monk:
|
||||
case Class::Bard:
|
||||
case Class::Ranger:
|
||||
bot_iter->SetPullFlag();
|
||||
|
||||
return;
|
||||
default:
|
||||
bot_puller = bot_iter;
|
||||
break;
|
||||
case Class::Warrior:
|
||||
case Class::ShadowKnight:
|
||||
case Class::Paladin:
|
||||
case Class::Berserker:
|
||||
case Class::Beastlord:
|
||||
if (!bot_puller) {
|
||||
|
||||
bot_puller = bot_iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (bot_puller->GetClass()) {
|
||||
case Class::Druid:
|
||||
case Class::Shaman:
|
||||
case Class::Cleric:
|
||||
case Class::Wizard:
|
||||
case Class::Necromancer:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
bot_puller = bot_iter;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
case Class::Druid:
|
||||
case Class::Shaman:
|
||||
case Class::Cleric:
|
||||
if (!bot_puller) {
|
||||
|
||||
bot_puller = bot_iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (bot_puller->GetClass()) {
|
||||
case Class::Wizard:
|
||||
case Class::Necromancer:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
bot_puller = bot_iter;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
case Class::Wizard:
|
||||
case Class::Necromancer:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
if (!bot_puller) {
|
||||
bot_puller = bot_iter;
|
||||
}
|
||||
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!backup_puller_found) {
|
||||
switch (bot_iter->GetClass()) {
|
||||
case Class::Warrior:
|
||||
case Class::ShadowKnight:
|
||||
case Class::Paladin:
|
||||
case Class::Berserker:
|
||||
case Class::Beastlord:
|
||||
backup_bot_puller = bot_iter;
|
||||
backup_puller_found = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
bot_puller = bot_iter;
|
||||
|
||||
if (!backup_puller_found && !alternate_puller_found) {
|
||||
alternate_bot_puller = bot_iter;
|
||||
alternate_puller_found = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bot_puller = backup_bot_puller ? backup_bot_puller : alternate_bot_puller;
|
||||
|
||||
if (bot_puller) {
|
||||
bot_puller->SetPullFlag();
|
||||
}
|
||||
|
||||
@@ -17,12 +17,6 @@ void bot_command_release(Client *c, const Seperator *sep)
|
||||
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
|
||||
for (auto bot_iter : sbl) {
|
||||
bot_iter->WipeHateList();
|
||||
|
||||
if (bot_iter->GetPet() && bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||
bot_iter->GetPet()->WipeHateList();
|
||||
bot_iter->GetPet()->SetTarget(nullptr);
|
||||
}
|
||||
|
||||
bot_iter->SetPauseAI(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag)
|
||||
bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag)
|
||||
{
|
||||
if (
|
||||
bot_name.empty() ||
|
||||
@@ -207,7 +207,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
|
||||
bot_count = BotDataRepository::Count(
|
||||
database,
|
||||
fmt::format(
|
||||
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'",
|
||||
"`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(
|
||||
database,
|
||||
fmt::format(
|
||||
"`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'",
|
||||
"`owner_id` = {} AND `class` = {}",
|
||||
owner_id,
|
||||
class_id
|
||||
)
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
|
||||
/* 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 LoadBotsList(const uint32 owner_id, std::list<BotsAvailableList>& bots_list, bool by_account = false);
|
||||
|
||||
|
||||
+2
-38
@@ -21,7 +21,6 @@
|
||||
|
||||
#include "../common/types.h"
|
||||
#include "../common/timer.h"
|
||||
#include "mob.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -68,7 +67,7 @@ struct BotSpellSetting {
|
||||
|
||||
struct BotSpells {
|
||||
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
|
||||
uint32 time_cancast; // when we can cast this spell next
|
||||
int32 recast_delay;
|
||||
@@ -86,7 +85,7 @@ struct BotSpells {
|
||||
struct BotSpells_wIndex {
|
||||
uint32 index; //index of AIBot_spells
|
||||
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
||||
uint16 spellid; // <= 0 = no spell
|
||||
int16 spellid; // <= 0 = no spell
|
||||
int16 manacost; // -1 = use spdat, -2 = no cast time
|
||||
uint32 time_cancast; // when we can cast this spell next
|
||||
int32 recast_delay;
|
||||
@@ -152,39 +151,4 @@ struct BotSpellTypesByClass {
|
||||
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
|
||||
|
||||
@@ -1459,7 +1459,7 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
|
||||
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;
|
||||
|
||||
if (caster && caster->GetOwner()) {
|
||||
|
||||
@@ -38,11 +38,7 @@ inline void SetupStateZone()
|
||||
SetupZone("soldungb");
|
||||
zone->Process();
|
||||
// depop the zone controller
|
||||
auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID);
|
||||
if (controller != nullptr) {
|
||||
controller->Depop();
|
||||
}
|
||||
|
||||
entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->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);
|
||||
}
|
||||
|
||||
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries;
|
||||
RunTest(
|
||||
fmt::format("Spawns > All NPC's killed (0 NPCs) ([{}] Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
|
||||
RunTest("Spawns > All NPC's killed (0 NPCs) (115 Corpses)", true, condition);
|
||||
|
||||
std::vector<uint32_t> spawn2_ids = {};
|
||||
|
||||
@@ -385,15 +377,11 @@ inline void TestSpawns()
|
||||
zone->Shutdown();
|
||||
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) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest(
|
||||
fmt::format("Spawns > After restore (0 NPCs) ([{}] Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
RunTest("Spawns > After restore (0 NPCs) (115 Corpses)", true, condition);
|
||||
|
||||
for (auto &e: entity_list.GetCorpseList()) {
|
||||
auto c = e.second;
|
||||
@@ -417,15 +405,11 @@ inline void TestSpawns()
|
||||
|
||||
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) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest(
|
||||
fmt::format("Spawns > After respawn ([{}] NPCs) ([{}] Corpses)", entries, entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
RunTest("Spawns > After respawn (115 NPCs) (115 Corpses)", true, condition);
|
||||
|
||||
for (auto &c: entity_list.GetCorpseList()) {
|
||||
c.second->DepopNPCCorpse();
|
||||
@@ -433,15 +417,11 @@ inline void TestSpawns()
|
||||
|
||||
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) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest(
|
||||
fmt::format("Spawns > After respawn ([{}] NPCs) (0 Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
RunTest("Spawns > After respawn (115 NPCs) (0 Corpses)", true, condition);
|
||||
|
||||
// lets set NPC's up with a predictable loottable for testing
|
||||
uint32_t loottable_id = SeedLootTable();
|
||||
@@ -482,28 +462,20 @@ inline void TestSpawns()
|
||||
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) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest(
|
||||
fmt::format("Spawns > Kill 10 NPC's before save/restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
RunTest("Spawns > Kill 10 NPC's before save/restore (105 NPCs) (10 Corpses)", true, condition);
|
||||
|
||||
zone->Shutdown();
|
||||
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) {
|
||||
PrintEntityCounts();
|
||||
}
|
||||
RunTest(
|
||||
fmt::format("Spawns > After restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
RunTest("Spawns > After restore (105 NPCs) (10 Corpses)", true, condition);
|
||||
|
||||
// validate that all corpses and npc's have cloak of flames
|
||||
bool test_failed = false;
|
||||
@@ -600,14 +572,6 @@ inline void TestSpawns()
|
||||
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();
|
||||
|
||||
zone->Process();
|
||||
@@ -623,20 +587,16 @@ inline void TestSpawns()
|
||||
npc->SetEntityVariable("previously_spawned", "true");
|
||||
}
|
||||
|
||||
Timer::RollForward(max_respawn); // longest respawn time in zone
|
||||
Timer::RollForward(302401); // longest respawn time in zone
|
||||
zone->Process();
|
||||
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) {
|
||||
PrintEntityCounts();
|
||||
PrintZoneNpcs();
|
||||
}
|
||||
RunTest(
|
||||
fmt::format("Spawns > After respawn, ensure we have expected entity counts ([{}] NPCs) (10 Corpses)", entries),
|
||||
true,
|
||||
condition
|
||||
);
|
||||
RunTest("Spawns > After respawn, ensure we have expected entity counts (115 NPCs) (10 Corpses)", true, condition);
|
||||
|
||||
entity_list.MobProcess(); // processing depops
|
||||
|
||||
|
||||
+1
-56
@@ -995,8 +995,6 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
if(!ClientDataLoaded())
|
||||
return false;
|
||||
|
||||
BenchTimer timer;
|
||||
|
||||
/* Wrote current basics to PP for saves */
|
||||
if (!m_lock_save_position) {
|
||||
m_pp.x = m_Position.x;
|
||||
@@ -1107,8 +1105,6 @@ bool Client::Save(uint8 iCommitNow) {
|
||||
database.botdb.SaveBotSettings(this);
|
||||
}
|
||||
|
||||
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
|
||||
}
|
||||
@@ -11479,6 +11423,7 @@ void Client::SaveSpells()
|
||||
}
|
||||
|
||||
CharacterSpellsRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID()));
|
||||
|
||||
if (!character_spells.empty()) {
|
||||
CharacterSpellsRepository::InsertMany(database, character_spells);
|
||||
}
|
||||
|
||||
+4
-11
@@ -73,7 +73,6 @@ namespace EQ
|
||||
#include "../common/guild_base.h"
|
||||
#include "../common/repositories/buyer_buy_lines_repository.h"
|
||||
#include "../common/repositories/character_evolving_items_repository.h"
|
||||
#include "../common/repositories/player_titlesets_repository.h"
|
||||
|
||||
#include "bot_structs.h"
|
||||
|
||||
@@ -484,9 +483,6 @@ public:
|
||||
|
||||
virtual bool Save() { return Save(0); }
|
||||
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 */
|
||||
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
|
||||
@@ -1256,10 +1252,9 @@ public:
|
||||
void ResetAllCastbarCooldowns();
|
||||
void ResetCastbarCooldownBySpellID(uint32 spell_id);
|
||||
|
||||
bool CheckTitle(int title_set);
|
||||
void EnableTitle(int title_set, bool insert = true);
|
||||
const std::vector<PlayerTitlesetsRepository::PlayerTitlesets>& GetTitles() { return m_player_title_sets; };
|
||||
void RemoveTitle(int title_set);
|
||||
bool CheckTitle(int titleset);
|
||||
void EnableTitle(int titleset);
|
||||
void RemoveTitle(int titleset);
|
||||
|
||||
void EnteringMessages(Client* client);
|
||||
void SendRules();
|
||||
@@ -2082,8 +2077,7 @@ private:
|
||||
uint16 trader_id;
|
||||
uint16 customer_id;
|
||||
uint32 account_creation;
|
||||
bool first_login;
|
||||
bool ingame;
|
||||
uint8 firstlogon;
|
||||
uint32 mercid; // current merc
|
||||
uint8 mercSlot; // selected merc slot
|
||||
time_t m_trader_transaction_date;
|
||||
@@ -2260,7 +2254,6 @@ private:
|
||||
bool m_exp_enabled;
|
||||
|
||||
std::vector<EXPModifier> m_exp_modifiers;
|
||||
std::vector<PlayerTitlesetsRepository::PlayerTitlesets> m_player_title_sets;
|
||||
|
||||
//Anti Spam Stuff
|
||||
Timer *KarmaUpdateTimer;
|
||||
|
||||
+67
-61
@@ -1,8 +1,6 @@
|
||||
#include "bot.h"
|
||||
#include "client.h"
|
||||
|
||||
#define NO_BOT_LIMIT -1;
|
||||
|
||||
bool Client::GetBotOption(BotOwnerOption boo) const {
|
||||
if (boo < _booCount) {
|
||||
return bot_owner_options[boo];
|
||||
@@ -20,25 +18,25 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) {
|
||||
uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
||||
uint32 bot_creation_limit = RuleI(Bots, CreationLimit);
|
||||
|
||||
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
|
||||
if (Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
|
||||
return RuleI(Bots, MinStatusBypassCreateLimit);
|
||||
}
|
||||
|
||||
const auto bucket_name = fmt::format(
|
||||
"bot_creation_limit{}",
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||
(
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format(
|
||||
"_{}",
|
||||
Strings::ToLower(GetClassIDName(class_id))
|
||||
) :
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
auto bucket_value = GetBucket(bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_creation_limit = Strings::ToInt(bucket_value);
|
||||
}
|
||||
|
||||
if (class_id && bucket_value.empty()) {
|
||||
bot_creation_limit = NO_BOT_LIMIT;
|
||||
bot_creation_limit = Strings::ToUnsignedInt(bucket_value);
|
||||
}
|
||||
|
||||
return bot_creation_limit;
|
||||
@@ -47,53 +45,61 @@ uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
||||
int Client::GetBotRequiredLevel(uint8 class_id) {
|
||||
int bot_character_level = RuleI(Bots, BotCharacterLevel);
|
||||
|
||||
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassBotLevelRequirement)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto bucket_name = fmt::format(
|
||||
"bot_required_level{}",
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||
(
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format(
|
||||
"_{}",
|
||||
Strings::ToLower(GetClassIDName(class_id))
|
||||
) :
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
auto bucket_value = GetBucket(bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_character_level = Strings::ToInt(bucket_value);
|
||||
}
|
||||
|
||||
if (class_id && bucket_value.empty()) {
|
||||
bot_character_level = NO_BOT_LIMIT;
|
||||
}
|
||||
|
||||
return bot_character_level;
|
||||
}
|
||||
|
||||
int Client::GetBotSpawnLimit(uint8 class_id)
|
||||
{
|
||||
int Client::GetBotSpawnLimit(uint8 class_id) {
|
||||
int bot_spawn_limit = RuleI(Bots, SpawnLimit);
|
||||
|
||||
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
|
||||
if (Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
|
||||
return RuleI(Bots, MinStatusBypassSpawnLimit);
|
||||
}
|
||||
|
||||
const auto bucket_name = fmt::format(
|
||||
"bot_spawn_limit{}",
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||
(
|
||||
class_id && IsPlayerClass(class_id) ?
|
||||
fmt::format(
|
||||
"_{}",
|
||||
Strings::ToLower(GetClassIDName(class_id))
|
||||
) :
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
auto bucket_value = GetBucket(bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
||||
if (class_id && !bot_spawn_limit && bucket_value.empty()) {
|
||||
const auto new_bucket_name = "bot_spawn_limit";
|
||||
|
||||
bucket_value = GetBucket(new_bucket_name);
|
||||
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
||||
|
||||
return bot_spawn_limit;
|
||||
}
|
||||
}
|
||||
|
||||
if (class_id && bucket_value.empty()) {
|
||||
return NO_BOT_LIMIT;
|
||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
||||
}
|
||||
|
||||
if (RuleB(Bots, QuestableSpawnLimit)) {
|
||||
@@ -105,53 +111,53 @@ int Client::GetBotSpawnLimit(uint8 class_id)
|
||||
|
||||
auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls
|
||||
|
||||
if (results.Success() && results.RowCount()) {
|
||||
auto row = results.begin();
|
||||
bot_spawn_limit = Strings::ToInt(row[0]);
|
||||
if (!results.Success() || !results.RowCount()) {
|
||||
return bot_spawn_limit;
|
||||
}
|
||||
|
||||
auto row = results.begin();
|
||||
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()) {
|
||||
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
|
||||
if (!zones_list.empty()) {
|
||||
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
|
||||
|
||||
if (it != zones_list.end()) {
|
||||
const auto &zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||
if (it != zones_list.end()) {
|
||||
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||
|
||||
if (zones_list.size() == zones_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
|
||||
if (zones_list.size() == zones_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
|
||||
|
||||
if (new_limit < bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
|
||||
if (new_limit < bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto &zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
||||
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
||||
|
||||
if (!zones_forced_list.empty()) {
|
||||
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
|
||||
if (!zones_forced_list.empty()) {
|
||||
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
|
||||
|
||||
if (it != zones_forced_list.end()) {
|
||||
const auto &zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||
if (it != zones_forced_list.end()) {
|
||||
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||
|
||||
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
|
||||
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
|
||||
|
||||
if (new_limit != bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
|
||||
if (new_limit != bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +92,6 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!evolving_items_manager.GetEvolvingItemsCache().contains(inst->GetID())) {
|
||||
LogEvolveItem(
|
||||
"Character ID {} has an evolving item that is not found in the db. Please check your "
|
||||
"items_evolving_details table for item id {}",
|
||||
CharacterID(),
|
||||
inst->GetID()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type;
|
||||
auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type;
|
||||
|
||||
@@ -293,31 +283,24 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app)
|
||||
}
|
||||
|
||||
std::unique_ptr<EQ::ItemInstance> const inst(database.CreateItem(item_id));
|
||||
if (!inst) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogEvolveItemDetail(
|
||||
"Character ID <green>[{}] requested to view final evolve item id <yellow>[{}] for evolve item id <yellow>[{}]",
|
||||
CharacterID(),
|
||||
item_id,
|
||||
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id)
|
||||
);
|
||||
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id));
|
||||
|
||||
inst->SetEvolveProgression(100);
|
||||
|
||||
LogEvolveItemDetail(
|
||||
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID()
|
||||
);
|
||||
SendItemPacket(0, inst.get(), ItemPacketViewLink);
|
||||
if (inst) {
|
||||
LogEvolveItemDetail(
|
||||
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID());
|
||||
SendItemPacket(0, inst.get(), ItemPacketViewLink);
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
|
||||
{
|
||||
if (!inst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+44
-76
@@ -807,44 +807,22 @@ void Client::CompleteConnect()
|
||||
m_last_position_before_bulk_update = GetPosition();
|
||||
|
||||
/* This sub event is for if a player logs in for the first time since entering world. */
|
||||
if (ingame) {
|
||||
auto e = CharacterDataRepository::FindOne(
|
||||
database,
|
||||
CharacterID()
|
||||
);
|
||||
|
||||
bool is_first_login = e.first_login == 0;
|
||||
|
||||
if (firstlogon == 1) {
|
||||
RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{});
|
||||
|
||||
if (parse->PlayerHasQuestSub(EVENT_CONNECT)) {
|
||||
const std::string& export_string = fmt::format(
|
||||
"{} {} {}",
|
||||
e.last_login,
|
||||
time(nullptr) - e.last_login,
|
||||
is_first_login ? 1 : 0
|
||||
);
|
||||
parse->EventPlayer(EVENT_CONNECT, this, export_string, 0);
|
||||
parse->EventPlayer(EVENT_CONNECT, this, "", 0);
|
||||
}
|
||||
|
||||
RecordStats();
|
||||
|
||||
if (is_first_login) {
|
||||
e.first_login = time(nullptr);
|
||||
TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID()));
|
||||
BuyerRepository::DeleteBuyer(database, CharacterID());
|
||||
LogTradingDetail(
|
||||
"Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.",
|
||||
/**
|
||||
* Update last login since this doesn't get updated until a late save later so we can update online status
|
||||
*/
|
||||
database.QueryDatabase(
|
||||
StringFormat(
|
||||
"UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u",
|
||||
CharacterID()
|
||||
);
|
||||
}
|
||||
|
||||
e.last_login = time(nullptr);
|
||||
|
||||
const int updated = CharacterDataRepository::UpdateOne(database, e);
|
||||
if (!updated) {
|
||||
LogError("Failed to update login time for character_id [{}]", CharacterID());
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
||||
InvokeChangePetName(false);
|
||||
@@ -893,7 +871,7 @@ void Client::CompleteConnect()
|
||||
entity_list.SendFindableNPCList(this);
|
||||
|
||||
if (IsInAGuild()) {
|
||||
if (ingame) {
|
||||
if (firstlogon == 1) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), true);
|
||||
SendGuildMembersList();
|
||||
}
|
||||
@@ -1004,6 +982,7 @@ void Client::CompleteConnect()
|
||||
safe_delete(p);
|
||||
}
|
||||
|
||||
RecordStats();
|
||||
AutoGrantAAPoints();
|
||||
|
||||
// set initial position for mob tracking
|
||||
@@ -1328,14 +1307,14 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
|
||||
/* Load Character Data */
|
||||
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
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
for (auto row : results) {
|
||||
if (row[4] && Strings::ToInt(row[4]) > 0) {
|
||||
guild_id = Strings::ToInt(row[4]);
|
||||
guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE;
|
||||
guild_id = Strings::ToInt(row[4]);
|
||||
guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE;
|
||||
guild_tribute_opt_in = row[7] ? Strings::ToBool(row[7]) : 0;
|
||||
}
|
||||
|
||||
@@ -1343,21 +1322,10 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
||||
SetExtraHaste(Strings::ToInt(row[8]), false);
|
||||
SetIllusionBlock(Strings::ToBool(row[9]));
|
||||
|
||||
if (LFP) {
|
||||
LFP = Strings::ToInt(row[0]);
|
||||
}
|
||||
|
||||
if (LFG) {
|
||||
LFG = Strings::ToInt(row[1]);
|
||||
}
|
||||
|
||||
if (row[3]) {
|
||||
first_login = Strings::ToUnsignedInt(row[3]);
|
||||
}
|
||||
|
||||
if (row[10]) {
|
||||
ingame = Strings::ToBool(row[10]);
|
||||
}
|
||||
if (LFP) { LFP = Strings::ToInt(row[0]); }
|
||||
if (LFG) { LFG = Strings::ToInt(row[1]); }
|
||||
if (row[3])
|
||||
firstlogon = Strings::ToInt(row[3]);
|
||||
}
|
||||
|
||||
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...
|
||||
// item loss will occur when they use the 'empty' slots, if this is not done
|
||||
m_inv.SetGMInventory(true);
|
||||
loaditems = database.GetInventory(this);
|
||||
database.LoadCharacterData(cid, &m_pp, &m_epp);
|
||||
database.LoadCharacterBandolier(cid, &m_pp);
|
||||
database.LoadCharacterBindPoint(cid, &m_pp);
|
||||
database.LoadCharacterMaterialColor(cid, &m_pp);
|
||||
database.LoadCharacterPotionBelt(cid, &m_pp);
|
||||
database.LoadCharacterCurrency(cid, &m_pp);
|
||||
database.LoadCharacterSkills(cid, &m_pp);
|
||||
database.LoadCharacterInspectMessage(cid, &m_inspect_message);
|
||||
database.LoadCharacterSpellBook(cid, &m_pp);
|
||||
database.LoadCharacterMemmedSpells(cid, &m_pp);
|
||||
database.LoadCharacterLanguages(cid, &m_pp);
|
||||
database.LoadCharacterLeadershipAbilities(cid, &m_pp);
|
||||
database.LoadCharacterTribute(this);
|
||||
database.LoadCharacterEXPModifier(this);
|
||||
database.LoadCharacterTitleSets(this);
|
||||
loaditems = database.GetInventory(this); /* Load Character Inventory */
|
||||
database.LoadCharacterBandolier(cid, &m_pp); /* Load Character Bandolier */
|
||||
database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */
|
||||
database.LoadCharacterMaterialColor(cid, &m_pp); /* Load Character Material */
|
||||
database.LoadCharacterPotionBelt(cid, &m_pp); /* Load Character Potion Belt */
|
||||
database.LoadCharacterCurrency(cid, &m_pp); /* Load Character Currency into 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); /* Load Character Skills */
|
||||
database.LoadCharacterInspectMessage(cid, &m_inspect_message); /* Load Character Inspect Message */
|
||||
database.LoadCharacterSpellBook(cid, &m_pp); /* Load Character Spell Book */
|
||||
database.LoadCharacterMemmedSpells(cid, &m_pp); /* Load Character Memorized Spells */
|
||||
database.LoadCharacterLanguages(cid, &m_pp); /* Load Character Languages */
|
||||
database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */
|
||||
database.LoadCharacterTribute(this); /* Load CharacterTribute */
|
||||
database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */
|
||||
|
||||
// this pattern is strange
|
||||
// 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) {
|
||||
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);
|
||||
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.
|
||||
if (m_ClientVersionBit & EQ::versions::maskUFAndLater) {
|
||||
if (!ingame) {
|
||||
if (!firstlogon) {
|
||||
pet->SetTaunting(m_petinfo.taunting);
|
||||
}
|
||||
}
|
||||
@@ -10239,11 +10209,11 @@ void Client::Handle_OP_LoadSpellSet(const EQApplicationPacket *app)
|
||||
printf("Wrong size of LoadSpellSet_Struct! Expected: %zu, Got: %i\n", sizeof(LoadSpellSet_Struct), app->size);
|
||||
return;
|
||||
}
|
||||
LoadSpellSet_Struct *ss = (LoadSpellSet_Struct *) app->pBuffer;
|
||||
for (int i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) {
|
||||
if (ss->spell[i] != 0xFFFFFFFF) {
|
||||
int i;
|
||||
LoadSpellSet_Struct* ss = (LoadSpellSet_Struct*)app->pBuffer;
|
||||
for (i = 0; i < EQ::spells::SPELL_GEM_COUNT; i++) {
|
||||
if (ss->spell[i] != 0xFFFFFFFF)
|
||||
UnmemSpell(i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15597,9 +15567,7 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
|
||||
switch (in->Code) {
|
||||
case ClickTrader: {
|
||||
LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code);
|
||||
auto outapp =
|
||||
std::make_unique<EQApplicationPacket>(OP_TraderShop, static_cast<uint32>(sizeof(TraderClick_Struct))
|
||||
);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_TraderShop, sizeof(TraderClick_Struct));
|
||||
auto data = (TraderClick_Struct *) outapp->pBuffer;
|
||||
auto trader_client = entity_list.GetClientByID(in->TraderID);
|
||||
|
||||
|
||||
@@ -217,8 +217,6 @@ bool Client::Process() {
|
||||
GetMerc()->Depop();
|
||||
}
|
||||
instalog = true;
|
||||
|
||||
camp_timer.Disable();
|
||||
}
|
||||
|
||||
if (IsStunned() && stunned_timer.Check())
|
||||
@@ -727,7 +725,7 @@ void Client::OnDisconnect(bool hard_disconnect) {
|
||||
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 */
|
||||
ClearAllProximities();
|
||||
|
||||
+1
-11
@@ -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("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
|
||||
command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
||||
command_add("zonevariable", "[clear|delete|set|view] - Modify zone variables for your current zone", AccountStatus::GMAdmin, command_zonevariable) ||
|
||||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
|
||||
) {
|
||||
command_deinit();
|
||||
@@ -514,15 +513,7 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status)
|
||||
parse->EventPlayer(EVENT_GM_COMMAND, c, message, 0);
|
||||
}
|
||||
|
||||
bool log_command = true;
|
||||
for (auto &cmd: Strings::Split(RuleS(Logging, PlayerEventsIgnoreGMCommands), ",")) {
|
||||
if (Strings::Contains(command, Strings::ToLower(cmd))) {
|
||||
log_command = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && log_command) {
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && message != "#help") {
|
||||
auto e = PlayerEvent::GMCommandEvent{
|
||||
.message = message,
|
||||
.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/zonebootup.cpp"
|
||||
#include "gm_commands/zoneshutdown.cpp"
|
||||
#include "gm_commands/zonevariable.cpp"
|
||||
#include "gm_commands/zone_instance.cpp"
|
||||
#include "gm_commands/zone_shard.cpp"
|
||||
#include "gm_commands/zsave.cpp"
|
||||
|
||||
@@ -198,7 +198,6 @@ void command_zone_instance(Client *c, const Seperator *sep);
|
||||
void command_zone_shard(Client *c, const Seperator *sep);
|
||||
void command_zonebootup(Client *c, const Seperator *sep);
|
||||
void command_zoneshutdown(Client *c, const Seperator *sep);
|
||||
void command_zonevariable(Client *c, const Seperator *sep);
|
||||
void command_zsave(Client *c, const Seperator *sep);
|
||||
|
||||
#include "bot.h"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "data_bucket.h"
|
||||
#include "zonedb.h"
|
||||
#include "mob.h"
|
||||
#include "client.h"
|
||||
#include "worldserver.h"
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
@@ -360,8 +359,7 @@ bool DataBucket::GetDataBuckets(Mob *mob)
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
|
||||
}
|
||||
else if (mob->IsClient()) {
|
||||
uint32 account_id = mob->CastToClient()->AccountID();
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id});
|
||||
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1125,8 +1125,8 @@ void EntityList::AESpell(
|
||||
RuleI(Range, MobCloseScanDistance),
|
||||
distance
|
||||
);
|
||||
auto list = caster_mob->GetCloseMobList(distance);
|
||||
for (auto& it: list) {
|
||||
|
||||
for (auto& it: caster_mob->GetCloseMobList(distance)) {
|
||||
current_mob = it.second;
|
||||
if (!current_mob) {
|
||||
continue;
|
||||
|
||||
@@ -2543,14 +2543,6 @@ void PerlembParser::ExportEventVariables(
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_CONNECT: {
|
||||
Seperator sep(data);
|
||||
ExportVar(package_name.c_str(), "last_login", sep.arg[0]);
|
||||
ExportVar(package_name.c_str(), "seconds_since_last_login", sep.arg[1]);
|
||||
ExportVar(package_name.c_str(), "is_first_login", sep.arg[2]);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
|
||||
+1
-30
@@ -505,9 +505,6 @@ void EntityList::MobProcess()
|
||||
zone->GetSecondsBeforeIdle(),
|
||||
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
|
||||
);
|
||||
|
||||
CheckToClearTraderAndBuyerTables();
|
||||
|
||||
mob_settle_timer->Disable();
|
||||
}
|
||||
|
||||
@@ -2338,7 +2335,7 @@ void EntityList::QueueClientsGuild(const EQApplicationPacket *app, uint32 guild_
|
||||
void EntityList::QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 guild_id)
|
||||
{
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct));
|
||||
auto data = reinterpret_cast<GuildBankItemUpdate_Struct *>(outapp->pBuffer);
|
||||
auto data = reinterpret_cast<GuildBankItemUpdate_Struct *>(outapp->pBuffer);
|
||||
|
||||
memcpy(data, gbius, sizeof(GuildBankItemUpdate_Struct));
|
||||
|
||||
@@ -3162,13 +3159,6 @@ void EntityList::Depop(bool StartSpawnTimer)
|
||||
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->Depop(StartSpawnTimer);
|
||||
}
|
||||
@@ -6012,22 +6002,3 @@ void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
|
||||
c->SetDecayTimer(decay_time);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityList::CheckToClearTraderAndBuyerTables()
|
||||
{
|
||||
if (zone->GetZoneID() == Zones::BAZAAR) {
|
||||
TraderRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", zone->GetZoneID(), zone->GetInstanceID()
|
||||
)
|
||||
);
|
||||
BuyerRepository::DeleteBuyers(database, zone->GetZoneID(), zone->GetInstanceID());
|
||||
|
||||
LogTradingDetail(
|
||||
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]",
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,7 +581,6 @@ public:
|
||||
void SendMerchantEnd(Mob* merchant);
|
||||
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
||||
void RestoreCorpse(NPC* npc, uint32_t decay_time);
|
||||
void CheckToClearTraderAndBuyerTables();
|
||||
|
||||
protected:
|
||||
friend class Zone;
|
||||
|
||||
@@ -1693,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");
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(sep->arg[1], "npc_tint_id")) {
|
||||
if (sep->IsNumber(2)) {
|
||||
const uint32 npc_tint_id = (Strings::ToUnsignedInt(sep->arg[2]));
|
||||
|
||||
n.npc_tint_id = npc_tint_id;
|
||||
|
||||
d = fmt::format(
|
||||
"Set NPCTintID {} for {}",
|
||||
npc_tint_id,
|
||||
npc_id_string
|
||||
);
|
||||
} else {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
"Usage: #npcedit npc_tint_id [id] - Sets an NPC's NPCTintID [0 - 78 for RoF2]"
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
SendNPCEditSubCommands(c);
|
||||
return;
|
||||
|
||||
@@ -8,11 +8,6 @@ extern WorldServer worldserver;
|
||||
|
||||
void command_task(Client *c, const Seperator *sep)
|
||||
{
|
||||
if (!RuleB(TaskSystem, EnableTaskSystem)) {
|
||||
c->Message(Chat::White, "This command cannot be used while the Task system is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
const int arguments = sep->argnum;
|
||||
if (!arguments) {
|
||||
c->Message(Chat::White, "Syntax: #task [subcommand]");
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -406,7 +406,6 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack)
|
||||
c.second->SendGuildDeletePacket(s->guild_id);
|
||||
c.second->RefreshGuildInfo();
|
||||
c.second->MessageString(Chat::Guild, GUILD_DISBANDED);
|
||||
c.second->SendGuildList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1094,8 +1094,8 @@ int lua_faction_value() {
|
||||
return quest_manager.FactionValue();
|
||||
}
|
||||
|
||||
bool lua_check_title(uint32 title_set) {
|
||||
return quest_manager.checktitle(title_set);
|
||||
void lua_check_title(uint32 title_set) {
|
||||
quest_manager.checktitle(title_set);
|
||||
}
|
||||
|
||||
void lua_enable_title(uint32 title_set) {
|
||||
|
||||
@@ -945,12 +945,6 @@ bool Lua_NPC::IsResumedFromZoneSuspend()
|
||||
return self->IsResumedFromZoneSuspend();
|
||||
}
|
||||
|
||||
void Lua_NPC::SetNPCTintIndex(uint32 id)
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
|
||||
}
|
||||
|
||||
luabind::scope lua_register_npc() {
|
||||
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -1097,7 +1091,6 @@ luabind::scope lua_register_npc() {
|
||||
.def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType)
|
||||
.def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro)
|
||||
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
|
||||
.def("SetNPCTintIndex", &Lua_NPC::SetNPCTintIndex)
|
||||
.def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID)
|
||||
.def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum)
|
||||
.def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill)
|
||||
|
||||
@@ -199,8 +199,6 @@ public:
|
||||
void ReturnHandinItems(Lua_Client c);
|
||||
Lua_Spawn GetSpawn(lua_State* L);
|
||||
bool IsResumedFromZoneSuspend();
|
||||
void SetNPCTintIndex(uint32 id);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -353,7 +353,6 @@ LuaParser::LuaParser() {
|
||||
PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss;
|
||||
PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked;
|
||||
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item;
|
||||
PlayerArgumentDispatch[EVENT_CONNECT] = handle_player_connect;
|
||||
|
||||
ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click;
|
||||
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;
|
||||
|
||||
@@ -1809,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
|
||||
void handle_item_click(
|
||||
QuestInterface *parse,
|
||||
|
||||
@@ -865,15 +865,6 @@ void handle_player_read_item(
|
||||
std::vector<std::any> *extra_pointers
|
||||
);
|
||||
|
||||
void handle_player_connect(
|
||||
QuestInterface *parse,
|
||||
lua_State* L,
|
||||
Client* client,
|
||||
std::string data,
|
||||
uint32 extra_data,
|
||||
std::vector<std::any> *extra_pointers
|
||||
);
|
||||
|
||||
// Item
|
||||
void handle_item_click(
|
||||
QuestInterface *parse,
|
||||
|
||||
+3
-3
@@ -727,10 +727,10 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
|
||||
return self->GetBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
bool Lua_Zone::ClearVariables()
|
||||
void Lua_Zone::ClearVariables()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->ClearVariables();
|
||||
Lua_Safe_Call_Void();
|
||||
self->ClearVariables();
|
||||
}
|
||||
|
||||
bool Lua_Zone::DeleteVariable(const std::string& variable_name)
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ public:
|
||||
void SetInstanceTimeRemaining(uint32 time_remaining);
|
||||
void SetIsHotzone(bool is_hotzone);
|
||||
void ShowZoneGlobalLoot(Lua_Client c);
|
||||
bool ClearVariables();
|
||||
void ClearVariables();
|
||||
bool DeleteVariable(const std::string& variable_name);
|
||||
std::string GetVariable(const std::string& variable_name);
|
||||
luabind::object GetVariables(lua_State* L);
|
||||
|
||||
@@ -678,7 +678,6 @@ int main(int argc, char **argv)
|
||||
safe_delete(Config);
|
||||
|
||||
if (zone != 0) {
|
||||
zone->SetSaveZoneState(false);
|
||||
zone->Shutdown(true);
|
||||
}
|
||||
//Fix for Linux world server problem.
|
||||
|
||||
+27
-28
@@ -101,8 +101,7 @@ Mob::Mob(
|
||||
bool in_always_aggro,
|
||||
int32 in_heroic_strikethrough,
|
||||
bool in_keeps_sold_items,
|
||||
int64 in_hp_regen_per_second,
|
||||
uint32 npc_tint_id
|
||||
int64 in_hp_regen_per_second
|
||||
) :
|
||||
attack_timer(2000),
|
||||
attack_dw_timer(2000),
|
||||
@@ -290,7 +289,6 @@ Mob::Mob(
|
||||
always_aggro = in_always_aggro;
|
||||
heroic_strikethrough = in_heroic_strikethrough;
|
||||
keeps_sold_items = in_keeps_sold_items;
|
||||
m_npc_tint_id = npc_tint_id;
|
||||
|
||||
InitializeBuffSlots();
|
||||
|
||||
@@ -1287,24 +1285,23 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
|
||||
}
|
||||
|
||||
ns->spawn.heading = FloatToEQ12(m_Position.w);
|
||||
ns->spawn.x = FloatToEQ19(m_Position.x); //((int32)x_pos)<<3;
|
||||
ns->spawn.y = FloatToEQ19(m_Position.y); //((int32)y_pos)<<3;
|
||||
ns->spawn.z = FloatToEQ19(m_Position.z); //((int32)z_pos)<<3;
|
||||
ns->spawn.spawnId = GetID();
|
||||
ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
|
||||
ns->spawn.max_hp = 100; // this field needs a better name
|
||||
ns->spawn.race = (use_model) ? use_model : race;
|
||||
ns->spawn.runspeed = runspeed;
|
||||
ns->spawn.walkspeed = walkspeed;
|
||||
ns->spawn.class_ = class_;
|
||||
ns->spawn.gender = gender;
|
||||
ns->spawn.level = level;
|
||||
ns->spawn.PlayerState = GetPlayerState();
|
||||
ns->spawn.deity = deity;
|
||||
ns->spawn.animation = 0;
|
||||
ns->spawn.findable = findable ? 1 : 0;
|
||||
ns->spawn.npc_tint_id = GetNpcTintId();
|
||||
ns->spawn.heading = FloatToEQ12(m_Position.w);
|
||||
ns->spawn.x = FloatToEQ19(m_Position.x);//((int32)x_pos)<<3;
|
||||
ns->spawn.y = FloatToEQ19(m_Position.y);//((int32)y_pos)<<3;
|
||||
ns->spawn.z = FloatToEQ19(m_Position.z);//((int32)z_pos)<<3;
|
||||
ns->spawn.spawnId = GetID();
|
||||
ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
|
||||
ns->spawn.max_hp = 100; //this field needs a better name
|
||||
ns->spawn.race = (use_model) ? use_model : race;
|
||||
ns->spawn.runspeed = runspeed;
|
||||
ns->spawn.walkspeed = walkspeed;
|
||||
ns->spawn.class_ = class_;
|
||||
ns->spawn.gender = gender;
|
||||
ns->spawn.level = level;
|
||||
ns->spawn.PlayerState = GetPlayerState();
|
||||
ns->spawn.deity = deity;
|
||||
ns->spawn.animation = 0;
|
||||
ns->spawn.findable = findable?1:0;
|
||||
|
||||
UpdateActiveLight();
|
||||
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];
|
||||
@@ -1315,8 +1312,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
ns->spawn.NPC = IsClient() ? 0 : 1;
|
||||
ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
|
||||
ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic!
|
||||
ns->spawn.untargetable = IsTargetable();
|
||||
|
||||
|
||||
ns->spawn.petOwnerId = ownerid;
|
||||
|
||||
ns->spawn.haircolor = haircolor;
|
||||
@@ -1702,6 +1698,13 @@ void Mob::StopMoving()
|
||||
|
||||
void Mob::StopMoving(float new_heading)
|
||||
{
|
||||
if (IsBot()) {
|
||||
auto bot = CastToBot();
|
||||
|
||||
bot->SetCombatJitterFlag(false);
|
||||
bot->SetCombatOutOfRangeJitterFlag(false);
|
||||
}
|
||||
|
||||
StopNavigation();
|
||||
RotateTo(new_heading);
|
||||
|
||||
@@ -4618,12 +4621,8 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id)
|
||||
{
|
||||
CastToClient()->GetPP().zone_id = zone_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() {
|
||||
|
||||
+2
-5
@@ -192,8 +192,7 @@ public:
|
||||
bool in_always_aggros_foes,
|
||||
int32 in_heroic_strikethrough,
|
||||
bool keeps_sold_items,
|
||||
int64 in_hp_regen_per_second = 0,
|
||||
uint32 npc_tint_id = 0
|
||||
int64 in_hp_regen_per_second = 0
|
||||
);
|
||||
virtual ~Mob();
|
||||
|
||||
@@ -963,7 +962,7 @@ public:
|
||||
uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id);
|
||||
bool TryFadeEffect(int slot);
|
||||
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);
|
||||
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 ****
|
||||
@@ -1067,7 +1066,6 @@ public:
|
||||
void SendWearChangeAndLighting(int8 last_texture);
|
||||
inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; }
|
||||
bool UpdateActiveLight(); // returns true if change, false if no change
|
||||
uint32 GetNpcTintId() { return m_npc_tint_id; }
|
||||
|
||||
EQ::LightSourceProfile* GetLightProfile() { return &m_Light; }
|
||||
|
||||
@@ -1599,7 +1597,6 @@ protected:
|
||||
bool rare_spawn;
|
||||
int32 heroic_strikethrough;
|
||||
bool keeps_sold_items;
|
||||
uint32 m_npc_tint_id;
|
||||
|
||||
uint32 m_PlayerState;
|
||||
uint32 GetPlayerState() { return m_PlayerState; }
|
||||
|
||||
@@ -933,11 +933,16 @@ void MobMovementManager::SendCommandToClients(
|
||||
|
||||
float MobMovementManager::FixHeading(float in)
|
||||
{
|
||||
int h = static_cast<int>(in) % 512;
|
||||
if (h < 0) {
|
||||
h += 512;
|
||||
auto h = in;
|
||||
while (h > 512.0) {
|
||||
h -= 512.0;
|
||||
}
|
||||
return static_cast<float>(h);
|
||||
|
||||
while (h < 0.0) {
|
||||
h += 512.0;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void MobMovementManager::DumpStats(Client *client)
|
||||
|
||||
+8
-14
@@ -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->heroic_strikethrough,
|
||||
npc_type_data->keeps_sold_items,
|
||||
npc_type_data->hp_regen_per_second,
|
||||
npc_type_data->m_npc_tint_id
|
||||
npc_type_data->hp_regen_per_second
|
||||
),
|
||||
attacked_timer(CombatEventTimer_expire),
|
||||
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;
|
||||
ignore_despawn = npc_type_data->ignore_despawn;
|
||||
m_targetable = !npc_type_data->untargetable;
|
||||
m_npc_tint_id = npc_type_data->m_npc_tint_id;
|
||||
|
||||
npc_scale_manager->ScaleNPC(this);
|
||||
|
||||
@@ -1258,11 +1256,10 @@ uint32 ZoneDatabase::CreateNewNPCCommand(
|
||||
e.Avoidance = n->GetAvoidanceRating();
|
||||
e.heroic_strikethrough = n->GetHeroicStrikethrough();
|
||||
|
||||
e.see_hide = n->SeeHide();
|
||||
e.see_improved_hide = n->SeeImprovedHide();
|
||||
e.see_invis = n->SeeInvisible();
|
||||
e.see_invis_undead = n->SeeInvisibleUndead();
|
||||
e.npc_tint_id = n->GetNpcTintId();
|
||||
e.see_hide = n->SeeHide();
|
||||
e.see_improved_hide = n->SeeImprovedHide();
|
||||
e.see_invis = n->SeeInvisible();
|
||||
e.see_invis_undead = n->SeeInvisibleUndead();
|
||||
|
||||
|
||||
e = NpcTypesRepository::InsertOne(*this, e);
|
||||
@@ -1402,7 +1399,6 @@ uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client* c, NPC* n)
|
||||
e.loottable_id = n->GetLoottableID();
|
||||
e.merchant_id = n->MerchantType;
|
||||
e.face = n->GetLuclinFace();
|
||||
e.npc_tint_id = n->GetNpcTintId();
|
||||
|
||||
const int updated = NpcTypesRepository::UpdateOne(*this, e);
|
||||
|
||||
@@ -1543,7 +1539,6 @@ uint32 ZoneDatabase::AddNPCTypes(
|
||||
e.runspeed = n->GetRunspeed();
|
||||
e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
||||
e.sec_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
||||
e.npc_tint_id = n->GetNpcTintId();
|
||||
|
||||
e = NpcTypesRepository::InsertOne(*this, e);
|
||||
|
||||
@@ -2174,10 +2169,9 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
PetOnSpawn(ns);
|
||||
ns->spawn.is_npc = 1;
|
||||
UpdateActiveLight();
|
||||
ns->spawn.light = GetActiveLightType();
|
||||
ns->spawn.show_name = NPCTypedata->show_name;
|
||||
ns->spawn.trader = false;
|
||||
ns->spawn.npc_tint_id = GetNpcTintId();
|
||||
ns->spawn.light = GetActiveLightType();
|
||||
ns->spawn.show_name = NPCTypedata->show_name;
|
||||
ns->spawn.trader = false;
|
||||
}
|
||||
|
||||
void NPC::PetOnSpawn(NewSpawn_Struct* ns)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user