Compare commits

...

56 Commits

Author SHA1 Message Date
Akkadius 05f566ad07 tweak 2025-06-22 13:42:45 -05:00
Chris Miles c7a8d796fc Update CMakeLists.txt 2025-06-22 13:21:11 -05:00
Chris Miles 77163ec137 Update eqemu_logsys.h 2025-06-22 04:23:03 -05:00
Chris Miles e846bb86b6 [Code] Remove Regex Compile Bloat (#4947) 2025-06-22 02:08:15 -05:00
Chris Miles 3e6a3e2168 [Build] Significantly Improve Build Times Using Unity Builds (#4948) 2025-06-22 02:08:03 -05:00
Alex King 2aebf1a78a [Code] Remove Unused MZoneShutdown Mutex (#4946) 2025-06-22 01:49:39 -05:00
Chris Miles 1be7e56b86 [Databuckets] Move Databuckets to Common (#4918)
* [Databuckets] Move Databuckets to Common

* Fix linking issue
2025-06-16 16:48:29 -05:00
Chris Miles a0ff9d67a1 [Fix] Bulk Send Corpses after Idle State (#4910) 2025-06-09 14:31:28 -05:00
Chris Miles befee1c729 [Quests] Support Multiple Quest, Plugin, and Lua Module Paths (#4906)
* [Quests] Add Support for Multiple Load Paths

* Adjust load paths

* plugin != m_lua_module_directories
2025-06-09 12:49:46 -05:00
nytmyr 3d70063a68 [Doors] Fix door saving for versions (#4905)
* [Doors] Fix door saving for versions

- Door saving wasn't saving to the proper version on `#door save`

* Update doors.cpp

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-06-09 12:47:56 -05:00
JJ 4e28bcf85e Bump libuv to 1.50.0 (#4898) 2025-06-09 12:40:37 -05:00
JJ 687d10960a [Logs] Fix output for tasks in logs (#4907) 2025-06-09 12:32:45 -05:00
Chris Miles 567d46c3d6 [Performance] Auto Idle / AFK (#4903)
* [Performance] AFK Client Packet Filtering

* Player feedback

* Update client_packet.cpp

* Fixes

* Streamline updates to SetAFK

* Decouple idling and AFK and manual AFK

* Reset clock timer when we take AFK or idle off

* Exclude bard songs in non combat zones from resetting timer

* GM exclusion adjustments
2025-05-22 13:08:32 -05:00
Chris Miles 53cc2de459 [World API] Input Validation (#4904)
* [World API] Input Validation

* Update eqemu_api_world_data_service.cpp

* Add db ping to player events processor, move back into main thread
2025-05-22 13:08:17 -05:00
Chris Miles cb866cba31 [Release] 23.7.0 (#4902) 2025-05-19 12:31:37 -05:00
JJ e2162c08da [CLI] Add custom database version output (#4901)
* Add custom database version output to CLI from #4892

* Spacing alignment
2025-05-18 11:28:10 -05:00
Chris Miles e657953b8f [Netcode] Resend Logic Adjustments (#4900)
* Timeout adjustment

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Stuff

* Update daybreak_connection.h

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp
2025-05-18 11:19:37 -05:00
JJ eb366e67b7 [CLI] Fix MySQL check in database dumper (#4897) 2025-05-16 18:56:38 -05:00
JJ 5b728a42f7 Update zonelist.h (#4896) 2025-05-16 18:17:38 -04:00
Chris Miles a1d414d64c [Netcode] Resend Logic Adjustments (#4895)
* [Netcode] Resend Logic Adjustments

* Update daybreak_connection.h
2025-05-16 16:46:07 -05:00
Chris Miles 907029ed76 [Database] Remove Transaction Wrapped Character Save (#4894) 2025-05-16 16:07:45 -05:00
Chris Miles 894f22fba0 [Fix] Deadlock on failed #copycharacter commands (#4887) 2025-05-16 13:39:34 -05:00
Chris Miles 7ec09d7e0f [Player Events] Add rule to ignore configured GM commands (#4888) 2025-05-16 13:39:24 -05:00
Chris Miles c82266790a [Zone State] Load New Spawn2 Data When Present (#4889) 2025-05-16 13:39:16 -05:00
Chris Miles ec31fddbae [Logging] Auto Update Log Category Names (#4890) 2025-05-16 13:39:08 -05:00
Chris Miles fb49ce2404 [Rules] Auto Update Rule Notes from Source (#4891)
* [Rules] Auto Update Rule Notes from Source

* Update rulesys.cpp
2025-05-16 13:38:59 -05:00
Chris Miles 276b7e238a [Database] Add Custom Database Migrations for Operators (#4892)
* [Database] Add Custom Database Migrations for Operators

* Changes

* Update database_update_manifest_custom.cpp
2025-05-16 13:38:51 -05:00
Chris Miles c7a463420b [World] Fix Rarer Reload Deadlock (#4893) 2025-05-16 13:38:42 -05:00
Chris Miles a56bb52808 Update README.md 2025-05-15 15:29:05 -05:00
Chris Miles 0bbdb58679 Update README.md 2025-05-15 15:26:07 -05:00
Alex King f29478c105 [Commands] Add #zonevariable Command (#4882)
* [Commands] Add #zonevariable Command

* Update zonevariable.cpp

* Argument safety

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-05-14 20:53:43 -05:00
Chris Miles 888a88f966 [Release] 23.6.0 (#4885) 2025-05-14 20:50:49 -05:00
Alex King 3b617a6652 [Quest API] Add Last Login and First Login Flags to EVENT_CONNECT (#4866)
* [Quest API] Add Last Login and First Login Flags to EVENT_CONNECT

* Push

* Update base_character_data_repository.h

* Update version field

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-05-14 20:46:25 -05:00
Alex King c36c336bc7 [Performance] Store Player Title Sets in Client Memory (#4836)
* [Performance] Store Player Title Sets in Zone Memory

* Move to client memory

* Update client_packet.cpp

* Update zonedb.cpp

* Save only when necessary

* Single Insert

* Add optional insert flag

* Update client.h

* Consolidation
2025-05-14 20:46:11 -05:00
zrix-eq 4a9779635d [Feature] Add Character:TradeskillUpMinChance rule (#4867) 2025-05-14 20:27:09 -05:00
Mitch Freeman 20da490bda [Feature] Enable spawn attribute for NPCTintID (#4871)
* Add scripting for NPCTintIndex

* AddNPCTintID

Add NPCTintID to spawn logic

* Update base_npc_types_repository.h

* Correct version.h
2025-05-14 20:26:35 -05:00
Mitch Freeman 1221e88d92 [Fix] Add trader/buyer cleanup actions (#4843)
* Add trader/buyer cleanup actions

Add trader/buyer db cleanup for
- on zone idle
- on client first login
- when world drops a zone connection
- in Client::ProcessMovePC

Cleanup several compiler warnings

* Formatting Updates
2025-05-14 20:24:59 -05:00
nytmyr a2b28b2e16 [Bots] Correct ^pull logic and add checks for Enchanter pets (#4827)
* [Bots] Fix bots getting stuck on ^pull

- Removed initial LoS check from bots and rely only on owner when told to ^pull. This prevents bots from getting stuck in a loop if they don't have LoS when trying to initiate the pull.

* [Bots] Add valid state checks to ^clickitem

- Bots previously weren't checking if they were in a valid state or not before trying to click an item when told (in a death state, held, feared, silenced, in group/raid, etc.)

* [Bots] Add valid state checks to ^clickitem

- Bots previously weren't checking if they were in a valid state or not before trying to click an item when told (in a death state, held, feared, silenced, in group/raid, etc.)

Miscommit

* undo

* Cleanup and Fixes

- Cleanup
- Fixed Animation Empathy checks and consolidates
- Enchanter pets will honor Animation Empathy rules all around
- Pets will properly guard if allowed when pulling
2025-05-14 20:21:25 -05:00
zimp-wow 3d607d352c [Fix] Fix Object Name Init, User Refs, and Client Sync on Close (#4861)
* [Bugfix] Fix uninitialized char* in object.cpp.

* [Bugfix] Clear object user and user tradeskill object on reset.

* [Bugfix] Send clear object packet on Object::Close()
2025-05-14 20:20:57 -05:00
JJ 83cd8119c8 [CLI] ETL Settings Output (#4873)
* Update etl_get_settings.cpp

* Update etl_get_settings.cpp

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-05-14 20:19:30 -05:00
nytmyr 4de8fbbd56 [Bots] Fix creation limit, spawn limit, level requirement checks (#4868)
* [Bots] Fix creation limit, spawn limit, level requirement checks

- Previously if buckets were being used to control any of these values and the appropriate rule was set to 0, unset class specific buckets would override the main limit buckets.
- For example, if `Bots:SpawnLimit` is set to `0` and a player has their `bot_spawn_limit` set to `5` but they don't have a class bucket set for the class they're attempting to spawn a Cleric, the unset `bot_spawn_limit_Cleric` would return 0 and prevent Clerics from being spawned.
- This affected spawn limits, creation limits and level requirements to use bots if controlled by buckets.
- `#gm on` is required to be on for those beyond the ruled min status requirements to bypass the limits.

Rewrote checks and tested every scenario of set unset rules/buckets.

* Cleanup, fix bot count

- Fixes QueryBotCount to not account for soft deleted bots (`-deleted-`)
2025-05-14 20:13:50 -05:00
nytmyr 780120036d [Bots] Move all spell_id instances to uint16 (#4876)
* [Bots] Move all spell_id instances to uint16

* Alignment
2025-05-14 19:58:18 -05:00
catapultam-habeo f3697e633c [Fix] Prevent Ranged Attack from being triggered at arbitrary rate (#4879) 2025-05-14 19:55:14 -05:00
hbingram 24f8d88333 [Fix] Fix breaking change to UF patches caused by Big Bags update (#4883)
* Fix breaking change to UF patches caused by Big Bags update

* Update comment

* Removed original line that was commented.
2025-05-14 19:51:48 -05:00
carolus21rex 21bd906a4d [Crash] Fix crash bug with pbae and quest scripts spawning mobs (#4884)
If a PBAE is in progress while a mob pops it can cause a crash. Forcing a copy before the ae has a chance to pop a mob should fix the weirdness
2025-05-14 19:50:12 -05:00
JJ 138612bc88 Only resume NPC if npc_id is ready (#4875) 2025-05-02 16:25:18 -04:00
dependabot[bot] 5919bb4dea Bump golang.org/x/net in /utils/scripts/build/should-release (#4864)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

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

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