Compare commits

...

48 Commits

Author SHA1 Message Date
Akkadius 9be2485330 [Hotfix] Backfill expire_at (not sure why this didn't make it in there to begin with) 2025-03-31 02:34:35 -05:00
Akkadius a2bf10624a Update database_update_manifest.cpp 2025-03-31 02:29:25 -05:00
Akkadius ed58d16f1f [Hotfix] Make sure we don't expire default value instances 2025-03-31 02:25:07 -05:00
Akkadius e9b45fb360 Update CHANGELOG.md 2025-03-30 17:05:27 -05:00
Chris Miles 803972873a [Database] Wrap PurgeExpiredInstances in a Transaction (#4824) 2025-03-30 17:03:57 -05:00
Chris Miles d9e57eca79 [Fix] Instance DZ Creation (#4823)
* [Fix] Instance DZ Creation

* Update dynamic_zone_base.cpp
2025-03-30 16:59:29 -05:00
JJ 6429dc80d3 [Release] 23.4.0 (#4822)
* Update CHANGELOG.md

* Update package.json

* Update version.h
2025-03-30 16:12:42 -05:00
JJ c4262b3fa6 [Cleanup] UCS Member Count (#4819)
* [Cleanup] Fix SendChannelMembers size in UCS ChatChannelList

* Part 2

* Forgot null terminator
2025-03-30 14:46:46 -05:00
Chris Miles 92128b98fd [Instances] Add expire_at Column (#4820)
* [Instances] Add `expire_at` Column

* expire_at update

* Update servertalk.h

* Add rule Instances:ExpireOffsetTimeSeconds
2025-03-30 14:46:02 -05:00
Chris Miles b9cfdea76c [Zone] Zone State Automated Testing and Improvements (#4808)
* [Zone] Zone State Automated Testing and Improvements

* Spawn condition

* Update zone.cpp

* Remove redundant logic

* Update zone_state.cpp

* TestZLocationDrift

* Protect NPC resumed NPC's from being able to die
2025-03-30 01:45:28 -05:00
Chris Miles c8a7066d0e [Performance] Send Smarter Emote Packets (#4818) 2025-03-29 19:50:44 -05:00
JJ 950cc4a325 [Cleanup] Control flow defaults missed in recent bot updates (#4817) 2025-03-29 19:07:31 -05:00
Chris Miles 82b48fe6e8 [Reload] Add Reload for Maps / Navs (#4816) 2025-03-29 18:24:17 -05:00
Chris Miles ca9c1fdd24 [API] Expose Zoneserver Compile Metadata (#4815) 2025-03-29 18:14:02 -05:00
Chris Miles fe08961d25 [Crash] Fix Repop Race Condition Crash (#4814)
* [Crash] Fix Repop Race Condition Crash

* True fix
2025-03-29 17:39:40 -05:00
Chris Miles 5b9f7ff4c9 [Fix] Globally Reloading Quests when not loaded (#4813) 2025-03-29 17:10:40 -05:00
Chris Miles 23743a4050 [Fix] Fix Instance Creation Race Condition (#4803)
* [Fix] Fix Instance Creation Race Condition

* Rejigger

* Update database_instances.cpp

* Update database_instances.cpp

* Update database_instances.cpp

* Update database_instances.cpp
2025-03-29 17:10:20 -05:00
nytmyr 444d688ad2 [Bots] Line of Sight and Mez optimizations and cleanup (#4746)
* [Bots] Line of Sight and Mez optimizations and cleanup

- Renames `Map:CheckForLoSCheat` to `Map:CheckForDoorLoSCheat` to better reflect what it does.
- Renames `Map:RangeCheckForLoSCheat` to `Map:RangeCheckForDoorLoSCheat` to better reflect what it does.
- Adds the rule `Pets:PetsRequireLoS` to determine whether or not commanded pet attacks require an addition layer of LoS checks for edge-cases.
- Adds the rule `Bots:BotsRequireLoS` to determine whether or not bots require LoS to `^attack`, `^pull` and `^precombat`.
- Adds the rule `Map:ZonesToCheckDoorCheat` to control what if any zones will be checked..
- Corrects, removes and adds LoS checks where necessary.
- Improves door checking logic for locked or triggered doors that could be blocking LoS.
- Cleans up false positives for door cheat checks.
- Adds `drawbox` option to `#door` command. This will spawn points at the center and each corner of the door's "box". It will also spawn points at your and your target's location.
- Improves Mez and AE Mez logic
- Adds more details to the rule `Bots:EpicPetSpellName`

* Remove leftover debugging

* Change return to continue for GetFirstIncomingMobToMez checks

* Move mez chance fail to beginning of cast process
2025-03-29 16:01:31 -05:00
nytmyr d554eb3423 [Bots] Fix rule Bots:FinishBuffing (#4788) 2025-03-29 15:17:33 -05:00
nytmyr deb298dda7 [Bots] Enraged positioning (#4789)
- Non-taunting melee bots will now properly go behind their target when it is enraged.
- Reduces how often taunting bots adjust their positioning by removing unnecessary rules.
- Cleans up CombatPositioning a bit
2025-03-29 15:00:08 -05:00
nytmyr 19e785b842 [Bots] Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors (#4791)
* [Bots] Fix error copy/paste

Oops

* Eliminate false errors on empty rules and add more sanity checks

- `Bots:ZonesWithSpawnLimits` - This is to be used when zones will only allow up to x amount of bots. Example: A player can normally spawn 5 bots but a zone in this rule has a lower limit of up to 3, this rule would override their normal limit if their normal limit is above the listed zone's max in `Bots:ZoneSpawnLimits`.
- `Bots:ZonesWithForcedSpawnLimits` - Zones in this rule will override any spawn limits high or low and force it. If one player can normally spawn 2 and another player can spawn 10 but a zone listed forces a limit of 5, all players will be able to spawn 5. Follows the limits set in `Bots:ZoneForcedSpawnLimits`
2025-03-29 14:59:09 -05:00
Chris Miles 7b9691d486 [Performance] Reduce LFGuild Chatter (#4794) 2025-03-29 14:58:01 -05:00
Chris Miles 30fddcc5a0 [Performance] Reduce UpdateWho S2S Chatter to World (#4792)
* [Performance] Reduce UpdateWho S2S chatter

* Add rule to change this dynamically
2025-03-29 14:56:32 -05:00
Chris Miles 235e59a2d8 [Database] Fix Respawn Times Table (#4802) 2025-03-29 14:54:26 -05:00
Chris Miles bc1ffe0716 [Performance] Reduce Adventure S2S chatter (#4793) 2025-03-29 14:48:41 -05:00
Chris Miles 44497414db [Instance] Clear Respawn Timers on Creation (#4801)
* [DZ] Clear Respawn Timers on Creation

* Revert "[DZ] Clear Respawn Timers on Creation"

This reverts commit ae18b77e83.

* Clear respawn times on instance creation
2025-03-29 14:47:25 -05:00
Chris Miles 96e34fe8f7 [Performance] Improve Character Select DB Performance (#4799) 2025-03-29 14:46:57 -05:00
Alex King ceca28d2a3 [Cleanup] Remove Extraneous Time Type in ShowZoneData (#4806) 2025-03-29 14:44:26 -05:00
Alex King b040571427 [Commands] Add Instance Support to #zoneshutdown (#4807)
* [Commands] Add Instance Support to #zoneshutdown

* Update zoneserver.cpp

* Update zoneshutdown.cpp
2025-03-29 14:44:08 -05:00
Chris Miles 49664cc1a0 [Performance] Reduce CorpseOwnerOnline S2S Chatter to World (#4795) 2025-03-29 14:30:00 -05:00
nytmyr 5e4fd43920 [Bots] Prevent bot pets from despawning on #repop (#4790) 2025-03-29 14:29:29 -05:00
Chris Miles 937b947597 [Performance] Have World Send Smarter Guild Updates (#4796)
* [Performance] Have World Send Smarter Guild Updates

* Updates to correct incorrect guild window details (permissions, etc) not being sent on guild creation.

---------

Co-authored-by: Mitch Freeman <65987027+neckkola@users.noreply.github.com>
2025-03-29 14:27:49 -05:00
Chris Miles bb70850421 [Fix] Zone State Variables Load First (#4798) 2025-03-29 14:26:12 -05:00
Chris Miles 46511365a7 [Crash] Fix Rarer World Crash with Player Event Thread Processor (#4800)
* [Crash] Fix Rarer World Crash with Player Event thread processor

* Update main.cpp
2025-03-29 14:26:00 -05:00
Chris Miles 6d69ac7a98 [Performance] Add several database indexes (#4811)
* [Performance] Add several database indexes

* Update database_update_manifest.cpp

* Update database_update_manifest.cpp

* Push
2025-03-29 14:23:28 -05:00
Alex King 938937c271 [Cleanup] Remove Unused Command Methods (#4805)
* [Cleanup] Remove Unused Command Methods

* Update command.h
2025-03-29 14:23:10 -05:00
Chris Miles cd808416c8 [Command] Add #show zone_variables (#4812) 2025-03-29 14:22:14 -05:00
Chris Miles a05d0752f6 [Fix] Zone state edge case with 0 hp (#4787) 2025-03-29 14:20:23 -05:00
Mitch Freeman 799609fb21 [Fix] AllowFVNoDrop Flag trades (#4809) 2025-03-26 21:38:13 -04:00
catapultam-habeo 213fe6a9e9 [Feature] Implement /changename & related script bindings. Clean up #set name (#4770)
* initial work, need to clean up gm commands still

* cleaned up command, works without kicking char select now

* remove thj-specific methods

* add script hooks

* actually clear flag

* rework questmgr::rename

* remove unnecessary logging

* revert

* added missing binding to perl api and updated some text

* don't return a value

* Fix some bad argument types.

* adjust case

* alpha order

* refactor some old string stuff

* don't quote integers, bob

---------

Co-authored-by: Zimp <zimp@zenryo.xyz>
Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-03-19 21:00:45 -05:00
Alex King be6a5d5f50 [Quest API] Add Support for NPC ID and NPC Name Specificity (#4781)
* [Quest API] Add Support for NPC ID and NPC Name Specificity

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2025-03-19 17:55:00 -05:00
nytmyr 1af29bd7b1 [Bots] Fix IsValidSpellTypeBySpellID to account for all types (#4764)
* [Bots] Fix IsValidSpellTypeBySpellID to account for all types

* Formatting
2025-03-19 17:43:15 -05:00
zimp-wow ef945e6e99 [Fix] Fix zone crash when attempting to add a disappearing client to hate list. (#4782) 2025-03-19 16:26:54 -05:00
Chris Miles 9528c1e7fc [Fix] Zone State Entity Variable Load Pre-Spawn (#4785)
* [Fix] Zone state ent variable pre-spawn

* Update zone_save_state.cpp

* Update zone_save_state.cpp

* Update spawn2.cpp

* Update zone_save_state.cpp

* Update zone_save_state.cpp
2025-03-19 16:21:36 -05:00
Chris Miles fd6e5f465d [Fix] Zone State Position Fix (#4784) 2025-03-19 16:17:25 -05:00
nytmyr d00125abe1 [Bots] Charmed Pets were breaking Mob respawns (#4780)
- When bots would charm a pet, once they zoned or camped the pet would poof and not trigger a respawn so the NPC which was charmed would never respawn until the zone was shut down or server restarted.
2025-03-16 19:12:48 -04:00
Akkadius 5d69235a4c [Release] 23.3.4 2025-03-14 13:09:53 -05:00
Mitch Freeman e93785f885 [Fix] Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase (#4778) 2025-03-13 22:30:00 -05:00
97 changed files with 3434 additions and 1500 deletions
+93
View File
@@ -1,3 +1,96 @@
## [23.4.0] 3/30/2025
### API
* Expose Zoneserver Compile Metadata ([#4815](https://github.com/EQEmu/Server/pull/4815)) @Akkadius 2025-03-29
### Bots
* Charmed Pets were breaking Mob respawns ([#4780](https://github.com/EQEmu/Server/pull/4780)) @nytmyr 2025-03-16
* Enraged positioning ([#4789](https://github.com/EQEmu/Server/pull/4789)) @nytmyr 2025-03-29
* Fix IsValidSpellTypeBySpellID to account for all types ([#4764](https://github.com/EQEmu/Server/pull/4764)) @nytmyr 2025-03-19
* Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors ([#4791](https://github.com/EQEmu/Server/pull/4791)) @nytmyr 2025-03-29
* Fix rule Bots:FinishBuffing ([#4788](https://github.com/EQEmu/Server/pull/4788)) @nytmyr 2025-03-29
* Line of Sight and Mez optimizations and cleanup ([#4746](https://github.com/EQEmu/Server/pull/4746)) @nytmyr 2025-03-29
* Prevent bot pets from despawning on #repop ([#4790](https://github.com/EQEmu/Server/pull/4790)) @nytmyr 2025-03-29
### Code
* Control flow defaults missed in recent bot updates ([#4817](https://github.com/EQEmu/Server/pull/4817)) @joligario 2025-03-30
* Remove Extraneous Time Type in ShowZoneData ([#4806](https://github.com/EQEmu/Server/pull/4806)) @Kinglykrab 2025-03-29
* Remove Unused Command Methods ([#4805](https://github.com/EQEmu/Server/pull/4805)) @Kinglykrab 2025-03-29
* UCS Member Count ([#4819](https://github.com/EQEmu/Server/pull/4819)) @joligario 2025-03-30
### Commands
* Add #show zone_variables ([#4812](https://github.com/EQEmu/Server/pull/4812)) @Akkadius 2025-03-29
* Add Instance Support to #zoneshutdown ([#4807](https://github.com/EQEmu/Server/pull/4807)) @Kinglykrab 2025-03-29
### Crash
* Fix Rarer World Crash with Player Event Thread Processor ([#4800](https://github.com/EQEmu/Server/pull/4800)) @Akkadius 2025-03-29
* Fix Repop Race Condition Crash ([#4814](https://github.com/EQEmu/Server/pull/4814)) @Akkadius 2025-03-29
### Database
* Fix Respawn Times Table ([#4802](https://github.com/EQEmu/Server/pull/4802)) @Akkadius 2025-03-29
* Wrap PurgeExpiredInstances in a Transaction ([#4824](https://github.com/EQEmu/Server/pull/4824)) @Akkadius 2025-03-30
### Feature
* Implement /changename & related script bindings. Clean up #set name ([#4770](https://github.com/EQEmu/Server/pull/4770)) @catapultam-habeo 2025-03-20
### Fixes
* AllowFVNoDrop Flag trades ([#4809](https://github.com/EQEmu/Server/pull/4809)) @neckkola 2025-03-27
* Fix Instance Creation Race Condition ([#4803](https://github.com/EQEmu/Server/pull/4803)) @Akkadius 2025-03-29
* Fix zone crash when attempting to add a disappearing client to hate list. ([#4782](https://github.com/EQEmu/Server/pull/4782)) @zimp-wow 2025-03-19
* Globally Reloading Quests when not loaded ([#4813](https://github.com/EQEmu/Server/pull/4813)) @Akkadius 2025-03-29
* Instance DZ Creation ([#4823](https://github.com/EQEmu/Server/pull/4823)) @Akkadius 2025-03-30
* Zone State Entity Variable Load Pre-Spawn ([#4785](https://github.com/EQEmu/Server/pull/4785)) @Akkadius 2025-03-19
* Zone State Position Fix ([#4784](https://github.com/EQEmu/Server/pull/4784)) @Akkadius 2025-03-19
* Zone State Variables Load First ([#4798](https://github.com/EQEmu/Server/pull/4798)) @Akkadius 2025-03-29
* Zone state edge case with 0 hp ([#4787](https://github.com/EQEmu/Server/pull/4787)) @Akkadius 2025-03-29
### Instance
* Clear Respawn Timers on Creation ([#4801](https://github.com/EQEmu/Server/pull/4801)) @Akkadius 2025-03-29
### Instances
* Add `expire_at` Column ([#4820](https://github.com/EQEmu/Server/pull/4820)) @Akkadius 2025-03-30
### Performance
* Add several database indexes ([#4811](https://github.com/EQEmu/Server/pull/4811)) @Akkadius 2025-03-29
* Have World Send Smarter Guild Updates ([#4796](https://github.com/EQEmu/Server/pull/4796)) @Akkadius 2025-03-29
* Improve Character Select DB Performance ([#4799](https://github.com/EQEmu/Server/pull/4799)) @Akkadius 2025-03-29
* Reduce Adventure S2S chatter ([#4793](https://github.com/EQEmu/Server/pull/4793)) @Akkadius 2025-03-29
* Reduce CorpseOwnerOnline S2S Chatter to World ([#4795](https://github.com/EQEmu/Server/pull/4795)) @Akkadius 2025-03-29
* Reduce LFGuild Chatter ([#4794](https://github.com/EQEmu/Server/pull/4794)) @Akkadius 2025-03-29
* Reduce UpdateWho S2S Chatter to World ([#4792](https://github.com/EQEmu/Server/pull/4792)) @Akkadius 2025-03-29
* Send Smarter Emote Packets ([#4818](https://github.com/EQEmu/Server/pull/4818)) @Akkadius 2025-03-30
### Quest API
* Add Support for NPC ID and NPC Name Specificity ([#4781](https://github.com/EQEmu/Server/pull/4781)) @Kinglykrab 2025-03-19
### Reload
* Add Reload for Maps / Navs ([#4816](https://github.com/EQEmu/Server/pull/4816)) @Akkadius 2025-03-29
### Zone
* Zone State Automated Testing and Improvements ([#4808](https://github.com/EQEmu/Server/pull/4808)) @Akkadius 2025-03-30
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
## [23.3.4] 3/14/2025
### Fixes
* Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase ([#4778](https://github.com/EQEmu/Server/pull/4778)) @neckkola 2025-03-14
* Fix for rare circumstance where NPC's would have 0 health on restore @Akkadius
## [23.3.3] 3/13/2025
### Database
+37
View File
@@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::UpdateNameByID(const int character_id, const std::string& new_name)
{
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
auto l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`id` = {}",
character_id
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
e.name = new_name;
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::IsNameUsed(const std::string& name)
{
if (RuleB(Bots, Enabled)) {
@@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name)
return !character_data.empty();
}
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
bool Database::IsPetNameUsed(const std::string& name)
{
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
return !pet_name_data.empty();
}
uint32 Database::GetServerType()
{
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
+3
View File
@@ -103,6 +103,7 @@ public:
bool ReserveName(uint32 account_id, const std::string& name);
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
bool UpdateName(const std::string& old_name, const std::string& new_name);
bool UpdateNameByID(const int character_id, const std::string& new_name);
bool CopyCharacter(
const std::string& source_character_name,
const std::string& destination_character_name,
@@ -116,6 +117,7 @@ public:
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
bool CheckNameFilter(const std::string& name, bool surname = false);
bool IsNameUsed(const std::string& name);
bool IsPetNameUsed(const std::string& name);
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
uint32 GetAccountIDByChar(uint32 character_id);
@@ -139,6 +141,7 @@ public:
bool CheckInstanceExpired(uint16 instance_id);
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
bool GetUnusedInstanceID(uint16& instance_id);
bool TryGetUnusedInstanceID(uint16& instance_id);
bool IsGlobalInstance(uint16 instance_id);
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
bool RemoveClientsFromInstance(uint16 instance_id);
+89 -3
View File
@@ -6792,7 +6792,7 @@ UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 341) + 5810)
},
ManifestEntry{
.version = 9304,
.description = "2024_12_01_2024_update_guild_bank",
.description = "2024_12_01_update_guild_bank",
.check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'",
.condition = "empty",
.match = "",
@@ -6914,7 +6914,7 @@ CREATE TABLE `zone_state_spawns` (
},
ManifestEntry{
.version = 9308,
.description = "2025_add_multivalue_support_to_evolving_subtype.sql",
.description = "2025_03_29_add_multivalue_support_to_evolving_subtype.sql",
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
.condition = "missing",
.match = "varchar(200)",
@@ -6986,7 +6986,6 @@ ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
.match = "idx_zone_instance",
.sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id);
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
@@ -6998,6 +6997,93 @@ ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
.match = "",
.sql = R"(
TRUNCATE TABLE zone_state_spawns;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9315,
.description = "2025_03_29_character_tribute_index.sql",
.check = "SHOW INDEX FROM character_tribute",
.condition = "missing",
.match = "idx_character_id",
.sql = R"(
ALTER TABLE character_tribute ADD INDEX idx_character_id (character_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9316,
.description = "2025_03_29_player_titlesets_index.sql",
.check = "SHOW INDEX FROM player_titlesets",
.condition = "missing",
.match = "idx_char_id",
.sql = R"(
ALTER TABLE player_titlesets ADD INDEX idx_char_id (char_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9317,
.description = "2025_03_29_respawn_times_instance_index.sql",
.check = "SHOW INDEX FROM respawn_times",
.condition = "missing",
.match = "idx_instance_id",
.sql = R"(
ALTER TABLE respawn_times ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9318,
.description = "2025_03_29_zone_state_spawns_instance_index.sql",
.check = "SHOW INDEX FROM zone_state_spawns",
.condition = "missing",
.match = "idx_instance_id",
.sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9319,
.description = "2025_03_29_data_buckets_expires_index.sql",
.check = "SHOW INDEX FROM data_buckets",
.condition = "missing",
.match = "idx_expires",
.sql = R"(
CREATE INDEX idx_expires ON data_buckets (expires);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9320,
.description = "2025_03_23_add_respawn_times_expire_at.sql",
.check = "SHOW COLUMNS FROM `respawn_times` LIKE 'expire_at'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `respawn_times`
ADD COLUMN `expire_at` int(11) UNSIGNED NULL DEFAULT 0 AFTER `duration`;
UPDATE respawn_times set expire_at = `start` + `duration`; -- backfill existing data
CREATE INDEX `idx_expire_at` ON `respawn_times` (`expire_at`);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9321,
.description = "2025_03_30_instance_list_add_expire_at.sql",
.check = "SHOW COLUMNS FROM `instance_list` LIKE 'expire_at'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `instance_list`
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
},
+45 -18
View File
@@ -128,11 +128,35 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version
e.version = version;
e.start_time = std::time(nullptr);
e.duration = duration;
e.expire_at = e.start_time + duration;
return InstanceListRepository::InsertOne(*this, e).id;
RespawnTimesRepository::ClearInstanceTimers(*this, e.id);
InstanceListRepository::ReplaceOne(*this, e);
return instance_id > 0 && e.id;
}
bool Database::GetUnusedInstanceID(uint16 &instance_id)
{
// attempt to get an unused instance id
for (int a = 0; a < 10; a++) {
uint16 attempted_id = 0;
if (TryGetUnusedInstanceID(attempted_id)) {
auto i = InstanceListRepository::NewEntity();
i.id = attempted_id;
i.notes = "Prefetching";
auto n = InstanceListRepository::InsertOne(*this, i);
if (n.id > 0) {
instance_id = n.id;
return true;
}
}
}
instance_id = 0;
return false;
}
bool Database::TryGetUnusedInstanceID(uint16 &instance_id)
{
uint32 max_reserved_instance_id = RuleI(Instances, ReservedInstances);
uint32 max_instance_id = 32000;
@@ -537,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list<uint32> &ch
void Database::PurgeExpiredInstances()
{
/**
* Delay purging by a day so that we can continue using adjacent free instance id's
* from the table without risking the chance we immediately re-allocate a zone that freshly expired but
* has not been fully de-allocated
*/
auto l = InstanceListRepository::GetWhere(
*this,
"(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND never_expires = 0"
fmt::format(
"expire_at <= (UNIX_TIMESTAMP() - {}) and expire_at != 0 AND never_expires = 0",
RuleI(Instances, ExpireOffsetTimeSeconds)
)
);
if (l.empty()) {
return;
@@ -555,20 +577,24 @@ void Database::PurgeExpiredInstances()
instance_ids.emplace_back(std::to_string(e.id));
}
const auto imploded_instance_ids = Strings::Implode(",", instance_ids);
const auto ids = Strings::Implode(",", instance_ids);
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids);
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids);
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_ids));
TransactionBegin();
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
CharacterCorpsesRepository::BuryInstances(*this, ids);
DynamicZoneMembersRepository::DeleteByManyInstances(*this, ids);
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", ids));
if (RuleB(Zone, StateSavingOnShutdown)) {
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids));
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", ids));
}
TransactionCommit();
LogInfo("Purged [{}] expired instances", l.size());
}
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
@@ -580,6 +606,7 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
i.start_time = std::time(nullptr);
i.duration = new_duration;
i.expire_at = i.start_time + i.duration;
InstanceListRepository::UpdateOne(*this, i);
}
+4 -3
View File
@@ -58,15 +58,16 @@ uint32_t DynamicZoneBase::CreateInstance()
insert_instance.start_time = static_cast<int>(std::chrono::system_clock::to_time_t(m_start_time));
insert_instance.duration = static_cast<int>(m_duration.count());
insert_instance.never_expires = m_never_expires;
insert_instance.expire_at = insert_instance.start_time + insert_instance.duration;
auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance);
if (instance.id == 0)
auto instance = InstanceListRepository::ReplaceOne(GetDatabase(), insert_instance);
if (!instance)
{
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
return 0;
}
m_instance_id = instance.id;
m_instance_id = unused_instance_id;
return m_instance_id;
}
+2
View File
@@ -287,6 +287,8 @@ N(OP_InstillDoubt),
N(OP_InterruptCast),
N(OP_InvokeChangePetName),
N(OP_InvokeChangePetNameImmediate),
N(OP_InvokeNameChangeImmediate),
N(OP_InvokeNameChangeLazy),
N(OP_ItemLinkClick),
N(OP_ItemLinkResponse),
N(OP_ItemLinkText),
+16 -9
View File
@@ -5832,21 +5832,28 @@ struct ChangeSize_Struct
/*16*/
};
enum ChangeNameResponse : int {
Denied = 0, // 5167: "You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name."
Accepted = 1, // 5976: "Your request for a name change was successful."
Timeout = -1, // 5977: "Your request for a name change has timed out. Please try again later."
ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later."
RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes."
Ineligible = -4, // 5980: "Your character is not eligible for a name change."
Pending = -5 // 5193: "You already have a name change pending. Please wait until it is fully processed before attempting another name change."
};
struct AltChangeName_Struct {
/*00*/ char new_name[64];
/*40*/ char old_name[64];
/*80*/ int response_code;
};
struct ChangePetName_Struct {
/*00*/ char new_pet_name[64];
/*40*/ char pet_owner_name[64];
/*80*/ int response_code;
};
enum ChangePetNameResponse : int {
Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name.
Accepted = 1, // 5976 Your request for a name change was successful.
Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes.
NotEligible = -4, // 5980 Your character is not eligible for a name change.
Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change.
Unhandled = -1
};
// New OpCode/Struct for SoD+
struct GroupMakeLeader_Struct
{
+16 -8
View File
@@ -906,24 +906,32 @@ bool EQ::ItemInstance::IsSlotAllowed(int16 slot_id) const {
bool EQ::ItemInstance::IsDroppable(bool recurse) const
{
if (!m_item)
if (!m_item) {
return false;
}
/*if (m_ornamentidfile) // not implemented
return false;*/
if (m_attuned)
if (m_attuned) {
return false;
/*if (m_item->FVNoDrop != 0) // not implemented
return false;*/
if (m_item->NoDrop == 0)
}
if (RuleI(World, FVNoDropFlag) == FVNoDropFlagRule::Enabled && m_item->FVNoDrop == 0) {
return true;
}
if (m_item->NoDrop == 0) {
return false;
}
if (recurse) {
for (auto iter : m_contents) {
if (!iter.second)
for (auto iter: m_contents) {
if (!iter.second) {
continue;
}
if (!iter.second->IsDroppable(recurse))
if (!iter.second->IsDroppable(recurse)) {
return false;
}
}
}
@@ -25,6 +25,7 @@ public:
uint8_t is_global;
uint32_t start_time;
uint32_t duration;
uint64_t expire_at;
uint8_t never_expires;
std::string notes;
};
@@ -43,6 +44,7 @@ public:
"is_global",
"start_time",
"duration",
"expire_at",
"never_expires",
"notes",
};
@@ -57,6 +59,7 @@ public:
"is_global",
"start_time",
"duration",
"expire_at",
"never_expires",
"notes",
};
@@ -105,6 +108,7 @@ public:
e.is_global = 0;
e.start_time = 0;
e.duration = 0;
e.expire_at = 0;
e.never_expires = 0;
e.notes = "";
@@ -149,8 +153,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
return e;
}
@@ -189,8 +194,9 @@ public:
v.push_back(columns[3] + " = " + std::to_string(e.is_global));
v.push_back(columns[4] + " = " + std::to_string(e.start_time));
v.push_back(columns[5] + " = " + std::to_string(e.duration));
v.push_back(columns[6] + " = " + std::to_string(e.never_expires));
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
v.push_back(columns[6] + " = " + std::to_string(e.expire_at));
v.push_back(columns[7] + " = " + std::to_string(e.never_expires));
v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'");
auto results = db.QueryDatabase(
fmt::format(
@@ -218,6 +224,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -255,6 +262,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -296,8 +304,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
all_entries.push_back(e);
}
@@ -328,8 +337,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
all_entries.push_back(e);
}
@@ -410,6 +420,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -440,6 +451,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -19,10 +19,11 @@
class BaseRespawnTimesRepository {
public:
struct RespawnTimes {
int32_t id;
int32_t start;
int32_t duration;
int16_t instance_id;
int32_t id;
int32_t start;
int32_t duration;
uint32_t expire_at;
int16_t instance_id;
};
static std::string PrimaryKey()
@@ -36,6 +37,7 @@ public:
"id",
"start",
"duration",
"expire_at",
"instance_id",
};
}
@@ -46,6 +48,7 @@ public:
"id",
"start",
"duration",
"expire_at",
"instance_id",
};
}
@@ -90,6 +93,7 @@ public:
e.id = 0;
e.start = 0;
e.duration = 0;
e.expire_at = 0;
e.instance_id = 0;
return e;
@@ -130,7 +134,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
return e;
}
@@ -167,7 +172,8 @@ public:
v.push_back(columns[0] + " = " + std::to_string(e.id));
v.push_back(columns[1] + " = " + std::to_string(e.start));
v.push_back(columns[2] + " = " + std::to_string(e.duration));
v.push_back(columns[3] + " = " + std::to_string(e.instance_id));
v.push_back(columns[3] + " = " + std::to_string(e.expire_at));
v.push_back(columns[4] + " = " + std::to_string(e.instance_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -192,6 +198,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
auto results = db.QueryDatabase(
@@ -225,6 +232,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
@@ -262,7 +270,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
all_entries.push_back(e);
}
@@ -290,7 +299,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
all_entries.push_back(e);
}
@@ -368,6 +378,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
auto results = db.QueryDatabase(
@@ -394,6 +405,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
+2 -40
View File
@@ -7,49 +7,11 @@
class InstanceListRepository: public BaseInstanceListRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* InstanceListRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* InstanceListRepository::GetWhereNeverExpires()
* InstanceListRepository::GetWhereXAndY()
* InstanceListRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
static int UpdateDuration(Database& db, uint16 instance_id, uint32_t new_duration)
{
auto results = db.QueryDatabase(
fmt::format(
"UPDATE `{}` SET `duration` = {} WHERE `{}` = {}",
"UPDATE `{}` SET `duration` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}",
TableName(),
new_duration,
PrimaryKey(),
@@ -65,7 +27,7 @@ public:
auto results = db.QueryDatabase(
fmt::format(
SQL(
SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
WHERE `id` = {}
),
TableName(),
+6 -37
View File
@@ -8,47 +8,11 @@
class RespawnTimesRepository: public BaseRespawnTimesRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* RespawnTimesRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* RespawnTimesRepository::GetWhereNeverExpires()
* RespawnTimesRepository::GetWhereXAndY()
* RespawnTimesRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
static void ClearExpiredRespawnTimers(Database& db)
{
db.QueryDatabase(
fmt::format(
"DELETE FROM `{}` WHERE (`start` + `duration`) < UNIX_TIMESTAMP(NOW())",
"DELETE FROM `{}` WHERE `expire_at` < UNIX_TIMESTAMP(NOW())",
TableName()
)
);
@@ -77,6 +41,11 @@ public:
return ((r.start + r.duration) - time_seconds);
}
static void ClearInstanceTimers(Database &db, int32_t id)
{
RespawnTimesRepository::DeleteWhere(db, fmt::format("`instance_id` = {}", id));
}
};
#endif //EQEMU_RESPAWN_TIMES_REPOSITORY_H
+14 -10
View File
@@ -54,7 +54,7 @@ RULE_INT(Character, CorpseDecayTime, 604800000, "Time after which the corpse dec
RULE_INT(Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, CorpseResTime, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
RULE_INT(Character, CorpseOwnerOnlineCheckTime, 300, "How often corpse will check if its owner is online DEFAULT: 300 (5 minutes)")
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items")
RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
@@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
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_CATEGORY_END()
RULE_CATEGORY(Mercs)
@@ -260,6 +261,7 @@ RULE_INT(Guild, TributeTime, 600000, "Time in ms for guild tributes. Default is
RULE_INT(Guild, TributeTimeRefreshInterval, 180000, "Time in ms to send all guild members a Tribute Time refresh. Default is 3 mins.")
RULE_INT(Guild, TributePlatConversionRate, 10, "The conversion rate of platinum donations. Default is 10 guild favor to 1 platinum.")
RULE_BOOL(Guild, UseCharacterMaxLevelForGuildTributes, true, "Guild Tributes will adhere to Character:MaxLevel. Default is true.")
RULE_BOOL(Guild, EnableLFGuild, false, "Enable the LFGuild system (Requires queryserv)")
RULE_CATEGORY_END()
RULE_CATEGORY(Skills)
@@ -289,6 +291,7 @@ RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep cl
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.")
RULE_BOOL(Pets, AlwaysAllowPetRename, false, "Enable this option to allow /changepetname to work without enabling a pet name change via scripts.")
RULE_BOOL(Pets, PetsRequireLoS, false, "Whether or not pets require line of sight to be told to attack their target")
RULE_CATEGORY_END()
RULE_CATEGORY(GM)
@@ -378,6 +381,7 @@ RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs t
RULE_BOOL(Zone, StateSaveBuffs, true, "Set to true if you want buffs to be saved on shutdown")
RULE_INT(Zone, StateSaveClearDays, 7, "Clears state save data older than this many days")
RULE_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
RULE_INT(Zone, UpdateWhoTimer, 120, "Seconds between updates to /who list, CLE stale timer")
RULE_CATEGORY_END()
RULE_CATEGORY(Map)
@@ -387,9 +391,10 @@ RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining wheth
RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging")
RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20, "At runtime in SendTo: maximum change in Z to allow the BestZ code to apply")
RULE_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeking the best Z position")
RULE_BOOL(Map, CheckForLoSCheat, false, "Runs predefined zone checks to check for LoS cheating through doors and such.")
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.")
RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.")
RULE_BOOL(Map, CheckForDoorLoSCheat, true, "Runs LoS checks to prevent cheating through doors.")
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check. Must modify source to create these.")
RULE_REAL(Map, RangeCheckForDoorLoSCheat, 250.0, "Default 250.0. Range to check if a door is blocking LoS from the target.")
RULE_STRING(Map, ZonesToCheckDoorCheat, "89,103", "Zones that will check for the door LoS cheat. You can leave it blank to disable, 'all' to check all zones or use a comma-delimited list of zones. Default Sebilis & Chardok")
RULE_CATEGORY_END()
RULE_CATEGORY(Pathing)
@@ -813,6 +818,7 @@ RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt t
RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastHateLine, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastMez, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastAEMez, 40, "The chance for a bot to attempt to cast the given spell type in combat. Default 40%.")
RULE_INT(Bots, PercentChanceToCastSlow, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastDebuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
@@ -824,8 +830,6 @@ RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 500, "The minimum delay in m
RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2500ms.")
RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 1000, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 1000ms.")
RULE_INT(Bots, MaxDelayBetweenOutCombatCastAttempts, 2500, "The maximum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 2500ms.")
RULE_INT(Bots, MezChance, 60, "60 Default. Chance for a bot to attempt to Mez a target after validating it is eligible.")
RULE_INT(Bots, AEMezChance, 35, "35 Default. Chance for a bot to attempt to AE Mez targets after validating they are eligible.")
RULE_INT(Bots, MezSuccessDelay, 2500, "2500 (2.5 sec) Default. Delay between successful Mez attempts.")
RULE_INT(Bots, AEMezSuccessDelay, 5000, "5000 (5 sec) Default. Delay between successful AEMez attempts.")
RULE_INT(Bots, MezFailDelay, 1250, "1250 (1.25 sec) Default. Delay between failed Mez attempts.")
@@ -835,14 +839,14 @@ RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks")
RULE_BOOL(Bots, RunSpellTypeChecksOnBoot, false, "This will run a series of checks to find potential errors in your bot_spells_entries table on boot and output to LogBotSpellTypeChecks")
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel")
RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level")
RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement")
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.")
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their usable spell list to cast. Empty uses Manifest Elements - 'SumMageMultiElement'")
RULE_INT(Bots, ReclaimEnergySpellID, 331, "Spell ID for reclaim energy when using ^petsettype. Default 331")
RULE_BOOL(Bots, UseSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.")
RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots")
@@ -860,8 +864,6 @@ RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a
RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.")
RULE_INT(Bots, DistanceTauntingBotsStickMainHate, 10, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.")
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")
@@ -902,6 +904,7 @@ RULE_STRING(Bots, ZonesWithForcedSpawnLimits, "", "Comma-delimited list of zones
RULE_STRING(Bots, ZoneForcedSpawnLimits, "", "Comma-delimited list of forced spawn limits for zones.")
RULE_INT(Bots, AICastSpellTypeDelay, 100, "Delay in milliseconds between AI cast attempts for each spell type. Default 100ms")
RULE_INT(Bots, AICastSpellTypeHeldDelay, 2500, "Delay in milliseconds between AI cast attempts for each spell type that is held or disabled. Default 2500ms (2.5s)")
RULE_BOOL(Bots, BotsRequireLoS, true, "Whether or not bots require line of sight to be told to attack their target")
RULE_CATEGORY_END()
RULE_CATEGORY(Chat)
@@ -1093,6 +1096,7 @@ RULE_CATEGORY(Instances)
RULE_INT(Instances, ReservedInstances, 100, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running")
RULE_BOOL(Instances, RecycleInstanceIds, true, "Setting whether free instance IDs should be recycled to prevent them from gradually running out at 32k")
RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires")
RULE_INT(Instances, ExpireOffsetTimeSeconds, 3600, "Amount of seconds to beyond instance expiration time we wait to purge the entry from the database. (Default: 1 Hour)")
RULE_CATEGORY_END()
RULE_CATEGORY(Expedition)
+2
View File
@@ -22,6 +22,7 @@ namespace ServerReload {
LevelEXPMods,
Logs,
Loot,
Maps,
Merchants,
NPCEmotes,
NPCSpells,
@@ -61,6 +62,7 @@ namespace ServerReload {
"Level EXP Mods",
"Logs",
"Loot",
"Maps",
"Merchants",
"NPC Emotes",
"NPC Spells",
+49 -49
View File
@@ -1456,41 +1456,42 @@ bool IsCompleteHealSpell(uint16 spell_id)
}
bool IsFastHealSpell(uint16 spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHP) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
);
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHP) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
);
if (!spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
);
}
if (!spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
);
}
if (spell_id && IsValidSpell(spell_id)) {
if (
spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (
spells[spell_id].base_value[i] > 0 &&
(
spells[spell_id].effect_id[i] == SE_CurrentHP ||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
)
) {
return true;
}
}
}
}
if (IsValidSpell(spell_id)) {
if (
spell_id != SPELL_MINOR_HEALING &&
(spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (
spells[spell_id].base_value[i] > 0 &&
(
spells[spell_id].effect_id[i] == SE_CurrentHP ||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
)
) {
return true;
}
}
}
}
return false;
return false;
}
bool IsVeryFastHealSpell(uint16 spell_id)
@@ -1509,8 +1510,9 @@ bool IsVeryFastHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (
spell_id != SPELL_MINOR_HEALING &&
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
@@ -1548,8 +1550,13 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (spell_id == SPELL_MINOR_HEALING) {
return true;
}
if (
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
spells[spell_id].target_type == ST_Target &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
@@ -1589,9 +1596,14 @@ bool IsRegularPetHealSpell(uint16 spell_id)
);
}
if (spell_id && IsValidSpell(spell_id)) {
if (IsValidSpell(spell_id)) {
if (spell_id == SPELL_MINOR_HEALING) {
return true;
}
if (
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) &&
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet) &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
!IsGroupSpell(spell_id)
@@ -1630,7 +1642,7 @@ bool IsRegularGroupHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (
IsGroupSpell(spell_id) &&
!IsCompleteHealSpell(spell_id) &&
@@ -2796,18 +2808,6 @@ bool IsLichSpell(uint16 spell_id)
);
}
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los) {
if (!IsValidSpell(spell_id)) {
return false;
}
if (!has_los && IsTargetRequiredForSpell(spell_id)) {
return false;
}
return true;
}
bool IsInstantHealSpell(uint32 spell_id) {
if (!IsValidSpell(spell_id)) {
return false;
+5 -3
View File
@@ -215,6 +215,7 @@
#define SPELL_AMPLIFICATION 2603
#define SPELL_DIVINE_REZ 2738
#define SPELL_NATURES_RECOVERY 2520
#define SPELL_MINOR_HEALING 200
#define SPELL_ADRENALINE_SWELL 14445
#define SPELL_ADRENALINE_SWELL_RK2 14446
#define SPELL_ADRENALINE_SWELL_RK3 14447
@@ -899,8 +900,8 @@ const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellTy
const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root);
// Bot related functions
bool IsBotSpellTypeDetrimental (uint16 spell_type);
bool IsBotSpellTypeBeneficial (uint16 spell_type);
bool IsBotSpellTypeDetrimental(uint16 spell_type);
bool IsBotSpellTypeBeneficial(uint16 spell_type);
bool BotSpellTypeUsesTargetSettings(uint16 spell_type);
bool IsBotSpellTypeInnate (uint16 spell_type);
bool IsAEBotSpellType(uint16 spell_type);
@@ -916,6 +917,8 @@ bool IsCommandedBotSpellType(uint16 spell_type);
bool IsPullingBotSpellType(uint16 spell_type);
uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id);
uint16 GetPetBotSpellType(uint16 spell_type);
bool IsBotBuffSpellType(uint16 spell_type);
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id);
// These should not be used to determine spell category..
// They are a graphical affects (effects?) index only
@@ -1813,7 +1816,6 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id);
uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id);
bool IsBlankSpellEffect(uint16 spell_id, int effect_index);
bool IsValidSpell(uint32 spell_id);
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los = true);
bool IsSummonSpell(uint16 spell_id);
bool IsDamageSpell(uint16 spell_id);
bool IsAnyDamageSpell(uint16 spell_id);
+40 -252
View File
@@ -1,4 +1,5 @@
#include "spdat.h"
#include "../zone/bot.h"
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
switch (spell_type) {
@@ -417,264 +418,23 @@ uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) {
return UINT16_MAX;
}
uint16 correct_type = UINT16_MAX;
SPDat_Spell_Struct spell = spells[spell_id];
std::string teleport_zone = spell.teleport_zone;
uint16 correct_type = spell_type;
if (IsCharmSpell(spell_id)) {
correct_type = BotSpellTypes::Charm;
}
else if (IsFearSpell(spell_id)) {
correct_type = BotSpellTypes::Fear;
}
else if (IsEffectInSpell(spell_id, SE_Revive)) {
correct_type = BotSpellTypes::Resurrect;
}
else if (IsHarmonySpell(spell_id)) {
correct_type = BotSpellTypes::Lull;
}
else if (
teleport_zone.compare("") &&
!IsEffectInSpell(spell_id, SE_GateToHomeCity) &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
) {
correct_type = BotSpellTypes::Teleport;
}
else if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Succor)
) {
correct_type = BotSpellTypes::Succor;
}
else if (IsEffectInSpell(spell_id, SE_BindAffinity)) {
correct_type = BotSpellTypes::BindAffinity;
}
else if (IsEffectInSpell(spell_id, SE_Identify)) {
correct_type = BotSpellTypes::Identify;
}
else if (
spell_type == BotSpellTypes::Levitate &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Levitate)
) {
correct_type = BotSpellTypes::Levitate;
}
else if (
spell_type == BotSpellTypes::Rune &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
) {
correct_type = BotSpellTypes::Rune;
}
else if (
spell_type == BotSpellTypes::WaterBreathing &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_WaterBreathing)
) {
correct_type = BotSpellTypes::WaterBreathing;
}
else if (
spell_type == BotSpellTypes::Size &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
) {
correct_type = BotSpellTypes::Size;
}
else if (
spell_type == BotSpellTypes::Invisibility &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))
) {
correct_type = BotSpellTypes::Invisibility;
}
else if (
spell_type == BotSpellTypes::MovementSpeed &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
correct_type = BotSpellTypes::MovementSpeed;
}
else if (
!teleport_zone.compare("") &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))
) {
correct_type = BotSpellTypes::SendHome;
}
else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) {
correct_type = BotSpellTypes::SummonCorpse;
}
if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) {
correct_type = UINT16_MAX;
if (correct_type == UINT16_MAX) {
if (
IsSummonPetSpell(spell_id) ||
IsEffectInSpell(spell_id, SE_TemporaryPets)
) {
correct_type = BotSpellTypes::Pet;
}
else if (IsMesmerizeSpell(spell_id)) {
correct_type = BotSpellTypes::Mez;
}
else if (IsEscapeSpell(spell_id)) {
correct_type = BotSpellTypes::Escape;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Root)
) {
if (IsAnyAESpell(spell_id)) {
correct_type = BotSpellTypes::AERoot;
}
else {
correct_type = BotSpellTypes::Root;
}
}
else if (
IsDetrimentalSpell(spell_id) &&
IsLifetapSpell(spell_id)
) {
correct_type = BotSpellTypes::Lifetap;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
correct_type = BotSpellTypes::Snare;
}
else if (
IsDetrimentalSpell(spell_id) &&
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
) {
correct_type = BotSpellTypes::DOT;
}
else if (IsDispelSpell(spell_id)) {
correct_type = BotSpellTypes::Dispel;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsSlowSpell(spell_id)
) {
correct_type = BotSpellTypes::Slow;
}
else if (
IsDebuffSpell(spell_id) &&
!IsHateReduxSpell(spell_id) &&
!IsHateSpell(spell_id)
) {
correct_type = BotSpellTypes::Debuff;
}
else if (IsHateReduxSpell(spell_id)) {
correct_type = BotSpellTypes::HateRedux;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsHateSpell(spell_id)
) {
correct_type = BotSpellTypes::HateLine;
}
else if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
IsBardSong(spell_id)
) {
if (
spell_type == BotSpellTypes::InCombatBuffSong ||
spell_type == BotSpellTypes::OutOfCombatBuffSong ||
spell_type == BotSpellTypes::PreCombatBuffSong
) {
correct_type = spell_type;
}
else {
correct_type = BotSpellTypes::OutOfCombatBuffSong;
}
}
else if (
!IsBardSong(spell_id) &&
(
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
)
) {
correct_type = BotSpellTypes::InCombatBuff;
}
else if (
spell_type == BotSpellTypes::PreCombatBuff &&
IsAnyBuffSpell(spell_id) &&
!IsBardSong(spell_id)
) {
correct_type = BotSpellTypes::PreCombatBuff;
}
else if (
(IsCureSpell(spell_id) && spell_type == BotSpellTypes::Cure) ||
(IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id))
) {
correct_type = BotSpellTypes::Cure;
}
else if (IsAnyNukeOrStunSpell(spell_id)) {
if (IsAnyAESpell(spell_id)) {
if (IsAERainSpell(spell_id)) {
correct_type = BotSpellTypes::AERains;
}
else if (IsPBAENukeSpell(spell_id)) {
correct_type = BotSpellTypes::PBAENuke;
}
else if (IsStunSpell(spell_id)) {
correct_type = BotSpellTypes::AEStun;
}
else {
correct_type = BotSpellTypes::AENukes;
}
}
else if (IsStunSpell(spell_id)) {
correct_type = BotSpellTypes::Stun;
}
else {
correct_type = BotSpellTypes::Nuke;
}
}
else if (IsAnyHealSpell(spell_id)) {
if (IsGroupSpell(spell_id)) {
if (IsGroupCompleteHealSpell(spell_id)) {
correct_type = BotSpellTypes::GroupCompleteHeals;
}
else if (IsGroupHealOverTimeSpell(spell_id)) {
correct_type = BotSpellTypes::GroupHoTHeals;
}
else if (IsRegularGroupHealSpell(spell_id)) {
correct_type = BotSpellTypes::GroupHeals;
}
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
return correct_type;
for (int i = end; i >= start; --i) {
if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) {
continue;
}
if (IsVeryFastHealSpell(spell_id)) {
correct_type = BotSpellTypes::VeryFastHeals;
}
else if (IsFastHealSpell(spell_id)) {
correct_type = BotSpellTypes::FastHeals;
}
else if (IsCompleteHealSpell(spell_id)) {
correct_type = BotSpellTypes::CompleteHeal;
}
else if (IsHealOverTimeSpell(spell_id)) {
correct_type = BotSpellTypes::HoTHeals;
}
else if (IsRegularSingleTargetHealSpell(spell_id)) {
correct_type = BotSpellTypes::RegularHeal;
}
else if (IsRegularPetHealSpell(spell_id)) {
correct_type = BotSpellTypes::RegularHeal;
}
}
else if (IsAnyBuffSpell(spell_id)) {
correct_type = BotSpellTypes::Buff;
if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) {
correct_type = i;
if (IsResistanceOnlySpell(spell_id)) {
correct_type = BotSpellTypes::ResistBuffs;
}
else if (IsDamageShieldOnlySpell(spell_id)) {
correct_type = BotSpellTypes::DamageShields;
break;
}
}
}
@@ -708,3 +468,31 @@ uint16 GetPetBotSpellType(uint16 spell_type) {
return spell_type;
}
bool IsBotBuffSpellType(uint16 spell_type) {
switch (spell_type) {
case BotSpellTypes::Buff:
case BotSpellTypes::PetBuffs:
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
case BotSpellTypes::DamageShields:
case BotSpellTypes::PetDamageShields:
return true;
default:
return false;
}
return false;
}
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id) {
if (!BotSpellTypeRequiresTarget(spell_type)) {
return false;
}
if (!IsTargetRequiredForSpell(spell_id)) {
return false;
}
return true;
}
+22
View File
@@ -196,3 +196,25 @@ const uint32 Timer::SetCurrentTime()
return current_time;
}
const uint32 Timer::RollForward(uint32 seconds)
{
struct timeval read_time{};
uint32 this_time;
gettimeofday(&read_time, nullptr);
this_time = read_time.tv_sec * 1000 + read_time.tv_usec / 1000;
if (last_time == 0) {
current_time = 0;
}
else {
current_time += this_time - last_time;
}
last_time = this_time;
// Roll forward the specified number of seconds (converted to milliseconds)
current_time += seconds * 1000;
return current_time;
}
+1
View File
@@ -51,6 +51,7 @@ public:
inline uint32 GetDuration() { return(timer_time); }
static const uint32 SetCurrentTime();
static const uint32 RollForward(uint32 seconds);
static const uint32 GetCurrentTime();
static const uint32 GetTimeSeconds();
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.3.3-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.4.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9314
#define CURRENT_BINARY_DATABASE_VERSION 9321
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.3.3",
"version": "23.4.0",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+1 -1
View File
@@ -177,7 +177,7 @@ int main()
}
if (player_event_process_timer.Check()) {
std::jthread player_event_thread(&PlayerEventLogs::Process, &player_event_logs);
player_event_logs.Process();
}
};
+2 -2
View File
@@ -177,7 +177,7 @@ void ChatChannelList::SendAllChannels(Client *c) {
std::string Message;
char CountString[10];
char CountString[13];
while(iterator.MoreElements()) {
@@ -408,7 +408,7 @@ void ChatChannel::SendChannelMembers(Client *c) {
if(!c) return;
char CountString[10];
char CountString[13];
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
+3
View File
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
OP_InvokeChangePetNameImmediate=0x046d
OP_InvokeChangePetName=0x4506
OP_ChangePetName=0x5dab
OP_InvokeNameChangeImmediate=0x4fe2
OP_InvokeNameChangeLazy=0x2f2e
+1
View File
@@ -57,6 +57,7 @@ echo "# Running NPC hand-in tests"
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
./bin/zone tests:zone-state 2>&1 | tee -a test_output.log
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
echo "Error found in test output! Failing build."
+69
View File
@@ -1850,3 +1850,72 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
}
return guild_members;
}
#include <unordered_set>
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
{
std::vector<uint32_t> zone_server_ids;
std::unordered_set<uint32_t> seen_ids;
LinkedListIterator<ClientListEntry *> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone) {
iterator.Advance();
continue;
}
if (!cle->Server()) {
iterator.Advance();
continue;
}
if (cle->GuildID() == guild_id) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
}
}
iterator.Advance();
}
return zone_server_ids;
}
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
{
std::vector<uint32_t> zone_server_ids;
std::unordered_set<uint32_t> seen_ids;
LinkedListIterator<ClientListEntry *> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone) {
iterator.Advance();
continue;
}
if (!cle->Server()) {
iterator.Advance();
continue;
}
if (cle->Admin() > 0) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
}
}
iterator.Advance();
}
return zone_server_ids;
}
+2
View File
@@ -60,6 +60,8 @@ public:
void CLCheckStale();
void CLEKeepAlive(uint32 numupdates, uint32* wid);
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
std::vector<uint32_t> GetZoneServersWithGMs();
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
bool IsAccountInGame(uint32 iLSID);
+5 -2
View File
@@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
m_expire_time = now + new_remaining;
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
InstanceListRepository::UpdateDuration(database,
GetInstanceID(), static_cast<uint32_t>(m_duration.count()));
InstanceListRepository::UpdateDuration(
database,
GetInstanceID(),
static_cast<uint32_t>(m_duration.count())
);
SendZonesDurationUpdate(); // update zone caches and actual instance's timer
}
+2
View File
@@ -32,6 +32,8 @@ void callGetZoneList(Json::Value &response)
row["client_address"] = zone->GetCAddress();
row["client_local_address"] = zone->GetCLocalAddress();
row["client_port"] = zone->GetCPort();
row["compile_version"] = zone->GetCurrentVersion();
row["compile_date"] = zone->GetCompileDate();
row["compile_time"] = zone->GetCompileTime();
row["id"] = zone->GetID();
row["instance_id"] = zone->GetInstanceID();
+11 -5
View File
@@ -381,11 +381,19 @@ int main(int argc, char **argv)
}
);
Timer player_event_process_timer(1000);
if (player_event_logs.LoadDatabaseConnection()) {
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();
@@ -448,10 +456,6 @@ int main(int argc, char **argv)
}
}
if (player_event_process_timer.Check()) {
std::jthread event_thread(&PlayerEventLogs::Process, &player_event_logs);
}
if (PurgeInstanceTimer.Check()) {
database.PurgeExpiredInstances();
database.PurgeAllDeletedDataBuckets();
@@ -502,6 +506,8 @@ 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();
+22 -17
View File
@@ -50,7 +50,7 @@ void WorldGuildManager::SendGuildRefresh(uint32 guild_id, bool name, bool motd,
s->motd_change = motd;
s->rank_change = rank;
s->relation_change = relation;
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack);
}
@@ -61,7 +61,7 @@ void WorldGuildManager::SendCharRefresh(uint32 old_guild_id, uint32 guild_id, ui
s->guild_id = guild_id;
s->old_guild_id = old_guild_id;
s->char_id = charid;
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack);
}
@@ -70,7 +70,7 @@ void WorldGuildManager::SendGuildDelete(uint32 guild_id) {
auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct));
ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer;
s->guild_id = guild_id;
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack);
}
@@ -85,15 +85,14 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
ServerGuildRefresh_Struct *s = (ServerGuildRefresh_Struct *) pack->pBuffer;
LogGuilds("Received and broadcasting guild refresh for [{}], changes: name=[{}], motd=[{}], rank=d, relation=[{}]", s->guild_id, s->name_change, s->motd_change, s->rank_change, s->relation_change);
//broadcast this packet to all zones.
zoneserver_list.SendPacket(pack);
//preform a local refresh.
if(!RefreshGuild(s->guild_id)) {
LogGuilds("Unable to preform local refresh on guild [{}]", s->guild_id);
//can we do anything?
BaseGuildManager::RefreshGuild(s->guild_id);
}
//broadcast this packet to all zones.
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break;
}
@@ -108,7 +107,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
//broadcast this update to any zone with a member in this guild.
//because im sick of this not working, sending it to all zones, just spends a bit more bandwidth.
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break;
}
@@ -147,7 +146,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
auto s = (ServerGuildID_Struct *)pack->pBuffer;
RefreshGuild(s->guild_id);
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break;
}
case ServerOP_GuildPermissionUpdate:
@@ -179,7 +178,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
sg->function_value
);
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(sg->guild_id, pack);
}
else {
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.",
@@ -213,7 +212,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
rnc->rank,
rnc->rank_name
);
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(rnc->guild_id, pack);
}
else {
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
@@ -230,13 +229,13 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
case ServerOP_GuildChannel:
case ServerOP_GuildURL:
case ServerOP_GuildMemberRemove:
case ServerOP_GuildSendGuildList:
case ServerOP_GuildMembersList:
{
zoneserver_list.SendPacketToBootedZones(pack);
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
break;
}
case ServerOP_GuildMemberAdd:
case ServerOP_GuildMemberAdd:
{
auto in = (ServerOP_GuildMessage_Struct *)pack->pBuffer;
auto guild = GetGuildByGuildID(in->guild_id);
@@ -244,9 +243,15 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
BaseGuildManager::RefreshGuild(in->guild_id);
}
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
break;
}
case ServerOP_GuildSendGuildList: {
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToBootedZones(pack);
break;
}
default:
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
break;
@@ -451,6 +456,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa
data->tribute_timer = time;
data->trophy_timer = 0;
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, sp);
safe_delete(sp)
}
+246 -282
View File
@@ -29,6 +29,10 @@
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/zone_store.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/character_bind_repository.h"
#include "../common/repositories/character_material_repository.h"
#include "../common/repositories/start_zones_repository.h"
WorldDatabase database;
WorldDatabase content_db;
@@ -50,187 +54,177 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
character_limit = 8;
}
std::string character_list_query = fmt::format(
SQL(
SELECT
`id`,
`name`,
`gender`,
`race`,
`class`,
`level`,
`deity`,
`last_login`,
`time_played`,
`hair_color`,
`beard_color`,
`eye_color_1`,
`eye_color_2`,
`hair_style`,
`beard`,
`face`,
`drakkin_heritage`,
`drakkin_tattoo`,
`drakkin_details`,
`zone_id`
FROM
`character_data`
WHERE
`account_id` = {}
AND
`deleted_at` IS NULL
ORDER BY `name`
LIMIT {}
),
account_id,
character_limit
auto characters = CharacterDataRepository::GetWhere(
database,
fmt::format(
"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
account_id,
character_limit
)
);
auto results = database.QueryDatabase(character_list_query);
size_t character_count = results.RowCount();
if (character_count == 0) {
size_t character_count = characters.size();
if (characters.empty()) {
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0;
auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0;
cs->TotalChars = character_limit;
return;
}
std::vector<uint32_t> character_ids;
for (auto &e: characters) {
character_ids.push_back(e.id);
}
const auto& inventories = InventoryRepository::GetWhere(
*this,
fmt::format(
"`character_id` IN ({}) AND `slot_id` BETWEEN {} AND {}",
Strings::Join(character_ids, ","),
EQ::invslot::slotHead,
EQ::invslot::slotFeet
)
);
const auto& character_binds = CharacterBindRepository::GetWhere(
*this,
fmt::format(
"`id` IN ({}) ORDER BY id, slot",
Strings::Join(character_ids, ",")
)
);
const auto& character_materials = CharacterMaterialRepository::GetWhere(
*this,
fmt::format(
"`id` IN ({}) ORDER BY id, slot",
Strings::Join(character_ids, ",")
)
);
size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
unsigned char *buff_ptr = (*out_app)->pBuffer;
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr;
auto *cs = (CharacterSelect_Struct *) buff_ptr;
cs->CharCount = character_count;
cs->CharCount = character_count;
cs->TotalChars = character_limit;
buff_ptr += sizeof(CharacterSelect_Struct);
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp;
EQ::InventoryProfile inventory_profile;
for (auto &e: characters) {
auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp;
EQ::InventoryProfile inv;
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
inventory_profile.SetInventoryVersion(client_version);
inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
inv.SetInventoryVersion(client_version);
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
uint32 character_id = Strings::ToUnsignedInt(row[0]);
uint8 has_home = 0;
uint8 has_bind = 0;
uint32 character_id = e.id;
uint8 has_home = 0;
uint8 has_bind = 0;
memset(&pp, 0, sizeof(PlayerProfile_Struct));
memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name));
strcpy(p_character_select_entry_struct->Name, row[1]);
p_character_select_entry_struct->Class = (uint8) Strings::ToUnsignedInt(row[4]);
p_character_select_entry_struct->Race = (uint32) Strings::ToUnsignedInt(row[3]);
p_character_select_entry_struct->Level = (uint8) Strings::ToUnsignedInt(row[5]);
p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class;
p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race;
p_character_select_entry_struct->Zone = (uint16) Strings::ToUnsignedInt(row[19]);
p_character_select_entry_struct->Instance = 0;
p_character_select_entry_struct->Gender = (uint8) Strings::ToUnsignedInt(row[2]);
p_character_select_entry_struct->Face = (uint8) Strings::ToUnsignedInt(row[15]);
memset(cse->Name, 0, sizeof(cse->Name));
strcpy(cse->Name, e.name.c_str());
cse->Class = e.class_;
cse->Race = e.race;
cse->Level = e.level;
cse->ShroudClass = cse->Class;
cse->ShroudRace = cse->Race;
cse->Zone = e.zone_id;
cse->Instance = 0;
cse->Gender = e.gender;
cse->Face = e.face;
for (uint32 material_slot = 0; material_slot < EQ::textures::materialCount; material_slot++) {
p_character_select_entry_struct->Equip[material_slot].Material = 0;
p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0;
p_character_select_entry_struct->Equip[material_slot].EliteModel = 0;
p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0;
p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0;
p_character_select_entry_struct->Equip[material_slot].Color = 0;
for (auto &s: cse->Equip) {
s.Material = 0;
s.Unknown1 = 0;
s.EliteModel = 0;
s.HerosForgeModel = 0;
s.Unknown2 = 0;
s.Color = 0;
}
p_character_select_entry_struct->Unknown15 = 0xFF;
p_character_select_entry_struct->Unknown19 = 0xFF;
p_character_select_entry_struct->DrakkinTattoo = (uint32) Strings::ToInt(row[17]);
p_character_select_entry_struct->DrakkinDetails = (uint32) Strings::ToInt(row[18]);
p_character_select_entry_struct->Deity = (uint32) Strings::ToInt(row[6]);
p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below
p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below
p_character_select_entry_struct->HairColor = (uint8) Strings::ToInt(row[9]);
p_character_select_entry_struct->BeardColor = (uint8) Strings::ToInt(row[10]);
p_character_select_entry_struct->EyeColor1 = (uint8) Strings::ToInt(row[11]);
p_character_select_entry_struct->EyeColor2 = (uint8) Strings::ToInt(row[12]);
p_character_select_entry_struct->HairStyle = (uint8) Strings::ToInt(row[13]);
p_character_select_entry_struct->Beard = (uint8) Strings::ToInt(row[14]);
p_character_select_entry_struct->GoHome = 0; // Processed Below
p_character_select_entry_struct->Tutorial = 0; // Processed Below
p_character_select_entry_struct->DrakkinHeritage = (uint32) Strings::ToInt(row[16]);
p_character_select_entry_struct->Unknown1 = 0;
p_character_select_entry_struct->Enabled = 1;
p_character_select_entry_struct->LastLogin = (uint32) Strings::ToInt(row[7]); // RoF2 value: 1212696584
p_character_select_entry_struct->Unknown2 = 0;
cse->Unknown15 = 0xFF;
cse->Unknown19 = 0xFF;
cse->DrakkinTattoo = e.drakkin_tattoo;
cse->DrakkinDetails = e.drakkin_details;
cse->Deity = e.deity;
cse->PrimaryIDFile = 0; // Processed Below
cse->SecondaryIDFile = 0; // Processed Below
cse->HairColor = e.hair_color;
cse->BeardColor = e.beard_color;
cse->EyeColor1 = e.eye_color_1;
cse->EyeColor2 = e.eye_color_2;
cse->HairStyle = e.hair_style;
cse->Beard = e.beard;
cse->GoHome = 0; // Processed Below
cse->Tutorial = 0; // Processed Below
cse->DrakkinHeritage = e.drakkin_heritage;
cse->Unknown1 = 0;
cse->Enabled = 1;
cse->LastLogin = e.last_login; // RoF2 value: 1212696584
cse->Unknown2 = 0;
if (RuleB(World, EnableReturnHomeButton)) {
int now = time(nullptr);
if ((now - Strings::ToInt(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome))
p_character_select_entry_struct->GoHome = 1;
if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
cse->GoHome = 1;
}
}
if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial)))
p_character_select_entry_struct->Tutorial = 1;
if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
cse->Tutorial = 1;
}
/**
* Bind
*/
character_list_query = fmt::format(
SQL(
SELECT
`zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`
FROM
`character_bind`
WHERE
`id` = {}
LIMIT 5
),
character_id
);
auto results_bind = database.QueryDatabase(character_list_query);
auto bind_count = results_bind.RowCount();
for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) {
if (row_b[6] && Strings::ToInt(row_b[6]) == 4) {
has_home = 1;
// If our bind count is less than 5, we need to actually make use of this data so lets parse it
if (bind_count < 5) {
pp.binds[4].zone_id = Strings::ToInt(row_b[0]);
pp.binds[4].instance_id = Strings::ToInt(row_b[1]);
pp.binds[4].x = Strings::ToFloat(row_b[2]);
pp.binds[4].y = Strings::ToFloat(row_b[3]);
pp.binds[4].z = Strings::ToFloat(row_b[4]);
pp.binds[4].heading = Strings::ToFloat(row_b[5]);
}
// binds
int bind_count = 0;
for (auto &bind : character_binds) {
if (bind.id != e.id) {
continue;
}
if (row_b[6] && Strings::ToInt(row_b[6]) == 0)
if (bind.slot == 4) {
has_home = 1;
pp.binds[4].zone_id = bind.zone_id;
pp.binds[4].instance_id = bind.instance_id;
pp.binds[4].x = bind.x;
pp.binds[4].y = bind.y;
pp.binds[4].z = bind.z;
pp.binds[4].heading = bind.heading;
}
if (bind.slot == 0) {
has_bind = 1;
}
bind_count++;
}
if (has_home == 0 || has_bind == 0) {
std::string character_list_query = fmt::format(
SQL(
SELECT
`zone_id`, `bind_id`, `x`, `y`, `z`, `heading`
FROM
`start_zones`
WHERE
`player_class` = {}
AND
`player_deity` = {}
AND
`player_race` = {} {}
),
p_character_select_entry_struct->Class,
p_character_select_entry_struct->Deity,
p_character_select_entry_struct->Race,
ContentFilterCriteria::apply().c_str()
const auto &start_zones = StartZonesRepository::GetWhere(
content_db,
fmt::format(
"`player_class` = {} AND `player_deity` = {} AND `player_race` = {} {}",
cse->Class,
cse->Deity,
cse->Race,
ContentFilterCriteria::apply().c_str()
)
);
auto results_bind = content_db.QueryDatabase(character_list_query);
for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) {
/* If a bind_id is specified, make them start there */
if (Strings::ToInt(row_d[1]) != 0) {
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[1]);
if (!start_zones.empty()) {
pp.binds[4].zone_id = start_zones[0].zone_id;
pp.binds[4].x = start_zones[0].x;
pp.binds[4].y = start_zones[0].y;
pp.binds[4].z = start_zones[0].z;
pp.binds[4].heading = start_zones[0].heading;
if (start_zones[0].bind_id != 0) {
pp.binds[4].zone_id = start_zones[0].bind_id;
auto z = GetZone(pp.binds[4].zone_id);
if (z) {
pp.binds[4].x = z->safe_x;
@@ -238,168 +232,151 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
pp.binds[4].z = z->safe_z;
pp.binds[4].heading = z->safe_heading;
}
}
/* Otherwise, use the zone and coordinates given */
else {
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[0]);
float x = Strings::ToFloat(row_d[2]);
float y = Strings::ToFloat(row_d[3]);
float z = Strings::ToFloat(row_d[4]);
float heading = Strings::ToFloat(row_d[5]);
if (x == 0 && y == 0 && z == 0 && heading == 0) {
} else {
pp.binds[4].zone_id = start_zones[0].zone_id;
pp.binds[4].x = start_zones[0].x;
pp.binds[4].y = start_zones[0].y;
pp.binds[4].z = start_zones[0].z;
pp.binds[4].heading = start_zones[0].heading;
if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
auto zone = GetZone(pp.binds[4].zone_id);
if (zone) {
x = zone->safe_x;
y = zone->safe_y;
z = zone->safe_z;
heading = zone->safe_heading;
pp.binds[4].x = zone->safe_x;
pp.binds[4].y = zone->safe_y;
pp.binds[4].z = zone->safe_z;
pp.binds[4].heading = zone->safe_heading;
}
}
pp.binds[4].x = x;
pp.binds[4].y = y;
pp.binds[4].z = z;
pp.binds[4].heading = heading;
}
}
pp.binds[0] = pp.binds[4];
/* If no home bind set, set it */
if (has_home == 0) {
std::string query = fmt::format(
SQL(
REPLACE INTO
`character_bind`
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
),
character_id,
pp.binds[4].zone_id,
0,
pp.binds[4].x,
pp.binds[4].y,
pp.binds[4].z,
pp.binds[4].heading,
4
);
auto results_bset = QueryDatabase(query);
else {
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
}
/* If no regular bind set, set it */
pp.binds[0] = pp.binds[4];
// If we don't have home set, set it
if (has_home == 0) {
auto bind = CharacterBindRepository::NewEntity();
bind.id = character_id;
bind.zone_id = pp.binds[4].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[4].x;
bind.y = pp.binds[4].y;
bind.z = pp.binds[4].z;
bind.heading = pp.binds[4].heading;
bind.slot = 4;
CharacterBindRepository::ReplaceOne(*this, bind);
}
// If we don't have regular bind set, set it
if (has_bind == 0) {
std::string query = fmt::format(
SQL(
REPLACE INTO
`character_bind`
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
),
character_id,
pp.binds[0].zone_id,
0,
pp.binds[0].x,
pp.binds[0].y,
pp.binds[0].z,
pp.binds[0].heading,
0
);
auto results_bset = QueryDatabase(query);
auto bind = CharacterBindRepository::NewEntity();
bind.id = character_id;
bind.zone_id = pp.binds[0].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[0].x;
bind.y = pp.binds[0].y;
bind.z = pp.binds[0].z;
bind.heading = pp.binds[0].heading;
bind.slot = 0;
CharacterBindRepository::ReplaceOne(*this, bind);
}
}
/* If our bind count is less than 5, then we have null data that needs to be filled in. */
// If our bind count is less than 5, then we have null data that needs to be filled in
if (bind_count < 5) {
// we know that home and main bind must be valid here, so we don't check those
// we also use home to fill in the null data like live does.
std::vector<CharacterBindRepository::CharacterBind> binds;
for (int i = 1; i < 4; i++) {
if (pp.binds[i].zone_id != 0) // we assume 0 is the only invalid one ...
if (pp.binds[i].zone_id != 0) { // we assume 0 is the only invalid one ...
continue;
}
std::string query = fmt::format(
SQL(
REPLACE INTO
`character_bind`
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
),
character_id,
pp.binds[4].zone_id,
0,
pp.binds[4].x,
pp.binds[4].y,
pp.binds[4].z,
pp.binds[4].heading,
i
);
auto results_bset = QueryDatabase(query);
auto bind = CharacterBindRepository::NewEntity();
bind.slot = i;
bind.id = character_id;
bind.zone_id = pp.binds[4].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[4].x;
bind.y = pp.binds[4].y;
bind.z = pp.binds[4].z;
bind.heading = pp.binds[4].heading;
binds.emplace_back(bind);
}
CharacterBindRepository::ReplaceMany(*this, binds);
}
character_list_query = fmt::format(
SQL(
SELECT
`slot`, `red`, `green`, `blue`, `use_tint`, `color`
FROM
`character_material`
WHERE
`id` = {}
),
character_id
);
auto results_b = database.QueryDatabase(character_list_query);
uint8 slot = 0;
for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) {
slot = Strings::ToInt(row_b[0]);
pp.item_tint.Slot[slot].Red = Strings::ToInt(row_b[1]);
pp.item_tint.Slot[slot].Green = Strings::ToInt(row_b[2]);
pp.item_tint.Slot[slot].Blue = Strings::ToInt(row_b[3]);
pp.item_tint.Slot[slot].UseTint = Strings::ToInt(row_b[4]);
for (auto &cm : character_materials) {
pp.item_tint.Slot[cm.slot].Red = cm.red;
pp.item_tint.Slot[cm.slot].Green = cm.green;
pp.item_tint.Slot[cm.slot].Blue = cm.blue;
pp.item_tint.Slot[cm.slot].UseTint = cm.use_tint;
pp.item_tint.Slot[cm.slot].Color = cm.color;
}
if (GetCharSelInventory(account_id, p_character_select_entry_struct->Name, &inventory_profile)) {
const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0;
if (GetCharSelInventory(inventories, e, &inv)) {
const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0;
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
if (inventory_slot == INVALID_INDEX) { continue; }
inst = inventory_profile.GetItem(inventory_slot);
if (inst == nullptr)
inst = inv.GetItem(inventory_slot);
if (inst == nullptr) {
continue;
}
item = inst->GetItem();
if (item == nullptr)
if (item == nullptr) {
continue;
}
if (matslot > 6) {
uint32 item_id_file = 0;
// Weapon Models
if (inst->GetOrnamentationIDFile() != 0) {
item_id_file = inst->GetOrnamentationIDFile();
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
} else {
cse->Equip[matslot].Material = item_id_file;
}
else {
if (strlen(item->IDFile) > 2) {
item_id_file = Strings::ToInt(&item->IDFile[2]);
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
cse->Equip[matslot].Material = item_id_file;
}
}
if (matslot == EQ::textures::weaponPrimary) {
p_character_select_entry_struct->PrimaryIDFile = item_id_file;
} else {
p_character_select_entry_struct->SecondaryIDFile = item_id_file;
cse->PrimaryIDFile = item_id_file;
}
} else {
else {
cse->SecondaryIDFile = item_id_file;
}
}
else {
// Armor Materials/Models
uint32 color = (
pp.item_tint.Slot[matslot].UseTint ?
pp.item_tint.Slot[matslot].Color :
inst->GetColor()
pp.item_tint.Slot[matslot].Color :
inst->GetColor()
);
p_character_select_entry_struct->Equip[matslot].Material = item->Material;
p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial;
p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
p_character_select_entry_struct->Equip[matslot].Color = color;
cse->Equip[matslot].Material = item->Material;
cse->Equip[matslot].EliteModel = item->EliteMaterial;
cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
cse->Equip[matslot].Color = color;
}
}
} else {
printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name);
}
else {
printf("Error loading inventory for %s\n", cse->Name);
}
buff_ptr += sizeof(CharacterSelectEntry_Struct);
}
@@ -849,34 +826,21 @@ bool WorldDatabase::LoadCharacterCreateCombos()
return true;
}
// this is a slightly modified version of SharedDatabase::GetInventory(...) for character select use-only
bool WorldDatabase::GetCharSelInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv)
bool WorldDatabase::GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
)
{
if (!account_id || !name || !inv) {
if (inventories.empty() || !character.id || !inv) {
return false;
}
const uint32 character_id = GetCharacterID(name);
for (const auto& e : inventories) {
if (e.character_id != character.id) {
continue;
}
if (!character_id) {
return false;
}
const auto& l = InventoryRepository::GetWhere(
*this,
fmt::format(
"`character_id` = {} AND `slot_id` BETWEEN {} AND {}",
character_id,
EQ::invslot::slotHead,
EQ::invslot::slotFeet
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
switch (e.slot_id) {
case EQ::invslot::slotFace:
case EQ::invslot::slotEar2:
+7 -1
View File
@@ -20,6 +20,8 @@
#include "../common/shareddb.h"
#include "../common/eq_packet.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/character_data_repository.h"
struct PlayerProfile_Struct;
struct CharCreate_Struct;
@@ -43,7 +45,11 @@ private:
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
void SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
bool GetCharSelInventory(uint32 account_id, char* name, EQ::InventoryProfile* inv);
bool GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
);
};
extern WorldDatabase database;
+68 -14
View File
@@ -36,11 +36,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "shared_task_manager.h"
#include "dynamic_zone_manager.h"
#include "ucs.h"
#include "clientlist.h"
extern uint32 numzones;
extern EQ::Random emu_random;
extern WebInterfaceList web_interface;
extern SharedTaskManager shared_task_manager;
extern ClientList client_list;
volatile bool UCSServerAvailable_ = false;
void CatchSignal(int sig_num);
@@ -515,19 +517,27 @@ void ZSList::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to_mins
SendEmoteMessageRaw(to, to_guilddbid, to_minstatus, type, buffer);
}
void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_minstatus, uint32 type, const char* message) {
if (!message)
void ZSList::SendEmoteMessageRaw(
const char *to,
uint32 to_guilddbid,
int16 to_minstatus,
uint32 type,
const char *message
)
{
if (!message) {
return;
}
auto pack = new ServerPacket;
pack->opcode = ServerOP_EmoteMessage;
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
pack->opcode = ServerOP_EmoteMessage;
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*)pack->pBuffer;
ServerEmoteMessage_Struct *sem = (ServerEmoteMessage_Struct *) pack->pBuffer;
if (to) {
strcpy((char *)sem->to, to);
strcpy((char *) sem->to, to);
}
else {
sem->to[0] = 0;
@@ -535,22 +545,37 @@ void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_m
sem->guilddbid = to_guilddbid;
sem->minstatus = to_minstatus;
sem->type = type;
sem->type = type;
strcpy(&sem->message[0], message);
char tempto[64] = { 0 };
if (to)
char tempto[64] = {0};
if (to) {
strn0cpy(tempto, to, 64);
}
if (tempto[0] == 0) {
SendPacket(pack);
if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
} else {
SendPacket(pack);
}
}
else {
ZoneServer* zs = FindByName(to);
if (zs != 0)
ZoneServer *zs = FindByName(to);
if (zs) {
zs->SendPacket(pack);
else
}
else if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
}
else {
SendPacket(pack);
}
}
delete pack;
}
@@ -871,6 +896,33 @@ bool ZSList::SendPacketToBootedZones(ServerPacket* pack)
return true;
}
bool ZSList::SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket* pack)
{
auto servers = client_list.GetGuildZoneServers(guild_id);
for (auto const& z : zone_server_list) {
for (auto const& server_id : servers) {
if (z->GetID() == server_id && z->GetZoneID() > 0) {
z->SendPacket(pack);
}
}
}
return true;
}
bool ZSList::SendPacketToZonesWithGMs(ServerPacket* pack)
{
for (auto const &z: zone_server_list) {
for (auto const &server_id: client_list.GetZoneServersWithGMs()) {
if (z->GetID() == server_id && z->GetZoneID() > 0) {
z->SendPacket(pack);
}
}
}
return true;
}
void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
{
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
@@ -894,6 +946,8 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
ServerReload::Type::Commands,
ServerReload::Type::PerlExportSettings,
ServerReload::Type::DataBucketsCache,
ServerReload::Type::Quests,
ServerReload::Type::QuestsTimerReset,
ServerReload::Type::WorldRepop,
ServerReload::Type::WorldWithRespawn
};
+2
View File
@@ -29,6 +29,8 @@ public:
bool SendPacket(ServerPacket *pack);
bool SendPacket(uint32 zoneid, ServerPacket *pack);
bool SendPacket(uint32 zoneid, uint16 instanceid, ServerPacket *pack);
bool SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket *pack);
bool SendPacketToZonesWithGMs(ServerPacket *pack);
bool SendPacketToBootedZones(ServerPacket* pack);
bool SetLockedZone(uint16 iZoneID, bool iLock);
+16 -8
View File
@@ -571,7 +571,13 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
);
}
}
zoneserver_list.SendPacket(pack);
if (scm->guilddbid > 0) {
zoneserver_list.SendPacketToZonesWithGuild(scm->guilddbid, pack);
} else if (scm->chan_num == ChatChannel_GMSAY) {
zoneserver_list.SendPacketToZonesWithGMs(pack);
} else {
zoneserver_list.SendPacket(pack);
}
}
break;
@@ -729,13 +735,15 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
zs = zoneserver_list.FindByID(s->zone_server_id);
} else if (s->zone_id) {
zs = zoneserver_list.FindByName(ZoneName(s->zone_id));
} else if (s->instance_id) {
zs = zoneserver_list.FindByInstanceID(s->instance_id);
} else {
zoneserver_list.SendEmoteMessage(
s->admin_name,
0,
AccountStatus::Player,
Chat::White,
"Error: SOP_ZoneShutdown: neither ID nor name specified"
"Error: SOP_ZoneShutdown: Zone ID, Instance ID, nor Zone Short Name specified"
);
}
@@ -1510,7 +1518,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
guild->tribute.timer.Disable();
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
}
break;
}
@@ -1553,7 +1561,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
guild_mgr.UpdateDbGuildFavor(data->guild_id, data->favor);
guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining);
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
}
break;
}
@@ -1587,7 +1595,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
data->time_remaining = in->time_remaining;
strn0cpy(data->player_name, in->player_name, sizeof(data->player_name));
zoneserver_list.SendPacketToBootedZones(out);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, out);
safe_delete(out);
}
break;
@@ -1610,7 +1618,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->tribute_id_2_tier = guild->tribute.id_2_tier;
out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
safe_delete(sp);
}
@@ -1630,7 +1638,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->tribute_timer = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
out->trophy_timer = 0;
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
safe_delete(sp);
}
@@ -1654,7 +1662,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->member_time = in->member_time;
strn0cpy(out->player_name, in->player_name, sizeof(out->player_name));
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(out->guild_id, sp);
safe_delete(sp)
}
break;
+2
View File
@@ -53,6 +53,8 @@ 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; }
inline std::string GetCompileDate() const { return COMPILE_DATE; }
const char* GetCompileTime() const{ return compiled; }
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
inline uint32 GetZoneID() const { return zone_server_zone_id; }
+2
View File
@@ -173,6 +173,7 @@ SET(zone_sources
zone_event_scheduler.cpp
zone_npc_factions.cpp
zone_reload.cpp
zone_save_state.cpp
zoning.cpp
)
@@ -292,6 +293,7 @@ SET(zone_headers
zonedump.h
zone_cli.h
zone_reload.h
zone_save_state.h
zone_cli.cpp)
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
+13 -1
View File
@@ -2507,6 +2507,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
return false;
}
if (m_resumed_from_zone_suspend && !IsQueuedForCorpse()) {
LogInfo("NPC [{}] is resumed from zone suspend, cannot kill until zone resume is complete.", GetCleanName());
SetHP(0);
return false;
}
if (IsMultiQuestEnabled()) {
for (auto &i: m_hand_in.items) {
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
@@ -2627,7 +2633,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
bool pet_owner_is_client = give_exp->IsPet() && owner->IsClient();
bool pet_owner_is_bot = give_exp->IsPet() && owner->IsBot();
bool owner_is_client = owner->IsClient();
bool is_in_same_group_or_raid = (
pet_owner_is_client ||
(pet_owner_is_bot && owner->IsInGroupOrRaid(ulimate_owner)) ||
@@ -3041,9 +3047,15 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (!other)
return;
if (other->IsDestroying())
return;
if (other == this)
return;
if (other->IsClient() && (other->CastToClient()->IsZoning() || other->CastToClient()->Connected() == false))
return;
if (other->IsTrap())
return;
+356 -156
View File
@@ -2287,7 +2287,7 @@ void Bot::AI_Process()
// COMBAT RANGE CALCS
bool front_mob = InFrontMob(tar, GetX(), GetY());
bool behind_mob = BehindMob(tar, GetX(), GetY());
uint8 stop_melee_level = GetStopMeleeLevel();
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);
@@ -2314,7 +2314,7 @@ void Bot::AI_Process()
if (PULLING_BOT || RETURNING_BOT) {
if (!TargetValidation(tar)) { return; }
if (!DoLosChecks(tar)) {
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
return;
}
@@ -2442,7 +2442,7 @@ void Bot::AI_Process()
ranged_timer.Start();
}
else if (!IsBotRanged() && GetLevel() < stop_melee_level) {
else if (!IsBotRanged() && !stop_melee_level) {
if (
IsTaunting() ||
!GetMaxMeleeRange() ||
@@ -3128,7 +3128,6 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level;
if (IsTaunting()) { // Taunting bots
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
@@ -3143,7 +3142,7 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range);
}
else if (is_stop_melee_level) { // Casters
else if (input.stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
@@ -3176,14 +3175,17 @@ bool Bot::IsValidTarget(
return false;
}
SetHasLoS(DoLosChecks(tar));
bool invalid_target_state = false;
if (HOLDING ||
!tar->IsNPC() ||
(tar->IsMezzed() && !HasBotAttackFlag(tar)) ||
(!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) ||
lo_distance > leash_distance ||
tar_distance > leash_distance ||
(!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) ||
(!GetAttackingFlag() && !HasLoS()) ||
!IsAttackAllowed(tar)
) {
invalid_target_state = true;
@@ -3578,8 +3580,16 @@ void Bot::Depop() {
entity_list.RemoveFromHateLists(this);
RemoveAllAuras();
if (HasPet())
GetPet()->Depop();
Mob* bot_pet = GetPet();
if (bot_pet) {
if (bot_pet->Charmed()) {
bot_pet->BuffFadeByEffect(SE_Charm);
}
else {
bot_pet->Depop();
}
}
_botOwner = nullptr;
_botOwnerCharacterID = 0;
@@ -3702,11 +3712,6 @@ bool Bot::Spawn(Client* botCharacterOwner) {
}
}
if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) {
OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
CheckBotSpells(); //This runs through a series of checks and outputs any spells that are set to the wrong spell type in the database
}
if (IsBotRanged()) {
ChangeBotRangedWeapons(true);
}
@@ -9531,7 +9536,14 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
return false;
}
if (!BotHasEnoughMana(spell_id)) {
if (
!BotHasEnoughMana(spell_id) &&
(
!RuleB(Bots, FinishBuffing) ||
IsEngaged() ||
IsBotBuffSpellType(spell_type)
)
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id));
return false;
}
@@ -9790,6 +9802,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
}
uint8 bot_class = GetClass();
auto spell = spells[spell_id];
switch (spell_type) {
case BotSpellTypes::Buff:
@@ -9813,7 +9826,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
case BotSpellTypes::SendHome:
if (
tar == this &&
spells[spell_id].target_type == ST_TargetsTarget
spell.target_type == ST_TargetsTarget
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id));
return false;
@@ -9843,7 +9856,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
}
if (
spells[spell_id].target_type == ST_Pet &&
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet) &&
(
!tar->IsPet() ||
(
@@ -10586,6 +10599,8 @@ BotSpell Bot::GetSpellByHealType(uint16 spell_type, Mob* tar) {
return GetBestBotSpellForHealOverTime(this, tar, spell_type);
case BotSpellTypes::GroupHoTHeals:
return GetBestBotSpellForGroupHealOverTime(this, tar, spell_type);
default:
return BotSpell(); // Return an empty BotSpell if no valid spell type is found
}
}
@@ -10637,7 +10652,7 @@ int Bot::GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 s
case BotSettingCategories::SpellTypeAnnounceCast:
return GetDefaultSpellTypeAnnounceCast(setting_type, stance);
default:
break;
return 0; // Default return value for unrecognized categories
}
}
@@ -10676,7 +10691,7 @@ int Bot::GetSetting(uint16 setting_category, uint16 setting_type) {
case BotSettingCategories::SpellTypeAnnounceCast:
return GetSpellTypeAnnounceCast(setting_type);
default:
break;
return 0; // Default return value for unrecognized categories
}
}
@@ -11403,7 +11418,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) {
return false;
}
if (!DoLosChecks(tar)) {
if (!HasLoS() && !DoLosChecks(tar)) {
return false;
}
@@ -11566,18 +11581,265 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
}
if (IsBotSpellTypeDetrimental(spell_type) && !IsDetrimentalSpell(spell_id)) {
return false;
}
if (IsBotSpellTypeBeneficial(spell_type) && !IsBeneficialSpell(spell_id)) {
return false;
}
auto spell = spells[spell_id];
std::string teleport_zone = spell.teleport_zone;
switch (spell_type) {
case BotSpellTypes::Buff:
case BotSpellTypes::PetBuffs:
if (
IsResistanceOnlySpell(spell_id) ||
IsDamageShieldOnlySpell(spell_id) ||
IsDamageShieldAndResistSpell(spell_id)
) {
return false;
case BotSpellTypes::Nuke:
if (IsAnyNukeOrStunSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return true;
return false;
case BotSpellTypes::RegularHeal:
case BotSpellTypes::PetRegularHeals:
if (
IsAnyHealSpell(spell_id) &&
!IsVeryFastHealSpell(spell_id) &&
!IsFastHealSpell(spell_id) &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
!IsBuffSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Root:
case BotSpellTypes::AERoot:
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_Root)) {
return true;
}
return false;
case BotSpellTypes::Buff:
case BotSpellTypes::PreCombatBuff:
case BotSpellTypes::PetBuffs:
if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
!IsBardSong(spell_id) &&
!IsResistanceOnlySpell(spell_id) &&
!IsDamageShieldOnlySpell(spell_id) &&
!IsDamageShieldAndResistSpell(spell_id)
) {
if (
spell_type != BotSpellTypes::PetBuffs &&
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet)
) {
return false;
}
return true;
}
return false;
case BotSpellTypes::Escape:
if (IsEscapeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Pet:
if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) {
return true;
}
return false;
case BotSpellTypes::Lifetap:
case BotSpellTypes::AELifetap:
if (IsDetrimentalSpell(spell_id) && IsLifetapSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Snare:
case BotSpellTypes::AESnare:
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
return true;
}
return false;
case BotSpellTypes::DOT:
case BotSpellTypes::AEDoT:
if (
IsDetrimentalSpell(spell_id) &&
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
) {
return true;
}
return false;
case BotSpellTypes::Dispel:
case BotSpellTypes::AEDispel:
if (IsDispelSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::InCombatBuff:
if (
!IsBardSong(spell_id) &&
(
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
)
) {
return true;
}
return false;
case BotSpellTypes::Mez:
case BotSpellTypes::AEMez:
if (IsMesmerizeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Charm:
if (IsCharmSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Slow:
case BotSpellTypes::AESlow:
if (IsDetrimentalSpell(spell_id) && IsSlowSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Debuff:
case BotSpellTypes::AEDebuff:
if (
IsDebuffSpell(spell_id) &&
!IsHateReduxSpell(spell_id) &&
!IsHateSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Cure:
case BotSpellTypes::GroupCures:
case BotSpellTypes::PetCures:
if (IsCureSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Resurrect:
if (IsEffectInSpell(spell_id, SE_Revive)) {
return true;
}
return false;
case BotSpellTypes::HateRedux:
if (IsHateReduxSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::InCombatBuffSong:
case BotSpellTypes::OutOfCombatBuffSong:
case BotSpellTypes::PreCombatBuffSong:
if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
IsBardSong(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Fear:
case BotSpellTypes::AEFear:
if (IsFearSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Stun:
case BotSpellTypes::AEStun:
if (IsDetrimentalSpell(spell_id) && IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::HateLine:
case BotSpellTypes::AEHateLine:
if (IsDetrimentalSpell(spell_id) && IsHateSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::CompleteHeal:
case BotSpellTypes::GroupCompleteHeals:
case BotSpellTypes::PetCompleteHeals:
if (IsCompleteHealSpell(spell_id) || IsGroupCompleteHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::FastHeals:
case BotSpellTypes::PetFastHeals:
if (IsFastHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::VeryFastHeals:
case BotSpellTypes::PetVeryFastHeals:
if (IsVeryFastHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::GroupHeals:
if (IsRegularGroupHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::HoTHeals:
case BotSpellTypes::GroupHoTHeals:
case BotSpellTypes::PetHoTHeals:
if (IsHealOverTimeSpell(spell_id) || IsGroupHealOverTimeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::AENukes:
if (
IsDetrimentalSpell(spell_id) &&
!IsAERainSpell(spell_id) &&
!IsPBAENukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AERains:
if (IsAERainNukeSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::PBAENuke:
if (IsPBAENukeSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
if (IsResistanceBuffSpell(spell_id)) {
@@ -11587,44 +11849,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::DamageShields:
case BotSpellTypes::PetDamageShields:
if (IsEffectInSpell(spell_id, SE_DamageShield)) {
return true;
}
return false;
case BotSpellTypes::PBAENuke:
if (
IsPBAENukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AERains:
if (
IsAERainNukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AEStun:
case BotSpellTypes::Stun:
if (IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::AENukes:
case BotSpellTypes::Nuke:
if (!IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Lull:
if (IsHarmonySpell(spell_id)) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_DamageShield)) {
return true;
}
@@ -11632,13 +11857,18 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::Teleport:
if (
IsBeneficialSpell(spell_id) &&
(
IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate)
)
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
) {
return true;
}
return false;
case BotSpellTypes::Lull:
case BotSpellTypes::AELull:
if (IsHarmonySpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Succor:
if (
@@ -11662,25 +11892,21 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::Levitate:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Levitate)
) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Levitate)) {
return true;
}
return false;
case BotSpellTypes::Rune:
if (
IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) ||
IsEffectInSpell(spell_id, SE_Rune)
if (IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
) {
return true;
}
return false;
case BotSpellTypes::WaterBreathing:
if (IsEffectInSpell(spell_id, SE_WaterBreathing)) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) {
return true;
}
@@ -11688,29 +11914,22 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::Size:
if (
IsBeneficialSpell(spell_id) &&
(
IsEffectInSpell(spell_id, SE_ModelSize) ||
IsEffectInSpell(spell_id, SE_ChangeHeight)
)
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
) {
return true;
}
return false;
case BotSpellTypes::Invisibility:
if (
IsEffectInSpell(spell_id, SE_SeeInvis) ||
IsInvisibleSpell(spell_id)
if (IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_SeeInvis) ||IsInvisibleSpell(spell_id))
) {
return true;
}
return false;
case BotSpellTypes::MovementSpeed:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
return true;
}
@@ -11718,7 +11937,13 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::SendHome:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_GateToHomeCity)
(
IsEffectInSpell(spell_id, SE_GateToHomeCity) ||
(
teleport_zone.compare("") &&
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
)
)
) {
return true;
}
@@ -11806,74 +12031,53 @@ void Bot::DoCombatPositioning(
bool behind_mob,
bool front_mob
) {
if (HasTargetReflection()) {
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range
if (tar_distance <= melee_distance_max) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), false)) {
RunToGoalWithJitter(Goal);
if (!tar->IsFeared()) {
bool is_too_close = tar_distance < melee_distance_min;
bool los_adjust = !HasRequiredLoSForPositioning(tar);
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
if (rooted_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else if (
tar_distance < melee_distance_min ||
(!front_mob && IsTaunting())
) { // Back up any bots that are too close or if they're taunting and not in front of the mob
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else {
if (!tar->IsFeared()) {
if (
tar_distance < melee_distance_min ||
(GetBehindMob() && !behind_mob) ||
(IsTaunting() && !front_mob) ||
!HasRequiredLoSForPositioning(tar)
) { // Regular adjustment
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal);
return;
}
}
else if (
tar->IsEnraged() &&
!IsTaunting() &&
!stop_melee_level &&
!behind_mob
) { // Move non-taunting melee bots behind target during enrage
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
else {
if (IsTaunting()) { // Taunting adjustments
Mob* mob_tar = tar->GetTarget();
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
if (mob_tar) {
if (
RuleB(Bots, TauntingBotsFollowTopHate) &&
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
) { // If enabled, taunting bots will stick to top hate
Goal = mob_tar->GetPosition();
if (taunting_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
RunToGoalWithJitter(Goal);
return;
}
else { // Otherwise, stick to any other bots that are taunting
if (
mob_tar->IsBot() &&
mob_tar->CastToBot()->IsTaunting() &&
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
) {
Goal = mob_tar->GetPosition();
}
}
else {
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
if (enraged_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else { // Regular adjustments
bool regular_adjust =
is_too_close ||
los_adjust ||
(!GetBehindMob() && !front_mob) ||
(GetBehindMob() && !behind_mob);
if (regular_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal);
return;
@@ -11885,8 +12089,6 @@ void Bot::DoCombatPositioning(
}
DoFaceCheckNoJitter(tar);
return;
}
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) {
@@ -11983,7 +12185,11 @@ bool Bot::RequiresLoSForPositioning() {
}
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) {
if (IsBotSpellTypeDetrimental(i) && !GetSpellTypeHold(i)) {
if (IsHealBotSpellType(i) || i == BotSpellTypes::PBAENuke) {
continue;
}
if (!GetSpellTypeHold(i)) {
return true;
}
}
@@ -11996,7 +12202,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) {
return true;
}
if (RequiresLoSForPositioning() && !DoLosChecks(tar)) {
if (RequiresLoSForPositioning() && !HasLoS()) {
return false;
}
@@ -12011,10 +12217,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
for (auto& close_mob : caster->m_close_mobs) {
Mob* m = close_mob.second;
if (tar == m) {
continue;
}
switch (spell_type) {
case BotSpellTypes::AELull:
if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) {
@@ -12106,8 +12308,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
return false;
}
SetHasLoS(true);
return true;
}
+1 -2
View File
@@ -235,7 +235,7 @@ static std::map<uint16, std::string> botSubType_names = {
struct CombatRangeInput {
Mob* target;
float target_distance;
uint8 stop_melee_level;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
@@ -683,7 +683,6 @@ public:
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
// Spell lists
void CheckBotSpells();
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
static uint16 GetParentSpellType(uint16 spell_type);
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
+6 -1
View File
@@ -220,6 +220,11 @@ int bot_command_init(void)
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
std::vector<std::string> orphaned_bot_command_settings;
if (RuleB(Bots, RunSpellTypeChecksOnBoot)) {
LogBotSpellTypeChecks("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
database.botdb.CheckBotSpells();
}
database.botdb.MapCommandedSpellTypeMinLevels();
for (auto bcs_iter : bot_command_settings) {
@@ -813,7 +818,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
auto spell_type_itr = spell_map.find(spell_type);
auto class_itr = spell_type_itr->second.find(i);
const auto& spell_info = class_itr->second;
if (spell_info.min_level < UINT8_MAX) {
found = true;
+1 -1
View File
@@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
return;
}
if (!c->DoLosChecks(target_mob)) {
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
}
+8 -1
View File
@@ -525,6 +525,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
continue;
}
bool requires_los = !(IsAnyHealSpell(spell_id) && !IsPBAESpell(spell_id));
bot_iter->SetHasLoS(requires_los ? bot_iter->DoLosChecks(new_tar) : true);
if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) {
continue;
}
@@ -543,6 +546,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
tar = bot_iter;
}
bool los_required = bot_iter != tar && !IsAnyHealSpell(chosen_spell_id) && !IsPBAESpell(chosen_spell_id);
bot_iter->SetHasLoS(los_required ? bot_iter->DoLosChecks(new_tar) : true);
if (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) {
if (!first_found) {
first_found = bot_iter;
@@ -556,7 +562,8 @@ void bot_command_cast(Client* c, const Seperator* sep)
}
else {
bot_iter->SetCommandedSpell(true);
bot_iter->SetHasLoS(BotSpellTypeRequiresLoS(spell_type) ? bot_iter->DoLosChecks(new_tar) : true);
if (bot_iter->AICastSpell(new_tar, 100, spell_type, sub_target_type, sub_type)) {
if (!first_found) {
first_found = bot_iter;
+5 -1
View File
@@ -197,7 +197,11 @@ void bot_command_depart(Client* c, const Seperator* sep)
bot_iter->SetCommandedSpell(true);
if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) {
if (!IsValidSpell(itr->SpellId)) {
continue;
}
if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) {
continue;
}
+1 -1
View File
@@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep)
return;
}
if (!c->DoLosChecks(c->GetTarget())) {
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(c->GetTarget())) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
+1 -1
View File
@@ -48,7 +48,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
return;
}
if (!c->DoLosChecks(target_mob)) {
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
+136
View File
@@ -2523,6 +2523,142 @@ bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
return true;
}
void BotDatabase::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.maxlevel
);
}
}
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
uint16 parent_type = Bot::GetParentSpellType(correct_type);
if (s.type == parent_type || s.type == correct_type) {
continue;
}
if (correct_type != parent_type) {
correct_type = parent_type;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
correct_type = Bot::GetParentSpellType(correct_type);
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks(
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
correct_type,
spell_id,
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
}
}
}
void BotDatabase::MapCommandedSpellTypeMinLevels() {
commanded_spell_type_min_levels.clear();
+1
View File
@@ -130,6 +130,7 @@ public:
bool SaveBotSettings(Mob* m);
bool DeleteBotSettings(const uint32 bot_id);
void CheckBotSpells();
void MapCommandedSpellTypeMinLevels();
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
+89 -200
View File
@@ -41,7 +41,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
return false;
}
if (chance < 100 && zone->random.Int(0, 100) > chance) {
if (
!IsCommandedSpell() &&
zone->random.Int(0, 100) > chance
) {
return false;
}
@@ -61,10 +64,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
bot_spell.SpellIndex = 0;
bot_spell.ManaCost = 0;
if (BotSpellTypeRequiresLoS(spell_type) && tar != this) {
SetHasLoS(DoLosChecks(tar));
}
else {
if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) {
SetHasLoS(true);
}
@@ -218,8 +218,11 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, (IsAEBotSpellType(spell_type) || sub_target_type == CommandedSubTypes::AETarget), sub_target_type, sub_type);
for (const auto& s : bot_spell_list) {
if (!IsValidSpell(s.SpellId)) {
continue;
}
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
continue;
}
@@ -273,7 +276,11 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
if (!IsValidSpell(s.SpellId)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
continue;
}
@@ -281,7 +288,7 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type));
if (!add_mob) {
return false;
continue;
}
tar = add_mob;
@@ -327,7 +334,11 @@ bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
bot_spell = GetBestBotSpellForCure(this, tar, spell_type);
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
return false;
}
@@ -397,7 +408,11 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
bot_spell = GetFirstBotSpellBySpellType(this, spell_type);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
return false;
}
@@ -418,6 +433,10 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
}
bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) {
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
return false;
}
if (spell_type == BotSpellTypes::Stun || spell_type == BotSpellTypes::AEStun) {
uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal));
@@ -434,25 +453,25 @@ bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
) {
bot_spell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spell_type, IsAEBotSpellType(spell_type), tar);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS()) && spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard) {
if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) {
bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
if (!IsValidSpell(s.SpellId)) {
continue;
}
@@ -562,7 +581,9 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
// Allow bots to cast buff spells even if they are out of mana
if (
RuleB(Bots, FinishBuffing) &&
manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff
manaCost > hasMana &&
!IsEngaged() &&
IsBotBuffSpellType(AIBot_spells[i].type)
) {
SetMana(manaCost);
}
@@ -908,7 +929,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_ty
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -946,7 +971,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* caster, ui
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -987,7 +1016,11 @@ std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* caster, uint16 spell_type)
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1016,7 +1049,11 @@ std::vector<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* cas
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1103,7 +1140,11 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type) {
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1135,7 +1176,6 @@ BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (auto bot_spell_list_itr : bot_spell_list) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId;
@@ -1161,7 +1201,6 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_typ
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (auto bot_spell_list_itr : bot_spell_list) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId;
result.SpellIndex = bot_spell_list_itr.SpellIndex;
@@ -1186,7 +1225,6 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime);
for (auto bot_spell_list_itr : bot_spell_list) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsHealOverTimeSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId;
result.SpellIndex = bot_spell_list_itr.SpellIndex;
@@ -1243,7 +1281,6 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, u
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr->SpellId;
result.SpellIndex = bot_spell_list_itr->SpellIndex;
@@ -1268,7 +1305,6 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr->SpellId;
result.SpellIndex = bot_spell_list_itr->SpellIndex;
@@ -1298,7 +1334,6 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_ty
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = bot_spell_list_itr->SpellId;
@@ -1337,7 +1372,6 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = bot_spell_list_itr->SpellId;
@@ -1376,7 +1410,6 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = bot_spell_list_itr->SpellId;
@@ -1410,7 +1443,6 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Mez);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsMesmerizeSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
@@ -1433,11 +1465,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
if (caster && caster->GetOwner()) {
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
int spell_ae_range = caster->GetAOERange(spell_id);
int buff_count = 0;
bool is_pbae_spell = IsPBAESpell(spell_id);
NPC* npc = nullptr;
for (auto& close_mob : caster->m_close_mobs) {
buff_count = 0;
npc = close_mob.second->CastToNPC();
if (!npc) {
@@ -1448,29 +1479,29 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue;
}
if (is_pbae_spell) {
if (spell_ae_range < Distance(caster->GetPosition(), npc->GetPosition())) {
continue;
}
}
else {
if (spell_range < Distance(caster->GetPosition(), npc->GetPosition())) {
continue;
}
}
if (AE) {
int target_count = 0;
for (auto& close_mob : caster->m_close_mobs) {
Mob* m = close_mob.second;
if (npc == m) {
continue;
}
if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) {
continue;
}
if (IsPBAESpell(spell_id)) {
if (spell_ae_range < Distance(caster->GetPosition(), m->GetPosition())) {
continue;
}
}
else {
if (spell_range < Distance(m->GetPosition(), npc->GetPosition())) {
continue;
}
if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) {
continue;
}
if (caster->CastChecks(spell_id, m, spell_type, true, true)) {
@@ -1486,11 +1517,6 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue;
}
if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) {
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezFailDelay));
return result;
}
result = npc;
}
else {
@@ -1502,18 +1528,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue;
}
if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) {
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezAEFailDelay));
return result;
}
result = npc;
}
if (result) {
caster->SetHasLoS(true);
return result;
}
}
@@ -1534,7 +1552,6 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) {
std::string pet_type = GetBotMagicianPetType(caster);
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsSummonPetSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) &&
@@ -1716,7 +1733,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SE_CurrentHP, target_type);
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsPureNukeSpell(bot_spell_list_itr->SpellId) || IsDamageSpell(bot_spell_list_itr->SpellId)) {
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
continue;
@@ -1729,7 +1745,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
continue;
}
if (
caster->IsCommandedSpell() ||
!AE ||
@@ -1766,7 +1781,6 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType ta
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr)
{
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsStunSpell(bot_spell_list_itr->SpellId)) {
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
continue;
@@ -1830,7 +1844,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
bool spell_selected = false;
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) {
continue;
}
@@ -1887,8 +1900,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
if (!spell_selected) {
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) {
if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) {
spell_selected = true;
@@ -1926,7 +1937,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* caster, Mob *tar, uint16 spell_type) {
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1972,7 +1987,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* caster, Mob *tar, uint16 spell
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -2091,7 +2110,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
case BotSpellTypes::AERains:
case BotSpellTypes::AEStun:
case BotSpellTypes::AESnare:
case BotSpellTypes::AEMez:
case BotSpellTypes::AESlow:
case BotSpellTypes::AEDebuff:
case BotSpellTypes::AEFear:
@@ -2157,6 +2175,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
case BotSpellTypes::PetVeryFastHeals:
case BotSpellTypes::PetHoTHeals:
return RuleI(Bots, PercentChanceToCastHeal);
case BotSpellTypes::AEMez:
return RuleI(Bots, PercentChanceToCastAEMez);
default:
return RuleI(Bots, PercentChanceToCastOtherType);
}
@@ -2821,7 +2841,6 @@ BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type)
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsResurrectSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
@@ -2849,7 +2868,6 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsCharmSpell(bot_spell_list_itr->SpellId) &&
caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)
@@ -2865,132 +2883,3 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
return result;
}
void Bot::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
uint16 correct_type;
uint16 parent_type;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, spell_id
, s.npc_spells_id
, GetSpellName(spell_id)
, spell_id
, s.minlevel
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, spell_id
, s.npc_spells_id
, GetSpellName(spell_id)
, spell_id
, s.minlevel
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.maxlevel
);
}
}
correct_type = GetCorrectBotSpellType(s.type, spell_id);
parent_type = GetParentSpellType(correct_type);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
if (s.type == parent_type || s.type == correct_type) {
continue;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown."
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]"
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
, GetSpellTypeNameByID(correct_type)
, correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]"
, correct_type
, spell_id
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
, GetSpellTypeNameByID(correct_type)
, correct_type
);
}
}
}
+57
View File
@@ -0,0 +1,57 @@
#include "../../zone.h"
inline void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
} else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << expected << "\n";
std::cerr << " ❌ Got: " << actual << "\n";
std::exit(1);
}
}
inline void RunTest(const std::string &test_name, bool expected, bool actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
}
else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
std::exit(1);
}
}
inline void RunTest(const std::string &test_name, int expected, int actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
}
else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << expected << "\n";
std::cerr << " ❌ Got: " << actual << "\n";
std::exit(1);
}
}
extern Zone *zone;
inline void SetupZone(std::string zone_short_name, uint32 instance_id = 0) {
LogSys.SilenceConsoleLogging();
LogSys.log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0;
LogSys.log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0;
LogSys.log_settings[Logs::Spawns].log_to_console = std::getenv("DEBUG") ? 3 : 0;
// boot shell zone for testing
Zone::Bootup(ZoneID(zone_short_name), 0, false);
zone->StopShutdownTimer();
entity_list.Process();
entity_list.MobProcess();
LogSys.EnableConsoleLogging();
}
@@ -1,25 +1,13 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../zone.h"
#include "../client.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
extern Zone *zone;
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
} else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << expected << "\n";
std::cerr << " ❌ Got: " << actual << "\n";
std::exit(1);
}
}
void ZoneCLI::DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
@@ -1,8 +1,8 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../zone.h"
#include "../client.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
#include "../../common/json/json.hpp"
@@ -36,19 +36,6 @@ struct TestCase {
bool handin_check_result;
};
void RunTest(const std::string &test_name, bool expected, bool actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
}
else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
std::exit(1);
}
}
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{
if (expected == actual) {
@@ -75,7 +62,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
return j.dump();
}
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
@@ -1,13 +1,13 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../zone.h"
#include "../client.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
extern Zone *zone;
void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
File diff suppressed because it is too large Load Diff
+92 -18
View File
@@ -882,9 +882,13 @@ void Client::SendZoneInPackets()
//SendGuildMembers();
SendGuildURL();
SendGuildChannel();
SendGuildLFGuildStatus();
if (RuleB(Guild, EnableLFGuild)) {
SendGuildLFGuildStatus();
}
}
if (RuleB(Guild, EnableLFGuild)) {
SendLFGuildStatus();
}
SendLFGuildStatus();
//No idea why live sends this if even were not in a guild
SendGuildMOTD();
@@ -2549,40 +2553,59 @@ void Client::ChangeLastName(std::string last_name) {
safe_delete(outapp);
}
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
// Deprecated, this packet does not actually work in ROF2
bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname)
{
// check duplicate name
bool used_name = database.IsNameUsed((const char*) in_firstname);
if (used_name) {
if (!ChangeFirstName(in_firstname)) {
return false;
}
// update character_
if(!database.UpdateName(GetName(), in_firstname))
return false;
// update pp
memset(m_pp.name, 0, sizeof(m_pp.name));
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
strcpy(name, m_pp.name);
Save();
// send name update packet
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
strn0cpy(gmn->gmname,gmname,64);
strn0cpy(gmn->gmname,gmname.c_str(),64);
strn0cpy(gmn->oldname,GetName(),64);
strn0cpy(gmn->newname,in_firstname,64);
strn0cpy(gmn->newname,in_firstname.c_str(),64);
gmn->unknown[0] = 1;
gmn->unknown[1] = 1;
gmn->unknown[2] = 1;
entity_list.QueueClients(this, outapp, false);
safe_delete(outapp);
// success
return true;
}
bool Client::ChangeFirstName(const std::string in_firstname)
{
// check duplicate name
bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname);
if (used_name || !database.CheckNameFilter(in_firstname, false)) {
return false;
}
// update character_
if(!database.UpdateNameByID(CharacterID(), in_firstname))
return false;
// Send Name Update to Clients
SendRename(this, GetName(), in_firstname.c_str());
SetName(in_firstname.c_str());
// update pp
memset(m_pp.name, 0, sizeof(m_pp.name));
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str());
strcpy(name, m_pp.name);
Save();
// Update the active char in account table
database.UpdateLiveChar(in_firstname, AccountID());
// finally, update the /who list
UpdateWho();
// success
ClearNameChange();
return true;
}
@@ -4737,6 +4760,57 @@ bool Client::KeyRingRemove(uint32 item_id)
);
}
bool Client::IsNameChangeAllowed() {
if (RuleB(Character, AlwaysAllowNameChange)) {
return true;
}
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
auto b = DataBucket::GetData(k);
if (!b.value.empty()) {
return true;
}
return false;
}
bool Client::ClearNameChange() {
if (!IsNameChangeAllowed()) {
return false;
}
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
DataBucket::DeleteData(k);
return true;
}
void Client::InvokeChangeNameWindow(bool immediate) {
if (!IsNameChangeAllowed()) {
return;
}
auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy;
auto outapp = new EQApplicationPacket(packet_op, 0);
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::GrantNameChange() {
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
k.value = "allowed"; // potentially put a timestamp here
DataBucket::SetData(k);
InvokeChangeNameWindow(true);
}
bool Client::IsPetNameChangeAllowed() {
if (RuleB(Pets, AlwaysAllowPetRename)) {
return true;
+8 -1
View File
@@ -332,6 +332,10 @@ public:
bool KeyRingClear();
bool KeyRingRemove(uint32 item_id);
void KeyRingList();
bool IsNameChangeAllowed();
void InvokeChangeNameWindow(bool immediate = true);
bool ClearNameChange();
void GrantNameChange();
bool IsPetNameChangeAllowed();
void GrantPetNameChange();
void ClearPetNameChange();
@@ -443,6 +447,8 @@ public:
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
void CancelBuyerTradeWindow();
void CancelTraderTradeWindow();
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
@@ -509,7 +515,8 @@ public:
bool AutoAttackEnabled() const { return auto_attack; }
bool AutoFireEnabled() const { return auto_fire; }
bool ChangeFirstName(const char* in_firstname,const char* gmname);
bool ChangeFirstName(const std::string in_firstname,const std::string gmname);
bool ChangeFirstName(const std::string in_firstname);
void Duck();
void Stand();
+34 -32
View File
@@ -120,48 +120,46 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
}
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
int i = 0;
for (const auto& result : zones_list) {
try {
if (
std::stoul(result) == zone->GetZoneID() &&
std::stoul(zones_limits_list[i]) < bot_spawn_limit
) {
bot_spawn_limit = std::stoul(zones_limits_list[i]);
if (!zones_list.empty()) {
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
break;
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 (new_limit < bot_spawn_limit) {
bot_spawn_limit = new_limit;
}
} catch (const std::exception& e) {
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
}
}
++i;
}
catch (const std::exception& e) {
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
}
}
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
i = 0;
for (const auto& result : zones_forced_list) {
try {
if (
std::stoul(result) == zone->GetZoneID() &&
std::stoul(zones_forced_limits_list[i]) != bot_spawn_limit
) {
bot_spawn_limit = std::stoul(zones_forced_limits_list[i]);
if (!zones_forced_list.empty()) {
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
break;
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 (new_limit != bot_spawn_limit) {
bot_spawn_limit = new_limit;
}
} catch (const std::exception& e) {
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
}
}
++i;
}
catch (const std::exception& e) {
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
}
}
@@ -258,6 +256,8 @@ int Client::GetDefaultBotSettings(uint8 setting_type, uint16 bot_setting) {
return GetDefaultSpellTypeMinThreshold(bot_setting);
case BotSettingCategories::SpellMaxThreshold:
return GetDefaultSpellTypeMaxThreshold(bot_setting);
default:
return 0; // default return for any unsupported setting type
}
}
@@ -271,6 +271,8 @@ int Client::GetBotSetting(uint8 setting_type, uint16 bot_setting) {
return GetSpellTypeMinThreshold(bot_setting);
case BotSettingCategories::SpellMaxThreshold:
return GetSpellTypeMaxThreshold(bot_setting);
default:
return 0; // default return for any unsupported setting type
}
}
+37 -14
View File
@@ -73,6 +73,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/character_stats_record_repository.h"
#include "dialogue_window.h"
#include "../common/rulesys.h"
#include "../common/repositories/adventure_members_repository.h"
extern QueryServ* QServ;
extern Zone* zone;
@@ -826,6 +827,10 @@ void Client::CompleteConnect()
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
InvokeChangePetName(false);
}
if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) {
InvokeChangeNameWindow(false);
}
}
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
@@ -909,11 +914,14 @@ void Client::CompleteConnect()
SendDynamicZoneUpdates();
/** Request adventure info **/
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
strcpy((char*)pack->pBuffer, GetName());
worldserver.SendPacket(pack);
delete pack;
// Request adventure info
auto members = AdventureMembersRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
if (!members.empty()) {
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
strcpy((char*)pack->pBuffer, GetName());
worldserver.SendPacket(pack);
delete pack;
}
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) {
EQApplicationPacket *outapp = MakeBuffsPacket(false);
@@ -4256,7 +4264,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
else {
OnDisconnect(true);
}
return;
}
@@ -4548,14 +4556,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) {
auto p = (ChangePetName_Struct *) app->pBuffer;
if (!IsPetNameChangeAllowed()) {
p->response_code = ChangePetNameResponse::NotEligible;
p->response_code = ChangeNameResponse::Ineligible;
QueuePacket(app);
return;
}
p->response_code = ChangePetNameResponse::Denied;
p->response_code = ChangeNameResponse::Denied;
if (ChangePetName(p->new_pet_name)) {
p->response_code = ChangePetNameResponse::Accepted;
p->response_code = ChangeNameResponse::Accepted;
}
QueuePacket(app);
@@ -6776,6 +6784,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
{
if (app->size == sizeof(AltChangeName_Struct)) {
auto p = (AltChangeName_Struct *) app->pBuffer;
if (!IsNameChangeAllowed()) {
p->response_code = ChangeNameResponse::Ineligible;
QueuePacket(app);
return;
}
p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied;
QueuePacket(app);
return;
}
if (app->size != sizeof(GMName_Struct)) {
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
return;
@@ -7961,7 +7984,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
}
SetGuildID(new_guild_id);
SendGuildList();
UpdateWho();
guild_mgr.MemberAdd(new_guild_id, CharacterID(), GetLevel(), GetClass(), GUILD_LEADER, GetZoneID(), GetName());
guild_mgr.SendGuildRefresh(new_guild_id, true, true, true, true);
guild_mgr.SendToWorldSendGuildList();
@@ -8130,7 +8153,7 @@ void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app)
if (!invitee) {
Message(
Chat::Red,
"Prospective guild member %s must be in zone to preform guild operations on them.",
"Prospective guild member %s must be in zone to perform guild operations on them.",
gc->othername
);
return;
@@ -11043,7 +11066,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
if (!target)
break;
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) {
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) {
mypet->SayString(this, NOT_LEGAL_TARGET);
break;
}
@@ -11111,7 +11134,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
break;
}
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) {
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) {
mypet->SayString(this, NOT_LEGAL_TARGET);
break;
}
@@ -15463,7 +15486,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
);
Message(
Chat::Yellow,
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
"Direct inventory delivery is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
);
in->method = BazaarByDirectToInventory;
in->sub_action = Failed;
+1 -1
View File
@@ -245,7 +245,7 @@ int command_init(void)
command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", AccountStatus::GMLeadAdmin, command_zonebootup) ||
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", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
) {
command_deinit();
-5
View File
@@ -111,7 +111,6 @@ void command_ipban(Client *c, const Seperator *sep);
void command_kick(Client *c, const Seperator *sep);
void command_killallnpcs(Client *c, const Seperator *sep);
void command_kill(Client *c, const Seperator *sep);
void command_level(Client *c, const Seperator *sep);
void command_list(Client *c, const Seperator *sep);
void command_lootsim(Client *c, const Seperator *sep);
void command_load_shared_memory(Client *c, const Seperator *sep);
@@ -139,7 +138,6 @@ void command_nudge(Client *c, const Seperator *sep);
void command_nukebuffs(Client *c, const Seperator *sep);
void command_nukeitem(Client *c, const Seperator *sep);
void command_object(Client *c, const Seperator *sep);
void command_oocmute(Client *c, const Seperator *sep);
void command_parcels(Client *c, const Seperator *sep);
void command_path(Client *c, const Seperator *sep);
void command_peqzone(Client *c, const Seperator *sep);
@@ -147,7 +145,6 @@ void command_petitems(Client *c, const Seperator *sep);
void command_picklock(Client *c, const Seperator *sep);
void command_profanity(Client *c, const Seperator *sep);
void command_push(Client *c, const Seperator *sep);
void command_pvp(Client *c, const Seperator *sep);
void command_raidloot(Client* c, const Seperator* sep);
void command_randomfeatures(Client *c, const Seperator *sep);
void command_refreshgroup(Client *c, const Seperator *sep);
@@ -201,8 +198,6 @@ void command_zone_instance(Client *c, const Seperator *sep);
void command_zone_shard(Client *c, const Seperator *sep);
void command_zonebootup(Client *c, const Seperator *sep);
void command_zoneshutdown(Client *c, const Seperator *sep);
void command_zopp(Client *c, const Seperator *sep);
void command_zsafecoords(Client *c, const Seperator *sep);
void command_zsave(Client *c, const Seperator *sep);
#include "bot.h"
+2 -2
View File
@@ -290,7 +290,7 @@ Corpse::Corpse(Client *c, int32 rez_exp, KilledByTypes in_killed_by) : Mob(
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
m_loot_cooldown_timer.SetTimer(10);
m_check_rezzable_timer.SetTimer(1000);
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
m_corpse_rezzable_timer.Disable();
SetRezTimer(true);
@@ -583,7 +583,7 @@ Corpse::Corpse(
m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer));
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
m_loot_cooldown_timer.SetTimer(10);
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
m_check_rezzable_timer.SetTimer(1000);
m_corpse_rezzable_timer.Disable();
+68
View File
@@ -35,6 +35,9 @@
#include <string.h>
#include <glm/ext/matrix_transform.hpp>
#include <numbers>
#define OPEN_DOOR 0x02
#define CLOSE_DOOR 0x03
#define OPEN_INVDOOR 0x03
@@ -970,3 +973,68 @@ bool Doors::GetIsDoorBlacklisted()
bool Doors::IsDoorBlacklisted() {
return m_is_blacklisted_to_open;
}
bool Doors::IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size, float door_depth, bool draw_box) {
glm::vec4 door_loc = GetPosition();
float half_size = door_size * 0.5f;
float half_depth = door_depth * 0.5f;
float normalized_heading = std::fmod(door_loc.w, 512.0f);
float heading_radians = normalized_heading * (std::numbers::pi / 256.0f);
glm::mat4 door_rotation = glm::rotate(glm::mat4(1.0f), -heading_radians, glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 box_corner_one = glm::vec3(door_size, -half_depth, 0.0f);
glm::vec3 box_corner_two = glm::vec3(-door_size, -half_depth, 0.0f);
glm::vec3 box_corner_three = glm::vec3(-door_size, half_depth, 0.0f);
glm::vec3 box_corner_four = glm::vec3(door_size, half_depth, 0.0f);
glm::vec3 door_center_offset = glm::vec3(-(door_size * 0.75f), half_depth * 0.5f, 0.0f);
glm::vec3 door_center = glm::vec3(door_loc) + glm::vec3(door_rotation * glm::vec4(door_center_offset, 1.0f));
glm::mat4 transform = glm::translate(glm::mat4(1.0f), door_center) * door_rotation;
box_corner_one = glm::vec3(transform * glm::vec4(box_corner_one, 1.0f));
box_corner_two = glm::vec3(transform * glm::vec4(box_corner_two, 1.0f));
box_corner_three = glm::vec3(transform * glm::vec4(box_corner_three, 1.0f));
box_corner_four = glm::vec3(transform * glm::vec4(box_corner_four, 1.0f));
if (draw_box) {
NPC::SpawnZonePointNodeNPC("loc_a", loc_a);
NPC::SpawnZonePointNodeNPC("door_anchor", door_loc);
NPC::SpawnZonePointNodeNPC("loc_c", loc_c);
NPC::SpawnZonePointNodeNPC("box_corner_one", glm::vec4(box_corner_one.x, box_corner_one.y, box_corner_one.z, 0));
NPC::SpawnZonePointNodeNPC("box_corner_two", glm::vec4(box_corner_two.x, box_corner_two.y, box_corner_two.z, 0));
NPC::SpawnZonePointNodeNPC("box_corner_three", glm::vec4(box_corner_three.x, box_corner_three.y, box_corner_three.z, 0));
NPC::SpawnZonePointNodeNPC("box_corner_four", glm::vec4(box_corner_four.x, box_corner_four.y, box_corner_four.z, 0));
NPC::SpawnZonePointNodeNPC("box_center", glm::vec4(door_center.x, door_center.y, door_center.z, 0));
}
// Check if LoS intersects box
auto intersects_box = [](const glm::vec3& a, const glm::vec3& b, const glm::vec3& p1, const glm::vec3& p2) {
glm::vec3 ab = b - a;
glm::vec3 p1p2 = p2 - p1;
glm::vec3 cross = glm::cross(ab, p1p2);
float cross_magnitude_squared = glm::dot(cross, cross);
if (cross_magnitude_squared < 1e-6f) {
return false; // Lines are parallel or coincident
}
float t = glm::dot(glm::cross(p1 - a, p1p2), cross) / cross_magnitude_squared;
float u = glm::dot(glm::cross(p1 - a, ab), cross) / cross_magnitude_squared;
return (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f);
};
// Check intersection with each edge of the door bounding box
glm::vec3 loc_a_vec3(loc_a.x, loc_a.y, loc_a.z);
glm::vec3 loc_c_vec3(loc_c.x, loc_c.y, loc_c.z);
if (
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_one, box_corner_two) ||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_two, box_corner_three) ||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_three, box_corner_four) ||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_four, box_corner_one)
) {
return true;
}
return false;
}
+1
View File
@@ -76,6 +76,7 @@ public:
bool IsDestinationZoneSame() const;
bool IsDoorBlacklisted();
bool IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size = 15, float door_depth = 5.0f, bool draw_box = false);
const char* GetDoorZone() const { return m_zone_name; }
+19 -5
View File
@@ -3143,20 +3143,23 @@ void EntityList::Depop(bool StartSpawnTimer)
{
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
NPC *pnpc = it->second;
if (pnpc) {
Mob *own = pnpc->GetOwner();
//do not depop player's pets...
if (own && own->IsClient())
//do not depop player/bot pets...
if (own && own->IsOfClientBot()) {
continue;
}
if (pnpc->IsHorse())
if (pnpc->IsHorse()) {
continue;
}
if (pnpc->IsFindable())
if (pnpc->IsFindable()) {
UpdateFindableNPCState(pnpc, true);
}
pnpc->WipeHateList();
pnpc->Depop(StartSpawnTimer);
}
}
@@ -5988,3 +5991,14 @@ void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
return;
}
void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
{
uint16 corpse_id = npc->GetID();
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
auto c = entity_list.GetCorpseByID(corpse_id);
if (c) {
c->UnLock();
c->SetDecayTimer(decay_time);
}
}
+1
View File
@@ -580,6 +580,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);
protected:
friend class Zone;
+27
View File
@@ -35,6 +35,26 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
);
}
if (arg1 == "drawbox") {
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
if (door) {
uint16 door_size = 15;
float door_depth = 5.0f;
if (sep->IsNumber(2) && atof(sep->arg[2]) > 0) {
door_size = atof(sep->arg[2]);
}
if (sep->IsNumber(3) && atof(sep->arg[3]) > 0) {
door_depth = atof(sep->arg[3]);
}
door->IsDoorBetween(c->GetPosition(), (c->GetTarget() ? c->GetTarget()->GetPosition() : c->GetPosition()), door_size, door_depth, true);
}
}
// edit menu
if (arg1 == "edit") {
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
@@ -544,6 +564,13 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state");
c->Message(Chat::White, "#door setincline <incline> | Sets selected door incline");
c->Message(Chat::White, "#door opentype <opentype> | Sets selected door opentype");
c->Message(
Chat::White,
fmt::format(
"{} <door_size> <door_depth> | Draws a box for the door, default size = 15, depth = 5 if none defined",
Saylink::Silent("#door drawbox")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
+2 -6
View File
@@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep)
std::string new_name = sep->arg[2];
std::string old_name = t->GetCleanName();
if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) {
if (t->ChangeFirstName(new_name, c->GetCleanName())) {
c->Message(
Chat::White,
fmt::format(
@@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep)
).c_str()
);
c->Message(Chat::White, "Sending player to char select.");
t->Kick("Name was changed");
return;
}
c->Message(
Chat::White,
fmt::format(
"Unable to rename {}. Check that the new name '{}' isn't already taken.",
"Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid",
old_name,
new_name
).c_str()
+2
View File
@@ -49,6 +49,7 @@
#include "show/zone_loot.cpp"
#include "show/zone_points.cpp"
#include "show/zone_status.cpp"
#include "show/zone_variables.cpp"
void command_show(Client *c, const Seperator *sep)
{
@@ -110,6 +111,7 @@ void command_show(Client *c, const Seperator *sep)
Cmd{.cmd = "zone_loot", .u = "zone_loot", .fn = ShowZoneLoot, .a = {"#viewzoneloot"}},
Cmd{.cmd = "zone_points", .u = "zone_points", .fn = ShowZonePoints, .a = {"#showzonepoints"}},
Cmd{.cmd = "zone_status", .u = "zone_status", .fn = ShowZoneStatus, .a = {"#zonestatus"}},
Cmd{.cmd = "zone_variables", .u = "zone_variables", .fn = ShowZoneVariables},
};
// Check for arguments
-5
View File
@@ -81,11 +81,6 @@ void ShowZoneData(Client *c, const Seperator *sep)
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
);
popup_table += DialogueWindow::TableRow(
DialogueWindow::TableCell("Time Type") +
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
);
popup_table += DialogueWindow::TableRow(
DialogueWindow::TableCell("Experience Multiplier") +
DialogueWindow::TableCell(
+16
View File
@@ -0,0 +1,16 @@
#include "../../client.h"
#include "../../zone.h"
extern Zone* zone;
void ShowZoneVariables(Client *c, const Seperator *sep)
{
if (!zone->GetVariables().empty()) {
c->Message(Chat::White, "Zone Variables:");
for (auto &key: zone->GetVariables()) {
c->Message(Chat::White, fmt::format("{}: {}", key, zone->GetVariable(key)).c_str());
}
} else {
c->Message(Chat::White, "No zone variables set.");
}
}
+55 -13
View File
@@ -6,8 +6,18 @@ extern WorldServer worldserver;
void command_zoneshutdown(Client *c, const Seperator *sep)
{
const int arguments = sep->argnum;
if (!arguments) {
c->Message(Chat::White, "Usage: #zoneshutdown [Zone ID|Zone Short Name]");
if (arguments < 2) {
c->Message(Chat::White, "Usage: #zoneshutdown instance [Instance ID]");
c->Message(Chat::White, "Usage: #zoneshutdown zone [Zone ID|Zone Short Name]");
return;
}
bool is_instance = !strcasecmp(sep->arg[1], "instance");
bool is_zone = !strcasecmp(sep->arg[1], "zone");
if (!is_instance && !is_zone) {
c->Message(Chat::White, "Usage: #zoneshutdown instance [Instance ID]");
c->Message(Chat::White, "Usage: #zoneshutdown zone [Zone ID|Zone Short Name]");
return;
}
@@ -16,23 +26,55 @@ void command_zoneshutdown(Client *c, const Seperator *sep)
return;
}
const uint32 zone_id = sep->IsNumber(1) ? Strings::ToUnsignedInt(sep->arg[1]) : ZoneID(sep->arg[1]);
uint32 zone_id = 0;
uint16 instance_id = 0;
std::string message = "";
if (!zone_id) {
c->Message(
Chat::White,
fmt::format(
"Zone '{}' does not exist.",
sep->arg[1]
).c_str()
);
return;
if (is_instance) {
instance_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : 0;
if (!database.CheckInstanceExists(instance_id)) {
c->Message(
Chat::White,
fmt::format(
"Instance ID '{}' does not exist.",
instance_id
).c_str()
);
return;
}
message = fmt::format("Instance ID {}", instance_id);
} else if (is_zone) {
zone_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : ZoneID(sep->arg[2]);
if (!zone_id) {
c->Message(
Chat::White,
fmt::format(
"Zone '{}' does not exist.",
sep->arg[1]
).c_str()
);
return;
}
message = fmt::format("{} (ID {})", ZoneLongName(zone_id), zone_id);
}
c->Message(
Chat::White,
fmt::format(
"Attempting to shut down {}.",
message
).c_str()
);
auto pack = new ServerPacket(ServerOP_ZoneShutdown, sizeof(ServerZoneStateChange_Struct));
auto *s = (ServerZoneStateChange_Struct *) pack->pBuffer;
s->zone_id = zone_id;
s->zone_id = zone_id;
s->instance_id = instance_id;
strn0cpy(s->admin_name, c->GetName(), sizeof(s->admin_name));
+1 -1
View File
@@ -195,7 +195,7 @@ void Client::SendGuildList()
std::stringstream ss;
cereal::BinaryOutputArchive ar(ss);
ar(guilds_list);
{ ar(guilds_list); }
uint32 packet_size = ss.str().length();
+21
View File
@@ -3496,6 +3496,24 @@ std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name)
return self->GetAccountBucketRemaining(bucket_name);
}
void Lua_Client::GrantNameChange()
{
Lua_Safe_Call_Void();
self->GrantNameChange();
}
bool Lua_Client::IsNameChangeAllowed()
{
Lua_Safe_Call_Bool();
return self->IsNameChangeAllowed();
}
bool Lua_Client::ClearNameChange()
{
Lua_Safe_Call_Bool();
return self->ClearNameChange();
}
std::string Lua_Client::GetBandolierName(uint8 bandolier_slot)
{
Lua_Safe_Call_String();
@@ -3635,6 +3653,7 @@ luabind::scope lua_register_client() {
.def("CashReward", &Lua_Client::CashReward)
.def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName)
.def("GrantPetNameChange", &Lua_Client::GrantPetNameChange)
.def("ClearNameChange", &Lua_Client::ClearNameChange)
.def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID)
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill)
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill)
@@ -3851,6 +3870,7 @@ luabind::scope lua_register_client() {
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantNameChange", &Lua_Client::GrantNameChange)
.def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID)
.def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank)
.def("HasAugmentEquippedByID", (bool(Lua_Client::*)(uint32))&Lua_Client::HasAugmentEquippedByID)
@@ -3881,6 +3901,7 @@ luabind::scope lua_register_client() {
.def("IsInAGuild", (bool(Lua_Client::*)(void))&Lua_Client::IsInAGuild)
.def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD)
.def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding)
.def("IsNameChangeAllowed", &Lua_Client::IsNameChangeAllowed)
.def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped)
.def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting)
.def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding)
+4
View File
@@ -609,6 +609,10 @@ public:
void ShowZoneShardMenu();
void GrantPetNameChange();
void GrantNameChange();
bool IsNameChangeAllowed();
bool ClearNameChange();
Lua_Expedition CreateExpedition(luabind::object expedition_info);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages);
+11 -1
View File
@@ -500,6 +500,9 @@ int main(int argc, char **argv)
}
Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect
Timer UpdateWhoTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // updates who list every 2 minutes
Timer WorldserverProcess(1000);
#ifdef EQPROFILE
#ifdef PROFILE_DUMP_TIME
Timer profile_dump_timer(PROFILE_DUMP_TIME * 1000);
@@ -615,6 +618,10 @@ int main(int argc, char **argv)
}
}
if (WorldserverProcess.Check()) {
worldserver.Process();
}
if (is_zone_loaded) {
{
entity_list.GroupProcess();
@@ -647,7 +654,10 @@ int main(int argc, char **argv)
InterserverTimer.Start();
database.ping();
content_db.ping();
entity_list.UpdateWho();
if (UpdateWhoTimer.Check()) {
UpdateWhoTimer.SetTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // in-case it was changed
entity_list.UpdateWho();
}
}
};
+40 -47
View File
@@ -131,7 +131,8 @@ Mob::Mob(
m_scan_close_mobs_timer(6000),
m_see_close_mobs_timer(1000),
m_mob_check_moving_timer(1000),
bot_attack_flag_timer(10000)
bot_attack_flag_timer(10000),
m_destroying(false)
{
mMovementManager = &MobMovementManager::Get();
mMovementManager->AddMob(this);
@@ -531,6 +532,8 @@ Mob::Mob(
Mob::~Mob()
{
m_destroying = true;
entity_list.RemoveMobFromCloseLists(this);
m_close_mobs.clear();
@@ -8672,56 +8675,54 @@ bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) {
bool Mob::DoLosChecks(Mob* other) {
if (!CheckLosFN(other) || !CheckWaterLoS(other)) {
if (CheckLosCheatExempt(other)) {
if (RuleB(Map, EnableLoSCheatExemptions) && CheckLosCheatExempt(other)) {
return true;
}
return false;
}
if (!CheckLosCheat(other)) {
if (RuleB(Map, CheckForDoorLoSCheat) && !CheckDoorLoSCheat(other)) {
return false;
}
return true;
}
bool Mob::CheckLosCheat(Mob* other) {
if (RuleB(Map, CheckForLoSCheat)) {
for (auto itr : entity_list.GetDoorsList()) {
Doors* d = itr.second;
bool Mob::CheckDoorLoSCheat(Mob* other) {
if (!other->IsOfClientBotMerc() && other->CastToNPC()->IsOnHatelist(this)) {
return true;
}
const std::string& zones_to_check = RuleS(Map, ZonesToCheckDoorCheat);
if (zones_to_check.empty()) {
return true;
}
const auto& v = Strings::Split(zones_to_check, ",");
if (zones_to_check == "all" || std::find(v.begin(), v.end(), std::to_string(zone->GetZoneID())) != v.end()) {
for (auto itr: entity_list.GetDoorsList()) {
Doors *d = itr.second;
if (
!d->IsDoorOpen() &&
(
d->GetKeyItem() ||
d->GetLockpick() ||
d->IsDoorOpen() ||
d->IsDoorBlacklisted() ||
d->GetNoKeyring() != 0 ||
d->GetDoorParam() > 0
d->GetNoKeyring() != 0
)
) {
// If the door is a trigger door, check if the trigger door is open
if (d->GetTriggerDoorID() > 0) {
auto td = entity_list.GetDoorsByDoorID(d->GetTriggerDoorID());
) {
float distance = Distance(m_Position, d->GetPosition());
if (td) {
if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) {
continue;
}
}
if (distance > RuleR(Map, RangeCheckForDoorLoSCheat) || !CheckLosFN(d->GetX(), d->GetY(), d->GetZ(), GetSize())) {
continue;
}
if (DistanceNoZ(GetPosition(), d->GetPosition()) <= 50) {
auto who_to_door = DistanceNoZ(GetPosition(), d->GetPosition());
auto other_to_door = DistanceNoZ(other->GetPosition(), d->GetPosition());
auto who_to_other = DistanceNoZ(GetPosition(), other->GetPosition());
auto distance_difference = who_to_other - (who_to_door + other_to_door);
if (distance_difference >= (-1 * RuleR(Maps, RangeCheckForLoSCheat)) && distance_difference <= RuleR(Maps, RangeCheckForLoSCheat)) {
return false;
}
if (d->IsDoorBetween(GetPosition(), other->GetPosition(), d->GetSize())) {
return false;
}
}
}
@@ -8730,26 +8731,18 @@ bool Mob::CheckLosCheat(Mob* other) {
return true;
}
bool Mob::CheckLosCheatExempt(Mob* other)
{
if (RuleB(Map, EnableLoSCheatExemptions)) {
/* This is an exmaple of how to configure exemptions for LoS checks.
glm::vec4 exempt_check_who;
glm::vec4 exempt_check_other;
bool Mob::CheckLosCheatExempt(Mob* other) {
glm::vec4 exempt_check_who;
switch (zone->GetZoneID()) {
case POEARTHB:
exempt_check_who.x = 2051; exempt_check_who.y = 407; exempt_check_who.z = -219; //Middle of councilman spawns
//exempt_check_other.x = 1455; exempt_check_other.y = 415; exempt_check_other.z = -242;
//check to be sure the player and the target are outside of the councilman area
//if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop)
if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) {
return true;
}
default:
return false;
}
*/
switch (zone->GetZoneID()) {
case Zones::POEARTHB:
exempt_check_who.x = 2053; exempt_check_who.y = 408; exempt_check_who.z = -219; //Middle of councilman spawns
//if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) --- 800 from center of council to furthest corner in cove
if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) {
return true;
}
default:
return false;
}
return false;
+4 -2
View File
@@ -798,7 +798,7 @@ public:
static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget);
virtual bool CheckWaterLoS(Mob* m);
bool CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ);
bool CheckLosCheat(Mob* other); //door skipping checks for LoS
bool CheckDoorLoSCheat(Mob* other); //door skipping checks for LoS
bool CheckLosCheatExempt(Mob* other); //exemptions to bypass los
bool DoLosChecks(Mob* other);
inline void SetLastLosState(bool value) { last_los_check = value; }
@@ -1125,7 +1125,7 @@ public:
virtual void SetAttackTimer();
inline void SetInvul(bool invul) { invulnerable=invul; }
inline bool GetInvul(void) { return invulnerable; }
inline bool GetInvul() { return invulnerable; }
void SetExtraHaste(int haste, bool need_to_save = true);
inline int GetExtraHaste() { return extra_haste; }
virtual int GetHaste();
@@ -1510,6 +1510,7 @@ public:
void ClearDataBucketCache();
bool IsGuildmaster() const;
bool IsDestroying() const { return m_destroying; }
protected:
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
@@ -1932,6 +1933,7 @@ private:
EQ::InventoryProfile m_inv;
std::shared_ptr<HealRotation> m_target_of_heal_rotation;
bool m_manual_follow;
bool m_destroying;
void SetHeroicStrBonuses(StatBonuses* n);
void SetHeroicStaBonuses(StatBonuses* n);
-35
View File
@@ -132,8 +132,6 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
),
attacked_timer(CombatEventTimer_expire),
swarm_timer(100),
m_corpse_queue_timer(1000),
m_corpse_queue_shutoff_timer(30000),
m_resumed_from_zone_suspend_shutoff_timer(10000),
classattack_timer(1000),
monkattack_timer(1000),
@@ -624,28 +622,6 @@ bool NPC::Process()
// zone state corpse creation timer
if (RuleB(Zone, StateSavingOnShutdown)) {
// creates a corpse if the NPC is queued for corpse creation
if (m_corpse_queue_timer.Check()) {
if (IsQueuedForCorpse()) {
auto decay_timer = m_corpse_decay_time;
uint16 corpse_id = GetID();
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
auto c = entity_list.GetCorpseByID(corpse_id);
if (c) {
c->UnLock();
c->SetDecayTimer(decay_timer);
}
}
m_corpse_queue_timer.Disable();
m_corpse_queue_shutoff_timer.Disable();
}
// shuts off the corpse queue timer if it is still running
if (m_corpse_queue_shutoff_timer.Check()) {
m_corpse_queue_timer.Disable();
m_corpse_queue_shutoff_timer.Disable();
}
// shuts off the temporary spawn protected state of the NPC
if (m_resumed_from_zone_suspend_shutoff_timer.Check()) {
m_resumed_from_zone_suspend_shutoff_timer.Disable();
@@ -654,17 +630,6 @@ bool NPC::Process()
}
if (tic_timer.Check()) {
if (RuleB(Zone, StateSavingOnShutdown) && IsQueuedForCorpse()) {
auto decay_timer = m_corpse_decay_time;
uint16 corpse_id = GetID();
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
auto c = entity_list.GetCorpseByID(corpse_id);
if (c) {
c->UnLock();
c->SetDecayTimer(decay_timer);
}
}
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) {
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
}
+6 -10
View File
@@ -603,10 +603,9 @@ public:
// zone state save
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
inline bool IsQueuedForCorpse() { return m_queued_for_corpse; }
inline uint32_t SetCorpseDecayTime(uint32_t decay_time) { return m_corpse_decay_time = decay_time; }
inline bool IsQueuedForCorpse() const { return m_queued_for_corpse; }
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; }
inline bool IsResumedFromZoneSuspend() { return m_resumed_from_zone_suspend; }
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
int i = 0;
@@ -658,15 +657,12 @@ protected:
LootItems m_loot_items;
// zone state
bool m_resumed_from_zone_suspend = false;
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
uint32_t m_corpse_decay_time = 0; // decay time set on zone state restore
Timer m_corpse_queue_timer = {}; // this is to check for corpse creation on zone state restore
Timer m_corpse_queue_shutoff_timer = {};
bool m_resumed_from_zone_suspend = false;
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
// this is a 30-second timer that protects a NPC from having double assignment of loot
// this is a timer that protects a NPC from having double assignment of loot
// this is to prevent a player from killing a NPC and then zoning out and back in to get loot again
// if loot was to be assigned via script again, this protects double assignment for 30 seconds
// if loot was to be assigned via script again, this protects double assignment for a short time
Timer m_resumed_from_zone_suspend_shutoff_timer = {};
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
+18
View File
@@ -3261,6 +3261,21 @@ std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string buck
return self->GetAccountBucketRemaining(bucket_name);
}
void Perl_Client_GrantNameChange(Client* self)
{
self->GrantNameChange();
}
bool Perl_Client_IsNameChangeAllowed(Client* self)
{
return self->IsNameChangeAllowed();
}
bool Perl_Client_ClearNameChange(Client* self)
{
return self->ClearNameChange();
}
std::string Perl_Client_GetBandolierName(Client* self, uint8 bandolier_slot)
{
return self->GetBandolierName(bandolier_slot);
@@ -3393,6 +3408,7 @@ void perl_register_client()
package.add("CashReward", &Perl_Client_CashReward);
package.add("ChangeLastName", &Perl_Client_ChangeLastName);
package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange);
package.add("ClearNameChange", (bool(*)(Client*))&Perl_Client_ClearNameChange);
package.add("CharacterID", &Perl_Client_CharacterID);
package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill);
package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill);
@@ -3607,6 +3623,7 @@ void perl_register_client()
package.add("GrantAllAAPoints", (void(*)(Client*, uint8, bool))&Perl_Client_GrantAllAAPoints);
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int))&Perl_Client_GrantAlternateAdvancementAbility);
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int, bool))&Perl_Client_GrantAlternateAdvancementAbility);
package.add("GrantNameChange", (void(*)(Client*))&Perl_Client_GrantNameChange);
package.add("GuildID", &Perl_Client_GuildID);
package.add("GuildRank", &Perl_Client_GuildRank);
package.add("HasAugmentEquippedByID", &Perl_Client_HasAugmentEquippedByID);
@@ -3637,6 +3654,7 @@ void perl_register_client()
package.add("IsInAGuild", &Perl_Client_IsInAGuild);
package.add("IsLD", &Perl_Client_IsLD);
package.add("IsMedding", &Perl_Client_IsMedding);
package.add("IsNameChangeAllowed", (bool(*)(Client*))&Perl_Client_IsNameChangeAllowed);
package.add("IsRaidGrouped", &Perl_Client_IsRaidGrouped);
package.add("IsSitting", &Perl_Client_IsSitting);
package.add("IsStanding", &Perl_Client_IsStanding);
+12 -3
View File
@@ -939,6 +939,12 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
Strings::FindReplace(npc_name, "`", "-");
const std::string& npc_id_and_name = fmt::format(
"{}_{}",
npc_name,
npc_id
);
const std::string& global_path = fmt::format(
"{}/{}",
path.GetQuestsPath(),
@@ -959,13 +965,16 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
);
std::vector<std::string> file_names = {
fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID ./quests/zone/v0/10.ext
fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name ./quests/zone/v0/npc.ext
fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID (./quests/zone/v0/10.ext)
fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name (./quests/zone/v0/name.ext)
fmt::format("{}/{}", zone_versioned_path, npc_id_and_name), // Local versioned by NPC ID and NPC Name (./quests/zone/v0/10_name.ext)
fmt::format("{}/{}", zone_path, npc_id), // Local by NPC ID
fmt::format("{}/{}", zone_path, npc_name), // Local by NPC Name
fmt::format("{}/{}", zone_path, npc_id_and_name), // Local by NPC ID and NPC Name
fmt::format("{}/{}", global_path, npc_id), // Global by NPC ID
fmt::format("{}/{}", global_path, npc_name), // Global by NPC ID
fmt::format("{}/default", zone_versioned_path), // Zone Default ./quests/zone/v0/default.ext
fmt::format("{}/{}", global_path, npc_id_and_name), // Global by NPC ID and NPC Name
fmt::format("{}/default", zone_versioned_path), // Zone Versioned Default (./quests/zone/v0/default.ext)
fmt::format("{}/default", zone_path), // Zone Default
fmt::format("{}/default", global_path), // Global Default
};
+3 -3
View File
@@ -1310,15 +1310,14 @@ void QuestManager::rename(std::string name) {
QuestManagerCurrentQuestVars();
if (initiator) {
std::string current_name = initiator->GetName();
if (initiator->ChangeFirstName(name.c_str(), current_name.c_str())) {
if (initiator->ChangeFirstName(name)) {
initiator->Message(
Chat::White,
fmt::format(
"Successfully renamed to {}, kicking to character select.",
"Successfully renamed to {}.",
name
).c_str()
);
initiator->Kick("Name was changed.");
} else {
initiator->Message(
Chat::Red,
@@ -3507,6 +3506,7 @@ void QuestManager::UpdateInstanceTimer(uint16 instance_id, uint32 new_duration)
e.duration = new_duration;
e.start_time = std::time(nullptr);
e.expire_at = e.start_time + e.duration;
const int updated = InstanceListRepository::UpdateOne(database, e);
+16 -5
View File
@@ -191,16 +191,20 @@ bool Spawn2::Process() {
return false;
}
uint16 condition_value=1;
uint16 condition_value = 1;
if (condition_id > 0) {
condition_value = zone->spawn_conditions.GetCondition(zone->GetShortName(), zone->GetInstanceID(), condition_id);
condition_value = zone->spawn_conditions.GetCondition(
zone->GetShortName(),
zone->GetInstanceID(),
condition_id
);
}
//have the spawn group pick an NPC for us
uint32 npcid = 0;
if (RuleB(Zone, StateSavingOnShutdown) && currentnpcid && currentnpcid > 0) {
npcid = currentnpcid;
if (m_resumed_npc_id > 0) {
npcid = m_resumed_npc_id;
m_resumed_npc_id = 0;
} else {
npcid = spawn_group->GetNPCType(condition_value);
}
@@ -277,6 +281,13 @@ bool Spawn2::Process() {
npcthis = npc;
if (!m_entity_variables.empty()) {
for (auto &var : m_entity_variables) {
npc->SetEntityVariable(var.first, var.second);
}
m_entity_variables = {};
}
npc->SetResumedFromZoneSuspend(m_resumed_from_zone_suspend);
m_resumed_from_zone_suspend = false;
+4
View File
@@ -77,6 +77,8 @@ public:
inline NPC *GetNPC() const { return npcthis; }
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; }
protected:
friend class Zone;
@@ -104,6 +106,8 @@ private:
bool IsDespawned;
uint32 killcount;
bool m_resumed_from_zone_suspend = false;
uint32 m_resumed_npc_id = 0;
std::map<std::string, std::string> m_entity_variables = {};
};
class SpawnCondition {
+22
View File
@@ -1894,6 +1894,13 @@ void Client::SellToBuyer(const EQApplicationPacket *app)
break;
}
if (sell_line.purchase_method == BarterInBazaar && buyer->IsThereACustomer()) {
auto customer = entity_list.GetClientByID(buyer->GetCustomerID());
if (customer) {
customer->CancelBuyerTradeWindow();
}
}
if (!DoBarterBuyerChecks(sell_line)) {
return;
};
@@ -3825,3 +3832,18 @@ bool Client::DoBarterSellerChecks(BuyerLineSellItem_Struct &sell_line)
return true;
}
void Client::CancelBuyerTradeWindow()
{
auto end_session = new EQApplicationPacket(OP_Barter, sizeof(BuyerRemoveItemFromMerchantWindow_Struct));
auto data = reinterpret_cast<BuyerRemoveItemFromMerchantWindow_Struct *>(end_session->pBuffer);
data->action = Barter_BuyerInspectBegin;
FastQueuePacket(&end_session);
}
void Client::CancelTraderTradeWindow()
{
auto end_session = new EQApplicationPacket(OP_ShopEnd);
FastQueuePacket(&end_session);
}
+18 -1
View File
@@ -81,7 +81,6 @@ WorldServer::WorldServer()
cur_groupid = 0;
last_groupid = 0;
oocmuted = false;
m_process_timer = std::make_unique<EQ::Timer>(1000, true, std::bind(&WorldServer::Process, this));
}
WorldServer::~WorldServer() {
@@ -95,6 +94,7 @@ void WorldServer::Process()
if (it->second.reload_at_unix < std::time(nullptr)) {
ProcessReload(it->second);
it = m_reload_queue.erase(it);
break;
} else {
++it;
}
@@ -3786,6 +3786,13 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
return;
}
if (trader_pc->IsThereACustomer()) {
auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID());
if (customer) {
customer->CancelTraderTradeWindow();
}
}
auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number);
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, sizeof(TraderBuy_Struct));
auto data = (TraderBuy_Struct *) outapp->pBuffer;
@@ -3981,6 +3988,12 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
worldserver.SendPacket(pack);
return;
}
if (buyer->IsThereACustomer()) {
auto customer = entity_list.GetClientByID(buyer->GetCustomerID());
if (customer) {
customer->CancelBuyerTradeWindow();
}
}
BuyerLineSellItem_Struct sell_line{};
sell_line.item_id = in->buy_item_id;
@@ -4586,6 +4599,10 @@ void WorldServer::ProcessReload(const ServerReload::Request& request)
zone->ReloadLootTables();
break;
case ServerReload::Type::Maps:
zone->ReloadMaps();
break;
case ServerReload::Type::Merchants:
entity_list.ReloadMerchants();
break;
-1
View File
@@ -81,7 +81,6 @@ private:
ZoneEventScheduler *m_zone_scheduler;
// server reload queue
std::unique_ptr<EQ::Timer> m_process_timer;
std::mutex m_reload_mutex = {};
std::map<int, ServerReload::Request> m_reload_queue = {};
public:
+8 -6
View File
@@ -887,10 +887,7 @@ void Zone::Shutdown(bool quiet)
c.second->WorldKick();
}
bool does_zone_have_entities =
zone && zone->IsLoaded() &&
(!entity_list.GetNPCList().empty() || !entity_list.GetCorpseList().empty());
if (RuleB(Zone, StateSavingOnShutdown) && does_zone_have_entities) {
if (RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
SaveZoneState();
}
@@ -1537,7 +1534,6 @@ bool Zone::Process() {
spawn_conditions.Process();
if (spawn2_timer.Check()) {
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
EQ::InventoryProfile::CleanDirty();
@@ -3288,5 +3284,11 @@ bool Zone::VariableExists(const std::string& variable_name)
return m_zone_variables.find(variable_name) != m_zone_variables.end();
}
#include "zone_save_state.cpp"
void Zone::ReloadMaps()
{
zonemap = Map::LoadMapFile(map_name);
watermap = WaterMap::LoadWaterMapfile(map_name);
pathing = IPathfinder::Load(map_name);
}
#include "zone_loot.cpp"
+1
View File
@@ -479,6 +479,7 @@ public:
);
void SaveZoneState();
static void ClearZoneState(uint32 zone_id, uint32 instance_id);
void ReloadMaps();
private:
bool allow_mercs;
+14 -8
View File
@@ -29,17 +29,23 @@ void ZoneCLI::CommandHandler(int argc, char **argv)
auto function_map = EQEmuCommand::function_map;
// Register commands
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
function_map["tests:databuckets"] = &ZoneCLI::DataBuckets;
function_map["tests:npc-handins"] = &ZoneCLI::NpcHandins;
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::NpcHandinsMultiQuest;
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
function_map["tests:databuckets"] = &ZoneCLI::TestDataBuckets;
function_map["tests:npc-handins"] = &ZoneCLI::TestNpcHandins;
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::TestNpcHandinsMultiQuest;
function_map["tests:zone-state"] = &ZoneCLI::TestZoneState;
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
}
#include "cli/databuckets.cpp"
// cli
#include "cli/benchmark_databuckets.cpp"
#include "cli/sidecar_serve_http.cpp"
#include "cli/npc_handins.cpp"
#include "cli/npc_handins_multiquest.cpp"
// tests
#include "cli/tests/_test_util.cpp"
#include "cli/tests/databuckets.cpp"
#include "cli/tests/npc_handins.cpp"
#include "cli/tests/npc_handins_multiquest.cpp"
#include "cli/tests/zone_state.cpp"
+5 -4
View File
@@ -1,6 +1,7 @@
#ifndef EQEMU_ZONE_CLI_H
#define EQEMU_ZONE_CLI_H
#include <iostream>
#include "../common/cli/argh.h"
class ZoneCLI {
@@ -11,10 +12,10 @@ public:
static bool RanConsoleCommand(int argc, char **argv);
static bool RanSidecarCommand(int argc, char **argv);
static bool RanTestCommand(int argc, char **argv);
static void DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description);
static void NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
static void NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description);
static void TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description);
static void TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
static void TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description);
static void TestZoneState(int argc, char **argv, argh::parser &cmd, std::string &description);
};
#endif //EQEMU_ZONE_CLI_H
+8 -4
View File
@@ -368,18 +368,22 @@ void Zone::LoadLootDrops(const std::vector<uint32> in_lootdrop_ids)
m_lootdrops.emplace_back(e);
// add lootdrop entries
for (const auto &f: lootdrop_entries) {
if (e.id == f.lootdrop_id) {
// add lootdrop entries
for (const auto &h: lootdrop_entries) {
if (e.id == h.lootdrop_id) {
// check if lootdrop entry already exists in memory
has_entry = false;
for (const auto &g: m_lootdrop_entries) {
if (f.lootdrop_id == g.lootdrop_id && f.item_id == g.item_id && f.multiplier == g.multiplier) {
for (const auto &i: m_lootdrop_entries) {
if (h.lootdrop_id == i.lootdrop_id && h.item_id == i.item_id) {
has_entry = true;
break;
}
}
if (!has_entry) {
m_lootdrop_entries.emplace_back(h);
}
}
}
}
+76 -85
View File
@@ -4,46 +4,7 @@
#include "npc.h"
#include "corpse.h"
#include "zone.h"
#include "../common/repositories/zone_state_spawns_repository.h"
#include "../common/repositories/spawn2_disabled_repository.h"
struct LootEntryStateData {
uint32 item_id;
uint32_t lootdrop_id;
uint16 charges = 0; // used in dynamically added loot (AddItem)
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(lootdrop_id),
CEREAL_NVP(charges)
);
}
};
struct LootStateData {
uint32 copper = 0;
uint32 silver = 0;
uint32 gold = 0;
uint32 platinum = 0;
std::vector<LootEntryStateData> entries = {};
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(copper),
CEREAL_NVP(silver),
CEREAL_NVP(gold),
CEREAL_NVP(platinum),
CEREAL_NVP(entries)
);
}
};
#include "zone_save_state.h"
// IsZoneStateValid checks if the zone state is valid
// if these fields are all empty or zero value for an entire zone state, it's considered invalid
@@ -204,6 +165,33 @@ inline std::string GetLootSerialized(Corpse *c)
return "";
}
inline std::map<std::string, std::string> GetVariablesDeserialized(const std::string &entity_variables)
{
std::map<std::string, std::string> deserialized_map;
if (entity_variables.empty()) {
return deserialized_map;
}
if (!Strings::IsValidJson(entity_variables)) {
LogZoneState("Invalid JSON data for entity variables");
return deserialized_map;
}
try {
std::stringstream ss;
{
ss << entity_variables;
cereal::JSONInputArchive ar(ss);
ar(deserialized_map);
}
} catch (const std::exception &e) {
LogZoneState("Failed to load entity variables [{}]", e.what());
}
return deserialized_map;
}
inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
{
if (!RuleB(Zone, StateSaveEntityVariables)) {
@@ -214,25 +202,7 @@ inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
return;
}
if (!Strings::IsValidJson(entity_variables)) {
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
return;
}
std::map<std::string, std::string> deserialized_map;
try {
std::istringstream is(entity_variables);
{
cereal::JSONInputArchive archive(is);
archive(deserialized_map);
}
}
catch (const std::exception &e) {
LogZoneState("Failed to load entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
return;
}
for (const auto &[key, value]: deserialized_map) {
for (const auto &[key, value]: GetVariablesDeserialized(entity_variables)) {
n->SetEntityVariable(key, value);
}
}
@@ -310,11 +280,32 @@ inline std::vector<uint32_t> GetLootdropIds(const std::vector<ZoneStateSpawnsRep
return lootdrop_ids;
}
inline void LoadNPCStatePreSpawn(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
{
LoadNPCEntityVariables(n, s.entity_variables);
}
inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
{
n->SetHP(s.hp);
n->SetMana(s.mana);
n->SetEndurance(s.endurance);
if (s.hp > 0) {
n->SetHP(s.hp);
}
if (s.mana > 0) {
n->SetMana(s.mana);
}
if (s.endurance > 0) {
n->SetEndurance(s.endurance);
}
// if these are zero for some reason, we need to reset the max hp
if (!s.is_corpse) {
if (s.hp == 0 || n->GetHP() == 0) {
n->SetMaxHP();
}
if (s.mana == 0 || n->GetMana() == 0) {
n->RestoreMana();
}
}
if (s.grid) {
n->AssignWaypoints(s.grid, s.current_waypoint);
@@ -323,20 +314,21 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
n->SetResumedFromZoneSuspend(false);
LoadLootStateData(zone, n, s.loot_data);
n->SetResumedFromZoneSuspend(true);
LoadNPCEntityVariables(n, s.entity_variables);
LoadNPCBuffs(n, s.buffs);
if (s.is_corpse) {
auto decay_time = s.decay_in_seconds * 1000;
if (decay_time > 0) {
n->SetQueuedToCorpse();
n->SetCorpseDecayTime(decay_time);
entity_list.RestoreCorpse(n, decay_time);
}
else {
n->Depop();
}
}
n->SetPosition(s.x, s.y, s.z);
n->SetHeading(s.heading);
n->SetResumedFromZoneSuspend(true);
}
@@ -422,12 +414,17 @@ bool Zone::LoadZoneState(
zone->initgrids_timer.Trigger();
zone->Process();
// load zone variables first
int count = 0;
for (auto &s: spawn_states) {
if (s.is_zone) {
LoadZoneVariables(zone, s.entity_variables);
continue;
break;
}
}
// spawn2
for (auto &s: spawn_states) {
if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) {
continue;
}
@@ -467,10 +464,13 @@ bool Zone::LoadZoneState(
);
if (spawn_time_left == 0) {
new_spawn->SetCurrentNPCID(s.npc_id);
new_spawn->SetResumedNPCID(s.npc_id);
new_spawn->SetResumedFromZoneSuspend(true);
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
}
count++;
spawn2_list.Insert(new_spawn);
new_spawn->Process();
auto n = new_spawn->GetNPC();
@@ -505,29 +505,13 @@ bool Zone::LoadZoneState(
npc->SetQueuedToCorpse();
}
LoadNPCStatePreSpawn(zone, npc, s);
entity_list.AddNPC(npc, true, true);
LoadNPCState(zone, npc, s);
}
// any NPC that is spawned by the spawn system
for (auto &e: entity_list.GetNPCList()) {
auto npc = e.second;
if (npc->GetSpawnGroupId() == 0) {
continue;
}
for (auto &s: spawn_states) {
bool is_same_npc =
s.npc_id == npc->GetNPCTypeID() &&
s.spawn2_id == npc->GetSpawnPointID() &&
s.spawngroup_id == npc->GetSpawnGroupId();
if (is_same_npc) {
LoadNPCState(zone, npc, s);
}
}
}
return !spawn_states.empty();
}
@@ -558,7 +542,7 @@ inline void SaveNPCState(NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
auto buffs = n->GetBuffs();
if (buffs) {
std::vector<Buffs_Struct> valid_buffs;
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) {
valid_buffs.push_back(buffs[index]);
}
@@ -600,6 +584,7 @@ void Zone::SaveZoneState()
std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> spawns = {};
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
iterator.Reset();
int count = 0;
while (iterator.MoreElements()) {
Spawn2 *sp = iterator.GetData();
auto s = ZoneStateSpawnsRepository::NewEntity();
@@ -629,6 +614,7 @@ void Zone::SaveZoneState()
spawns.emplace_back(s);
iterator.Advance();
count++;
}
// npc's that are not in the spawn2 list
@@ -704,6 +690,11 @@ void Zone::SaveZoneState()
return;
}
if (spawns.empty()) {
LogInfo("No zone state data to save");
return;
}
ZoneStateSpawnsRepository::InsertMany(database, spawns);
LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size()));
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <cereal/archives/json.hpp>
#include <cereal/types/map.hpp>
#include "npc.h"
#include "corpse.h"
#include "zone.h"
#include "../common/repositories/zone_state_spawns_repository.h"
#include "../common/repositories/spawn2_disabled_repository.h"
struct LootEntryStateData {
uint32 item_id = 0;
uint32_t lootdrop_id = 0;
uint16 charges = 0; // used in dynamically added loot (AddItem)
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(lootdrop_id),
CEREAL_NVP(charges)
);
}
};
struct LootStateData {
uint32 copper = 0;
uint32 silver = 0;
uint32 gold = 0;
uint32 platinum = 0;
std::vector<LootEntryStateData> entries = {};
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(copper),
CEREAL_NVP(silver),
CEREAL_NVP(gold),
CEREAL_NVP(platinum),
CEREAL_NVP(entries)
);
}
};
+1
View File
@@ -164,6 +164,7 @@ void ZoneDatabase::UpdateRespawnTime(uint32 spawn2_id, uint16 instance_id, uint3
.id = static_cast<int32_t>(spawn2_id),
.start = static_cast<int32_t>(current_time),
.duration = static_cast<int32_t>(time_left),
.expire_at = current_time + time_left,
.instance_id = static_cast<int16_t>(instance_id)
}
);