Compare commits

...

43 Commits

Author SHA1 Message Date
KayenEQ d18a30b047 root no break variable update
root no break variable update
2025-04-06 14:34:52 -04:00
KayenEQ 359f90a987 Root break update
Roots will not break from damage spells if spell skill is set to 200. Live mechanic.
2025-04-03 18:42:53 -04:00
Chris Miles 5babc864b9 [Fix] Regression in World SendEmoteMessageRaw (#4837) 2025-04-03 11:17:09 -05:00
Chris Miles 60a2dd8616 [Crash] Fix rarer exception crash issue in PlayerEventLogs::ProcessBatchQueue (#4835) 2025-04-02 19:22:40 -05:00
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
84 changed files with 2605 additions and 938 deletions
+86
View File
@@ -1,3 +1,89 @@
## [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 ## [23.3.4] 3/14/2025
### Fixes ### Fixes
+1
View File
@@ -141,6 +141,7 @@ public:
bool CheckInstanceExpired(uint16 instance_id); bool CheckInstanceExpired(uint16 instance_id);
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration); bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
bool GetUnusedInstanceID(uint16& instance_id); bool GetUnusedInstanceID(uint16& instance_id);
bool TryGetUnusedInstanceID(uint16& instance_id);
bool IsGlobalInstance(uint16 instance_id); bool IsGlobalInstance(uint16 instance_id);
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id); bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
bool RemoveClientsFromInstance(uint16 instance_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{ ManifestEntry{
.version = 9304, .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'", .check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'",
.condition = "empty", .condition = "empty",
.match = "", .match = "",
@@ -6914,7 +6914,7 @@ CREATE TABLE `zone_state_spawns` (
}, },
ManifestEntry{ ManifestEntry{
.version = 9308, .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'", .check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
.condition = "missing", .condition = "missing",
.match = "varchar(200)", .match = "varchar(200)",
@@ -6986,7 +6986,6 @@ ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
.match = "idx_zone_instance", .match = "idx_zone_instance",
.sql = R"( .sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id); 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 .content_schema_update = false
}, },
@@ -6998,6 +6997,93 @@ ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
.match = "", .match = "",
.sql = R"( .sql = R"(
TRUNCATE TABLE zone_state_spawns; 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 .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.version = version;
e.start_time = std::time(nullptr); e.start_time = std::time(nullptr);
e.duration = duration; 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) 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_reserved_instance_id = RuleI(Instances, ReservedInstances);
uint32 max_instance_id = 32000; uint32 max_instance_id = 32000;
@@ -537,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list<uint32> &ch
void Database::PurgeExpiredInstances() 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( auto l = InstanceListRepository::GetWhere(
*this, *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()) { if (l.empty()) {
return; return;
@@ -555,20 +577,24 @@ void Database::PurgeExpiredInstances()
instance_ids.emplace_back(std::to_string(e.id)); 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)); TransactionBegin();
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids)); InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids); SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids); CharacterCorpsesRepository::BuryInstances(*this, ids);
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); DynamicZoneMembersRepository::DeleteByManyInstances(*this, ids);
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids)); DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_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)) { 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) 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.start_time = std::time(nullptr);
i.duration = new_duration; i.duration = new_duration;
i.expire_at = i.start_time + i.duration;
InstanceListRepository::UpdateOne(*this, i); 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.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.duration = static_cast<int>(m_duration.count());
insert_instance.never_expires = m_never_expires; 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); auto instance = InstanceListRepository::ReplaceOne(GetDatabase(), insert_instance);
if (instance.id == 0) if (!instance)
{ {
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id); LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
return 0; return 0;
} }
m_instance_id = instance.id; m_instance_id = unused_instance_id;
return m_instance_id; return m_instance_id;
} }
+8
View File
@@ -181,9 +181,17 @@ void PlayerEventLogs::ProcessBatchQueue()
// Helper to deserialize event data // Helper to deserialize event data
auto Deserialize = [](const std::string &data, auto &out) { auto Deserialize = [](const std::string &data, auto &out) {
if (!Strings::IsValidJson(data)) {
return;
}
// cpp exceptions are terrible, don't ever use them
try {
std::stringstream ss(data); std::stringstream ss(data);
cereal::JSONInputArchive ar(ss); cereal::JSONInputArchive ar(ss);
out.serialize(ar); out.serialize(ar);
}
catch (const std::exception &e) {}
}; };
// Helper to assign ETL table ID // Helper to assign ETL table ID
+16 -8
View File
@@ -906,26 +906,34 @@ bool EQ::ItemInstance::IsSlotAllowed(int16 slot_id) const {
bool EQ::ItemInstance::IsDroppable(bool recurse) const bool EQ::ItemInstance::IsDroppable(bool recurse) const
{ {
if (!m_item) if (!m_item) {
return false; return false;
}
/*if (m_ornamentidfile) // not implemented /*if (m_ornamentidfile) // not implemented
return false;*/ return false;*/
if (m_attuned) if (m_attuned) {
return false; 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; return false;
}
if (recurse) { if (recurse) {
for (auto iter : m_contents) { for (auto iter: m_contents) {
if (!iter.second) if (!iter.second) {
continue; continue;
}
if (!iter.second->IsDroppable(recurse)) if (!iter.second->IsDroppable(recurse)) {
return false; return false;
} }
} }
}
return true; return true;
} }
@@ -25,6 +25,7 @@ public:
uint8_t is_global; uint8_t is_global;
uint32_t start_time; uint32_t start_time;
uint32_t duration; uint32_t duration;
uint64_t expire_at;
uint8_t never_expires; uint8_t never_expires;
std::string notes; std::string notes;
}; };
@@ -43,6 +44,7 @@ public:
"is_global", "is_global",
"start_time", "start_time",
"duration", "duration",
"expire_at",
"never_expires", "never_expires",
"notes", "notes",
}; };
@@ -57,6 +59,7 @@ public:
"is_global", "is_global",
"start_time", "start_time",
"duration", "duration",
"expire_at",
"never_expires", "never_expires",
"notes", "notes",
}; };
@@ -105,6 +108,7 @@ public:
e.is_global = 0; e.is_global = 0;
e.start_time = 0; e.start_time = 0;
e.duration = 0; e.duration = 0;
e.expire_at = 0;
e.never_expires = 0; e.never_expires = 0;
e.notes = ""; e.notes = "";
@@ -149,8 +153,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0; 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.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.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.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.notes = row[7] ? row[7] : ""; e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
return e; return e;
} }
@@ -189,8 +194,9 @@ public:
v.push_back(columns[3] + " = " + std::to_string(e.is_global)); 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[4] + " = " + std::to_string(e.start_time));
v.push_back(columns[5] + " = " + std::to_string(e.duration)); 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[6] + " = " + std::to_string(e.expire_at));
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'"); v.push_back(columns[7] + " = " + std::to_string(e.never_expires));
v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'");
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -218,6 +224,7 @@ public:
v.push_back(std::to_string(e.is_global)); 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.start_time));
v.push_back(std::to_string(e.duration)); 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(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'"); 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.is_global));
v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration)); 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(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'"); 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.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.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.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.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.notes = row[7] ? row[7] : ""; 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); 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.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.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.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.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.notes = row[7] ? row[7] : ""; 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); 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.is_global));
v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration)); 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(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'"); 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.is_global));
v.push_back(std::to_string(e.start_time)); v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration)); 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(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'"); v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -22,6 +22,7 @@ public:
int32_t id; int32_t id;
int32_t start; int32_t start;
int32_t duration; int32_t duration;
uint32_t expire_at;
int16_t instance_id; int16_t instance_id;
}; };
@@ -36,6 +37,7 @@ public:
"id", "id",
"start", "start",
"duration", "duration",
"expire_at",
"instance_id", "instance_id",
}; };
} }
@@ -46,6 +48,7 @@ public:
"id", "id",
"start", "start",
"duration", "duration",
"expire_at",
"instance_id", "instance_id",
}; };
} }
@@ -90,6 +93,7 @@ public:
e.id = 0; e.id = 0;
e.start = 0; e.start = 0;
e.duration = 0; e.duration = 0;
e.expire_at = 0;
e.instance_id = 0; e.instance_id = 0;
return e; return e;
@@ -130,7 +134,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; 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.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 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; return e;
} }
@@ -167,7 +172,8 @@ public:
v.push_back(columns[0] + " = " + std::to_string(e.id)); v.push_back(columns[0] + " = " + std::to_string(e.id));
v.push_back(columns[1] + " = " + std::to_string(e.start)); v.push_back(columns[1] + " = " + std::to_string(e.start));
v.push_back(columns[2] + " = " + std::to_string(e.duration)); 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( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -192,6 +198,7 @@ public:
v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start)); v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration)); 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)); v.push_back(std::to_string(e.instance_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
@@ -225,6 +232,7 @@ public:
v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start)); v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration)); 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)); v.push_back(std::to_string(e.instance_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); 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.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 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.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); all_entries.push_back(e);
} }
@@ -290,7 +299,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; 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.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 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); 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.id));
v.push_back(std::to_string(e.start)); v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration)); 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)); v.push_back(std::to_string(e.instance_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
@@ -394,6 +405,7 @@ public:
v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start)); v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration)); 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)); v.push_back(std::to_string(e.instance_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
+2 -40
View File
@@ -7,49 +7,11 @@
class InstanceListRepository: public BaseInstanceListRepository { class InstanceListRepository: public BaseInstanceListRepository {
public: 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) static int UpdateDuration(Database& db, uint16 instance_id, uint32_t new_duration)
{ {
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
"UPDATE `{}` SET `duration` = {} WHERE `{}` = {}", "UPDATE `{}` SET `duration` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}",
TableName(), TableName(),
new_duration, new_duration,
PrimaryKey(), PrimaryKey(),
@@ -65,7 +27,7 @@ public:
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
SQL( SQL(
SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}` SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
WHERE `id` = {} WHERE `id` = {}
), ),
TableName(), TableName(),
+6 -37
View File
@@ -8,47 +8,11 @@
class RespawnTimesRepository: public BaseRespawnTimesRepository { class RespawnTimesRepository: public BaseRespawnTimesRepository {
public: 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) static void ClearExpiredRespawnTimers(Database& db)
{ {
db.QueryDatabase( db.QueryDatabase(
fmt::format( fmt::format(
"DELETE FROM `{}` WHERE (`start` + `duration`) < UNIX_TIMESTAMP(NOW())", "DELETE FROM `{}` WHERE `expire_at` < UNIX_TIMESTAMP(NOW())",
TableName() TableName()
) )
); );
@@ -77,6 +41,11 @@ public:
return ((r.start + r.duration) - time_seconds); 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 #endif //EQEMU_RESPAWN_TIMES_REPOSITORY_H
+12 -9
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, 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, 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, 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, LeaveCorpses, true, "Setting whether you leave a corpse behind")
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items") 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") RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
@@ -261,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, 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_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, 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_END()
RULE_CATEGORY(Skills) RULE_CATEGORY(Skills)
@@ -290,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_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_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, 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_END()
RULE_CATEGORY(GM) RULE_CATEGORY(GM)
@@ -379,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_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_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_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_END()
RULE_CATEGORY(Map) RULE_CATEGORY(Map)
@@ -388,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_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_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_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, CheckForDoorLoSCheat, true, "Runs LoS checks to prevent cheating through doors.")
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.") RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check. Must modify source to create these.")
RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.") 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_END()
RULE_CATEGORY(Pathing) RULE_CATEGORY(Pathing)
@@ -814,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, 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, 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, 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, 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, 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%.") RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
@@ -825,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, 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, 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, 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, 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, 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.") RULE_INT(Bots, MezFailDelay, 1250, "1250 (1.25 sec) Default. Delay between failed Mez attempts.")
@@ -843,7 +846,7 @@ RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cas
RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel") 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, 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_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_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_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") RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots")
@@ -861,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, 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_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.") RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
RULE_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, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.") RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.") RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")
@@ -903,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_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, 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_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_END()
RULE_CATEGORY(Chat) RULE_CATEGORY(Chat)
@@ -1094,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_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_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, 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_END()
RULE_CATEGORY(Expedition) RULE_CATEGORY(Expedition)
+2
View File
@@ -22,6 +22,7 @@ namespace ServerReload {
LevelEXPMods, LevelEXPMods,
Logs, Logs,
Loot, Loot,
Maps,
Merchants, Merchants,
NPCEmotes, NPCEmotes,
NPCSpells, NPCSpells,
@@ -61,6 +62,7 @@ namespace ServerReload {
"Level EXP Mods", "Level EXP Mods",
"Logs", "Logs",
"Loot", "Loot",
"Maps",
"Merchants", "Merchants",
"NPC Emotes", "NPC Emotes",
"NPC Spells", "NPC Spells",
-12
View File
@@ -2808,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) { bool IsInstantHealSpell(uint32 spell_id) {
if (!IsValidSpell(spell_id)) { if (!IsValidSpell(spell_id)) {
return false; return false;
+6 -3
View File
@@ -246,6 +246,8 @@
#define MAX_INVISIBILTY_LEVEL 254 #define MAX_INVISIBILTY_LEVEL 254
#define NO_ROOT_BREAK_SKILL_ID 200 //Live mechanic where if skill id in a spell is set to 200 it will prevent that spell from having a chance to break roots.
//instrument item id's used as song components //instrument item id's used as song components
#define INSTRUMENT_HAND_DRUM 13000 #define INSTRUMENT_HAND_DRUM 13000
#define INSTRUMENT_WOODEN_FLUTE 13001 #define INSTRUMENT_WOODEN_FLUTE 13001
@@ -900,8 +902,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); 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 // Bot related functions
bool IsBotSpellTypeDetrimental (uint16 spell_type); bool IsBotSpellTypeDetrimental(uint16 spell_type);
bool IsBotSpellTypeBeneficial (uint16 spell_type); bool IsBotSpellTypeBeneficial(uint16 spell_type);
bool BotSpellTypeUsesTargetSettings(uint16 spell_type); bool BotSpellTypeUsesTargetSettings(uint16 spell_type);
bool IsBotSpellTypeInnate (uint16 spell_type); bool IsBotSpellTypeInnate (uint16 spell_type);
bool IsAEBotSpellType(uint16 spell_type); bool IsAEBotSpellType(uint16 spell_type);
@@ -917,6 +919,8 @@ bool IsCommandedBotSpellType(uint16 spell_type);
bool IsPullingBotSpellType(uint16 spell_type); bool IsPullingBotSpellType(uint16 spell_type);
uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id); uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id);
uint16 GetPetBotSpellType(uint16 spell_type); 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.. // These should not be used to determine spell category..
// They are a graphical affects (effects?) index only // They are a graphical affects (effects?) index only
@@ -1814,7 +1818,6 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id);
uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id); uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id);
bool IsBlankSpellEffect(uint16 spell_id, int effect_index); bool IsBlankSpellEffect(uint16 spell_id, int effect_index);
bool IsValidSpell(uint32 spell_id); bool IsValidSpell(uint32 spell_id);
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los = true);
bool IsSummonSpell(uint16 spell_id); bool IsSummonSpell(uint16 spell_id);
bool IsDamageSpell(uint16 spell_id); bool IsDamageSpell(uint16 spell_id);
bool IsAnyDamageSpell(uint16 spell_id); bool IsAnyDamageSpell(uint16 spell_id);
+28
View File
@@ -468,3 +468,31 @@ uint16 GetPetBotSpellType(uint16 spell_type) {
return 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; 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); } inline uint32 GetDuration() { return(timer_time); }
static const uint32 SetCurrentTime(); static const uint32 SetCurrentTime();
static const uint32 RollForward(uint32 seconds);
static const uint32 GetCurrentTime(); static const uint32 GetCurrentTime();
static const uint32 GetTimeSeconds(); static const uint32 GetTimeSeconds();
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables // Build variables
// these get injected during the build pipeline // these get injected during the build pipeline
#define CURRENT_VERSION "23.3.4-dev" // always append -dev to the current version for custom-builds #define CURRENT_VERSION "23.4.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0" #define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__ #define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__ #define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9314 #define CURRENT_BINARY_DATABASE_VERSION 9321
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#endif #endif
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "eqemu-server", "name": "eqemu-server",
"version": "23.3.4", "version": "23.4.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EQEmu/Server.git" "url": "https://github.com/EQEmu/Server.git"
+1 -1
View File
@@ -177,7 +177,7 @@ int main()
} }
if (player_event_process_timer.Check()) { 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; std::string Message;
char CountString[10]; char CountString[13];
while(iterator.MoreElements()) { while(iterator.MoreElements()) {
@@ -408,7 +408,7 @@ void ChatChannel::SendChannelMembers(Client *c) {
if(!c) return; if(!c) return;
char CountString[10]; char CountString[13];
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus())); sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
+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 2>&1 | tee test_output.log
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a 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: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 if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
echo "Error found in test output! Failing build." echo "Error found in test output! Failing build."
+69
View File
@@ -1850,3 +1850,72 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
} }
return guild_members; 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 CLCheckStale();
void CLEKeepAlive(uint32 numupdates, uint32* wid); void CLEKeepAlive(uint32 numupdates, uint32* wid);
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0); void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
std::vector<uint32_t> GetZoneServersWithGMs();
void UpdateClientGuild(uint32 char_id, uint32 guild_id); void UpdateClientGuild(uint32 char_id, uint32 guild_id);
bool IsAccountInGame(uint32 iLSID); bool IsAccountInGame(uint32 iLSID);
+5 -2
View File
@@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
m_expire_time = now + new_remaining; m_expire_time = now + new_remaining;
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time); m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
InstanceListRepository::UpdateDuration(database, InstanceListRepository::UpdateDuration(
GetInstanceID(), static_cast<uint32_t>(m_duration.count())); database,
GetInstanceID(),
static_cast<uint32_t>(m_duration.count())
);
SendZonesDurationUpdate(); // update zone caches and actual instance's timer 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_address"] = zone->GetCAddress();
row["client_local_address"] = zone->GetCLocalAddress(); row["client_local_address"] = zone->GetCLocalAddress();
row["client_port"] = zone->GetCPort(); row["client_port"] = zone->GetCPort();
row["compile_version"] = zone->GetCurrentVersion();
row["compile_date"] = zone->GetCompileDate();
row["compile_time"] = zone->GetCompileTime(); row["compile_time"] = zone->GetCompileTime();
row["id"] = zone->GetID(); row["id"] = zone->GetID();
row["instance_id"] = zone->GetInstanceID(); 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()) { if (player_event_logs.LoadDatabaseConnection()) {
player_event_logs.Init(); 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) { auto loop_fn = [&](EQ::Timer* t) {
Timer::SetCurrentTime(); 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()) { if (PurgeInstanceTimer.Check()) {
database.PurgeExpiredInstances(); database.PurgeExpiredInstances();
database.PurgeAllDeletedDataBuckets(); database.PurgeAllDeletedDataBuckets();
@@ -502,6 +506,8 @@ int main(int argc, char **argv)
EQ::EventLoop::Get().Run(); EQ::EventLoop::Get().Run();
event_log_processor.request_stop();
LogInfo("World main loop completed"); LogInfo("World main loop completed");
LogInfo("Shutting down zone connections (if any)"); LogInfo("Shutting down zone connections (if any)");
zoneserver_list.KillAll(); zoneserver_list.KillAll();
+20 -15
View File
@@ -50,7 +50,7 @@ void WorldGuildManager::SendGuildRefresh(uint32 guild_id, bool name, bool motd,
s->motd_change = motd; s->motd_change = motd;
s->rank_change = rank; s->rank_change = rank;
s->relation_change = relation; s->relation_change = relation;
zoneserver_list.SendPacket(pack); zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(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->guild_id = guild_id;
s->old_guild_id = old_guild_id; s->old_guild_id = old_guild_id;
s->char_id = charid; s->char_id = charid;
zoneserver_list.SendPacket(pack); zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack); safe_delete(pack);
} }
@@ -70,7 +70,7 @@ void WorldGuildManager::SendGuildDelete(uint32 guild_id) {
auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct)); auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct));
ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer; ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer;
s->guild_id = guild_id; s->guild_id = guild_id;
zoneserver_list.SendPacket(pack); zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack); safe_delete(pack);
} }
@@ -85,15 +85,14 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
ServerGuildRefresh_Struct *s = (ServerGuildRefresh_Struct *) pack->pBuffer; 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); 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. //preform a local refresh.
if(!RefreshGuild(s->guild_id)) { if(!RefreshGuild(s->guild_id)) {
LogGuilds("Unable to preform local refresh on guild [{}]", s->guild_id); BaseGuildManager::RefreshGuild(s->guild_id);
//can we do anything?
} }
//broadcast this packet to all zones.
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break; break;
} }
@@ -108,7 +107,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
//broadcast this update to any zone with a member in this guild. //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. //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; break;
} }
@@ -147,7 +146,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
auto s = (ServerGuildID_Struct *)pack->pBuffer; auto s = (ServerGuildID_Struct *)pack->pBuffer;
RefreshGuild(s->guild_id); RefreshGuild(s->guild_id);
zoneserver_list.SendPacket(pack); zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break; break;
} }
case ServerOP_GuildPermissionUpdate: case ServerOP_GuildPermissionUpdate:
@@ -179,7 +178,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
sg->function_value sg->function_value
); );
zoneserver_list.SendPacketToBootedZones(pack); zoneserver_list.SendPacketToZonesWithGuild(sg->guild_id, pack);
} }
else { else {
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.", 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,
rnc->rank_name rnc->rank_name
); );
zoneserver_list.SendPacketToBootedZones(pack); zoneserver_list.SendPacketToZonesWithGuild(rnc->guild_id, pack);
} }
else { else {
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.", LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
@@ -230,10 +229,10 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
case ServerOP_GuildChannel: case ServerOP_GuildChannel:
case ServerOP_GuildURL: case ServerOP_GuildURL:
case ServerOP_GuildMemberRemove: case ServerOP_GuildMemberRemove:
case ServerOP_GuildSendGuildList:
case ServerOP_GuildMembersList: case ServerOP_GuildMembersList:
{ {
zoneserver_list.SendPacketToBootedZones(pack); auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
break; break;
} }
case ServerOP_GuildMemberAdd: case ServerOP_GuildMemberAdd:
@@ -244,9 +243,15 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
BaseGuildManager::RefreshGuild(in->guild_id); BaseGuildManager::RefreshGuild(in->guild_id);
} }
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
break;
}
case ServerOP_GuildSendGuildList: {
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToBootedZones(pack); zoneserver_list.SendPacketToBootedZones(pack);
break; break;
} }
default: default:
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode); LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
break; break;
@@ -451,6 +456,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa
data->tribute_timer = time; data->tribute_timer = time;
data->trophy_timer = 0; data->trophy_timer = 0;
zoneserver_list.SendPacketToBootedZones(sp); zoneserver_list.SendPacketToZonesWithGuild(guild_id, sp);
safe_delete(sp) safe_delete(sp)
} }
+234 -270
View File
@@ -29,6 +29,10 @@
#include "../common/repositories/inventory_repository.h" #include "../common/repositories/inventory_repository.h"
#include "../common/repositories/criteria/content_filter_criteria.h" #include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/zone_store.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 database;
WorldDatabase content_db; WorldDatabase content_db;
@@ -50,187 +54,177 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
character_limit = 8; character_limit = 8;
} }
std::string character_list_query = fmt::format( auto characters = CharacterDataRepository::GetWhere(
SQL( database,
SELECT fmt::format(
`id`, "`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
`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, account_id,
character_limit character_limit
)
); );
auto results = database.QueryDatabase(character_list_query); size_t character_count = characters.size();
size_t character_count = results.RowCount(); if (characters.empty()) {
if (character_count == 0) {
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct)); *out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer; auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0; cs->CharCount = 0;
cs->TotalChars = character_limit; cs->TotalChars = character_limit;
return; 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); size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size); *out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
unsigned char *buff_ptr = (*out_app)->pBuffer; 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; cs->TotalChars = character_limit;
buff_ptr += sizeof(CharacterSelect_Struct); buff_ptr += sizeof(CharacterSelect_Struct);
for (auto row = results.begin(); row != results.end(); ++row) { for (auto &e: characters) {
CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr; auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp; PlayerProfile_Struct pp;
EQ::InventoryProfile inventory_profile; EQ::InventoryProfile inv;
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version)); pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
inventory_profile.SetInventoryVersion(client_version); inv.SetInventoryVersion(client_version);
inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support 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]); uint32 character_id = e.id;
uint8 has_home = 0; uint8 has_home = 0;
uint8 has_bind = 0; uint8 has_bind = 0;
memset(&pp, 0, sizeof(PlayerProfile_Struct)); memset(&pp, 0, sizeof(PlayerProfile_Struct));
memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name)); memset(cse->Name, 0, sizeof(cse->Name));
strcpy(p_character_select_entry_struct->Name, row[1]); strcpy(cse->Name, e.name.c_str());
p_character_select_entry_struct->Class = (uint8) Strings::ToUnsignedInt(row[4]); cse->Class = e.class_;
p_character_select_entry_struct->Race = (uint32) Strings::ToUnsignedInt(row[3]); cse->Race = e.race;
p_character_select_entry_struct->Level = (uint8) Strings::ToUnsignedInt(row[5]); cse->Level = e.level;
p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class; cse->ShroudClass = cse->Class;
p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race; cse->ShroudRace = cse->Race;
p_character_select_entry_struct->Zone = (uint16) Strings::ToUnsignedInt(row[19]); cse->Zone = e.zone_id;
p_character_select_entry_struct->Instance = 0; cse->Instance = 0;
p_character_select_entry_struct->Gender = (uint8) Strings::ToUnsignedInt(row[2]); cse->Gender = e.gender;
p_character_select_entry_struct->Face = (uint8) Strings::ToUnsignedInt(row[15]); cse->Face = e.face;
for (uint32 material_slot = 0; material_slot < EQ::textures::materialCount; material_slot++) { for (auto &s: cse->Equip) {
p_character_select_entry_struct->Equip[material_slot].Material = 0; s.Material = 0;
p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0; s.Unknown1 = 0;
p_character_select_entry_struct->Equip[material_slot].EliteModel = 0; s.EliteModel = 0;
p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0; s.HerosForgeModel = 0;
p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0; s.Unknown2 = 0;
p_character_select_entry_struct->Equip[material_slot].Color = 0; s.Color = 0;
} }
p_character_select_entry_struct->Unknown15 = 0xFF; cse->Unknown15 = 0xFF;
p_character_select_entry_struct->Unknown19 = 0xFF; cse->Unknown19 = 0xFF;
p_character_select_entry_struct->DrakkinTattoo = (uint32) Strings::ToInt(row[17]); cse->DrakkinTattoo = e.drakkin_tattoo;
p_character_select_entry_struct->DrakkinDetails = (uint32) Strings::ToInt(row[18]); cse->DrakkinDetails = e.drakkin_details;
p_character_select_entry_struct->Deity = (uint32) Strings::ToInt(row[6]); cse->Deity = e.deity;
p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below cse->PrimaryIDFile = 0; // Processed Below
p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below cse->SecondaryIDFile = 0; // Processed Below
p_character_select_entry_struct->HairColor = (uint8) Strings::ToInt(row[9]); cse->HairColor = e.hair_color;
p_character_select_entry_struct->BeardColor = (uint8) Strings::ToInt(row[10]); cse->BeardColor = e.beard_color;
p_character_select_entry_struct->EyeColor1 = (uint8) Strings::ToInt(row[11]); cse->EyeColor1 = e.eye_color_1;
p_character_select_entry_struct->EyeColor2 = (uint8) Strings::ToInt(row[12]); cse->EyeColor2 = e.eye_color_2;
p_character_select_entry_struct->HairStyle = (uint8) Strings::ToInt(row[13]); cse->HairStyle = e.hair_style;
p_character_select_entry_struct->Beard = (uint8) Strings::ToInt(row[14]); cse->Beard = e.beard;
p_character_select_entry_struct->GoHome = 0; // Processed Below cse->GoHome = 0; // Processed Below
p_character_select_entry_struct->Tutorial = 0; // Processed Below cse->Tutorial = 0; // Processed Below
p_character_select_entry_struct->DrakkinHeritage = (uint32) Strings::ToInt(row[16]); cse->DrakkinHeritage = e.drakkin_heritage;
p_character_select_entry_struct->Unknown1 = 0; cse->Unknown1 = 0;
p_character_select_entry_struct->Enabled = 1; cse->Enabled = 1;
p_character_select_entry_struct->LastLogin = (uint32) Strings::ToInt(row[7]); // RoF2 value: 1212696584 cse->LastLogin = e.last_login; // RoF2 value: 1212696584
p_character_select_entry_struct->Unknown2 = 0; cse->Unknown2 = 0;
if (RuleB(World, EnableReturnHomeButton)) { if (RuleB(World, EnableReturnHomeButton)) {
int now = time(nullptr); int now = time(nullptr);
if ((now - Strings::ToInt(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome)) if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
p_character_select_entry_struct->GoHome = 1; cse->GoHome = 1;
}
} }
if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial))) if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
p_character_select_entry_struct->Tutorial = 1; cse->Tutorial = 1;
}
/** // binds
* Bind int bind_count = 0;
*/ for (auto &bind : character_binds) {
character_list_query = fmt::format( if (bind.id != e.id) {
SQL( continue;
SELECT }
`zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`
FROM if (bind.slot == 4) {
`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; has_home = 1;
// If our bind count is less than 5, we need to actually make use of this data so lets parse it pp.binds[4].zone_id = bind.zone_id;
if (bind_count < 5) { pp.binds[4].instance_id = bind.instance_id;
pp.binds[4].zone_id = Strings::ToInt(row_b[0]); pp.binds[4].x = bind.x;
pp.binds[4].instance_id = Strings::ToInt(row_b[1]); pp.binds[4].y = bind.y;
pp.binds[4].x = Strings::ToFloat(row_b[2]); pp.binds[4].z = bind.z;
pp.binds[4].y = Strings::ToFloat(row_b[3]); pp.binds[4].heading = bind.heading;
pp.binds[4].z = Strings::ToFloat(row_b[4]);
pp.binds[4].heading = Strings::ToFloat(row_b[5]);
}
} }
if (row_b[6] && Strings::ToInt(row_b[6]) == 0) if (bind.slot == 0) {
has_bind = 1; has_bind = 1;
} }
if (has_home == 0 || has_bind == 0) { bind_count++;
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()
);
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 (has_home == 0 || has_bind == 0) {
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()
)
);
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); auto z = GetZone(pp.binds[4].zone_id);
if (z) { if (z) {
pp.binds[4].x = z->safe_x; 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].z = z->safe_z;
pp.binds[4].heading = z->safe_heading; pp.binds[4].heading = z->safe_heading;
} }
} } else {
/* Otherwise, use the zone and coordinates given */ pp.binds[4].zone_id = start_zones[0].zone_id;
else { pp.binds[4].x = start_zones[0].x;
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[0]); pp.binds[4].y = start_zones[0].y;
float x = Strings::ToFloat(row_d[2]); pp.binds[4].z = start_zones[0].z;
float y = Strings::ToFloat(row_d[3]); pp.binds[4].heading = start_zones[0].heading;
float z = Strings::ToFloat(row_d[4]);
float heading = Strings::ToFloat(row_d[5]); if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
if (x == 0 && y == 0 && z == 0 && heading == 0) {
auto zone = GetZone(pp.binds[4].zone_id); auto zone = GetZone(pp.binds[4].zone_id);
if (zone) { if (zone) {
x = zone->safe_x; pp.binds[4].x = zone->safe_x;
y = zone->safe_y; pp.binds[4].y = zone->safe_y;
z = zone->safe_z; pp.binds[4].z = zone->safe_z;
heading = zone->safe_heading; 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;
} }
} }
else {
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
}
pp.binds[0] = pp.binds[4]; pp.binds[0] = pp.binds[4];
/* If no home bind set, set it */
// If we don't have home set, set it
if (has_home == 0) { if (has_home == 0) {
std::string query = fmt::format( auto bind = CharacterBindRepository::NewEntity();
SQL( bind.id = character_id;
REPLACE INTO bind.zone_id = pp.binds[4].zone_id;
`character_bind` bind.instance_id = 0;
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`) bind.x = pp.binds[4].x;
VALUES ({}, {}, {}, {}, {}, {}, {}, {}) bind.y = pp.binds[4].y;
), bind.z = pp.binds[4].z;
character_id, bind.heading = pp.binds[4].heading;
pp.binds[4].zone_id, bind.slot = 4;
0, CharacterBindRepository::ReplaceOne(*this, bind);
pp.binds[4].x,
pp.binds[4].y,
pp.binds[4].z,
pp.binds[4].heading,
4
);
auto results_bset = QueryDatabase(query);
} }
/* If no regular bind set, set it */
// If we don't have regular bind set, set it
if (has_bind == 0) { if (has_bind == 0) {
std::string query = fmt::format( auto bind = CharacterBindRepository::NewEntity();
SQL( bind.id = character_id;
REPLACE INTO bind.zone_id = pp.binds[0].zone_id;
`character_bind` bind.instance_id = 0;
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`) bind.x = pp.binds[0].x;
VALUES ({}, {}, {}, {}, {}, {}, {}, {}) bind.y = pp.binds[0].y;
), bind.z = pp.binds[0].z;
character_id, bind.heading = pp.binds[0].heading;
pp.binds[0].zone_id, bind.slot = 0;
0, CharacterBindRepository::ReplaceOne(*this, bind);
pp.binds[0].x,
pp.binds[0].y,
pp.binds[0].z,
pp.binds[0].heading,
0
);
auto results_bset = QueryDatabase(query);
} }
} }
/* 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) { if (bind_count < 5) {
// we know that home and main bind must be valid here, so we don't check those // 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. // 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++) { 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; 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);
}
} }
character_list_query = fmt::format( auto bind = CharacterBindRepository::NewEntity();
SQL(
SELECT bind.slot = i;
`slot`, `red`, `green`, `blue`, `use_tint`, `color` bind.id = character_id;
FROM bind.zone_id = pp.binds[4].zone_id;
`character_material` bind.instance_id = 0;
WHERE bind.x = pp.binds[4].x;
`id` = {} bind.y = pp.binds[4].y;
), bind.z = pp.binds[4].z;
character_id bind.heading = pp.binds[4].heading;
); binds.emplace_back(bind);
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]);
} }
if (GetCharSelInventory(account_id, p_character_select_entry_struct->Name, &inventory_profile)) { CharacterBindRepository::ReplaceMany(*this, binds);
}
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(inventories, e, &inv)) {
const EQ::ItemData *item = nullptr; const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr; const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0; int16 inventory_slot = 0;
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) { for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot); inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
if (inventory_slot == INVALID_INDEX) { continue; } if (inventory_slot == INVALID_INDEX) { continue; }
inst = inventory_profile.GetItem(inventory_slot); inst = inv.GetItem(inventory_slot);
if (inst == nullptr) if (inst == nullptr) {
continue; continue;
}
item = inst->GetItem(); item = inst->GetItem();
if (item == nullptr) if (item == nullptr) {
continue; continue;
}
if (matslot > 6) { if (matslot > 6) {
uint32 item_id_file = 0; uint32 item_id_file = 0;
// Weapon Models // Weapon Models
if (inst->GetOrnamentationIDFile() != 0) { if (inst->GetOrnamentationIDFile() != 0) {
item_id_file = inst->GetOrnamentationIDFile(); item_id_file = inst->GetOrnamentationIDFile();
p_character_select_entry_struct->Equip[matslot].Material = item_id_file; cse->Equip[matslot].Material = item_id_file;
} else { }
else {
if (strlen(item->IDFile) > 2) { if (strlen(item->IDFile) > 2) {
item_id_file = Strings::ToInt(&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) { if (matslot == EQ::textures::weaponPrimary) {
p_character_select_entry_struct->PrimaryIDFile = item_id_file; cse->PrimaryIDFile = item_id_file;
} else {
p_character_select_entry_struct->SecondaryIDFile = item_id_file;
} }
} else { else {
cse->SecondaryIDFile = item_id_file;
}
}
else {
// Armor Materials/Models // Armor Materials/Models
uint32 color = ( uint32 color = (
pp.item_tint.Slot[matslot].UseTint ? pp.item_tint.Slot[matslot].UseTint ?
pp.item_tint.Slot[matslot].Color : pp.item_tint.Slot[matslot].Color :
inst->GetColor() inst->GetColor()
); );
p_character_select_entry_struct->Equip[matslot].Material = item->Material; cse->Equip[matslot].Material = item->Material;
p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial; cse->Equip[matslot].EliteModel = item->EliteMaterial;
p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot); cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
p_character_select_entry_struct->Equip[matslot].Color = color; 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); buff_ptr += sizeof(CharacterSelectEntry_Struct);
} }
@@ -849,34 +826,21 @@ bool WorldDatabase::LoadCharacterCreateCombos()
return true; return true;
} }
// this is a slightly modified version of SharedDatabase::GetInventory(...) for character select use-only bool WorldDatabase::GetCharSelInventory(
bool WorldDatabase::GetCharSelInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv) 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; return false;
} }
const uint32 character_id = GetCharacterID(name); for (const auto& e : inventories) {
if (e.character_id != character.id) {
if (!character_id) { continue;
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) { switch (e.slot_id) {
case EQ::invslot::slotFace: case EQ::invslot::slotFace:
case EQ::invslot::slotEar2: case EQ::invslot::slotEar2:
+7 -1
View File
@@ -20,6 +20,8 @@
#include "../common/shareddb.h" #include "../common/shareddb.h"
#include "../common/eq_packet.h" #include "../common/eq_packet.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/character_data_repository.h"
struct PlayerProfile_Struct; struct PlayerProfile_Struct;
struct CharCreate_Struct; struct CharCreate_Struct;
@@ -43,7 +45,11 @@ private:
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc); void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
void SetSoFDefaultStartZone(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; extern WorldDatabase database;
+65 -10
View File
@@ -36,11 +36,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "shared_task_manager.h" #include "shared_task_manager.h"
#include "dynamic_zone_manager.h" #include "dynamic_zone_manager.h"
#include "ucs.h" #include "ucs.h"
#include "clientlist.h"
extern uint32 numzones; extern uint32 numzones;
extern EQ::Random emu_random; extern EQ::Random emu_random;
extern WebInterfaceList web_interface; extern WebInterfaceList web_interface;
extern SharedTaskManager shared_task_manager; extern SharedTaskManager shared_task_manager;
extern ClientList client_list;
volatile bool UCSServerAvailable_ = false; volatile bool UCSServerAvailable_ = false;
void CatchSignal(int sig_num); 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); 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) { void ZSList::SendEmoteMessageRaw(
if (!message) const char *to,
uint32 to_guilddbid,
int16 to_minstatus,
uint32 type,
const char *message
)
{
if (!message) {
return; return;
}
auto pack = new ServerPacket; auto pack = new ServerPacket;
pack->opcode = ServerOP_EmoteMessage; pack->opcode = ServerOP_EmoteMessage;
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1; pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
pack->pBuffer = new uchar[pack->size]; pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size); memset(pack->pBuffer, 0, pack->size);
ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*)pack->pBuffer; ServerEmoteMessage_Struct *sem = (ServerEmoteMessage_Struct *) pack->pBuffer;
if (to) { if (to) {
strcpy((char *)sem->to, to); strcpy((char *) sem->to, to);
} }
else { else {
sem->to[0] = 0; sem->to[0] = 0;
@@ -537,21 +547,36 @@ void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_m
sem->minstatus = to_minstatus; sem->minstatus = to_minstatus;
sem->type = type; sem->type = type;
strcpy(&sem->message[0], message); strcpy(&sem->message[0], message);
char tempto[64] = { 0 }; char tempto[64] = {0};
if (to) if (to) {
strn0cpy(tempto, to, 64); strn0cpy(tempto, to, 64);
}
if (tempto[0] == 0) { if (tempto[0] == 0) {
if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
} else {
SendPacket(pack); SendPacket(pack);
} }
}
else { else {
ZoneServer* zs = FindByName(to); ZoneServer *zs = FindByName(to);
if (zs) {
if (zs != 0)
zs->SendPacket(pack); zs->SendPacket(pack);
else }
else if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
}
else {
SendPacket(pack); SendPacket(pack);
} }
}
delete pack; delete pack;
} }
@@ -871,6 +896,34 @@ bool ZSList::SendPacketToBootedZones(ServerPacket* pack)
return true; 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)
{
auto servers = client_list.GetZoneServersWithGMs();
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;
}
void ZSList::SendServerReload(ServerReload::Type type, uchar *packet) void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
{ {
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request)); static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
@@ -894,6 +947,8 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
ServerReload::Type::Commands, ServerReload::Type::Commands,
ServerReload::Type::PerlExportSettings, ServerReload::Type::PerlExportSettings,
ServerReload::Type::DataBucketsCache, ServerReload::Type::DataBucketsCache,
ServerReload::Type::Quests,
ServerReload::Type::QuestsTimerReset,
ServerReload::Type::WorldRepop, ServerReload::Type::WorldRepop,
ServerReload::Type::WorldWithRespawn ServerReload::Type::WorldWithRespawn
}; };
+2
View File
@@ -29,6 +29,8 @@ public:
bool SendPacket(ServerPacket *pack); bool SendPacket(ServerPacket *pack);
bool SendPacket(uint32 zoneid, ServerPacket *pack); bool SendPacket(uint32 zoneid, ServerPacket *pack);
bool SendPacket(uint32 zoneid, uint16 instanceid, 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 SendPacketToBootedZones(ServerPacket* pack);
bool SetLockedZone(uint16 iZoneID, bool iLock); bool SetLockedZone(uint16 iZoneID, bool iLock);
+15 -7
View File
@@ -571,8 +571,14 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
); );
} }
} }
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); zoneserver_list.SendPacket(pack);
} }
}
break; break;
} }
@@ -729,13 +735,15 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
zs = zoneserver_list.FindByID(s->zone_server_id); zs = zoneserver_list.FindByID(s->zone_server_id);
} else if (s->zone_id) { } else if (s->zone_id) {
zs = zoneserver_list.FindByName(ZoneName(s->zone_id)); zs = zoneserver_list.FindByName(ZoneName(s->zone_id));
} else if (s->instance_id) {
zs = zoneserver_list.FindByInstanceID(s->instance_id);
} else { } else {
zoneserver_list.SendEmoteMessage( zoneserver_list.SendEmoteMessage(
s->admin_name, s->admin_name,
0, 0,
AccountStatus::Player, AccountStatus::Player,
Chat::White, 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(); guild->tribute.timer.Disable();
zoneserver_list.SendPacketToBootedZones(pack); zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
} }
break; 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.UpdateDbGuildFavor(data->guild_id, data->favor);
guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining); guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining);
zoneserver_list.SendPacketToBootedZones(pack); zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
} }
break; break;
} }
@@ -1587,7 +1595,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
data->time_remaining = in->time_remaining; data->time_remaining = in->time_remaining;
strn0cpy(data->player_name, in->player_name, sizeof(data->player_name)); 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); safe_delete(out);
} }
break; 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->tribute_id_2_tier = guild->tribute.id_2_tier;
out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id); out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
zoneserver_list.SendPacketToBootedZones(sp); zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
safe_delete(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->tribute_timer = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
out->trophy_timer = 0; out->trophy_timer = 0;
zoneserver_list.SendPacketToBootedZones(sp); zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
safe_delete(sp); safe_delete(sp);
} }
@@ -1654,7 +1662,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->member_time = in->member_time; out->member_time = in->member_time;
strn0cpy(out->player_name, in->player_name, sizeof(out->player_name)); 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) safe_delete(sp)
} }
break; break;
+2
View File
@@ -53,6 +53,8 @@ public:
inline const char* GetZoneName() const { return zone_name; } inline const char* GetZoneName() const { return zone_name; }
inline const char* GetZoneLongName() const { return long_name; } inline const char* GetZoneLongName() const { return long_name; }
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
inline std::string GetCompileDate() const { return COMPILE_DATE; }
const char* GetCompileTime() const{ return compiled; } const char* GetCompileTime() const{ return compiled; }
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); } void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
inline uint32 GetZoneID() const { return zone_server_zone_id; } inline uint32 GetZoneID() const { return zone_server_zone_id; }
+2
View File
@@ -173,6 +173,7 @@ SET(zone_sources
zone_event_scheduler.cpp zone_event_scheduler.cpp
zone_npc_factions.cpp zone_npc_factions.cpp
zone_reload.cpp zone_reload.cpp
zone_save_state.cpp
zoning.cpp zoning.cpp
) )
@@ -292,6 +293,7 @@ SET(zone_headers
zonedump.h zonedump.h
zone_cli.h zone_cli.h
zone_reload.h zone_reload.h
zone_save_state.h
zone_cli.cpp) zone_cli.cpp)
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers}) ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
+9 -1
View File
@@ -2507,6 +2507,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
return false; 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()) { if (IsMultiQuestEnabled()) {
for (auto &i: m_hand_in.items) { for (auto &i: m_hand_in.items) {
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) { if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
@@ -4433,9 +4439,10 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
if (IsValidSpell(spell_id) && !iBuffTic) { if (IsValidSpell(spell_id) && !iBuffTic) {
//see if root will break //see if root will break
if (IsRooted() && !FromDamageShield) // neotoyko: only spells cancel root if (IsRooted() && !FromDamageShield && spells[spell_id].skill != NO_ROOT_BREAK_SKILL_ID) { // neotoyko: only spells cancel root
TryRootFadeByDamage(buffslot, attacker); TryRootFadeByDamage(buffslot, attacker);
} }
}
else if (!IsValidSpell(spell_id)) else if (!IsValidSpell(spell_id))
{ {
//increment chances of interrupting //increment chances of interrupting
@@ -6217,6 +6224,7 @@ bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) {
- If multiple roots on target, always and only checks first root slot and if broken only removes that slots root. - If multiple roots on target, always and only checks first root slot and if broken only removes that slots root.
- Only roots on determental spells can be broken by damage. - Only roots on determental spells can be broken by damage.
- Root break chance values obtained from live parses. - Root break chance values obtained from live parses.
- If casting skill is set to 200 then spell can not break root
*/ */
if (!attacker || !spellbonuses.Root[SBIndex::ROOT_EXISTS] || spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] < 0) { if (!attacker || !spellbonuses.Root[SBIndex::ROOT_EXISTS] || spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] < 0) {
+54 -68
View File
@@ -2287,7 +2287,7 @@ void Bot::AI_Process()
// COMBAT RANGE CALCS // COMBAT RANGE CALCS
bool front_mob = InFrontMob(tar, GetX(), GetY()); bool front_mob = InFrontMob(tar, GetX(), GetY());
bool behind_mob = BehindMob(tar, GetX(), GetY()); bool behind_mob = BehindMob(tar, GetX(), GetY());
uint8 stop_melee_level = GetStopMeleeLevel(); bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
tar_distance = sqrt(tar_distance); // sqrt this for future calculations tar_distance = sqrt(tar_distance); // sqrt this for future calculations
// Item variables // Item variables
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary); const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
@@ -2314,7 +2314,7 @@ void Bot::AI_Process()
if (PULLING_BOT || RETURNING_BOT) { if (PULLING_BOT || RETURNING_BOT) {
if (!TargetValidation(tar)) { return; } if (!TargetValidation(tar)) { return; }
if (!DoLosChecks(tar)) { if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
return; return;
} }
@@ -2442,7 +2442,7 @@ void Bot::AI_Process()
ranged_timer.Start(); ranged_timer.Start();
} }
else if (!IsBotRanged() && GetLevel() < stop_melee_level) { else if (!IsBotRanged() && !stop_melee_level) {
if ( if (
IsTaunting() || IsTaunting() ||
!GetMaxMeleeRange() || !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_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield(); bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage(); 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 if (IsTaunting()) { // Taunting bots
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier); 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_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range); o.melee_distance = std::min(max_distance, desired_range);
} }
else if (is_stop_melee_level) { // Casters else if (input.stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged(); float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2)); o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
@@ -3176,14 +3175,17 @@ bool Bot::IsValidTarget(
return false; return false;
} }
SetHasLoS(DoLosChecks(tar));
bool invalid_target_state = false; bool invalid_target_state = false;
if (HOLDING || if (HOLDING ||
!tar->IsNPC() || !tar->IsNPC() ||
(tar->IsMezzed() && !HasBotAttackFlag(tar)) || (tar->IsMezzed() && !HasBotAttackFlag(tar)) ||
(!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) || (!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) ||
lo_distance > leash_distance || lo_distance > leash_distance ||
tar_distance > leash_distance || tar_distance > leash_distance ||
(!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) || (!GetAttackingFlag() && !HasLoS()) ||
!IsAttackAllowed(tar) !IsAttackAllowed(tar)
) { ) {
invalid_target_state = true; invalid_target_state = true;
@@ -9534,7 +9536,14 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
return false; 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)); LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id));
return false; return false;
} }
@@ -10590,6 +10599,8 @@ BotSpell Bot::GetSpellByHealType(uint16 spell_type, Mob* tar) {
return GetBestBotSpellForHealOverTime(this, tar, spell_type); return GetBestBotSpellForHealOverTime(this, tar, spell_type);
case BotSpellTypes::GroupHoTHeals: case BotSpellTypes::GroupHoTHeals:
return GetBestBotSpellForGroupHealOverTime(this, tar, spell_type); return GetBestBotSpellForGroupHealOverTime(this, tar, spell_type);
default:
return BotSpell(); // Return an empty BotSpell if no valid spell type is found
} }
} }
@@ -10641,7 +10652,7 @@ int Bot::GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 s
case BotSettingCategories::SpellTypeAnnounceCast: case BotSettingCategories::SpellTypeAnnounceCast:
return GetDefaultSpellTypeAnnounceCast(setting_type, stance); return GetDefaultSpellTypeAnnounceCast(setting_type, stance);
default: default:
break; return 0; // Default return value for unrecognized categories
} }
} }
@@ -10680,7 +10691,7 @@ int Bot::GetSetting(uint16 setting_category, uint16 setting_type) {
case BotSettingCategories::SpellTypeAnnounceCast: case BotSettingCategories::SpellTypeAnnounceCast:
return GetSpellTypeAnnounceCast(setting_type); return GetSpellTypeAnnounceCast(setting_type);
default: default:
break; return 0; // Default return value for unrecognized categories
} }
} }
@@ -11407,7 +11418,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) {
return false; return false;
} }
if (!DoLosChecks(tar)) { if (!HasLoS() && !DoLosChecks(tar)) {
return false; return false;
} }
@@ -12020,21 +12031,26 @@ void Bot::DoCombatPositioning(
bool behind_mob, bool behind_mob,
bool front_mob bool front_mob
) { ) {
if (HasTargetReflection()) { if (!tar->IsFeared()) {
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range bool is_too_close = tar_distance < melee_distance_min;
if (tar_distance <= melee_distance_max) { bool los_adjust = !HasRequiredLoSForPositioning(tar);
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), false)) {
RunToGoalWithJitter(Goal);
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; return;
} }
} }
} }
else if ( else {
tar_distance < melee_distance_min || if (IsTaunting()) { // Taunting adjustments
(!front_mob && IsTaunting()) bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
) { // 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()))) { if (taunting_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
return; return;
@@ -12042,52 +12058,26 @@ void Bot::DoCombatPositioning(
} }
} }
else { else {
if (!tar->IsFeared()) { if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
if ( bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
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; if (enraged_adjust) {
}
}
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)) { if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
return; return;
} }
} }
if (IsTaunting()) { // Taunting adjustments
Mob* mob_tar = tar->GetTarget();
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();
RunToGoalWithJitter(Goal);
return;
} }
else { // Otherwise, stick to any other bots that are taunting else { // Regular adjustments
if ( bool regular_adjust =
mob_tar->IsBot() && is_too_close ||
mob_tar->CastToBot()->IsTaunting() && los_adjust ||
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate)) (!GetBehindMob() && !front_mob) ||
) { (GetBehindMob() && !behind_mob);
Goal = mob_tar->GetPosition();
if (regular_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
return; return;
@@ -12099,8 +12089,6 @@ void Bot::DoCombatPositioning(
} }
DoFaceCheckNoJitter(tar); 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) { 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) {
@@ -12197,7 +12185,11 @@ bool Bot::RequiresLoSForPositioning() {
} }
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { 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; return true;
} }
} }
@@ -12210,7 +12202,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) {
return true; return true;
} }
if (RequiresLoSForPositioning() && !DoLosChecks(tar)) { if (RequiresLoSForPositioning() && !HasLoS()) {
return false; return false;
} }
@@ -12225,10 +12217,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
for (auto& close_mob : caster->m_close_mobs) { for (auto& close_mob : caster->m_close_mobs) {
Mob* m = close_mob.second; Mob* m = close_mob.second;
if (tar == m) {
continue;
}
switch (spell_type) { switch (spell_type) {
case BotSpellTypes::AELull: case BotSpellTypes::AELull:
if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) {
@@ -12320,8 +12308,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
return false; return false;
} }
SetHasLoS(true);
return true; return true;
} }
+1 -1
View File
@@ -235,7 +235,7 @@ static std::map<uint16, std::string> botSubType_names = {
struct CombatRangeInput { struct CombatRangeInput {
Mob* target; Mob* target;
float target_distance; float target_distance;
uint8 stop_melee_level; bool stop_melee_level;
const EQ::ItemInstance* p_item; const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item; const EQ::ItemInstance* s_item;
}; };
+1 -1
View File
@@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
return; 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."); c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return; return;
} }
+7
View File
@@ -525,6 +525,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
continue; 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)) { if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) {
continue; continue;
} }
@@ -543,6 +546,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
tar = bot_iter; 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 (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) {
if (!first_found) { if (!first_found) {
first_found = bot_iter; first_found = bot_iter;
@@ -556,6 +562,7 @@ void bot_command_cast(Client* c, const Seperator* sep)
} }
else { else {
bot_iter->SetCommandedSpell(true); 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 (bot_iter->AICastSpell(new_tar, 100, spell_type, sub_target_type, sub_type)) {
if (!first_found) { if (!first_found) {
+5 -1
View File
@@ -197,7 +197,11 @@ void bot_command_depart(Client* c, const Seperator* sep)
bot_iter->SetCommandedSpell(true); 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; continue;
} }
+1 -1
View File
@@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep)
return; 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."); c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return; return;
+1 -1
View File
@@ -48,7 +48,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
return; 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."); c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return; return;
+87 -69
View File
@@ -41,7 +41,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
return false; return false;
} }
if (chance < 100 && zone->random.Int(0, 100) > chance) { if (
!IsCommandedSpell() &&
zone->random.Int(0, 100) > chance
) {
return false; 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.SpellIndex = 0;
bot_spell.ManaCost = 0; bot_spell.ManaCost = 0;
if (BotSpellTypeRequiresLoS(spell_type) && tar != this) { if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) {
SetHasLoS(DoLosChecks(tar));
}
else {
SetHasLoS(true); 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); 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) { 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; 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)); std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) { 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; 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)); Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type));
if (!add_mob) { if (!add_mob) {
return false; continue;
} }
tar = add_mob; 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); 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; 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); 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; 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) { 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) { if (spell_type == BotSpellTypes::Stun || spell_type == BotSpellTypes::AEStun) {
uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal)); uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal));
@@ -435,24 +454,24 @@ 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); 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; 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); 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); 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)); std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) { for (const auto& s : bot_spell_list) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { if (!IsValidSpell(s.SpellId)) {
continue; 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 // Allow bots to cast buff spells even if they are out of mana
if ( if (
RuleB(Bots, FinishBuffing) && RuleB(Bots, FinishBuffing) &&
manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff manaCost > hasMana &&
!IsEngaged() &&
IsBotBuffSpellType(AIBot_spells[i].type)
) { ) {
SetMana(manaCost); 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; 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); std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (auto bot_spell_list_itr : bot_spell_list) { 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 ( if (
IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) { IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId; 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); std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (auto bot_spell_list_itr : bot_spell_list) { 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)) { 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.SpellId = bot_spell_list_itr.SpellId;
result.SpellIndex = bot_spell_list_itr.SpellIndex; 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); std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime);
for (auto bot_spell_list_itr : bot_spell_list) { 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)) { 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.SpellId = bot_spell_list_itr.SpellId;
result.SpellIndex = bot_spell_list_itr.SpellIndex; 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); 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) { 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)) { 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.SpellId = bot_spell_list_itr->SpellId;
result.SpellIndex = bot_spell_list_itr->SpellIndex; 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); 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) { 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)) { 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.SpellId = bot_spell_list_itr->SpellId;
result.SpellIndex = bot_spell_list_itr->SpellIndex; 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); 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) { 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)) { if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = 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); 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) { 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)) { if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = 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); 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) { 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)) { if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = 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); 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) { 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 ( if (
IsMesmerizeSpell(bot_spell_list_itr->SpellId) && IsMesmerizeSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(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()) { if (caster && caster->GetOwner()) {
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range); int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
int spell_ae_range = caster->GetAOERange(spell_id); int spell_ae_range = caster->GetAOERange(spell_id);
int buff_count = 0; bool is_pbae_spell = IsPBAESpell(spell_id);
NPC* npc = nullptr; NPC* npc = nullptr;
for (auto& close_mob : caster->m_close_mobs) { for (auto& close_mob : caster->m_close_mobs) {
buff_count = 0;
npc = close_mob.second->CastToNPC(); npc = close_mob.second->CastToNPC();
if (!npc) { if (!npc) {
@@ -1448,30 +1479,30 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue; 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) { if (AE) {
int target_count = 0; int target_count = 0;
for (auto& close_mob : caster->m_close_mobs) { for (auto& close_mob : caster->m_close_mobs) {
Mob* m = close_mob.second; Mob* m = close_mob.second;
if (npc == m) {
continue;
}
if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) { if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) {
continue; continue;
} }
if (IsPBAESpell(spell_id)) { if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) {
if (spell_ae_range < Distance(caster->GetPosition(), m->GetPosition())) {
continue; continue;
} }
}
else {
if (spell_range < Distance(m->GetPosition(), npc->GetPosition())) {
continue;
}
}
if (caster->CastChecks(spell_id, m, spell_type, true, true)) { if (caster->CastChecks(spell_id, m, spell_type, true, true)) {
++target_count; ++target_count;
@@ -1486,11 +1517,6 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue; continue;
} }
if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) {
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezFailDelay));
return result;
}
result = npc; result = npc;
} }
else { else {
@@ -1502,18 +1528,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue; continue;
} }
if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) {
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezAEFailDelay));
return result;
}
result = npc; result = npc;
} }
if (result) { if (result) {
caster->SetHasLoS(true);
return result; return result;
} }
} }
@@ -1534,7 +1552,6 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) {
std::string pet_type = GetBotMagicianPetType(caster); 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) { 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 ( if (
IsSummonPetSpell(bot_spell_list_itr->SpellId) && IsSummonPetSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(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); 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) { 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 (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)) { if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
continue; continue;
@@ -1729,7 +1745,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
continue; continue;
} }
if ( if (
caster->IsCommandedSpell() || caster->IsCommandedSpell() ||
!AE || !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) 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 (IsStunSpell(bot_spell_list_itr->SpellId)) {
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) { if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
continue; continue;
@@ -1830,7 +1844,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
bool spell_selected = false; 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) { 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)) { if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) {
continue; continue;
} }
@@ -1887,8 +1900,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
if (!spell_selected) { 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) { 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->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) {
if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) { if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) {
spell_selected = true; 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; 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); const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) { 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; continue;
} }
@@ -2091,7 +2110,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
case BotSpellTypes::AERains: case BotSpellTypes::AERains:
case BotSpellTypes::AEStun: case BotSpellTypes::AEStun:
case BotSpellTypes::AESnare: case BotSpellTypes::AESnare:
case BotSpellTypes::AEMez:
case BotSpellTypes::AESlow: case BotSpellTypes::AESlow:
case BotSpellTypes::AEDebuff: case BotSpellTypes::AEDebuff:
case BotSpellTypes::AEFear: case BotSpellTypes::AEFear:
@@ -2157,6 +2175,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
case BotSpellTypes::PetVeryFastHeals: case BotSpellTypes::PetVeryFastHeals:
case BotSpellTypes::PetHoTHeals: case BotSpellTypes::PetHoTHeals:
return RuleI(Bots, PercentChanceToCastHeal); return RuleI(Bots, PercentChanceToCastHeal);
case BotSpellTypes::AEMez:
return RuleI(Bots, PercentChanceToCastAEMez);
default: default:
return RuleI(Bots, PercentChanceToCastOtherType); 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); 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) { 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 ( if (
IsResurrectSpell(bot_spell_list_itr->SpellId) && IsResurrectSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(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); 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) { 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 ( if (
IsCharmSpell(bot_spell_list_itr->SpellId) && IsCharmSpell(bot_spell_list_itr->SpellId) &&
caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_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/http/httplib.h"
#include "../../common/eqemu_logsys.h" #include "../../common/eqemu_logsys.h"
#include "../../common/platform.h" #include "../../common/platform.h"
#include "../zone.h" #include "../../zone.h"
#include "../client.h" #include "../../client.h"
#include "../../common/net/eqstream.h" #include "../../common/net/eqstream.h"
extern Zone *zone; extern Zone *zone;
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual) void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
{
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)
{ {
if (cmd[{"-h", "--help"}]) { if (cmd[{"-h", "--help"}]) {
return; return;
@@ -1,8 +1,8 @@
#include "../../common/http/httplib.h" #include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h" #include "../../common/eqemu_logsys.h"
#include "../../common/platform.h" #include "../../common/platform.h"
#include "../zone.h" #include "../../zone.h"
#include "../client.h" #include "../../client.h"
#include "../../common/net/eqstream.h" #include "../../common/net/eqstream.h"
#include "../../common/json/json.hpp" #include "../../common/json/json.hpp"
@@ -36,19 +36,6 @@ struct TestCase {
bool handin_check_result; 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) void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{ {
if (expected == actual) { if (expected == actual) {
@@ -75,7 +62,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
return j.dump(); 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"}]) { if (cmd[{"-h", "--help"}]) {
return; return;
@@ -1,13 +1,13 @@
#include "../../common/http/httplib.h" #include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h" #include "../../common/eqemu_logsys.h"
#include "../../common/platform.h" #include "../../common/platform.h"
#include "../zone.h" #include "../../zone.h"
#include "../client.h" #include "../../client.h"
#include "../../common/net/eqstream.h" #include "../../common/net/eqstream.h"
extern Zone *zone; 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"}]) { if (cmd[{"-h", "--help"}]) {
return; return;
File diff suppressed because it is too large Load Diff
+4
View File
@@ -882,9 +882,13 @@ void Client::SendZoneInPackets()
//SendGuildMembers(); //SendGuildMembers();
SendGuildURL(); SendGuildURL();
SendGuildChannel(); SendGuildChannel();
if (RuleB(Guild, EnableLFGuild)) {
SendGuildLFGuildStatus(); SendGuildLFGuildStatus();
} }
}
if (RuleB(Guild, EnableLFGuild)) {
SendLFGuildStatus(); SendLFGuildStatus();
}
//No idea why live sends this if even were not in a guild //No idea why live sends this if even were not in a guild
SendGuildMOTD(); SendGuildMOTD();
+28 -26
View File
@@ -120,48 +120,46 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
} }
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ","); const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
if (!zones_list.empty()) {
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
if (it != zones_list.end()) {
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ","); const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
int i = 0;
for (const auto& result : zones_list) { if (zones_list.size() == zones_limits_list.size()) {
try { try {
if ( auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
std::stoul(result) == zone->GetZoneID() &&
std::stoul(zones_limits_list[i]) < bot_spawn_limit
) {
bot_spawn_limit = std::stoul(zones_limits_list[i]);
break; 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_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
if (!zones_forced_list.empty()) {
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
if (it != zones_forced_list.end()) {
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ","); const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
i = 0;
for (const auto& result : zones_forced_list) { if (zones_forced_list.size() == zones_forced_limits_list.size()) {
try { try {
if ( auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
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]);
break; 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); return GetDefaultSpellTypeMinThreshold(bot_setting);
case BotSettingCategories::SpellMaxThreshold: case BotSettingCategories::SpellMaxThreshold:
return GetDefaultSpellTypeMaxThreshold(bot_setting); 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); return GetSpellTypeMinThreshold(bot_setting);
case BotSettingCategories::SpellMaxThreshold: case BotSettingCategories::SpellMaxThreshold:
return GetSpellTypeMaxThreshold(bot_setting); return GetSpellTypeMaxThreshold(bot_setting);
default:
return 0; // default return for any unsupported setting type
} }
} }
+9 -5
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 "../common/repositories/character_stats_record_repository.h"
#include "dialogue_window.h" #include "dialogue_window.h"
#include "../common/rulesys.h" #include "../common/rulesys.h"
#include "../common/repositories/adventure_members_repository.h"
extern QueryServ* QServ; extern QueryServ* QServ;
extern Zone* zone; extern Zone* zone;
@@ -913,11 +914,14 @@ void Client::CompleteConnect()
SendDynamicZoneUpdates(); SendDynamicZoneUpdates();
/** Request adventure info **/ // Request adventure info
auto members = AdventureMembersRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
if (!members.empty()) {
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64); auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
strcpy((char*)pack->pBuffer, GetName()); strcpy((char*)pack->pBuffer, GetName());
worldserver.SendPacket(pack); worldserver.SendPacket(pack);
delete pack; delete pack;
}
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) { if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) {
EQApplicationPacket *outapp = MakeBuffsPacket(false); EQApplicationPacket *outapp = MakeBuffsPacket(false);
@@ -7980,7 +7984,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
} }
SetGuildID(new_guild_id); SetGuildID(new_guild_id);
SendGuildList(); UpdateWho();
guild_mgr.MemberAdd(new_guild_id, CharacterID(), GetLevel(), GetClass(), GUILD_LEADER, GetZoneID(), GetName()); 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.SendGuildRefresh(new_guild_id, true, true, true, true);
guild_mgr.SendToWorldSendGuildList(); guild_mgr.SendToWorldSendGuildList();
@@ -8149,7 +8153,7 @@ void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app)
if (!invitee) { if (!invitee) {
Message( Message(
Chat::Red, 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 gc->othername
); );
return; return;
@@ -11062,7 +11066,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
if (!target) if (!target)
break; break;
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) { if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) {
mypet->SayString(this, NOT_LEGAL_TARGET); mypet->SayString(this, NOT_LEGAL_TARGET);
break; break;
} }
@@ -11130,7 +11134,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
break; break;
} }
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) { if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) {
mypet->SayString(this, NOT_LEGAL_TARGET); mypet->SayString(this, NOT_LEGAL_TARGET);
break; break;
} }
+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("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("zoneinstance", "[Instance ID] [X] [Y] [Z] - Teleport to specified Instance by ID (coordinates are optional)", AccountStatus::Guide, command_zone_instance) ||
command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) || command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
command_add("zoneshutdown", "[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_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
) { ) {
command_deinit(); 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_kick(Client *c, const Seperator *sep);
void command_killallnpcs(Client *c, const Seperator *sep); void command_killallnpcs(Client *c, const Seperator *sep);
void command_kill(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_list(Client *c, const Seperator *sep);
void command_lootsim(Client *c, const Seperator *sep); void command_lootsim(Client *c, const Seperator *sep);
void command_load_shared_memory(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_nukebuffs(Client *c, const Seperator *sep);
void command_nukeitem(Client *c, const Seperator *sep); void command_nukeitem(Client *c, const Seperator *sep);
void command_object(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_parcels(Client *c, const Seperator *sep);
void command_path(Client *c, const Seperator *sep); void command_path(Client *c, const Seperator *sep);
void command_peqzone(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_picklock(Client *c, const Seperator *sep);
void command_profanity(Client *c, const Seperator *sep); void command_profanity(Client *c, const Seperator *sep);
void command_push(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_raidloot(Client* c, const Seperator* sep);
void command_randomfeatures(Client *c, const Seperator *sep); void command_randomfeatures(Client *c, const Seperator *sep);
void command_refreshgroup(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_zone_shard(Client *c, const Seperator *sep);
void command_zonebootup(Client *c, const Seperator *sep); void command_zonebootup(Client *c, const Seperator *sep);
void command_zoneshutdown(Client *c, const Seperator *sep); void command_zoneshutdown(Client *c, const Seperator *sep);
void command_zopp(Client *c, const Seperator *sep);
void command_zsafecoords(Client *c, const Seperator *sep);
void command_zsave(Client *c, const Seperator *sep); void command_zsave(Client *c, const Seperator *sep);
#include "bot.h" #include "bot.h"
+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_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
m_loot_cooldown_timer.SetTimer(10); m_loot_cooldown_timer.SetTimer(10);
m_check_rezzable_timer.SetTimer(1000); 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(); m_corpse_rezzable_timer.Disable();
SetRezTimer(true); SetRezTimer(true);
@@ -583,7 +583,7 @@ Corpse::Corpse(
m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer)); m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer));
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS)); m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
m_loot_cooldown_timer.SetTimer(10); 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_check_rezzable_timer.SetTimer(1000);
m_corpse_rezzable_timer.Disable(); m_corpse_rezzable_timer.Disable();
+68
View File
@@ -35,6 +35,9 @@
#include <string.h> #include <string.h>
#include <glm/ext/matrix_transform.hpp>
#include <numbers>
#define OPEN_DOOR 0x02 #define OPEN_DOOR 0x02
#define CLOSE_DOOR 0x03 #define CLOSE_DOOR 0x03
#define OPEN_INVDOOR 0x03 #define OPEN_INVDOOR 0x03
@@ -970,3 +973,68 @@ bool Doors::GetIsDoorBlacklisted()
bool Doors::IsDoorBlacklisted() { bool Doors::IsDoorBlacklisted() {
return m_is_blacklisted_to_open; 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 IsDestinationZoneSame() const;
bool IsDoorBlacklisted(); 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; } 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) { for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
NPC *pnpc = it->second; NPC *pnpc = it->second;
if (pnpc) { if (pnpc) {
Mob *own = pnpc->GetOwner(); Mob *own = pnpc->GetOwner();
//do not depop player's pets... //do not depop player/bot pets...
if (own && own->IsClient()) if (own && own->IsOfClientBot()) {
continue; continue;
}
if (pnpc->IsHorse()) if (pnpc->IsHorse()) {
continue; continue;
}
if (pnpc->IsFindable()) if (pnpc->IsFindable()) {
UpdateFindableNPCState(pnpc, true); UpdateFindableNPCState(pnpc, true);
}
pnpc->WipeHateList(); pnpc->WipeHateList();
pnpc->Depop(StartSpawnTimer); pnpc->Depop(StartSpawnTimer);
} }
} }
@@ -5988,3 +5991,14 @@ void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
return; 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 SendMerchantEnd(Mob* merchant);
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false); void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
void RestoreCorpse(NPC* npc, uint32_t decay_time);
protected: protected:
friend class Zone; 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 // edit menu
if (arg1 == "edit") { if (arg1 == "edit") {
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); 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 setinvertstate [0|1] | Sets selected door invert state");
c->Message(Chat::White, "#door setincline <incline> | Sets selected door incline"); 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, "#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( c->Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
+2
View File
@@ -49,6 +49,7 @@
#include "show/zone_loot.cpp" #include "show/zone_loot.cpp"
#include "show/zone_points.cpp" #include "show/zone_points.cpp"
#include "show/zone_status.cpp" #include "show/zone_status.cpp"
#include "show/zone_variables.cpp"
void command_show(Client *c, const Seperator *sep) 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_loot", .u = "zone_loot", .fn = ShowZoneLoot, .a = {"#viewzoneloot"}},
Cmd{.cmd = "zone_points", .u = "zone_points", .fn = ShowZonePoints, .a = {"#showzonepoints"}}, 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_status", .u = "zone_status", .fn = ShowZoneStatus, .a = {"#zonestatus"}},
Cmd{.cmd = "zone_variables", .u = "zone_variables", .fn = ShowZoneVariables},
}; };
// Check for arguments // 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)) 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( popup_table += DialogueWindow::TableRow(
DialogueWindow::TableCell("Experience Multiplier") + DialogueWindow::TableCell("Experience Multiplier") +
DialogueWindow::TableCell( 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.");
}
}
+45 -3
View File
@@ -6,8 +6,18 @@ extern WorldServer worldserver;
void command_zoneshutdown(Client *c, const Seperator *sep) void command_zoneshutdown(Client *c, const Seperator *sep)
{ {
const int arguments = sep->argnum; const int arguments = sep->argnum;
if (!arguments) { if (arguments < 2) {
c->Message(Chat::White, "Usage: #zoneshutdown [Zone ID|Zone Short Name]"); 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; return;
} }
@@ -16,7 +26,27 @@ void command_zoneshutdown(Client *c, const Seperator *sep)
return; 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 (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) { if (!zone_id) {
c->Message( c->Message(
@@ -29,10 +59,22 @@ void command_zoneshutdown(Client *c, const Seperator *sep)
return; 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 pack = new ServerPacket(ServerOP_ZoneShutdown, sizeof(ServerZoneStateChange_Struct));
auto *s = (ServerZoneStateChange_Struct *) pack->pBuffer; 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)); strn0cpy(s->admin_name, c->GetName(), sizeof(s->admin_name));
+1 -1
View File
@@ -195,7 +195,7 @@ void Client::SendGuildList()
std::stringstream ss; std::stringstream ss;
cereal::BinaryOutputArchive ar(ss); cereal::BinaryOutputArchive ar(ss);
ar(guilds_list); { ar(guilds_list); }
uint32 packet_size = ss.str().length(); uint32 packet_size = ss.str().length();
+10
View File
@@ -500,6 +500,9 @@ int main(int argc, char **argv)
} }
Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect 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 EQPROFILE
#ifdef PROFILE_DUMP_TIME #ifdef PROFILE_DUMP_TIME
Timer profile_dump_timer(PROFILE_DUMP_TIME * 1000); 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) { if (is_zone_loaded) {
{ {
entity_list.GroupProcess(); entity_list.GroupProcess();
@@ -647,8 +654,11 @@ int main(int argc, char **argv)
InterserverTimer.Start(); InterserverTimer.Start();
database.ping(); database.ping();
content_db.ping(); content_db.ping();
if (UpdateWhoTimer.Check()) {
UpdateWhoTimer.SetTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // in-case it was changed
entity_list.UpdateWho(); entity_list.UpdateWho();
} }
}
}; };
EQ::Timer process_timer(loop_fn); EQ::Timer process_timer(loop_fn);
+26 -36
View File
@@ -8675,85 +8675,75 @@ bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) {
bool Mob::DoLosChecks(Mob* other) { bool Mob::DoLosChecks(Mob* other) {
if (!CheckLosFN(other) || !CheckWaterLoS(other)) { if (!CheckLosFN(other) || !CheckWaterLoS(other)) {
if (CheckLosCheatExempt(other)) { if (RuleB(Map, EnableLoSCheatExemptions) && CheckLosCheatExempt(other)) {
return true; return true;
} }
return false; return false;
} }
if (!CheckLosCheat(other)) { if (RuleB(Map, CheckForDoorLoSCheat) && !CheckDoorLoSCheat(other)) {
return false; return false;
} }
return true; return true;
} }
bool Mob::CheckLosCheat(Mob* other) { bool Mob::CheckDoorLoSCheat(Mob* other) {
if (RuleB(Map, CheckForLoSCheat)) { if (!other->IsOfClientBotMerc() && other->CastToNPC()->IsOnHatelist(this)) {
for (auto itr : entity_list.GetDoorsList()) { return true;
Doors* d = itr.second; }
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 ( if (
!d->IsDoorOpen() && !d->IsDoorOpen() &&
( (
d->GetKeyItem() || d->GetKeyItem() ||
d->GetLockpick() || d->GetLockpick() ||
d->IsDoorOpen() ||
d->IsDoorBlacklisted() || d->IsDoorBlacklisted() ||
d->GetNoKeyring() != 0 || d->GetNoKeyring() != 0
d->GetDoorParam() > 0
) )
) { ) {
// If the door is a trigger door, check if the trigger door is open float distance = Distance(m_Position, d->GetPosition());
if (d->GetTriggerDoorID() > 0) {
auto td = entity_list.GetDoorsByDoorID(d->GetTriggerDoorID());
if (td) { if (distance > RuleR(Map, RangeCheckForDoorLoSCheat) || !CheckLosFN(d->GetX(), d->GetY(), d->GetZ(), GetSize())) {
if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) {
continue; continue;
} }
}
}
if (DistanceNoZ(GetPosition(), d->GetPosition()) <= 50) { if (d->IsDoorBetween(GetPosition(), other->GetPosition(), d->GetSize())) {
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; return false;
} }
} }
} }
} }
}
return true; return true;
} }
bool Mob::CheckLosCheatExempt(Mob* other) 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_who;
glm::vec4 exempt_check_other;
switch (zone->GetZoneID()) { switch (zone->GetZoneID()) {
case POEARTHB: case Zones::POEARTHB:
exempt_check_who.x = 2051; exempt_check_who.y = 407; exempt_check_who.z = -219; //Middle of councilman spawns exempt_check_who.x = 2053; exempt_check_who.y = 408; exempt_check_who.z = -219; //Middle of councilman spawns
//exempt_check_other.x = 1455; exempt_check_other.y = 415; exempt_check_other.z = -242; //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
//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) { if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) {
return true; return true;
} }
default: default:
return false; return false;
} }
*/
}
return false; return false;
} }
+2 -2
View File
@@ -798,7 +798,7 @@ public:
static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget); static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget);
virtual bool CheckWaterLoS(Mob* m); virtual bool CheckWaterLoS(Mob* m);
bool CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ); 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 CheckLosCheatExempt(Mob* other); //exemptions to bypass los
bool DoLosChecks(Mob* other); bool DoLosChecks(Mob* other);
inline void SetLastLosState(bool value) { last_los_check = value; } inline void SetLastLosState(bool value) { last_los_check = value; }
@@ -1125,7 +1125,7 @@ public:
virtual void SetAttackTimer(); virtual void SetAttackTimer();
inline void SetInvul(bool invul) { invulnerable=invul; } 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); void SetExtraHaste(int haste, bool need_to_save = true);
inline int GetExtraHaste() { return extra_haste; } inline int GetExtraHaste() { return extra_haste; }
virtual int GetHaste(); virtual int GetHaste();
-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), attacked_timer(CombatEventTimer_expire),
swarm_timer(100), swarm_timer(100),
m_corpse_queue_timer(1000),
m_corpse_queue_shutoff_timer(30000),
m_resumed_from_zone_suspend_shutoff_timer(10000), m_resumed_from_zone_suspend_shutoff_timer(10000),
classattack_timer(1000), classattack_timer(1000),
monkattack_timer(1000), monkattack_timer(1000),
@@ -624,28 +622,6 @@ bool NPC::Process()
// zone state corpse creation timer // zone state corpse creation timer
if (RuleB(Zone, StateSavingOnShutdown)) { 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 // shuts off the temporary spawn protected state of the NPC
if (m_resumed_from_zone_suspend_shutoff_timer.Check()) { if (m_resumed_from_zone_suspend_shutoff_timer.Check()) {
m_resumed_from_zone_suspend_shutoff_timer.Disable(); m_resumed_from_zone_suspend_shutoff_timer.Disable();
@@ -654,17 +630,6 @@ bool NPC::Process()
} }
if (tic_timer.Check()) { 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)) { if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) {
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
} }
+4 -8
View File
@@ -603,10 +603,9 @@ public:
// zone state save // zone state save
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; } inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
inline bool IsQueuedForCorpse() { return m_queued_for_corpse; } inline bool IsQueuedForCorpse() const { return m_queued_for_corpse; }
inline uint32_t SetCorpseDecayTime(uint32_t decay_time) { return m_corpse_decay_time = decay_time; }
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; } 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) { inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
int i = 0; int i = 0;
@@ -660,13 +659,10 @@ protected:
// zone state // zone state
bool m_resumed_from_zone_suspend = false; bool m_resumed_from_zone_suspend = false;
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore 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 = {};
// 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 // 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 = {}; Timer m_resumed_from_zone_suspend_shutoff_timer = {};
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list; std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
+1
View File
@@ -3506,6 +3506,7 @@ void QuestManager::UpdateInstanceTimer(uint16 instance_id, uint32 new_duration)
e.duration = new_duration; e.duration = new_duration;
e.start_time = std::time(nullptr); e.start_time = std::time(nullptr);
e.expire_at = e.start_time + e.duration;
const int updated = InstanceListRepository::UpdateOne(database, e); const int updated = InstanceListRepository::UpdateOne(database, e);
+9 -5
View File
@@ -191,16 +191,20 @@ bool Spawn2::Process() {
return false; return false;
} }
uint16 condition_value=1; uint16 condition_value = 1;
if (condition_id > 0) { 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 //have the spawn group pick an NPC for us
uint32 npcid = 0; uint32 npcid = 0;
if (RuleB(Zone, StateSavingOnShutdown) && currentnpcid && currentnpcid > 0) { if (m_resumed_npc_id > 0) {
npcid = currentnpcid; npcid = m_resumed_npc_id;
m_resumed_npc_id = 0;
} else { } else {
npcid = spawn_group->GetNPCType(condition_value); npcid = spawn_group->GetNPCType(condition_value);
} }
+2
View File
@@ -78,6 +78,7 @@ public:
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; } inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; } 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 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: protected:
friend class Zone; friend class Zone;
@@ -105,6 +106,7 @@ private:
bool IsDespawned; bool IsDespawned;
uint32 killcount; uint32 killcount;
bool m_resumed_from_zone_suspend = false; bool m_resumed_from_zone_suspend = false;
uint32 m_resumed_npc_id = 0;
std::map<std::string, std::string> m_entity_variables = {}; std::map<std::string, std::string> m_entity_variables = {};
}; };
+5 -1
View File
@@ -81,7 +81,6 @@ WorldServer::WorldServer()
cur_groupid = 0; cur_groupid = 0;
last_groupid = 0; last_groupid = 0;
oocmuted = false; oocmuted = false;
m_process_timer = std::make_unique<EQ::Timer>(1000, true, std::bind(&WorldServer::Process, this));
} }
WorldServer::~WorldServer() { WorldServer::~WorldServer() {
@@ -95,6 +94,7 @@ void WorldServer::Process()
if (it->second.reload_at_unix < std::time(nullptr)) { if (it->second.reload_at_unix < std::time(nullptr)) {
ProcessReload(it->second); ProcessReload(it->second);
it = m_reload_queue.erase(it); it = m_reload_queue.erase(it);
break;
} else { } else {
++it; ++it;
} }
@@ -4599,6 +4599,10 @@ void WorldServer::ProcessReload(const ServerReload::Request& request)
zone->ReloadLootTables(); zone->ReloadLootTables();
break; break;
case ServerReload::Type::Maps:
zone->ReloadMaps();
break;
case ServerReload::Type::Merchants: case ServerReload::Type::Merchants:
entity_list.ReloadMerchants(); entity_list.ReloadMerchants();
break; break;
-1
View File
@@ -81,7 +81,6 @@ private:
ZoneEventScheduler *m_zone_scheduler; ZoneEventScheduler *m_zone_scheduler;
// server reload queue // server reload queue
std::unique_ptr<EQ::Timer> m_process_timer;
std::mutex m_reload_mutex = {}; std::mutex m_reload_mutex = {};
std::map<int, ServerReload::Request> m_reload_queue = {}; std::map<int, ServerReload::Request> m_reload_queue = {};
public: public:
+8 -6
View File
@@ -887,10 +887,7 @@ void Zone::Shutdown(bool quiet)
c.second->WorldKick(); c.second->WorldKick();
} }
bool does_zone_have_entities = if (RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
zone && zone->IsLoaded() &&
(!entity_list.GetNPCList().empty() || !entity_list.GetCorpseList().empty());
if (RuleB(Zone, StateSavingOnShutdown) && does_zone_have_entities) {
SaveZoneState(); SaveZoneState();
} }
@@ -1537,7 +1534,6 @@ bool Zone::Process() {
spawn_conditions.Process(); spawn_conditions.Process();
if (spawn2_timer.Check()) { if (spawn2_timer.Check()) {
LinkedListIterator<Spawn2 *> iterator(spawn2_list); LinkedListIterator<Spawn2 *> iterator(spawn2_list);
EQ::InventoryProfile::CleanDirty(); 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(); 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" #include "zone_loot.cpp"
+1
View File
@@ -479,6 +479,7 @@ public:
); );
void SaveZoneState(); void SaveZoneState();
static void ClearZoneState(uint32 zone_id, uint32 instance_id); static void ClearZoneState(uint32 zone_id, uint32 instance_id);
void ReloadMaps();
private: private:
bool allow_mercs; bool allow_mercs;
+12 -6
View File
@@ -31,15 +31,21 @@ void ZoneCLI::CommandHandler(int argc, char **argv)
// Register commands // Register commands
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets; function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp; function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
function_map["tests:databuckets"] = &ZoneCLI::DataBuckets; function_map["tests:databuckets"] = &ZoneCLI::TestDataBuckets;
function_map["tests:npc-handins"] = &ZoneCLI::NpcHandins; function_map["tests:npc-handins"] = &ZoneCLI::TestNpcHandins;
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::NpcHandinsMultiQuest; function_map["tests:npc-handins-multiquest"] = &ZoneCLI::TestNpcHandinsMultiQuest;
function_map["tests:zone-state"] = &ZoneCLI::TestZoneState;
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
} }
#include "cli/databuckets.cpp" // cli
#include "cli/benchmark_databuckets.cpp" #include "cli/benchmark_databuckets.cpp"
#include "cli/sidecar_serve_http.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 #ifndef EQEMU_ZONE_CLI_H
#define EQEMU_ZONE_CLI_H #define EQEMU_ZONE_CLI_H
#include <iostream>
#include "../common/cli/argh.h" #include "../common/cli/argh.h"
class ZoneCLI { class ZoneCLI {
@@ -11,10 +12,10 @@ public:
static bool RanConsoleCommand(int argc, char **argv); static bool RanConsoleCommand(int argc, char **argv);
static bool RanSidecarCommand(int argc, char **argv); static bool RanSidecarCommand(int argc, char **argv);
static bool RanTestCommand(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 TestDataBuckets(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 TestNpcHandins(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 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 #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); m_lootdrops.emplace_back(e);
// add lootdrop entries // add lootdrop entries
for (const auto &f: lootdrop_entries) { // add lootdrop entries
if (e.id == f.lootdrop_id) { for (const auto &h: lootdrop_entries) {
if (e.id == h.lootdrop_id) {
// check if lootdrop entry already exists in memory // check if lootdrop entry already exists in memory
has_entry = false; has_entry = false;
for (const auto &g: m_lootdrop_entries) { for (const auto &i: m_lootdrop_entries) {
if (f.lootdrop_id == g.lootdrop_id && f.item_id == g.item_id && f.multiplier == g.multiplier) { if (h.lootdrop_id == i.lootdrop_id && h.item_id == i.item_id) {
has_entry = true; has_entry = true;
break; break;
} }
} }
if (!has_entry) {
m_lootdrop_entries.emplace_back(h);
}
} }
} }
} }
+28 -61
View File
@@ -4,46 +4,7 @@
#include "npc.h" #include "npc.h"
#include "corpse.h" #include "corpse.h"
#include "zone.h" #include "zone.h"
#include "../common/repositories/zone_state_spawns_repository.h" #include "zone_save_state.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)
);
}
};
// IsZoneStateValid checks if the zone state is valid // 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 // if these fields are all empty or zero value for an entire zone state, it's considered invalid
@@ -336,6 +297,16 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
n->SetEndurance(s.endurance); 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) { if (s.grid) {
n->AssignWaypoints(s.grid, s.current_waypoint); n->AssignWaypoints(s.grid, s.current_waypoint);
} }
@@ -349,7 +320,7 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
auto decay_time = s.decay_in_seconds * 1000; auto decay_time = s.decay_in_seconds * 1000;
if (decay_time > 0) { if (decay_time > 0) {
n->SetQueuedToCorpse(); n->SetQueuedToCorpse();
n->SetCorpseDecayTime(decay_time); entity_list.RestoreCorpse(n, decay_time);
} }
else { else {
n->Depop(); n->Depop();
@@ -443,12 +414,17 @@ bool Zone::LoadZoneState(
zone->initgrids_timer.Trigger(); zone->initgrids_timer.Trigger();
zone->Process(); zone->Process();
// load zone variables first
int count = 0;
for (auto &s: spawn_states) { for (auto &s: spawn_states) {
if (s.is_zone) { if (s.is_zone) {
LoadZoneVariables(zone, s.entity_variables); LoadZoneVariables(zone, s.entity_variables);
continue; break;
}
} }
// spawn2
for (auto &s: spawn_states) {
if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) { if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) {
continue; continue;
} }
@@ -488,11 +464,13 @@ bool Zone::LoadZoneState(
); );
if (spawn_time_left == 0) { if (spawn_time_left == 0) {
new_spawn->SetCurrentNPCID(s.npc_id); new_spawn->SetResumedNPCID(s.npc_id);
new_spawn->SetResumedFromZoneSuspend(true); new_spawn->SetResumedFromZoneSuspend(true);
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables)); new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
} }
count++;
spawn2_list.Insert(new_spawn); spawn2_list.Insert(new_spawn);
new_spawn->Process(); new_spawn->Process();
auto n = new_spawn->GetNPC(); auto n = new_spawn->GetNPC();
@@ -534,24 +512,6 @@ bool Zone::LoadZoneState(
LoadNPCState(zone, npc, s); 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(); return !spawn_states.empty();
} }
@@ -624,6 +584,7 @@ void Zone::SaveZoneState()
std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> spawns = {}; std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> spawns = {};
LinkedListIterator<Spawn2 *> iterator(spawn2_list); LinkedListIterator<Spawn2 *> iterator(spawn2_list);
iterator.Reset(); iterator.Reset();
int count = 0;
while (iterator.MoreElements()) { while (iterator.MoreElements()) {
Spawn2 *sp = iterator.GetData(); Spawn2 *sp = iterator.GetData();
auto s = ZoneStateSpawnsRepository::NewEntity(); auto s = ZoneStateSpawnsRepository::NewEntity();
@@ -653,6 +614,7 @@ void Zone::SaveZoneState()
spawns.emplace_back(s); spawns.emplace_back(s);
iterator.Advance(); iterator.Advance();
count++;
} }
// npc's that are not in the spawn2 list // npc's that are not in the spawn2 list
@@ -728,6 +690,11 @@ void Zone::SaveZoneState()
return; return;
} }
if (spawns.empty()) {
LogInfo("No zone state data to save");
return;
}
ZoneStateSpawnsRepository::InsertMany(database, spawns); ZoneStateSpawnsRepository::InsertMany(database, spawns);
LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size())); 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), .id = static_cast<int32_t>(spawn2_id),
.start = static_cast<int32_t>(current_time), .start = static_cast<int32_t>(current_time),
.duration = static_cast<int32_t>(time_left), .duration = static_cast<int32_t>(time_left),
.expire_at = current_time + time_left,
.instance_id = static_cast<int16_t>(instance_id) .instance_id = static_cast<int16_t>(instance_id)
} }
); );