mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-23 00:42:27 +00:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12eb838ee9 | |||
| 213fe6a9e9 | |||
| be6a5d5f50 | |||
| 1af29bd7b1 | |||
| ef945e6e99 | |||
| 9528c1e7fc | |||
| fd6e5f465d | |||
| d00125abe1 | |||
| 5d69235a4c | |||
| e93785f885 | |||
| 3c2545cfaf | |||
| 8d1a9efac9 | |||
| f6b18fb003 | |||
| 00e77f190c | |||
| 9cb72a6ba7 | |||
| 8203c034bf | |||
| 33ae51f56f | |||
| 30c39194a3 | |||
| 051ce3736f | |||
| 84708edccf | |||
| da4e9ab95b | |||
| a8fea95eab | |||
| 53610c2f0f | |||
| 9f10c12874 | |||
| a0634adb3c | |||
| a2ed6be1f5 | |||
| c33ac40567 | |||
| 9ee095b354 | |||
| 7ab32af4dc | |||
| 0c301419c2 | |||
| d6a21be25e | |||
| 1d4ba082ad | |||
| 94553501ba | |||
| da824d5178 | |||
| 5a1df38900 | |||
| 8cd7148b29 | |||
| 09e079a45e | |||
| 4bc881da4b | |||
| 0615864d51 | |||
| 3638d157b2 | |||
| d41725e325 | |||
| 88580b69b6 | |||
| f7a6fe595a | |||
| 07d14c2681 | |||
| eac7a73fb6 | |||
| c5715f1f14 | |||
| 3902230fa1 | |||
| 212969f5cd | |||
| de4226fdc9 | |||
| 8b13434197 | |||
| 27274397ec | |||
| 2b79a36014 | |||
| 8cf52294e9 | |||
| 0ef79903f8 | |||
| ab14458f9e | |||
| 5b94e736b3 | |||
| c3b8cc9744 | |||
| 89e3b2c72e | |||
| 23c4aa241b | |||
| acb7584e26 | |||
| a885bd9322 | |||
| 20fe1926e0 | |||
| eb7118754b | |||
| 3611b49f68 | |||
| 511d8a8bb3 | |||
| 1598d2e17b | |||
| 805757ba87 | |||
| eb6ac25540 | |||
| cb634cf57d | |||
| 2f7ca2cdc8 | |||
| 425d24c1f4 | |||
| 875df8e64a | |||
| 7a2d2a0c51 | |||
| 5296202e56 | |||
| e2db8ffea8 | |||
| fa2ab11676 |
+169
@@ -1,3 +1,172 @@
|
||||
## [23.3.4] 3/14/2025
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase ([#4778](https://github.com/EQEmu/Server/pull/4778)) @neckkola 2025-03-14
|
||||
* Fix for rare circumstance where NPC's would have 0 health on restore @Akkadius
|
||||
|
||||
## [23.3.3] 3/13/2025
|
||||
|
||||
### Database
|
||||
|
||||
* Add indexes for data_buckets and zone_state_spawns ([#4771](https://github.com/EQEmu/Server/pull/4771)) @Akkadius 2025-03-11
|
||||
|
||||
### Fixes
|
||||
|
||||
* Update GuildBank to correctly handle items with charges equal to zero ([#4774](https://github.com/EQEmu/Server/pull/4774)) @neckkola 2025-03-13
|
||||
|
||||
### Networking
|
||||
|
||||
* Fix "port in use" error ([#4772](https://github.com/EQEmu/Server/pull/4772)) @Akkadius 2025-03-12
|
||||
|
||||
### Zone
|
||||
|
||||
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
|
||||
|
||||
## [23.3.2] 3/11/2025
|
||||
|
||||
### DynamicZones
|
||||
|
||||
* Bulk request dz member statuses on zone boot ([#4769](https://github.com/EQEmu/Server/pull/4769)) @hgtw 2025-03-11
|
||||
|
||||
### Zone
|
||||
|
||||
* Zone State Improvements (Continued) ([#4768](https://github.com/EQEmu/Server/pull/4768)) @Akkadius 2025-03-11
|
||||
|
||||
## [23.3.0] 3/8/2025
|
||||
|
||||
### Bots
|
||||
|
||||
* Fix buffs not overwriting lesser buffs ([#4756](https://github.com/EQEmu/Server/pull/4756)) @nytmyr 2025-03-06
|
||||
* Fix taunting bots positioning ([#4754](https://github.com/EQEmu/Server/pull/4754)) @nytmyr 2025-03-06
|
||||
* Move commanded spell map to zone ([#4755](https://github.com/EQEmu/Server/pull/4755)) @nytmyr 2025-03-06
|
||||
|
||||
### Code
|
||||
|
||||
* Fix typo in GM tradeskill combine message ([#4762](https://github.com/EQEmu/Server/pull/4762)) @nytmyr 2025-03-08
|
||||
|
||||
### Crash
|
||||
|
||||
* Bot aura crash fix ([#4752](https://github.com/EQEmu/Server/pull/4752)) @nytmyr 2025-03-06
|
||||
|
||||
### Databuckets
|
||||
|
||||
* Nested Databuckets Protections and Improvements ([#4748](https://github.com/EQEmu/Server/pull/4748)) @Akkadius 2025-03-04
|
||||
|
||||
### Feature
|
||||
|
||||
* Add Rule for dealing with augments when an item evolves ([#4758](https://github.com/EQEmu/Server/pull/4758)) @neckkola 2025-03-08
|
||||
* Allow assigning Helm Texture independently of Body Texture for Horses ([#4759](https://github.com/EQEmu/Server/pull/4759)) @catapultam-habeo 2025-03-08
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add crash checks for certain PlayerEventLogs ([#4761](https://github.com/EQEmu/Server/pull/4761)) @neckkola 2025-03-07
|
||||
* Correct incorrectly calculated stat caps with Heroic Stats ([#4760](https://github.com/EQEmu/Server/pull/4760)) @catapultam-habeo 2025-03-08
|
||||
* Fix sigabort crash from invalid JSON @Akkadius 2025-03-03
|
||||
* Forgot to push up some changes for test output @Akkadius 2025-03-04
|
||||
* Parcel Delivery Updates for two edge cases ([#4753](https://github.com/EQEmu/Server/pull/4753)) @neckkola 2025-03-06
|
||||
* Remove one port check in world @Akkadius 2025-03-03
|
||||
* Zero out currentnpcid whenever spawn is reset. ([#4763](https://github.com/EQEmu/Server/pull/4763)) @zimp-wow 2025-03-08
|
||||
|
||||
### Logging
|
||||
|
||||
* Convert JSON Error to Data Buckets Logging Category ([#4747](https://github.com/EQEmu/Server/pull/4747)) @Kinglykrab 2025-03-04
|
||||
|
||||
### Pets
|
||||
|
||||
* Fix renamed pets loading as blank names ([#4751](https://github.com/EQEmu/Server/pull/4751)) @nytmyr 2025-03-05
|
||||
|
||||
### Rules
|
||||
|
||||
* Fix EvolvingItems:PercentOfRaidExperience Description ([#4757](https://github.com/EQEmu/Server/pull/4757)) @Kinglykrab 2025-03-07
|
||||
|
||||
### Tests
|
||||
|
||||
* Cleanup Hand-in Tests ([#4749](https://github.com/EQEmu/Server/pull/4749)) @Akkadius 2025-03-04
|
||||
|
||||
### Zone
|
||||
|
||||
* Make zone controller less likely to be visible, immune to all forms of combat ([#4750](https://github.com/EQEmu/Server/pull/4750)) @Akkadius 2025-03-06
|
||||
* State Save Improvements ([#4765](https://github.com/EQEmu/Server/pull/4765)) @Akkadius 2025-03-08
|
||||
|
||||
## [23.2.0] 3/3/2025
|
||||
|
||||
### Crash
|
||||
|
||||
* Database SetMutex crash fix ([#4741](https://github.com/EQEmu/Server/pull/4741)) @Akkadius 2025-03-03
|
||||
* Fix Aura process crash with bots ([#4743](https://github.com/EQEmu/Server/pull/4743)) @Akkadius 2025-03-03
|
||||
* Fix crash in add loot code path ([#4745](https://github.com/EQEmu/Server/pull/4745)) @Akkadius 2025-03-03
|
||||
* Fix world repop crash ([#4742](https://github.com/EQEmu/Server/pull/4742)) @Akkadius 2025-03-03
|
||||
* Potential crash fix in scan close mobs ([#4744](https://github.com/EQEmu/Server/pull/4744)) @Akkadius 2025-03-03
|
||||
|
||||
### Fixes
|
||||
|
||||
* Cleanup zone buckets on instance purge. ([#4739](https://github.com/EQEmu/Server/pull/4739)) @zimp-wow 2025-03-02
|
||||
* Fix an error causing Endurance Regen to not be applied by items. ([#4738](https://github.com/EQEmu/Server/pull/4738)) @catapultam-habeo 2025-03-02
|
||||
|
||||
### World
|
||||
|
||||
* Check if port in use to avoid double booting mistakes ([#4740](https://github.com/EQEmu/Server/pull/4740)) @Akkadius 2025-03-03
|
||||
|
||||
## [23.1.0] 3/1/2025
|
||||
|
||||
### Bots
|
||||
|
||||
* Fix unresponsive bots in groups upon group wipe ([#4712](https://github.com/EQEmu/Server/pull/4712)) @nytmyr 2025-02-28
|
||||
|
||||
### Code
|
||||
|
||||
* More login <-> world code cleanup ([#4724](https://github.com/EQEmu/Server/pull/4724)) @Akkadius 2025-02-28
|
||||
|
||||
### Crash
|
||||
|
||||
* Check for directory existence before traversing in CheckForCompatibleQuestPlugins ([#4730](https://github.com/EQEmu/Server/pull/4730)) @Akkadius 2025-03-02
|
||||
* Fix filesystem crash / exception in DatabaseDumpService::RemoveCredentialsFile() ([#4731](https://github.com/EQEmu/Server/pull/4731)) @Akkadius 2025-03-01
|
||||
* Fix large file size crash in File::GetContents for windows ([#4735](https://github.com/EQEmu/Server/pull/4735)) @Akkadius 2025-03-02
|
||||
* Fix reload concurrency crash when ran from Spire ([#4733](https://github.com/EQEmu/Server/pull/4733)) @Akkadius 2025-03-02
|
||||
* Validate item in SE_SummonItemIntoBag ([#4734](https://github.com/EQEmu/Server/pull/4734)) @Akkadius 2025-03-02
|
||||
* World CLI validation ([#4728](https://github.com/EQEmu/Server/pull/4728)) @Akkadius 2025-03-01
|
||||
|
||||
### Database
|
||||
|
||||
* Remove force_interactive from big bag updates ([#4727](https://github.com/EQEmu/Server/pull/4727)) @Akkadius 2025-03-01
|
||||
|
||||
### Feature
|
||||
|
||||
* Add a rule for spells to bypass stacking rules ([#4716](https://github.com/EQEmu/Server/pull/4716)) @catapultam-habeo 2025-02-28
|
||||
* Evolving items Additions ([#4725](https://github.com/EQEmu/Server/pull/4725)) @neckkola 2025-03-01
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add character_pet_name to player tables schema @Akkadius 2025-03-02
|
||||
* Add client packets to questmanager:setguild ([#4732](https://github.com/EQEmu/Server/pull/4732)) @neckkola 2025-03-01
|
||||
* Clear m_completed_shared_tasks before reloading @Akkadius 2025-02-24
|
||||
* Fix AA Reset Error Message ([#4720](https://github.com/EQEmu/Server/pull/4720)) @Kinglykrab 2025-02-28
|
||||
* Fix Issue with Suffixes/Prefixes ([#4723](https://github.com/EQEmu/Server/pull/4723)) @Kinglykrab 2025-02-28
|
||||
* Fix Trading Items to Bot Pets ([#4721](https://github.com/EQEmu/Server/pull/4721)) @MortimerGreenwald 2025-02-28
|
||||
* Refactor ApplyItemBonuses to fix double-counting of ATK and recommended levels not correctly applying ([#4713](https://github.com/EQEmu/Server/pull/4713)) @catapultam-habeo 2025-03-01
|
||||
|
||||
### Loginserver
|
||||
|
||||
* Minor cleanup ([#4729](https://github.com/EQEmu/Server/pull/4729)) @Akkadius 2025-03-01
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add Key Ring Methods to Perl and Lua ([#4719](https://github.com/EQEmu/Server/pull/4719)) @Kinglykrab 2025-02-28
|
||||
* Implement eq.handin() and quest::handin() ([#4718](https://github.com/EQEmu/Server/pull/4718)) @Akkadius 2025-02-28
|
||||
|
||||
### Tasks
|
||||
|
||||
* Extend IsTaskCompleted to also be aware of shared task completion ([#4714](https://github.com/EQEmu/Server/pull/4714)) @Akkadius 2025-02-24
|
||||
|
||||
### Zone
|
||||
|
||||
* Implement Zone State Saving on Shutdown ([#4715](https://github.com/EQEmu/Server/pull/4715)) @Akkadius 2025-02-28
|
||||
|
||||
### Zone State
|
||||
|
||||
* Wrap all serialization/deserialization in try/catch ([#4726](https://github.com/EQEmu/Server/pull/4726)) @Akkadius 2025-03-01
|
||||
|
||||
## [23.0.2] 2/21/2025
|
||||
|
||||
### Bots
|
||||
|
||||
+38
-1
@@ -245,7 +245,7 @@ uint32 Database::CreateAccount(
|
||||
e.password = password;
|
||||
}
|
||||
|
||||
LogInfo("Account Attempting to be created: [{}:{}] status: {}", loginserver, name, status);
|
||||
LogInfo("Account attempting to be created loginserver [{}] name [{}] status [{}]", loginserver, name, status);
|
||||
|
||||
e = AccountRepository::InsertOne(*this, e);
|
||||
|
||||
@@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na
|
||||
return CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
bool Database::UpdateNameByID(const int character_id, const std::string& new_name)
|
||||
{
|
||||
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
|
||||
|
||||
auto l = CharacterDataRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`id` = {}",
|
||||
character_id
|
||||
)
|
||||
);
|
||||
|
||||
if (l.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& e = l.front();
|
||||
|
||||
e.name = new_name;
|
||||
|
||||
return CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
bool Database::IsNameUsed(const std::string& name)
|
||||
{
|
||||
if (RuleB(Bots, Enabled)) {
|
||||
@@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name)
|
||||
return !character_data.empty();
|
||||
}
|
||||
|
||||
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
|
||||
bool Database::IsPetNameUsed(const std::string& name)
|
||||
{
|
||||
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`name` = '{}'",
|
||||
Strings::Escape(name)
|
||||
)
|
||||
);
|
||||
|
||||
return !pet_name_data.empty();
|
||||
}
|
||||
|
||||
uint32 Database::GetServerType()
|
||||
{
|
||||
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
|
||||
|
||||
@@ -103,6 +103,7 @@ public:
|
||||
bool ReserveName(uint32 account_id, const std::string& name);
|
||||
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
|
||||
bool UpdateName(const std::string& old_name, const std::string& new_name);
|
||||
bool UpdateNameByID(const int character_id, const std::string& new_name);
|
||||
bool CopyCharacter(
|
||||
const std::string& source_character_name,
|
||||
const std::string& destination_character_name,
|
||||
@@ -116,6 +117,7 @@ public:
|
||||
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
|
||||
bool CheckNameFilter(const std::string& name, bool surname = false);
|
||||
bool IsNameUsed(const std::string& name);
|
||||
bool IsPetNameUsed(const std::string& name);
|
||||
|
||||
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
|
||||
uint32 GetAccountIDByChar(uint32 character_id);
|
||||
|
||||
@@ -596,7 +596,12 @@ void DatabaseDumpService::BuildCredentialsFile()
|
||||
void DatabaseDumpService::RemoveCredentialsFile()
|
||||
{
|
||||
if (File::Exists(CREDENTIALS_FILE)) {
|
||||
std::filesystem::remove(CREDENTIALS_FILE);
|
||||
try {
|
||||
std::filesystem::remove(CREDENTIALS_FILE);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
LogError("std::filesystem::remove err [{}]", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6417,7 +6417,7 @@ ADD COLUMN `guid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 AFTER `ornament_hero_mo
|
||||
ADD PRIMARY KEY (`account_id`, `slot_id`);
|
||||
)",
|
||||
.content_schema_update = false,
|
||||
.force_interactive = true
|
||||
.force_interactive = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9298,
|
||||
@@ -6481,7 +6481,7 @@ UPDATE `sharedbank` SET `slot_id` = ((`slot_id` - 2531) + 11010) WHERE `slot_id`
|
||||
UPDATE `sharedbank` SET `slot_id` = ((`slot_id` - 2541) + 11210) WHERE `slot_id` BETWEEN 2541 AND 2550; -- Shared Bank Bag 2
|
||||
)",
|
||||
.content_schema_update = false,
|
||||
.force_interactive = true
|
||||
.force_interactive = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9299,
|
||||
@@ -6845,7 +6845,7 @@ RENAME TABLE `expedition_lockouts` TO `dynamic_zone_lockouts`;
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
-- ✅ Drop old indexes
|
||||
-- Drop old indexes if exists
|
||||
DROP INDEX IF EXISTS `keys` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_npc_expires` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
||||
@@ -6863,11 +6863,141 @@ ALTER TABLE `data_buckets`
|
||||
MODIFY COLUMN `npc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
|
||||
MODIFY COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_id`;
|
||||
|
||||
-- ✅ Create optimized unique index with `key` first
|
||||
-- Create optimized unique index with `key` first
|
||||
CREATE UNIQUE INDEX `keys` ON data_buckets (`key`, character_id, npc_id, bot_id, account_id, zone_id, instance_id);
|
||||
|
||||
-- ✅ Create indexes for just instance_id (instance deletion)
|
||||
-- Create indexes for just instance_id (instance deletion)
|
||||
CREATE INDEX idx_instance_id ON data_buckets (instance_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9307,
|
||||
.description = "2025_02_17_zone_state_spawns.sql",
|
||||
.check = "SHOW TABLES LIKE 'zone_state_spawns'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
CREATE TABLE `zone_state_spawns` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`zone_id` int(11) unsigned DEFAULT NULL,
|
||||
`instance_id` int(11) unsigned DEFAULT NULL,
|
||||
`is_corpse` tinyint(11) DEFAULT 0,
|
||||
`decay_in_seconds` int(11) DEFAULT 0,
|
||||
`npc_id` int(10) unsigned DEFAULT NULL,
|
||||
`spawn2_id` int(10) unsigned NOT NULL,
|
||||
`spawngroup_id` int(10) unsigned NOT NULL,
|
||||
`x` float NOT NULL,
|
||||
`y` float NOT NULL,
|
||||
`z` float NOT NULL,
|
||||
`heading` float NOT NULL,
|
||||
`respawn_time` int(10) unsigned NOT NULL,
|
||||
`variance` int(10) unsigned NOT NULL,
|
||||
`grid` int(10) unsigned DEFAULT 0,
|
||||
`current_waypoint` int(11) DEFAULT 0,
|
||||
`path_when_zone_idle` smallint(6) DEFAULT 0,
|
||||
`condition_id` smallint(5) unsigned DEFAULT 0,
|
||||
`condition_min_value` smallint(6) DEFAULT 0,
|
||||
`enabled` smallint(6) DEFAULT 1,
|
||||
`anim` smallint(5) unsigned DEFAULT 0,
|
||||
`loot_data` text DEFAULT NULL,
|
||||
`entity_variables` text DEFAULT NULL,
|
||||
`buffs` text DEFAULT NULL,
|
||||
`hp` bigint(20) DEFAULT 0,
|
||||
`mana` bigint(20) DEFAULT 0,
|
||||
`endurance` bigint(20) DEFAULT 0,
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9308,
|
||||
.description = "2025_add_multivalue_support_to_evolving_subtype.sql",
|
||||
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
|
||||
.condition = "missing",
|
||||
.match = "varchar(200)",
|
||||
.sql = R"(
|
||||
ALTER TABLE `items_evolving_details`
|
||||
CHANGE COLUMN `sub_type` `sub_type` VARCHAR(200) NULL DEFAULT '0' AFTER `type`;
|
||||
)",
|
||||
.content_schema_update = true
|
||||
},
|
||||
// this one got missed being added to PEQ dumps so adding it again so it gets added when folks take a new release
|
||||
ManifestEntry{
|
||||
.version = 9309,
|
||||
.description = "2025_03_1_create_pet_names_table_if_not_exist.sql",
|
||||
.check = "SHOW TABLES LIKE 'character_pet_name'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
CREATE TABLE `character_pet_name` (
|
||||
`character_id` INT(11) NOT NULL PRIMARY KEY,
|
||||
`name` VARCHAR(64) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
)",
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9310,
|
||||
.description = "2025_03_7_expand_horse_def.sql",
|
||||
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
|
||||
.condition = "missing",
|
||||
.match = "TINYINT(2)",
|
||||
.sql = R"(
|
||||
ALTER TABLE `horses`
|
||||
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
|
||||
)",
|
||||
.content_schema_update = true
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9311,
|
||||
.description = "2025_03_09_add_zone_state_is_zone_field.sql",
|
||||
.check = "SHOW COLUMNS FROM `zone_state_spawns` LIKE 'is_zone'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `zone_state_spawns`
|
||||
ADD COLUMN `is_zone` tinyint(11) NULL DEFAULT 0 AFTER `is_corpse`;
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9312,
|
||||
.description = "2025_03_11_data_bucket_indexes.sql",
|
||||
.check = "SHOW INDEX FROM data_buckets",
|
||||
.condition = "missing",
|
||||
.match = "idx_zone_instance_expires",
|
||||
.sql = R"(
|
||||
DROP INDEX IF EXISTS `idx_zone_instance_expires` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_character_expires` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
||||
ALTER TABLE data_buckets ADD INDEX idx_zone_instance_expires (zone_id, instance_id, expires);
|
||||
ALTER TABLE data_buckets ADD INDEX idx_character_expires (character_id, expires);
|
||||
ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9313,
|
||||
.description = "2025_03_11_zone_state_spawns.sql",
|
||||
.check = "SHOW INDEX FROM zone_state_spawns",
|
||||
.condition = "missing",
|
||||
.match = "idx_zone_instance",
|
||||
.sql = R"(
|
||||
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id);
|
||||
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9314,
|
||||
.description = "2025_03_12_zone_state_spawns_one_time_truncate.sql",
|
||||
.check = "SELECT * FROM db_version WHERE version >= 9314",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
TRUNCATE TABLE zone_state_spawns;
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "../common/repositories/spawn_condition_values_repository.h"
|
||||
#include "repositories/spawn2_disabled_repository.h"
|
||||
#include "repositories/data_buckets_repository.h"
|
||||
|
||||
#include "repositories/zone_state_spawns_repository.h"
|
||||
#include "database.h"
|
||||
|
||||
#include <iomanip>
|
||||
@@ -480,6 +480,9 @@ void Database::DeleteInstance(uint16 instance_id)
|
||||
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
|
||||
CharacterCorpsesRepository::BuryInstance(*this, instance_id);
|
||||
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` = {}", instance_id));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::FlagInstanceByGroupLeader(uint32 zone_id, int16 version, uint32 character_id, uint32 group_id)
|
||||
@@ -562,6 +565,10 @@ void Database::PurgeExpiredInstances()
|
||||
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids);
|
||||
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
||||
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
||||
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_ids));
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
||||
|
||||
@@ -139,6 +139,7 @@ namespace DatabaseSchema {
|
||||
"character_pet_buffs",
|
||||
"character_pet_info",
|
||||
"character_pet_inventory",
|
||||
"character_pet_name",
|
||||
"character_peqzone_flags",
|
||||
"character_potionbelt",
|
||||
"character_skills",
|
||||
@@ -350,6 +351,7 @@ namespace DatabaseSchema {
|
||||
"shared_task_dynamic_zones",
|
||||
"shared_task_members",
|
||||
"shared_tasks",
|
||||
"zone_state_spawns",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -302,7 +302,9 @@ std::string DBcore::Escape(const std::string& s)
|
||||
|
||||
void DBcore::SetMutex(Mutex *mutex)
|
||||
{
|
||||
safe_delete(m_mutex);
|
||||
if (m_mutex && m_mutex != mutex) {
|
||||
safe_delete(m_mutex);
|
||||
}
|
||||
|
||||
DBcore::m_mutex = mutex;
|
||||
}
|
||||
|
||||
@@ -287,6 +287,8 @@ N(OP_InstillDoubt),
|
||||
N(OP_InterruptCast),
|
||||
N(OP_InvokeChangePetName),
|
||||
N(OP_InvokeChangePetNameImmediate),
|
||||
N(OP_InvokeNameChangeImmediate),
|
||||
N(OP_InvokeNameChangeLazy),
|
||||
N(OP_ItemLinkClick),
|
||||
N(OP_ItemLinkResponse),
|
||||
N(OP_ItemLinkText),
|
||||
|
||||
+17
-10
@@ -47,7 +47,7 @@ static const uint32 ADVANCED_LORE_LENGTH = 8192;
|
||||
*/
|
||||
#pragma pack(1)
|
||||
|
||||
struct LoginInfo_Struct {
|
||||
struct LoginInfo {
|
||||
/*000*/ char login_info[64];
|
||||
/*064*/ uint8 unknown064[124];
|
||||
/*188*/ uint8 zoning; // 01 if zoning, 00 if not
|
||||
@@ -5832,21 +5832,28 @@ struct ChangeSize_Struct
|
||||
/*16*/
|
||||
};
|
||||
|
||||
enum ChangeNameResponse : int {
|
||||
Denied = 0, // 5167: "You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name."
|
||||
Accepted = 1, // 5976: "Your request for a name change was successful."
|
||||
Timeout = -1, // 5977: "Your request for a name change has timed out. Please try again later."
|
||||
ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later."
|
||||
RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes."
|
||||
Ineligible = -4, // 5980: "Your character is not eligible for a name change."
|
||||
Pending = -5 // 5193: "You already have a name change pending. Please wait until it is fully processed before attempting another name change."
|
||||
};
|
||||
|
||||
struct AltChangeName_Struct {
|
||||
/*00*/ char new_name[64];
|
||||
/*40*/ char old_name[64];
|
||||
/*80*/ int response_code;
|
||||
};
|
||||
|
||||
struct ChangePetName_Struct {
|
||||
/*00*/ char new_pet_name[64];
|
||||
/*40*/ char pet_owner_name[64];
|
||||
/*80*/ int response_code;
|
||||
};
|
||||
|
||||
enum ChangePetNameResponse : int {
|
||||
Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name.
|
||||
Accepted = 1, // 5976 Your request for a name change was successful.
|
||||
Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes.
|
||||
NotEligible = -4, // 5980 Your character is not eligible for a name change.
|
||||
Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change.
|
||||
Unhandled = -1
|
||||
};
|
||||
|
||||
// New OpCode/Struct for SoD+
|
||||
struct GroupMakeLeader_Struct
|
||||
{
|
||||
|
||||
@@ -614,7 +614,7 @@ void EQEmuLogSys::EnableConsoleLogging()
|
||||
std::copy(std::begin(pre_silence_settings), std::end(pre_silence_settings), std::begin(log_settings));
|
||||
}
|
||||
|
||||
EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
|
||||
EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings(bool silent_load)
|
||||
{
|
||||
InjectTablesIfNotExist();
|
||||
|
||||
@@ -699,6 +699,10 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
|
||||
return this;
|
||||
}
|
||||
|
||||
if (silent_load) {
|
||||
SilenceConsoleLogging();
|
||||
}
|
||||
|
||||
LogInfo("Loaded [{}] log categories", categories.size());
|
||||
|
||||
auto webhooks = DiscordWebhooksRepository::GetWhere(*m_database, fmt::format("id < {}", MAX_DISCORD_WEBHOOK_ID));
|
||||
@@ -716,6 +720,10 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
|
||||
log_settings[Logs::Info].log_to_file = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::Info].log_to_console = static_cast<uint8>(Logs::General);
|
||||
|
||||
if (silent_load) {
|
||||
SilenceConsoleLogging();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@ namespace Logs {
|
||||
BotSpellChecks,
|
||||
BotSpellTypeChecks,
|
||||
NpcHandin,
|
||||
ZoneState,
|
||||
MaxCategoryID /* Don't Remove this */
|
||||
};
|
||||
|
||||
@@ -256,7 +257,8 @@ namespace Logs {
|
||||
"Bot Settings",
|
||||
"Bot Spell Checks",
|
||||
"Bot Spell Type Checks",
|
||||
"NpcHandin"
|
||||
"NpcHandin",
|
||||
"ZoneState"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -277,7 +279,7 @@ public:
|
||||
*/
|
||||
void CloseFileLogs();
|
||||
EQEmuLogSys *LoadLogSettingsDefaults();
|
||||
EQEmuLogSys *LoadLogDatabaseSettings();
|
||||
EQEmuLogSys *LoadLogDatabaseSettings(bool silent_load = false);
|
||||
|
||||
/**
|
||||
* @param directory_name
|
||||
|
||||
@@ -914,6 +914,16 @@
|
||||
OutF(LogSys, Logs::Detail, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogZoneState(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::General, Logs::ZoneState))\
|
||||
OutF(LogSys, Logs::General, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogZoneStateDetail(message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::ZoneState))\
|
||||
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define Log(debug_level, log_category, message, ...) do {\
|
||||
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
|
||||
+12
-13
@@ -39,6 +39,7 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -90,23 +91,21 @@ std::string File::GetCwd()
|
||||
|
||||
FileContentsResult File::GetContents(const std::string &file_name)
|
||||
{
|
||||
std::string error;
|
||||
std::ifstream f;
|
||||
f.open(file_name);
|
||||
std::string line;
|
||||
std::string lines;
|
||||
if (f.is_open()) {
|
||||
while (f) {
|
||||
std::getline(f, line);
|
||||
lines += line + "\n";
|
||||
}
|
||||
std::ifstream f(file_name, std::ios::in | std::ios::binary);
|
||||
if (!f) {
|
||||
return { .error = fmt::format("Couldn't open file [{}]", file_name) };
|
||||
}
|
||||
else {
|
||||
error = fmt::format("Couldn't open file [{}]", file_name);
|
||||
|
||||
constexpr size_t CHUNK_SIZE = 4096; // Read 4KB chunks
|
||||
std::string lines;
|
||||
std::vector<char> buffer(CHUNK_SIZE);
|
||||
|
||||
while (f.read(buffer.data(), CHUNK_SIZE) || f.gcount() > 0) {
|
||||
lines.append(buffer.data(), f.gcount());
|
||||
}
|
||||
|
||||
return FileContentsResult{
|
||||
.contents = lines,
|
||||
.error = error,
|
||||
.error = {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,3 +259,84 @@ bool IpUtil::IsIPAddress(const std::string &ip_address)
|
||||
}
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#pragma comment(lib, "ws2_32.lib") // Link against Winsock library
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h> // For inet_pton
|
||||
#pragma comment(lib, "ws2_32.lib") // Link against Winsock library
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h> // For inet_pton
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
bool IpUtil::IsPortInUse(const std::string& ip, int port) {
|
||||
bool in_use = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
std::cerr << "WSAStartup failed\n";
|
||||
return true; // Assume in use on failure
|
||||
}
|
||||
#endif
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) {
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
return true; // Assume in use on failure
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int opt = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&opt, sizeof(opt)); // Windows-specific
|
||||
#else
|
||||
int opt = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // Linux/macOS
|
||||
#endif
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
// Convert IP address from string to binary format
|
||||
if (inet_pton(AF_INET, ip.c_str(), &addr.sin_addr) <= 0) {
|
||||
std::cerr << "Invalid IP address format: " << ip << std::endl;
|
||||
#ifdef _WIN32
|
||||
closesocket(sock);
|
||||
WSACleanup();
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
return true; // Assume in use on failure
|
||||
}
|
||||
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
in_use = true; // Bind failed, port is in use
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
closesocket(sock);
|
||||
WSACleanup();
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
|
||||
return in_use;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
int port
|
||||
);
|
||||
static bool IsIPAddress(const std::string &ip_address);
|
||||
static bool IsPortInUse(const std::string& ip, int port);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ struct LootItem {
|
||||
uint16 trivial_max_level;
|
||||
uint16 npc_min_level;
|
||||
uint16 npc_max_level;
|
||||
uint32 lootdrop_id; // required for zone state referencing
|
||||
};
|
||||
|
||||
typedef std::list<LootItem*> LootItems;
|
||||
|
||||
@@ -25,7 +25,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet
|
||||
return;
|
||||
|
||||
if (opcode == ServerOP_UsertoWorldReq) {
|
||||
auto req_in = (UsertoWorldRequest_Struct*)p.Data();
|
||||
auto req_in = (UsertoWorldRequest*)p.Data();
|
||||
|
||||
EQ::Net::DynamicPacket req;
|
||||
size_t i = 0;
|
||||
@@ -45,7 +45,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet
|
||||
}
|
||||
|
||||
if (opcode == ServerOP_LSClientAuth) {
|
||||
auto req_in = (ClientAuth_Struct*)p.Data();
|
||||
auto req_in = (ClientAuth*)p.Data();
|
||||
|
||||
EQ::Net::DynamicPacket req;
|
||||
size_t i = 0;
|
||||
@@ -54,7 +54,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet
|
||||
req.PutData(i, req_in->key, 30); i += 30;
|
||||
req.PutUInt8(i, req_in->lsadmin); i += 1;
|
||||
req.PutUInt16(i, req_in->is_world_admin); i += 2;
|
||||
req.PutUInt32(i, req_in->ip); i += 4;
|
||||
req.PutUInt32(i, req_in->ip_address); i += 4;
|
||||
req.PutUInt8(i, req_in->is_client_from_local_network); i += 1;
|
||||
|
||||
EQ::Net::DynamicPacket out;
|
||||
|
||||
@@ -76,7 +76,7 @@ void PathManager::LoadPaths()
|
||||
constexpr int path_width = 0;
|
||||
constexpr int break_length = 70;
|
||||
|
||||
std::cout << std::endl;
|
||||
LogInfo("Loading server paths");
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
for (const auto& [name, in_path] : paths) {
|
||||
if (!in_path.empty()) {
|
||||
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
int16_t race;
|
||||
int8_t gender;
|
||||
int8_t texture;
|
||||
int8_t helmtexture;
|
||||
float mountspeed;
|
||||
std::string notes;
|
||||
};
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
"race",
|
||||
"gender",
|
||||
"texture",
|
||||
"helmtexture",
|
||||
"mountspeed",
|
||||
"notes",
|
||||
};
|
||||
@@ -54,6 +56,7 @@ public:
|
||||
"race",
|
||||
"gender",
|
||||
"texture",
|
||||
"helmtexture",
|
||||
"mountspeed",
|
||||
"notes",
|
||||
};
|
||||
@@ -96,13 +99,14 @@ public:
|
||||
{
|
||||
Horses e{};
|
||||
|
||||
e.id = 0;
|
||||
e.filename = "";
|
||||
e.race = 216;
|
||||
e.gender = 0;
|
||||
e.texture = 0;
|
||||
e.mountspeed = 0.75;
|
||||
e.notes = "Notes";
|
||||
e.id = 0;
|
||||
e.filename = "";
|
||||
e.race = 216;
|
||||
e.gender = 0;
|
||||
e.texture = 0;
|
||||
e.helmtexture = -1;
|
||||
e.mountspeed = 0.75;
|
||||
e.notes = "Notes";
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -139,13 +143,14 @@ public:
|
||||
if (results.RowCount() == 1) {
|
||||
Horses e{};
|
||||
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.filename = row[1] ? row[1] : "";
|
||||
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
|
||||
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.mountspeed = row[5] ? strtof(row[5], nullptr) : 0.75;
|
||||
e.notes = row[6] ? row[6] : "Notes";
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.filename = row[1] ? row[1] : "";
|
||||
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
|
||||
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.helmtexture = row[5] ? static_cast<int8_t>(atoi(row[5])) : -1;
|
||||
e.mountspeed = row[6] ? strtof(row[6], nullptr) : 0.75;
|
||||
e.notes = row[7] ? row[7] : "Notes";
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -183,8 +188,9 @@ public:
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.race));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.gender));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.texture));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.mountspeed));
|
||||
v.push_back(columns[6] + " = '" + Strings::Escape(e.notes) + "'");
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.helmtexture));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.mountspeed));
|
||||
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -211,6 +217,7 @@ public:
|
||||
v.push_back(std::to_string(e.race));
|
||||
v.push_back(std::to_string(e.gender));
|
||||
v.push_back(std::to_string(e.texture));
|
||||
v.push_back(std::to_string(e.helmtexture));
|
||||
v.push_back(std::to_string(e.mountspeed));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
@@ -247,6 +254,7 @@ public:
|
||||
v.push_back(std::to_string(e.race));
|
||||
v.push_back(std::to_string(e.gender));
|
||||
v.push_back(std::to_string(e.texture));
|
||||
v.push_back(std::to_string(e.helmtexture));
|
||||
v.push_back(std::to_string(e.mountspeed));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
@@ -282,13 +290,14 @@ public:
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
Horses e{};
|
||||
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.filename = row[1] ? row[1] : "";
|
||||
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
|
||||
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.mountspeed = row[5] ? strtof(row[5], nullptr) : 0.75;
|
||||
e.notes = row[6] ? row[6] : "Notes";
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.filename = row[1] ? row[1] : "";
|
||||
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
|
||||
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.helmtexture = row[5] ? static_cast<int8_t>(atoi(row[5])) : -1;
|
||||
e.mountspeed = row[6] ? strtof(row[6], nullptr) : 0.75;
|
||||
e.notes = row[7] ? row[7] : "Notes";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -313,13 +322,14 @@ public:
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
Horses e{};
|
||||
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.filename = row[1] ? row[1] : "";
|
||||
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
|
||||
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.mountspeed = row[5] ? strtof(row[5], nullptr) : 0.75;
|
||||
e.notes = row[6] ? row[6] : "Notes";
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.filename = row[1] ? row[1] : "";
|
||||
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
|
||||
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.helmtexture = row[5] ? static_cast<int8_t>(atoi(row[5])) : -1;
|
||||
e.mountspeed = row[6] ? strtof(row[6], nullptr) : 0.75;
|
||||
e.notes = row[7] ? row[7] : "Notes";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -399,6 +409,7 @@ public:
|
||||
v.push_back(std::to_string(e.race));
|
||||
v.push_back(std::to_string(e.gender));
|
||||
v.push_back(std::to_string(e.texture));
|
||||
v.push_back(std::to_string(e.helmtexture));
|
||||
v.push_back(std::to_string(e.mountspeed));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
@@ -428,6 +439,7 @@ public:
|
||||
v.push_back(std::to_string(e.race));
|
||||
v.push_back(std::to_string(e.gender));
|
||||
v.push_back(std::to_string(e.texture));
|
||||
v.push_back(std::to_string(e.helmtexture));
|
||||
v.push_back(std::to_string(e.mountspeed));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
class BaseItemsEvolvingDetailsRepository {
|
||||
public:
|
||||
struct ItemsEvolvingDetails {
|
||||
uint32_t id;
|
||||
uint32_t item_evo_id;
|
||||
uint32_t item_evolve_level;
|
||||
uint32_t item_id;
|
||||
uint32_t type;
|
||||
uint32_t sub_type;
|
||||
int64_t required_amount;
|
||||
uint32_t id;
|
||||
uint32_t item_evo_id;
|
||||
uint32_t item_evolve_level;
|
||||
uint32_t item_id;
|
||||
uint32_t type;
|
||||
std::string sub_type;
|
||||
int64_t required_amount;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -101,7 +101,7 @@ public:
|
||||
e.item_evolve_level = 0;
|
||||
e.item_id = 0;
|
||||
e.type = 0;
|
||||
e.sub_type = 0;
|
||||
e.sub_type = "0";
|
||||
e.required_amount = 0;
|
||||
|
||||
return e;
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
e.item_evolve_level = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.type = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.sub_type = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.sub_type = row[5] ? row[5] : "0";
|
||||
e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0;
|
||||
|
||||
return e;
|
||||
@@ -183,7 +183,7 @@ public:
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.item_evolve_level));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.item_id));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.type));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.sub_type));
|
||||
v.push_back(columns[5] + " = '" + Strings::Escape(e.sub_type) + "'");
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.required_amount));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -211,7 +211,7 @@ public:
|
||||
v.push_back(std::to_string(e.item_evolve_level));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.sub_type));
|
||||
v.push_back("'" + Strings::Escape(e.sub_type) + "'");
|
||||
v.push_back(std::to_string(e.required_amount));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -247,7 +247,7 @@ public:
|
||||
v.push_back(std::to_string(e.item_evolve_level));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.sub_type));
|
||||
v.push_back("'" + Strings::Escape(e.sub_type) + "'");
|
||||
v.push_back(std::to_string(e.required_amount));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
@@ -287,7 +287,7 @@ public:
|
||||
e.item_evolve_level = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.type = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.sub_type = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.sub_type = row[5] ? row[5] : "0";
|
||||
e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
@@ -318,7 +318,7 @@ public:
|
||||
e.item_evolve_level = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.type = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.sub_type = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.sub_type = row[5] ? row[5] : "0";
|
||||
e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
@@ -399,7 +399,7 @@ public:
|
||||
v.push_back(std::to_string(e.item_evolve_level));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.sub_type));
|
||||
v.push_back("'" + Strings::Escape(e.sub_type) + "'");
|
||||
v.push_back(std::to_string(e.required_amount));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -428,7 +428,7 @@ public:
|
||||
v.push_back(std::to_string(e.item_evolve_level));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.sub_type));
|
||||
v.push_back("'" + Strings::Escape(e.sub_type) + "'");
|
||||
v.push_back(std::to_string(e.required_amount));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
|
||||
@@ -0,0 +1,715 @@
|
||||
/**
|
||||
* DO NOT MODIFY THIS FILE
|
||||
*
|
||||
* This repository was automatically generated and is NOT to be modified directly.
|
||||
* Any repository modifications are meant to be made to the repository extending the base.
|
||||
* Any modifications to base repositories are to be made by the generator only
|
||||
*
|
||||
* @generator ./utils/scripts/generators/repository-generator.pl
|
||||
* @docs https://docs.eqemu.io/developer/repositories
|
||||
*/
|
||||
|
||||
#ifndef EQEMU_BASE_ZONE_STATE_SPAWNS_REPOSITORY_H
|
||||
#define EQEMU_BASE_ZONE_STATE_SPAWNS_REPOSITORY_H
|
||||
|
||||
#include "../../database.h"
|
||||
#include "../../strings.h"
|
||||
#include <ctime>
|
||||
|
||||
class BaseZoneStateSpawnsRepository {
|
||||
public:
|
||||
struct ZoneStateSpawns {
|
||||
int64_t id;
|
||||
uint32_t zone_id;
|
||||
uint32_t instance_id;
|
||||
int8_t is_corpse;
|
||||
int8_t is_zone;
|
||||
int32_t decay_in_seconds;
|
||||
uint32_t npc_id;
|
||||
uint32_t spawn2_id;
|
||||
uint32_t spawngroup_id;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float heading;
|
||||
uint32_t respawn_time;
|
||||
uint32_t variance;
|
||||
uint32_t grid;
|
||||
int32_t current_waypoint;
|
||||
int16_t path_when_zone_idle;
|
||||
uint16_t condition_id;
|
||||
int16_t condition_min_value;
|
||||
int16_t enabled;
|
||||
uint16_t anim;
|
||||
std::string loot_data;
|
||||
std::string entity_variables;
|
||||
std::string buffs;
|
||||
int64_t hp;
|
||||
int64_t mana;
|
||||
int64_t endurance;
|
||||
time_t created_at;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"zone_id",
|
||||
"instance_id",
|
||||
"is_corpse",
|
||||
"is_zone",
|
||||
"decay_in_seconds",
|
||||
"npc_id",
|
||||
"spawn2_id",
|
||||
"spawngroup_id",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"heading",
|
||||
"respawn_time",
|
||||
"variance",
|
||||
"grid",
|
||||
"current_waypoint",
|
||||
"path_when_zone_idle",
|
||||
"condition_id",
|
||||
"condition_min_value",
|
||||
"enabled",
|
||||
"anim",
|
||||
"loot_data",
|
||||
"entity_variables",
|
||||
"buffs",
|
||||
"hp",
|
||||
"mana",
|
||||
"endurance",
|
||||
"created_at",
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> SelectColumns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"zone_id",
|
||||
"instance_id",
|
||||
"is_corpse",
|
||||
"is_zone",
|
||||
"decay_in_seconds",
|
||||
"npc_id",
|
||||
"spawn2_id",
|
||||
"spawngroup_id",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"heading",
|
||||
"respawn_time",
|
||||
"variance",
|
||||
"grid",
|
||||
"current_waypoint",
|
||||
"path_when_zone_idle",
|
||||
"condition_id",
|
||||
"condition_min_value",
|
||||
"enabled",
|
||||
"anim",
|
||||
"loot_data",
|
||||
"entity_variables",
|
||||
"buffs",
|
||||
"hp",
|
||||
"mana",
|
||||
"endurance",
|
||||
"UNIX_TIMESTAMP(created_at)",
|
||||
};
|
||||
}
|
||||
|
||||
static std::string ColumnsRaw()
|
||||
{
|
||||
return std::string(Strings::Implode(", ", Columns()));
|
||||
}
|
||||
|
||||
static std::string SelectColumnsRaw()
|
||||
{
|
||||
return std::string(Strings::Implode(", ", SelectColumns()));
|
||||
}
|
||||
|
||||
static std::string TableName()
|
||||
{
|
||||
return std::string("zone_state_spawns");
|
||||
}
|
||||
|
||||
static std::string BaseSelect()
|
||||
{
|
||||
return fmt::format(
|
||||
"SELECT {} FROM {}",
|
||||
SelectColumnsRaw(),
|
||||
TableName()
|
||||
);
|
||||
}
|
||||
|
||||
static std::string BaseInsert()
|
||||
{
|
||||
return fmt::format(
|
||||
"INSERT INTO {} ({}) ",
|
||||
TableName(),
|
||||
ColumnsRaw()
|
||||
);
|
||||
}
|
||||
|
||||
static ZoneStateSpawns NewEntity()
|
||||
{
|
||||
ZoneStateSpawns e{};
|
||||
|
||||
e.id = 0;
|
||||
e.zone_id = 0;
|
||||
e.instance_id = 0;
|
||||
e.is_corpse = 0;
|
||||
e.is_zone = 0;
|
||||
e.decay_in_seconds = 0;
|
||||
e.npc_id = 0;
|
||||
e.spawn2_id = 0;
|
||||
e.spawngroup_id = 0;
|
||||
e.x = 0;
|
||||
e.y = 0;
|
||||
e.z = 0;
|
||||
e.heading = 0;
|
||||
e.respawn_time = 0;
|
||||
e.variance = 0;
|
||||
e.grid = 0;
|
||||
e.current_waypoint = 0;
|
||||
e.path_when_zone_idle = 0;
|
||||
e.condition_id = 0;
|
||||
e.condition_min_value = 0;
|
||||
e.enabled = 1;
|
||||
e.anim = 0;
|
||||
e.loot_data = "";
|
||||
e.entity_variables = "";
|
||||
e.buffs = "";
|
||||
e.hp = 0;
|
||||
e.mana = 0;
|
||||
e.endurance = 0;
|
||||
e.created_at = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static ZoneStateSpawns GetZoneStateSpawns(
|
||||
const std::vector<ZoneStateSpawns> &zone_state_spawnss,
|
||||
int zone_state_spawns_id
|
||||
)
|
||||
{
|
||||
for (auto &zone_state_spawns : zone_state_spawnss) {
|
||||
if (zone_state_spawns.id == zone_state_spawns_id) {
|
||||
return zone_state_spawns;
|
||||
}
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static ZoneStateSpawns FindOne(
|
||||
Database& db,
|
||||
int zone_state_spawns_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE {} = {} LIMIT 1",
|
||||
BaseSelect(),
|
||||
PrimaryKey(),
|
||||
zone_state_spawns_id
|
||||
)
|
||||
);
|
||||
|
||||
auto row = results.begin();
|
||||
if (results.RowCount() == 1) {
|
||||
ZoneStateSpawns e{};
|
||||
|
||||
e.id = row[0] ? strtoll(row[0], nullptr, 10) : 0;
|
||||
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.x = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.y = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.z = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
|
||||
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
|
||||
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
|
||||
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
|
||||
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
|
||||
e.loot_data = row[22] ? row[22] : "";
|
||||
e.entity_variables = row[23] ? row[23] : "";
|
||||
e.buffs = row[24] ? row[24] : "";
|
||||
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static int DeleteOne(
|
||||
Database& db,
|
||||
int zone_state_spawns_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {} = {}",
|
||||
TableName(),
|
||||
PrimaryKey(),
|
||||
zone_state_spawns_id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int UpdateOne(
|
||||
Database& db,
|
||||
const ZoneStateSpawns &e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.zone_id));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.instance_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.is_corpse));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.is_zone));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.decay_in_seconds));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.npc_id));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.spawn2_id));
|
||||
v.push_back(columns[8] + " = " + std::to_string(e.spawngroup_id));
|
||||
v.push_back(columns[9] + " = " + std::to_string(e.x));
|
||||
v.push_back(columns[10] + " = " + std::to_string(e.y));
|
||||
v.push_back(columns[11] + " = " + std::to_string(e.z));
|
||||
v.push_back(columns[12] + " = " + std::to_string(e.heading));
|
||||
v.push_back(columns[13] + " = " + std::to_string(e.respawn_time));
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.variance));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.grid));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.current_waypoint));
|
||||
v.push_back(columns[17] + " = " + std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(columns[18] + " = " + std::to_string(e.condition_id));
|
||||
v.push_back(columns[19] + " = " + std::to_string(e.condition_min_value));
|
||||
v.push_back(columns[20] + " = " + std::to_string(e.enabled));
|
||||
v.push_back(columns[21] + " = " + std::to_string(e.anim));
|
||||
v.push_back(columns[22] + " = '" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back(columns[23] + " = '" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back(columns[24] + " = '" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(columns[25] + " = " + std::to_string(e.hp));
|
||||
v.push_back(columns[26] + " = " + std::to_string(e.mana));
|
||||
v.push_back(columns[27] + " = " + std::to_string(e.endurance));
|
||||
v.push_back(columns[28] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"UPDATE {} SET {} WHERE {} = {}",
|
||||
TableName(),
|
||||
Strings::Implode(", ", v),
|
||||
PrimaryKey(),
|
||||
e.id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static ZoneStateSpawns InsertOne(
|
||||
Database& db,
|
||||
ZoneStateSpawns e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
v.push_back(std::to_string(e.spawngroup_id));
|
||||
v.push_back(std::to_string(e.x));
|
||||
v.push_back(std::to_string(e.y));
|
||||
v.push_back(std::to_string(e.z));
|
||||
v.push_back(std::to_string(e.heading));
|
||||
v.push_back(std::to_string(e.respawn_time));
|
||||
v.push_back(std::to_string(e.variance));
|
||||
v.push_back(std::to_string(e.grid));
|
||||
v.push_back(std::to_string(e.current_waypoint));
|
||||
v.push_back(std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(std::to_string(e.condition_id));
|
||||
v.push_back(std::to_string(e.condition_min_value));
|
||||
v.push_back(std::to_string(e.enabled));
|
||||
v.push_back(std::to_string(e.anim));
|
||||
v.push_back("'" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(std::to_string(e.hp));
|
||||
v.push_back(std::to_string(e.mana));
|
||||
v.push_back(std::to_string(e.endurance));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES ({})",
|
||||
BaseInsert(),
|
||||
Strings::Implode(",", v)
|
||||
)
|
||||
);
|
||||
|
||||
if (results.Success()) {
|
||||
e.id = results.LastInsertedID();
|
||||
return e;
|
||||
}
|
||||
|
||||
e = NewEntity();
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static int InsertMany(
|
||||
Database& db,
|
||||
const std::vector<ZoneStateSpawns> &entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
v.push_back(std::to_string(e.spawngroup_id));
|
||||
v.push_back(std::to_string(e.x));
|
||||
v.push_back(std::to_string(e.y));
|
||||
v.push_back(std::to_string(e.z));
|
||||
v.push_back(std::to_string(e.heading));
|
||||
v.push_back(std::to_string(e.respawn_time));
|
||||
v.push_back(std::to_string(e.variance));
|
||||
v.push_back(std::to_string(e.grid));
|
||||
v.push_back(std::to_string(e.current_waypoint));
|
||||
v.push_back(std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(std::to_string(e.condition_id));
|
||||
v.push_back(std::to_string(e.condition_min_value));
|
||||
v.push_back(std::to_string(e.enabled));
|
||||
v.push_back(std::to_string(e.anim));
|
||||
v.push_back("'" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(std::to_string(e.hp));
|
||||
v.push_back(std::to_string(e.mana));
|
||||
v.push_back(std::to_string(e.endurance));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseInsert(),
|
||||
Strings::Implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static std::vector<ZoneStateSpawns> All(Database& db)
|
||||
{
|
||||
std::vector<ZoneStateSpawns> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{}",
|
||||
BaseSelect()
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
ZoneStateSpawns e{};
|
||||
|
||||
e.id = row[0] ? strtoll(row[0], nullptr, 10) : 0;
|
||||
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.x = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.y = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.z = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
|
||||
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
|
||||
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
|
||||
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
|
||||
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
|
||||
e.loot_data = row[22] ? row[22] : "";
|
||||
e.entity_variables = row[23] ? row[23] : "";
|
||||
e.buffs = row[24] ? row[24] : "";
|
||||
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static std::vector<ZoneStateSpawns> GetWhere(Database& db, const std::string &where_filter)
|
||||
{
|
||||
std::vector<ZoneStateSpawns> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE {}",
|
||||
BaseSelect(),
|
||||
where_filter
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
ZoneStateSpawns e{};
|
||||
|
||||
e.id = row[0] ? strtoll(row[0], nullptr, 10) : 0;
|
||||
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.x = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.y = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.z = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
|
||||
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
|
||||
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
|
||||
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
|
||||
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
|
||||
e.loot_data = row[22] ? row[22] : "";
|
||||
e.entity_variables = row[23] ? row[23] : "";
|
||||
e.buffs = row[24] ? row[24] : "";
|
||||
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static int DeleteWhere(Database& db, const std::string &where_filter)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {}",
|
||||
TableName(),
|
||||
where_filter
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int Truncate(Database& db)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"TRUNCATE TABLE {}",
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int64 GetMaxId(Database& db)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT COALESCE(MAX({}), 0) FROM {}",
|
||||
PrimaryKey(),
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
|
||||
}
|
||||
|
||||
static int64 Count(Database& db, const std::string &where_filter = "")
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT COUNT(*) FROM {} {}",
|
||||
TableName(),
|
||||
(where_filter.empty() ? "" : "WHERE " + where_filter)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
|
||||
}
|
||||
|
||||
static std::string BaseReplace()
|
||||
{
|
||||
return fmt::format(
|
||||
"REPLACE INTO {} ({}) ",
|
||||
TableName(),
|
||||
ColumnsRaw()
|
||||
);
|
||||
}
|
||||
|
||||
static int ReplaceOne(
|
||||
Database& db,
|
||||
const ZoneStateSpawns &e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
v.push_back(std::to_string(e.spawngroup_id));
|
||||
v.push_back(std::to_string(e.x));
|
||||
v.push_back(std::to_string(e.y));
|
||||
v.push_back(std::to_string(e.z));
|
||||
v.push_back(std::to_string(e.heading));
|
||||
v.push_back(std::to_string(e.respawn_time));
|
||||
v.push_back(std::to_string(e.variance));
|
||||
v.push_back(std::to_string(e.grid));
|
||||
v.push_back(std::to_string(e.current_waypoint));
|
||||
v.push_back(std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(std::to_string(e.condition_id));
|
||||
v.push_back(std::to_string(e.condition_min_value));
|
||||
v.push_back(std::to_string(e.enabled));
|
||||
v.push_back(std::to_string(e.anim));
|
||||
v.push_back("'" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(std::to_string(e.hp));
|
||||
v.push_back(std::to_string(e.mana));
|
||||
v.push_back(std::to_string(e.endurance));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES ({})",
|
||||
BaseReplace(),
|
||||
Strings::Implode(",", v)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int ReplaceMany(
|
||||
Database& db,
|
||||
const std::vector<ZoneStateSpawns> &entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
v.push_back(std::to_string(e.spawngroup_id));
|
||||
v.push_back(std::to_string(e.x));
|
||||
v.push_back(std::to_string(e.y));
|
||||
v.push_back(std::to_string(e.z));
|
||||
v.push_back(std::to_string(e.heading));
|
||||
v.push_back(std::to_string(e.respawn_time));
|
||||
v.push_back(std::to_string(e.variance));
|
||||
v.push_back(std::to_string(e.grid));
|
||||
v.push_back(std::to_string(e.current_waypoint));
|
||||
v.push_back(std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(std::to_string(e.condition_id));
|
||||
v.push_back(std::to_string(e.condition_min_value));
|
||||
v.push_back(std::to_string(e.enabled));
|
||||
v.push_back(std::to_string(e.anim));
|
||||
v.push_back("'" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(std::to_string(e.hp));
|
||||
v.push_back(std::to_string(e.mana));
|
||||
v.push_back(std::to_string(e.endurance));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseReplace(),
|
||||
Strings::Implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BASE_ZONE_STATE_SPAWNS_REPOSITORY_H
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef EQEMU_ZONE_STATE_SPAWNS_REPOSITORY_H
|
||||
#define EQEMU_ZONE_STATE_SPAWNS_REPOSITORY_H
|
||||
|
||||
#include "../database.h"
|
||||
#include "../strings.h"
|
||||
#include "base/base_zone_state_spawns_repository.h"
|
||||
|
||||
class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository {
|
||||
public:
|
||||
static void PurgeInvalidZoneStates(Database &database)
|
||||
{
|
||||
std::string query = R"(
|
||||
SELECT zone_id, instance_id
|
||||
FROM zone_state_spawns
|
||||
GROUP BY zone_id, instance_id
|
||||
HAVING COUNT(*) = SUM(
|
||||
CASE
|
||||
WHEN hp = 0
|
||||
AND mana = 0
|
||||
AND endurance = 0
|
||||
AND (loot_data IS NULL OR loot_data = '')
|
||||
AND (entity_variables IS NULL OR entity_variables = '')
|
||||
AND (buffs IS NULL OR buffs = '')
|
||||
THEN 1 ELSE 0
|
||||
END
|
||||
);
|
||||
)";
|
||||
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto row: results) {
|
||||
uint32 zone_id = std::stoul(row[0]);
|
||||
uint32 instance_id = std::stoul(row[1]);
|
||||
|
||||
int rows = ZoneStateSpawnsRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`zone_id` = {} AND `instance_id` = {}",
|
||||
zone_id,
|
||||
instance_id
|
||||
)
|
||||
);
|
||||
|
||||
LogInfo(
|
||||
"Purged invalid zone state data for zone [{}] instance [{}] rows [{}]",
|
||||
zone_id,
|
||||
instance_id,
|
||||
Strings::Commify(rows)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void PurgeOldZoneStates(Database &database)
|
||||
{
|
||||
int days = RuleI(Zone, StateSaveClearDays);
|
||||
|
||||
std::string query = fmt::format(
|
||||
"DELETE FROM zone_state_spawns WHERE created_at < NOW() - INTERVAL {} DAY",
|
||||
days
|
||||
);
|
||||
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
LogError("Failed to purge old zone state data older than {} days.", days);
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.RowsAffected() > 0) {
|
||||
LogInfo(
|
||||
"Purged old zone state data older than days [{}] rows [{}]",
|
||||
days,
|
||||
Strings::Commify(results.RowsAffected())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_ZONE_STATE_SPAWNS_REPOSITORY_H
|
||||
+17
-11
@@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway
|
||||
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
|
||||
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
|
||||
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
|
||||
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Mercs)
|
||||
@@ -374,6 +375,10 @@ RULE_BOOL(Zone, AllowCrossZoneSpellsOnBots, false, "Set to true to allow cross z
|
||||
RULE_BOOL(Zone, AllowCrossZoneSpellsOnMercs, false, "Set to true to allow cross zone spells (cast/remove) to affect mercenaries")
|
||||
RULE_BOOL(Zone, AllowCrossZoneSpellsOnPets, false, "Set to true to allow cross zone spells (cast/remove) to affect pets")
|
||||
RULE_BOOL(Zone, ZoneShardQuestMenuOnly, false, "Set to true if you only want quests to show the zone shard menu")
|
||||
RULE_BOOL(Zone, StateSaveEntityVariables, 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_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Map)
|
||||
@@ -528,6 +533,7 @@ RULE_INT(Spells, TargetedAOEMaxTargets, 4, "Max number of targets a Targeted AOE
|
||||
RULE_INT(Spells, PointBlankAOEMaxTargets, 0, "Max number of targets a Point-Blank AOE spell can cast on. Set to 0 for no limit.")
|
||||
RULE_INT(Spells, DefaultAOEMaxTargets, 0, "Max number of targets that an AOE spell which does not meet other descriptions can cast on. Set to 0 for no limit.")
|
||||
RULE_BOOL(Spells, AllowFocusOnSkillDamageSpells, false, "Allow focus effects 185, 459, and 482 to enhance SkillAttack spell effect 193")
|
||||
RULE_STRING(Spells, AlwaysStackSpells, "", "Comma-Seperated list of spell IDs to always stack with every other spell, except themselves.")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Combat)
|
||||
@@ -830,7 +836,7 @@ RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range
|
||||
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
|
||||
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
|
||||
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
|
||||
RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks")
|
||||
RULE_BOOL(Bots, RunSpellTypeChecksOnBoot, false, "This will run a series of checks to find potential errors in your bot_spells_entries table on boot and output to LogBotSpellTypeChecks")
|
||||
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
|
||||
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
|
||||
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
|
||||
@@ -848,17 +854,15 @@ RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery A
|
||||
RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption")
|
||||
RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).")
|
||||
RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.")
|
||||
RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.")
|
||||
RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "If UseFlatNormalMeleeRange is enabled, multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.")
|
||||
RULE_REAL(Bots, PercentMinMeleeDistance, 0.75, "Multiplier of the their melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.")
|
||||
RULE_REAL(Bots, MaxDistanceForMelee, 20, "Maximum distance bots will stand for melee. Default 20 to allow all special attacks to land.")
|
||||
RULE_REAL(Bots, TauntNormalMeleeRangeDistance, 0.50, "Multiplier of the max melee range at which a taunting bot will stand in melee combat. 0.50 Recommended, closer than others .")
|
||||
RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.40, "Multiplier of their melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.")
|
||||
RULE_REAL(Bots, PercentMaxMeleeRangeDistance, 0.95, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.95 Recommended, max melee while disabling special attacks/taunt.")
|
||||
RULE_REAL(Bots, PercentMinMaxMeleeRangeDistance, 0.75, "Multiplier of the closest max melee range at which a bot will stand in melee combat before trying to adjust. 0.75 Recommended, max melee while disabling special attacks/taunt.")
|
||||
RULE_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
|
||||
RULE_REAL(Bots, LowerTauntingMeleeDistanceMultiplier, 0.25, "Closest % of the hit box a taunting melee bot will get to the target. Default 0.25")
|
||||
RULE_REAL(Bots, LowerMaxMeleeRangeDistanceMultiplier, 0.80, "Closest % of the hit box a max melee range melee bot will get to the target. Default 0.80")
|
||||
RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
|
||||
RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
|
||||
RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
|
||||
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
|
||||
RULE_BOOL(Bots, 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_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "True Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.")
|
||||
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
|
||||
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
|
||||
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")
|
||||
@@ -1145,6 +1149,7 @@ RULE_BOOL(Items, DisableSpellFocusEffects, false, "Enable this to disable Spell
|
||||
RULE_BOOL(Items, SummonItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots of items in Client::SummonItem")
|
||||
RULE_BOOL(Items, AugmentItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots by players")
|
||||
RULE_BOOL(Items, AlwaysReturnHandins, true, "Enable this to always return handins to the player")
|
||||
RULE_BOOL(Items, NPCUseRecommendedLevels, false, "Enable to have NPCs scale item stats by recommended levels")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Parcel)
|
||||
@@ -1160,8 +1165,9 @@ RULE_CATEGORY_END()
|
||||
RULE_CATEGORY(EvolvingItems)
|
||||
RULE_REAL(EvolvingItems, PercentOfSoloExperience, 0.1, "Percentage of solo experience allocated to evolving items that require experience.")
|
||||
RULE_REAL(EvolvingItems, PercentOfGroupExperience, 0.1, "Percentage of group experience allocated to evolving items that require experience.")
|
||||
RULE_REAL(EvolvingItems, PercentOfRaidExperience, 0.1, "Percentage of solo experience allocated to evolving items that require experience.")
|
||||
RULE_REAL(EvolvingItems, PercentOfRaidExperience, 0.1, "Percentage of raid experience allocated to evolving items that require experience.")
|
||||
RULE_INT(EvolvingItems, DelayUponEquipping, 30000, "Delay in ms before an evolving item will earn rewards after equipping. Default is 30000ms or 30s.")
|
||||
RULE_BOOL(EvolvingItems, DestroyAugmentsOnEvolve, false, "If this is enabled, any augments in an item will be destroyed when the item evolves. Otherwise, send augments to the player via the parcel system (requires that the Parcel System be enabled).")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
#undef RULE_CATEGORY
|
||||
|
||||
+68
-43
@@ -196,6 +196,7 @@
|
||||
#define ServerOP_DzSaveInvite 0x0466
|
||||
#define ServerOP_DzRequestInvite 0x0467
|
||||
#define ServerOP_DzMakeLeader 0x0468
|
||||
#define ServerOP_DzGetBulkMemberStatuses 0x0469
|
||||
|
||||
#define ServerOP_LSInfo 0x1000
|
||||
#define ServerOP_LSStatus 0x1001
|
||||
@@ -677,36 +678,53 @@ struct ServerLSPlayerZoneChange_Struct {
|
||||
uint32 to; // 0 = world
|
||||
};
|
||||
|
||||
struct ClientAuth_Struct {
|
||||
struct ClientAuth {
|
||||
uint32 loginserver_account_id; // ID# in login server's db
|
||||
char loginserver_name[64];
|
||||
char account_name[30]; // username in login server's db
|
||||
char key[30]; // the Key the client will present
|
||||
uint8 lsadmin; // login server admin level
|
||||
int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it
|
||||
uint32 ip;
|
||||
uint8 is_client_from_local_network; // 1 if the client is from the local network
|
||||
char loginserver_name[64];
|
||||
char account_name[30]; // username in login server's db
|
||||
char key[30]; // the key the client will present
|
||||
uint8 lsadmin; // login server admin level
|
||||
int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it
|
||||
uint32 ip_address;
|
||||
uint8 is_client_from_local_network; // 1 if the client is from the local network
|
||||
|
||||
template <class Archive>
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(loginserver_account_id, loginserver_name, account_name, key, lsadmin, is_world_admin, ip, is_client_from_local_network);
|
||||
ar(
|
||||
loginserver_account_id,
|
||||
loginserver_name,
|
||||
account_name,
|
||||
key,
|
||||
lsadmin,
|
||||
is_world_admin,
|
||||
ip_address,
|
||||
is_client_from_local_network
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct ClientAuthLegacy_Struct {
|
||||
struct ClientAuthLegacy {
|
||||
uint32 loginserver_account_id; // ID# in login server's db
|
||||
char loginserver_account_name[30]; // username in login server's db
|
||||
char key[30]; // the Key the client will present
|
||||
uint8 loginserver_admin_level; // login server admin level
|
||||
int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it
|
||||
uint32 ip;
|
||||
uint8 is_client_from_local_network; // 1 if the client is from the local network
|
||||
char loginserver_account_name[30]; // username in login server's db
|
||||
char key[30]; // the key the client will present
|
||||
uint8 loginserver_admin_level; // login server admin level
|
||||
int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it
|
||||
uint32 ip_address;
|
||||
uint8 is_client_from_local_network; // 1 if the client is from the local network
|
||||
|
||||
template <class Archive>
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(loginserver_account_id, loginserver_account_name, key, loginserver_admin_level, is_world_admin, ip, is_client_from_local_network);
|
||||
ar(
|
||||
loginserver_account_id,
|
||||
loginserver_account_name,
|
||||
key,
|
||||
loginserver_admin_level,
|
||||
is_world_admin,
|
||||
ip_address,
|
||||
is_client_from_local_network
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -834,38 +852,38 @@ struct ServerSyncWorldList_Struct {
|
||||
bool placeholder;
|
||||
};
|
||||
|
||||
struct UsertoWorldRequestLegacy_Struct {
|
||||
uint32 lsaccountid;
|
||||
uint32 worldid;
|
||||
uint32 FromID;
|
||||
uint32 ToID;
|
||||
char IPAddr[64];
|
||||
};
|
||||
|
||||
struct UsertoWorldRequest_Struct {
|
||||
uint32 lsaccountid;
|
||||
uint32 worldid;
|
||||
uint32 FromID;
|
||||
uint32 ToID;
|
||||
char IPAddr[64];
|
||||
char login[64];
|
||||
};
|
||||
|
||||
struct UsertoWorldResponseLegacy_Struct {
|
||||
struct UsertoWorldRequestLegacy {
|
||||
uint32 lsaccountid;
|
||||
uint32 worldid;
|
||||
int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed
|
||||
uint32 FromID;
|
||||
uint32 ToID;
|
||||
char IPAddr[64];
|
||||
};
|
||||
|
||||
struct UsertoWorldRequest {
|
||||
uint32 lsaccountid;
|
||||
uint32 worldid;
|
||||
uint32 FromID; // appears to be unused today
|
||||
uint32 ToID; // appears to be unused today
|
||||
char IPAddr[64];
|
||||
char login[64];
|
||||
};
|
||||
|
||||
struct UsertoWorldResponseLegacy {
|
||||
uint32 lsaccountid;
|
||||
uint32 worldid;
|
||||
int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed
|
||||
uint32 FromID;
|
||||
uint32 ToID;
|
||||
};
|
||||
|
||||
struct UsertoWorldResponse_Struct {
|
||||
struct UsertoWorldResponse {
|
||||
uint32 lsaccountid;
|
||||
uint32 worldid;
|
||||
int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed
|
||||
uint32 FromID;
|
||||
uint32 ToID;
|
||||
char login[64];
|
||||
int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed
|
||||
uint32 FromID; // appears to be unused today
|
||||
uint32 ToID; // appears to be unused today
|
||||
char login[64];
|
||||
};
|
||||
|
||||
// generic struct to be used for alot of simple zone->world questions
|
||||
@@ -1538,6 +1556,13 @@ struct ServerDzMemberStatuses_Struct {
|
||||
ServerDzMemberStatusEntry_Struct entries[0];
|
||||
};
|
||||
|
||||
struct ServerDzCerealData_Struct {
|
||||
uint16_t zone_id;
|
||||
uint16_t inst_id;
|
||||
uint32_t cereal_size;
|
||||
char cereal_data[1];
|
||||
};
|
||||
|
||||
struct ServerDzMovePC_Struct {
|
||||
uint32 dz_id;
|
||||
uint16 sender_zone_id;
|
||||
|
||||
+49
-37
@@ -1456,41 +1456,42 @@ bool IsCompleteHealSpell(uint16 spell_id)
|
||||
}
|
||||
|
||||
bool IsFastHealSpell(uint16 spell_id) {
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHP) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
|
||||
);
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHP) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
|
||||
);
|
||||
|
||||
if (!spell_id) {
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
|
||||
);
|
||||
}
|
||||
if (!spell_id) {
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id && IsValidSpell(spell_id)) {
|
||||
if (
|
||||
spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME &&
|
||||
spells[spell_id].good_effect &&
|
||||
!IsGroupSpell(spell_id)
|
||||
) {
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (
|
||||
spells[spell_id].base_value[i] > 0 &&
|
||||
(
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHP ||
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (
|
||||
spell_id != SPELL_MINOR_HEALING &&
|
||||
(spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) &&
|
||||
spells[spell_id].good_effect &&
|
||||
!IsGroupSpell(spell_id)
|
||||
) {
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (
|
||||
spells[spell_id].base_value[i] > 0 &&
|
||||
(
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHP ||
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsVeryFastHealSpell(uint16 spell_id)
|
||||
@@ -1509,8 +1510,9 @@ bool IsVeryFastHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (
|
||||
spell_id != SPELL_MINOR_HEALING &&
|
||||
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
|
||||
spells[spell_id].good_effect &&
|
||||
!IsGroupSpell(spell_id)
|
||||
@@ -1548,8 +1550,13 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (spell_id == SPELL_MINOR_HEALING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
|
||||
spells[spell_id].target_type == ST_Target &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
!IsHealOverTimeSpell(spell_id) &&
|
||||
@@ -1589,9 +1596,14 @@ bool IsRegularPetHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id && IsValidSpell(spell_id)) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (spell_id == SPELL_MINOR_HEALING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) &&
|
||||
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
|
||||
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet) &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
!IsHealOverTimeSpell(spell_id) &&
|
||||
!IsGroupSpell(spell_id)
|
||||
@@ -1630,7 +1642,7 @@ bool IsRegularGroupHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (
|
||||
IsGroupSpell(spell_id) &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
|
||||
+7
-5
@@ -215,6 +215,7 @@
|
||||
#define SPELL_AMPLIFICATION 2603
|
||||
#define SPELL_DIVINE_REZ 2738
|
||||
#define SPELL_NATURES_RECOVERY 2520
|
||||
#define SPELL_MINOR_HEALING 200
|
||||
#define SPELL_ADRENALINE_SWELL 14445
|
||||
#define SPELL_ADRENALINE_SWELL_RK2 14446
|
||||
#define SPELL_ADRENALINE_SWELL_RK3 14447
|
||||
@@ -736,11 +737,12 @@ namespace BotSpellTypes
|
||||
constexpr uint16 DiscUtility = 203;
|
||||
|
||||
constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this
|
||||
constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed
|
||||
constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this
|
||||
constexpr uint16 COMMANDED_END = BotSpellTypes::AELull; // Do not remove this, increment as needed
|
||||
constexpr uint16 DISCIPLINE_START = BotSpellTypes::Discipline; // Do not remove or change this
|
||||
constexpr uint16 DISCIPLINE_END = BotSpellTypes::DiscUtility; // Do not remove this, increment as needed
|
||||
constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed
|
||||
constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this
|
||||
constexpr uint16 COMMANDED_END = BotSpellTypes::AELull; // Do not remove this, increment as needed
|
||||
constexpr uint16 DISCIPLINE_START = BotSpellTypes::Discipline; // Do not remove or change this
|
||||
constexpr uint16 DISCIPLINE_END = BotSpellTypes::DiscUtility; // Do not remove this, increment as needed
|
||||
constexpr uint16 PARENT_TYPE_END = BotSpellTypes::PreCombatBuffSong; // This is the last ID of the original bot spell types, the rest are considered sub types.
|
||||
}
|
||||
|
||||
static std::map<uint16, std::string> spell_type_names = {
|
||||
|
||||
+12
-252
@@ -1,4 +1,5 @@
|
||||
#include "spdat.h"
|
||||
#include "../zone/bot.h"
|
||||
|
||||
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
|
||||
switch (spell_type) {
|
||||
@@ -417,264 +418,23 @@ uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
|
||||
uint16 correct_type = UINT16_MAX;
|
||||
SPDat_Spell_Struct spell = spells[spell_id];
|
||||
std::string teleport_zone = spell.teleport_zone;
|
||||
uint16 correct_type = spell_type;
|
||||
|
||||
if (IsCharmSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Charm;
|
||||
}
|
||||
else if (IsFearSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Fear;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_Revive)) {
|
||||
correct_type = BotSpellTypes::Resurrect;
|
||||
}
|
||||
else if (IsHarmonySpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Lull;
|
||||
}
|
||||
else if (
|
||||
teleport_zone.compare("") &&
|
||||
!IsEffectInSpell(spell_id, SE_GateToHomeCity) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Teleport;
|
||||
}
|
||||
else if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Succor)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Succor;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_BindAffinity)) {
|
||||
correct_type = BotSpellTypes::BindAffinity;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_Identify)) {
|
||||
correct_type = BotSpellTypes::Identify;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Levitate &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Levitate)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Levitate;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Rune &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Rune;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::WaterBreathing &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_WaterBreathing)
|
||||
) {
|
||||
correct_type = BotSpellTypes::WaterBreathing;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Size &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Size;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Invisibility &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Invisibility;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::MovementSpeed &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
||||
) {
|
||||
correct_type = BotSpellTypes::MovementSpeed;
|
||||
}
|
||||
else if (
|
||||
!teleport_zone.compare("") &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))
|
||||
) {
|
||||
correct_type = BotSpellTypes::SendHome;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) {
|
||||
correct_type = BotSpellTypes::SummonCorpse;
|
||||
}
|
||||
if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) {
|
||||
correct_type = UINT16_MAX;
|
||||
|
||||
if (correct_type == UINT16_MAX) {
|
||||
if (
|
||||
IsSummonPetSpell(spell_id) ||
|
||||
IsEffectInSpell(spell_id, SE_TemporaryPets)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Pet;
|
||||
}
|
||||
else if (IsMesmerizeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Mez;
|
||||
}
|
||||
else if (IsEscapeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Escape;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Root)
|
||||
) {
|
||||
if (IsAnyAESpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::AERoot;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::Root;
|
||||
}
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsLifetapSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Lifetap;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Snare;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
|
||||
) {
|
||||
correct_type = BotSpellTypes::DOT;
|
||||
}
|
||||
else if (IsDispelSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Dispel;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsSlowSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Slow;
|
||||
}
|
||||
else if (
|
||||
IsDebuffSpell(spell_id) &&
|
||||
!IsHateReduxSpell(spell_id) &&
|
||||
!IsHateSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Debuff;
|
||||
}
|
||||
else if (IsHateReduxSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::HateRedux;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsHateSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::HateLine;
|
||||
}
|
||||
else if (
|
||||
IsBuffSpell(spell_id) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsBardSong(spell_id)
|
||||
) {
|
||||
if (
|
||||
spell_type == BotSpellTypes::InCombatBuffSong ||
|
||||
spell_type == BotSpellTypes::OutOfCombatBuffSong ||
|
||||
spell_type == BotSpellTypes::PreCombatBuffSong
|
||||
) {
|
||||
correct_type = spell_type;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::OutOfCombatBuffSong;
|
||||
}
|
||||
}
|
||||
else if (
|
||||
!IsBardSong(spell_id) &&
|
||||
(
|
||||
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
|
||||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
|
||||
)
|
||||
) {
|
||||
correct_type = BotSpellTypes::InCombatBuff;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::PreCombatBuff &&
|
||||
IsAnyBuffSpell(spell_id) &&
|
||||
!IsBardSong(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::PreCombatBuff;
|
||||
}
|
||||
else if (
|
||||
(IsCureSpell(spell_id) && spell_type == BotSpellTypes::Cure) ||
|
||||
(IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Cure;
|
||||
}
|
||||
else if (IsAnyNukeOrStunSpell(spell_id)) {
|
||||
if (IsAnyAESpell(spell_id)) {
|
||||
if (IsAERainSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::AERains;
|
||||
}
|
||||
else if (IsPBAENukeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::PBAENuke;
|
||||
}
|
||||
else if (IsStunSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::AEStun;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::AENukes;
|
||||
}
|
||||
}
|
||||
else if (IsStunSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Stun;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::Nuke;
|
||||
}
|
||||
}
|
||||
else if (IsAnyHealSpell(spell_id)) {
|
||||
if (IsGroupSpell(spell_id)) {
|
||||
if (IsGroupCompleteHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::GroupCompleteHeals;
|
||||
}
|
||||
else if (IsGroupHealOverTimeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::GroupHoTHeals;
|
||||
}
|
||||
else if (IsRegularGroupHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::GroupHeals;
|
||||
}
|
||||
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
|
||||
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
|
||||
|
||||
return correct_type;
|
||||
for (int i = end; i >= start; --i) {
|
||||
if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsVeryFastHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::VeryFastHeals;
|
||||
}
|
||||
else if (IsFastHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::FastHeals;
|
||||
}
|
||||
else if (IsCompleteHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::CompleteHeal;
|
||||
}
|
||||
else if (IsHealOverTimeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::HoTHeals;
|
||||
}
|
||||
else if (IsRegularSingleTargetHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::RegularHeal;
|
||||
}
|
||||
else if (IsRegularPetHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::RegularHeal;
|
||||
}
|
||||
}
|
||||
else if (IsAnyBuffSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Buff;
|
||||
if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) {
|
||||
correct_type = i;
|
||||
|
||||
if (IsResistanceOnlySpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::ResistBuffs;
|
||||
}
|
||||
else if (IsDamageShieldOnlySpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::DamageShields;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,3 +936,11 @@ std::string Strings::Slugify(const std::string& input, const std::string& separa
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
bool Strings::IsValidJson(const std::string &json)
|
||||
{
|
||||
rapidjson::Document doc;
|
||||
rapidjson::ParseResult result = doc.Parse(json.c_str());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <cereal/external/rapidjson/document.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
// this doesn't appear to affect linux-based systems..need feedback for _WIN64
|
||||
@@ -188,6 +189,7 @@ public:
|
||||
}
|
||||
|
||||
static std::string Slugify(const std::string &input, const std::string &separator = "-");
|
||||
static bool IsValidJson(const std::string& json);
|
||||
};
|
||||
|
||||
const std::string StringFormat(const char *format, ...);
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "23.0.2-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "23.3.4-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,7 +42,7 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9306
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9314
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||
|
||||
#endif
|
||||
|
||||
@@ -143,7 +143,7 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne
|
||||
packet.ToString()
|
||||
);
|
||||
|
||||
if (packet.Length() < sizeof(UsertoWorldResponseLegacy_Struct)) {
|
||||
if (packet.Length() < sizeof(UsertoWorldResponseLegacy)) {
|
||||
LogError(
|
||||
"Received application packet from server that had opcode ServerOP_UsertoWorldResp, "
|
||||
"but was too small. Discarded to avoid buffer overrun"
|
||||
@@ -152,7 +152,7 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne
|
||||
return;
|
||||
}
|
||||
|
||||
auto *res = (UsertoWorldResponseLegacy_Struct *) packet.Data();
|
||||
auto *res = (UsertoWorldResponseLegacy *) packet.Data();
|
||||
|
||||
LogDebug("Trying to find client with user id of [{}]", res->lsaccountid);
|
||||
Client *c = server.client_manager->GetClient(res->lsaccountid, "eqemu");
|
||||
@@ -229,7 +229,7 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac
|
||||
packet.ToString()
|
||||
);
|
||||
|
||||
if (packet.Length() < sizeof(UsertoWorldResponse_Struct)) {
|
||||
if (packet.Length() < sizeof(UsertoWorldResponse)) {
|
||||
LogError(
|
||||
"Received application packet from server that had opcode ServerOP_UsertoWorldResp, "
|
||||
"but was too small. Discarded to avoid buffer overrun"
|
||||
@@ -238,7 +238,7 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac
|
||||
return;
|
||||
}
|
||||
|
||||
auto res = (UsertoWorldResponse_Struct *) packet.Data();
|
||||
auto res = (UsertoWorldResponse *) packet.Data();
|
||||
LogDebug("Trying to find client with user id of [{}]", res->lsaccountid);
|
||||
|
||||
Client *c = server.client_manager->GetClient(
|
||||
@@ -358,14 +358,12 @@ void WorldServer::ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet
|
||||
void WorldServer::HandleNewWorldserver(LoginserverNewWorldRequest *req)
|
||||
{
|
||||
if (m_is_server_logged_in) {
|
||||
LogError(
|
||||
"Login server was already marked as logged in, aborting"
|
||||
);
|
||||
LogInfo("Login server was already marked as logged in, returning");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HandleNewWorldserverValidation(req)) {
|
||||
LogError("WorldServer::HandleNewWorldserver failed validation rules");
|
||||
LogError("failed validation rules");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -494,7 +492,7 @@ void WorldServer::HandleWorldserverStatusUpdate(LoginserverWorldStatusUpdate *u)
|
||||
void WorldServer::SendClientAuthToWorld(Client *c)
|
||||
{
|
||||
EQ::Net::DynamicPacket outapp;
|
||||
ClientAuth_Struct a{};
|
||||
ClientAuth a{};
|
||||
|
||||
a.loginserver_account_id = c->GetAccountID();
|
||||
|
||||
@@ -503,7 +501,7 @@ void WorldServer::SendClientAuthToWorld(Client *c)
|
||||
|
||||
a.lsadmin = 0;
|
||||
a.is_world_admin = 0;
|
||||
a.ip = inet_addr(c->GetConnection()->GetRemoteAddr().c_str());
|
||||
a.ip_address = inet_addr(c->GetConnection()->GetRemoteAddr().c_str());
|
||||
strncpy(a.loginserver_name, &c->GetLoginServerName()[0], 64);
|
||||
|
||||
const std::string &client_address(c->GetConnection()->GetRemoteAddr());
|
||||
@@ -521,7 +519,7 @@ void WorldServer::SendClientAuthToWorld(Client *c)
|
||||
}
|
||||
|
||||
struct in_addr ip_addr{};
|
||||
ip_addr.s_addr = a.ip;
|
||||
ip_addr.s_addr = a.ip_address;
|
||||
|
||||
LogInfo(
|
||||
"Client authentication response: world_address [{}] client_address [{}]",
|
||||
|
||||
@@ -80,7 +80,7 @@ private:
|
||||
std::string m_server_version;
|
||||
bool m_is_server_authorized_to_list;
|
||||
bool m_is_server_logged_in;
|
||||
bool m_is_server_trusted;
|
||||
bool m_is_server_trusted; // this is primarily for worldserver being able to push updates to the loginserver
|
||||
|
||||
static void FormatWorldServerName(char *name, int8 server_list_type);
|
||||
};
|
||||
|
||||
@@ -154,9 +154,9 @@ void WorldServerManager::SendUserLoginToWorldRequest(
|
||||
|
||||
if (iter != m_world_servers.end()) {
|
||||
EQ::Net::DynamicPacket outapp;
|
||||
outapp.Resize(sizeof(UsertoWorldRequest_Struct));
|
||||
outapp.Resize(sizeof(UsertoWorldRequest));
|
||||
|
||||
auto *r = reinterpret_cast<UsertoWorldRequest_Struct *>(outapp.Data());
|
||||
auto *r = reinterpret_cast<UsertoWorldRequest *>(outapp.Data());
|
||||
r->worldid = server_id;
|
||||
r->lsaccountid = client_account_id;
|
||||
strncpy(r->login, client_loginserver.c_str(), 64);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "23.0.2",
|
||||
"version": "23.3.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
|
||||
OP_InvokeChangePetNameImmediate=0x046d
|
||||
OP_InvokeChangePetName=0x4506
|
||||
OP_ChangePetName=0x5dab
|
||||
|
||||
OP_InvokeNameChangeImmediate=0x4fe2
|
||||
OP_InvokeNameChangeLazy=0x2f2e
|
||||
|
||||
@@ -56,8 +56,9 @@ echo "# Running shared_memory"
|
||||
echo "# Running NPC hand-in tests"
|
||||
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
|
||||
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
|
||||
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
|
||||
|
||||
if grep -E -q "QueryErr|Error" test_output.log; then
|
||||
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
|
||||
echo "Error found in test output! Failing build."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/net v0.36.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
)
|
||||
|
||||
@@ -10,12 +10,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
+14
-14
@@ -447,30 +447,30 @@ void Client::SendPostEnterWorld() {
|
||||
|
||||
bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app)
|
||||
{
|
||||
if (app->size != sizeof(LoginInfo_Struct)) {
|
||||
if (app->size != sizeof(LoginInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *login_info = (LoginInfo_Struct *) app->pBuffer;
|
||||
auto *r = (LoginInfo *) app->pBuffer;
|
||||
|
||||
// Quagmire - max len for name is 18, pass 15
|
||||
char name[19] = {0};
|
||||
char password[16] = {0};
|
||||
strn0cpy(name, (char *) login_info->login_info, 18);
|
||||
strn0cpy(password, (char *) &(login_info->login_info[strlen(name) + 1]), 15);
|
||||
strn0cpy(name, (char *) r->login_info, 18);
|
||||
strn0cpy(password, (char *) &(r->login_info[strlen(name) + 1]), 15);
|
||||
|
||||
LogDebug("Receiving Login Info Packet from Client | name [{0}] password [{1}]", name, password);
|
||||
LogDebug("Receiving login info packet from client | name [{}] password [{}]", name, password);
|
||||
|
||||
if (strlen(password) <= 1) {
|
||||
LogInfo("Login without a password");
|
||||
return false;
|
||||
}
|
||||
|
||||
is_player_zoning = (login_info->zoning == 1);
|
||||
is_player_zoning = (r->zoning == 1);
|
||||
|
||||
uint32 id = Strings::ToInt(name);
|
||||
if (id == 0) {
|
||||
LogWarning("Receiving Login Info Packet from Client | account_id is 0 - disconnecting");
|
||||
LogWarning("Receiving login info packet from client | account_id is 0 - disconnecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -480,15 +480,15 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app)
|
||||
LogClientLogin("Checking authentication id [{}] passed", id);
|
||||
if (!is_player_zoning) {
|
||||
// Track who is in and who is out of the game
|
||||
char *inout= (char *) "";
|
||||
std::string in_out;
|
||||
|
||||
if (cle->GetOnline() == CLE_Status::Never){
|
||||
if (cle->GetOnline() == CLE_Status::Never) {
|
||||
// Desktop -> Char Select
|
||||
inout = (char *) "In";
|
||||
in_out = "in";
|
||||
}
|
||||
else {
|
||||
// Game -> Char Select
|
||||
inout=(char *) "Out";
|
||||
in_out = "out";
|
||||
}
|
||||
|
||||
// Always at Char select at this point.
|
||||
@@ -497,7 +497,7 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app)
|
||||
// Could use a Logging Out Completely message somewhere.
|
||||
cle->SetOnline(CLE_Status::CharSelect);
|
||||
|
||||
LogInfo("Account ({}) Logging({}) to character select :: LSID [{}] ", cle->AccountName(), inout, cle->LSID());
|
||||
LogInfo("Account ({}) Logging ({}) to character select :: LSID [{}] ", cle->AccountName(), in_out, cle->LSID());
|
||||
}
|
||||
else {
|
||||
cle->SetOnline();
|
||||
@@ -545,7 +545,7 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app)
|
||||
if (!skip_char_info && !custom_files_key.empty() && cle->Admin() < RuleI(World, CustomFilesAdminLevel)) {
|
||||
// Modified clients can utilize this unused block in login_info to send custom payloads on login
|
||||
// which indicates they are using custom client files with the correct version, based on key payload.
|
||||
const auto client_key = std::string(reinterpret_cast<char*>(login_info->unknown064));
|
||||
const auto client_key = std::string(reinterpret_cast<char*>(r->unknown064));
|
||||
if (custom_files_key != client_key) {
|
||||
std::string message = fmt::format("Missing Files [{}]", RuleS(World, CustomFilesUrl) );
|
||||
SendUnsupportedClientPacket(message);
|
||||
@@ -1434,7 +1434,7 @@ void Client::EnterWorld(bool TryBootup) {
|
||||
}
|
||||
else {
|
||||
if (TryBootup) {
|
||||
LogInfo("Attempting autobootup of [{}] ([{}]:[{}])", zone_name, zone_id, instance_id);
|
||||
LogInfo("Attempting autobootup of [{}] [{}] [{}]", zone_name, zone_id, instance_id);
|
||||
autobootup_timeout.Start();
|
||||
zone_waiting_for_bootup = zoneserver_list.TriggerBootup(zone_id, instance_id);
|
||||
if (zone_waiting_for_bootup == 0) {
|
||||
|
||||
+159
-182
@@ -1,20 +1,3 @@
|
||||
/* EQEMu: Everquest Server Emulator
|
||||
Copyright (C) 2001-2005 EQEMu Development Team (http://eqemulator.net)
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
||||
are required to give you total support for your newly bought product;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "../common/global_define.h"
|
||||
#include "cliententry.h"
|
||||
#include "clientlist.h"
|
||||
@@ -24,93 +7,86 @@
|
||||
#include "worlddb.h"
|
||||
#include "zoneserver.h"
|
||||
#include "world_config.h"
|
||||
#include "../common/guilds.h"
|
||||
#include "../common/strings.h"
|
||||
|
||||
extern uint32 numplayers;
|
||||
extern LoginServerList loginserverlist;
|
||||
extern ClientList client_list;
|
||||
extern volatile bool RunLoops;
|
||||
extern uint32 numplayers;
|
||||
extern LoginServerList loginserverlist;
|
||||
extern ClientList client_list;
|
||||
extern volatile bool RunLoops;
|
||||
extern SharedTaskManager shared_task_manager;
|
||||
|
||||
/**
|
||||
* @param in_id
|
||||
* @param in_loginserver_id
|
||||
* @param in_loginserver_name
|
||||
* @param in_login_name
|
||||
* @param in_login_key
|
||||
* @param in_is_world_admin
|
||||
* @param ip
|
||||
* @param local
|
||||
*/
|
||||
ClientListEntry::ClientListEntry(
|
||||
uint32 in_id,
|
||||
uint32 in_loginserver_id,
|
||||
const char *in_loginserver_name,
|
||||
const char *in_login_name,
|
||||
const char *in_login_key,
|
||||
int16 in_is_world_admin,
|
||||
uint32 ip,
|
||||
uint32 id,
|
||||
uint32 login_server_id,
|
||||
const char *login_server_name,
|
||||
const char *account_name,
|
||||
const char *login_key,
|
||||
int16 is_world_admin,
|
||||
uint32 ip_address,
|
||||
uint8 local
|
||||
)
|
||||
: id(in_id)
|
||||
: m_id(id)
|
||||
{
|
||||
ClearVars(true);
|
||||
|
||||
LogDebug(
|
||||
"in_id [{0}] in_loginserver_id [{1}] in_loginserver_name [{2}] in_login_name [{3}] in_login_key [{4}] "
|
||||
" in_is_world_admin [{5}] ip [{6}] local [{7}]",
|
||||
in_id,
|
||||
in_loginserver_id,
|
||||
in_loginserver_name,
|
||||
in_login_name,
|
||||
in_login_key,
|
||||
in_is_world_admin,
|
||||
ip,
|
||||
"id [{}] loginserver_id [{}] loginserver_name [{}] login_name [{}] login_key [{}] is_world_admin [{}] ip [{}] local [{}]",
|
||||
id,
|
||||
login_server_id,
|
||||
login_server_name,
|
||||
account_name,
|
||||
login_key,
|
||||
is_world_admin,
|
||||
ip_address,
|
||||
local
|
||||
);
|
||||
|
||||
pIP = ip;
|
||||
pLSID = in_loginserver_id;
|
||||
if (in_loginserver_id > 0) {
|
||||
paccountid = database.GetAccountIDFromLSID(in_loginserver_name, in_loginserver_id, paccountname, &padmin);
|
||||
m_ip_address = ip_address;
|
||||
m_login_server_id = login_server_id;
|
||||
if (login_server_id > 0) {
|
||||
m_account_id = database.GetAccountIDFromLSID(
|
||||
login_server_name,
|
||||
login_server_id,
|
||||
m_account_name,
|
||||
&m_admin
|
||||
);
|
||||
}
|
||||
|
||||
strn0cpy(loginserver_account_name, in_login_name, sizeof(loginserver_account_name));
|
||||
strn0cpy(plskey, in_login_key, sizeof(plskey));
|
||||
strn0cpy(source_loginserver, in_loginserver_name, sizeof(source_loginserver));
|
||||
pworldadmin = in_is_world_admin;
|
||||
plocal = (local == 1);
|
||||
strn0cpy(m_login_account_name, account_name, sizeof(m_login_account_name));
|
||||
strn0cpy(m_key, login_key, sizeof(m_key));
|
||||
strn0cpy(m_source_loginserver, login_server_name, sizeof(m_source_loginserver));
|
||||
|
||||
memset(pLFGComments, 0, 64);
|
||||
m_world_admin = is_world_admin;
|
||||
m_is_local = (local == 1);
|
||||
|
||||
memset(m_lfg_comments, 0, 64);
|
||||
}
|
||||
|
||||
ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer *iZS, ServerClientList_Struct *scl, CLE_Status iOnline)
|
||||
: id(in_id)
|
||||
ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer *z, ServerClientList_Struct *scl, CLE_Status online)
|
||||
: m_id(in_id)
|
||||
{
|
||||
ClearVars(true);
|
||||
|
||||
pIP = 0;
|
||||
pLSID = scl->LSAccountID;
|
||||
strn0cpy(loginserver_account_name, scl->name, sizeof(loginserver_account_name));
|
||||
strn0cpy(plskey, scl->lskey, sizeof(plskey));
|
||||
pworldadmin = 0;
|
||||
m_ip_address = 0;
|
||||
m_login_server_id = scl->LSAccountID;
|
||||
strn0cpy(m_login_account_name, scl->name, sizeof(m_login_account_name));
|
||||
strn0cpy(m_key, scl->lskey, sizeof(m_key));
|
||||
m_world_admin = 0;
|
||||
|
||||
paccountid = scl->AccountID;
|
||||
strn0cpy(paccountname, scl->AccountName, sizeof(paccountname));
|
||||
padmin = scl->Admin;
|
||||
m_account_id = scl->AccountID;
|
||||
strn0cpy(m_account_name, scl->AccountName, sizeof(m_account_name));
|
||||
m_admin = scl->Admin;
|
||||
|
||||
pinstance = 0;
|
||||
pLFGFromLevel = 0;
|
||||
pLFGToLevel = 0;
|
||||
pLFGMatchFilter = false;
|
||||
memset(pLFGComments, 0, 64);
|
||||
m_instance = 0;
|
||||
m_lfg_from_level = 0;
|
||||
m_lfg_to_level = 0;
|
||||
m_lfg_match_filter = false;
|
||||
memset(m_lfg_comments, 0, 64);
|
||||
|
||||
if (iOnline >= CLE_Status::Zoning) {
|
||||
Update(iZS, scl, iOnline);
|
||||
if (online >= CLE_Status::Zoning) {
|
||||
Update(z, scl, online);
|
||||
}
|
||||
else {
|
||||
SetOnline(iOnline);
|
||||
SetOnline(online);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,16 +96,16 @@ ClientListEntry::~ClientListEntry()
|
||||
Camp(); // updates zoneserver's numplayers
|
||||
client_list.RemoveCLEReferances(this);
|
||||
}
|
||||
for (auto& elem : tell_queue) {
|
||||
for (auto &elem: m_tell_queue) {
|
||||
safe_delete_array(elem);
|
||||
}
|
||||
tell_queue.clear();
|
||||
m_tell_queue.clear();
|
||||
}
|
||||
|
||||
void ClientListEntry::SetChar(uint32 iCharID, const char *iCharName)
|
||||
{
|
||||
pcharid = iCharID;
|
||||
strn0cpy(pname, iCharName, sizeof(pname));
|
||||
m_char_id = iCharID;
|
||||
strn0cpy(m_char_name, iCharName, sizeof(m_char_name));
|
||||
}
|
||||
|
||||
void ClientListEntry::SetOnline(CLE_Status iOnline)
|
||||
@@ -142,20 +118,20 @@ void ClientListEntry::SetOnline(CLE_Status iOnline)
|
||||
static_cast<int>(iOnline)
|
||||
);
|
||||
|
||||
if (iOnline >= CLE_Status::Online && pOnline < CLE_Status::Online) {
|
||||
if (iOnline >= CLE_Status::Online && m_online < CLE_Status::Online) {
|
||||
numplayers++;
|
||||
}
|
||||
else if (iOnline < CLE_Status::Online && pOnline >= CLE_Status::Online) {
|
||||
else if (iOnline < CLE_Status::Online && m_online >= CLE_Status::Online) {
|
||||
numplayers--;
|
||||
}
|
||||
if (iOnline != CLE_Status::Online || pOnline < CLE_Status::Online) {
|
||||
pOnline = iOnline;
|
||||
if (iOnline != CLE_Status::Online || m_online < CLE_Status::Online) {
|
||||
m_online = iOnline;
|
||||
}
|
||||
if (iOnline < CLE_Status::Zoning) {
|
||||
Camp();
|
||||
}
|
||||
if (pOnline >= CLE_Status::Online) {
|
||||
stale = 0;
|
||||
if (m_online >= CLE_Status::Online) {
|
||||
m_stale = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,49 +169,49 @@ void ClientListEntry::LSZoneChange(ZoneToZone_Struct *ztz)
|
||||
|
||||
void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_Status iOnline)
|
||||
{
|
||||
if (pzoneserver != iZS) {
|
||||
if (pzoneserver) {
|
||||
pzoneserver->RemovePlayer();
|
||||
LSUpdate(pzoneserver);
|
||||
if (m_zone_server != iZS) {
|
||||
if (m_zone_server) {
|
||||
m_zone_server->RemovePlayer();
|
||||
LSUpdate(m_zone_server);
|
||||
}
|
||||
if (iZS) {
|
||||
iZS->AddPlayer();
|
||||
LSUpdate(iZS);
|
||||
}
|
||||
}
|
||||
pzoneserver = iZS;
|
||||
pzone = scl->zone;
|
||||
pinstance = scl->instance_id;
|
||||
pcharid = scl->charid;
|
||||
m_zone_server = iZS;
|
||||
m_zone = scl->zone;
|
||||
m_instance = scl->instance_id;
|
||||
m_char_id = scl->charid;
|
||||
|
||||
strcpy(pname, scl->name);
|
||||
if (paccountid == 0) {
|
||||
paccountid = scl->AccountID;
|
||||
strcpy(paccountname, scl->AccountName);
|
||||
strcpy(loginserver_account_name, scl->AccountName);
|
||||
pIP = scl->IP;
|
||||
pLSID = scl->LSAccountID;
|
||||
strn0cpy(plskey, scl->lskey, sizeof(plskey));
|
||||
strcpy(m_char_name, scl->name);
|
||||
if (m_account_id == 0) {
|
||||
m_account_id = scl->AccountID;
|
||||
strcpy(m_account_name, scl->AccountName);
|
||||
strcpy(m_login_account_name, scl->AccountName);
|
||||
m_ip_address = scl->IP;
|
||||
m_login_server_id = scl->LSAccountID;
|
||||
strn0cpy(m_key, scl->lskey, sizeof(m_key));
|
||||
}
|
||||
padmin = scl->Admin;
|
||||
plevel = scl->level;
|
||||
pclass_ = scl->class_;
|
||||
prace = scl->race;
|
||||
panon = scl->anon;
|
||||
ptellsoff = scl->tellsoff;
|
||||
pguild_id = scl->guild_id;
|
||||
pguild_rank = scl->guild_rank;
|
||||
pguild_tribute_opt_in = scl->guild_tribute_opt_in;
|
||||
pLFG = scl->LFG;
|
||||
gm = scl->gm;
|
||||
pClientVersion = scl->ClientVersion;
|
||||
m_admin = scl->Admin;
|
||||
m_level = scl->level;
|
||||
m_class_ = scl->class_;
|
||||
m_race = scl->race;
|
||||
m_anon = scl->anon;
|
||||
m_tells_off = scl->tellsoff;
|
||||
m_guild_id = scl->guild_id;
|
||||
m_guild_rank = scl->guild_rank;
|
||||
m_guild_tribute_opt_in = scl->guild_tribute_opt_in;
|
||||
m_lfg = scl->LFG;
|
||||
m_gm = scl->gm;
|
||||
m_client_version = scl->ClientVersion;
|
||||
|
||||
// Fields from the LFG Window
|
||||
if ((scl->LFGFromLevel != 0) && (scl->LFGToLevel != 0)) {
|
||||
pLFGFromLevel = scl->LFGFromLevel;
|
||||
pLFGToLevel = scl->LFGToLevel;
|
||||
pLFGMatchFilter = scl->LFGMatchFilter;
|
||||
memcpy(pLFGComments, scl->LFGComments, sizeof(pLFGComments));
|
||||
m_lfg_from_level = scl->LFGFromLevel;
|
||||
m_lfg_to_level = scl->LFGToLevel;
|
||||
m_lfg_match_filter = scl->LFGMatchFilter;
|
||||
memcpy(m_lfg_comments, scl->LFGComments, sizeof(m_lfg_comments));
|
||||
}
|
||||
|
||||
SetOnline(iOnline);
|
||||
@@ -243,76 +219,76 @@ void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_
|
||||
|
||||
void ClientListEntry::LeavingZone(ZoneServer *iZS, CLE_Status iOnline)
|
||||
{
|
||||
if (iZS != 0 && iZS != pzoneserver) {
|
||||
if (iZS != 0 && iZS != m_zone_server) {
|
||||
return;
|
||||
}
|
||||
SetOnline(iOnline);
|
||||
|
||||
shared_task_manager.RemoveActiveInvitationByCharacterID(CharID());
|
||||
|
||||
if (pzoneserver) {
|
||||
pzoneserver->RemovePlayer();
|
||||
LSUpdate(pzoneserver);
|
||||
if (m_zone_server) {
|
||||
m_zone_server->RemovePlayer();
|
||||
LSUpdate(m_zone_server);
|
||||
}
|
||||
pzoneserver = 0;
|
||||
pzone = 0;
|
||||
m_zone_server = 0;
|
||||
m_zone = 0;
|
||||
}
|
||||
|
||||
void ClientListEntry::ClearVars(bool iAll)
|
||||
{
|
||||
if (iAll) {
|
||||
pOnline = CLE_Status::Never;
|
||||
stale = 0;
|
||||
m_online = CLE_Status::Never;
|
||||
m_stale = 0;
|
||||
|
||||
pLSID = 0;
|
||||
memset(loginserver_account_name, 0, sizeof(loginserver_account_name));
|
||||
memset(plskey, 0, sizeof(plskey));
|
||||
pworldadmin = 0;
|
||||
m_login_server_id = 0;
|
||||
memset(m_login_account_name, 0, sizeof(m_login_account_name));
|
||||
memset(m_key, 0, sizeof(m_key));
|
||||
m_world_admin = 0;
|
||||
|
||||
paccountid = 0;
|
||||
memset(paccountname, 0, sizeof(paccountname));
|
||||
padmin = AccountStatus::Player;
|
||||
m_account_id = 0;
|
||||
memset(m_account_name, 0, sizeof(m_account_name));
|
||||
m_admin = AccountStatus::Player;
|
||||
}
|
||||
pzoneserver = 0;
|
||||
pzone = 0;
|
||||
pcharid = 0;
|
||||
memset(pname, 0, sizeof(pname));
|
||||
plevel = 0;
|
||||
pclass_ = 0;
|
||||
prace = 0;
|
||||
panon = 0;
|
||||
ptellsoff = 0;
|
||||
pguild_id = GUILD_NONE;
|
||||
pguild_rank = 0;
|
||||
pLFG = 0;
|
||||
gm = 0;
|
||||
pClientVersion = 0;
|
||||
for (auto& elem : tell_queue) {
|
||||
m_zone_server = 0;
|
||||
m_zone = 0;
|
||||
m_char_id = 0;
|
||||
memset(m_char_name, 0, sizeof(m_char_name));
|
||||
m_level = 0;
|
||||
m_class_ = 0;
|
||||
m_race = 0;
|
||||
m_anon = 0;
|
||||
m_tells_off = 0;
|
||||
m_guild_id = GUILD_NONE;
|
||||
m_guild_rank = 0;
|
||||
m_lfg = 0;
|
||||
m_gm = 0;
|
||||
m_client_version = 0;
|
||||
for (auto &elem: m_tell_queue) {
|
||||
safe_delete_array(elem);
|
||||
}
|
||||
tell_queue.clear();
|
||||
m_tell_queue.clear();
|
||||
}
|
||||
|
||||
void ClientListEntry::Camp(ZoneServer *iZS)
|
||||
{
|
||||
if (iZS != 0 && iZS != pzoneserver) {
|
||||
if (iZS != 0 && iZS != m_zone_server) {
|
||||
return;
|
||||
}
|
||||
if (pzoneserver) {
|
||||
pzoneserver->RemovePlayer();
|
||||
LSUpdate(pzoneserver);
|
||||
if (m_zone_server) {
|
||||
m_zone_server->RemovePlayer();
|
||||
LSUpdate(m_zone_server);
|
||||
}
|
||||
|
||||
ClearVars();
|
||||
|
||||
stale = 0;
|
||||
m_stale = 0;
|
||||
}
|
||||
|
||||
bool ClientListEntry::CheckStale()
|
||||
{
|
||||
stale++;
|
||||
if (stale > 20) {
|
||||
if (pOnline > CLE_Status::Offline) {
|
||||
m_stale++;
|
||||
if (m_stale > 20) {
|
||||
if (m_online > CLE_Status::Offline) {
|
||||
SetOnline(CLE_Status::Offline);
|
||||
}
|
||||
|
||||
@@ -324,48 +300,50 @@ bool ClientListEntry::CheckStale()
|
||||
bool ClientListEntry::CheckAuth(uint32 loginserver_account_id, const char *key_password)
|
||||
{
|
||||
LogDebug(
|
||||
"ls_account_id [{0}] key_password [{1}] plskey [{2}]",
|
||||
"ls_account_id [{}] key_password [{}] key [{}]",
|
||||
loginserver_account_id,
|
||||
key_password,
|
||||
plskey
|
||||
m_key
|
||||
);
|
||||
if (pLSID == loginserver_account_id && strncmp(plskey, key_password, 10) == 0) {
|
||||
|
||||
if (m_login_server_id == loginserver_account_id && strncmp(m_key, key_password, 10) == 0) {
|
||||
LogDebug(
|
||||
"ls_account_id [{0}] key_password [{1}] plskey [{2}] lsid [{3}] paccountid [{4}]",
|
||||
"ls_account_id [{}] key_password [{}] m_key [{}] lsid [{}] m_account_id [{}]",
|
||||
loginserver_account_id,
|
||||
key_password,
|
||||
plskey,
|
||||
m_key,
|
||||
LSID(),
|
||||
paccountid
|
||||
m_account_id
|
||||
);
|
||||
|
||||
if (paccountid == 0 && LSID() > 0) {
|
||||
// create account if it doesn't exist
|
||||
if (m_account_id == 0 && LSID() > 0) {
|
||||
int16 default_account_status = WorldConfig::get()->DefaultStatus;
|
||||
|
||||
paccountid = database.CreateAccount(
|
||||
loginserver_account_name,
|
||||
m_account_id = database.CreateAccount(
|
||||
m_login_account_name,
|
||||
std::string(),
|
||||
default_account_status,
|
||||
source_loginserver,
|
||||
m_source_loginserver,
|
||||
LSID()
|
||||
);
|
||||
|
||||
if (!paccountid) {
|
||||
LogInfo(
|
||||
"Error adding local account for LS login: [{0}:{1}], duplicate name",
|
||||
source_loginserver,
|
||||
loginserver_account_name
|
||||
if (!m_account_id) {
|
||||
LogError(
|
||||
"Error adding local account for LS login [{}] [{}], duplicate name",
|
||||
m_source_loginserver,
|
||||
m_login_account_name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
strn0cpy(paccountname, loginserver_account_name, sizeof(paccountname));
|
||||
padmin = default_account_status;
|
||||
strn0cpy(m_account_name, m_login_account_name, sizeof(m_account_name));
|
||||
m_admin = default_account_status;
|
||||
}
|
||||
std::string lsworldadmin;
|
||||
if (database.GetVariable("honorlsworldadmin", lsworldadmin)) {
|
||||
if (Strings::ToInt(lsworldadmin) == 1 && pworldadmin != 0 && (padmin < pworldadmin || padmin == AccountStatus::Player)) {
|
||||
padmin = pworldadmin;
|
||||
if (Strings::ToInt(lsworldadmin) == 1 && m_world_admin != 0 &&
|
||||
(m_admin < m_world_admin || m_admin == AccountStatus::Player)) {
|
||||
m_admin = m_world_admin;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -380,8 +358,8 @@ void ClientListEntry::ProcessTellQueue()
|
||||
}
|
||||
|
||||
ServerPacket *pack;
|
||||
auto it = tell_queue.begin();
|
||||
while (it != tell_queue.end()) {
|
||||
auto it = m_tell_queue.begin();
|
||||
while (it != m_tell_queue.end()) {
|
||||
pack = new ServerPacket(
|
||||
ServerOP_ChannelMessage,
|
||||
sizeof(ServerChannelMessage_Struct) + strlen((*it)->message) + 1
|
||||
@@ -390,8 +368,7 @@ void ClientListEntry::ProcessTellQueue()
|
||||
Server()->SendPacket(pack);
|
||||
safe_delete(pack);
|
||||
safe_delete_array(*it);
|
||||
it = tell_queue.erase(it);
|
||||
it = m_tell_queue.erase(it);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+100
-118
@@ -8,8 +8,7 @@
|
||||
#include "../common/rulesys.h"
|
||||
#include <vector>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
typedef enum {
|
||||
Never,
|
||||
Offline,
|
||||
Online,
|
||||
@@ -18,7 +17,7 @@ typedef enum
|
||||
InZone
|
||||
} CLE_Status;
|
||||
|
||||
static const char * CLEStatusString[] = {
|
||||
static const char *CLEStatusString[] = {
|
||||
"Never",
|
||||
"Offline",
|
||||
"Online",
|
||||
@@ -33,148 +32,131 @@ struct ServerClientList_Struct;
|
||||
class ClientListEntry {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* @param in_loginserver_id
|
||||
* @param in_loginserver_name
|
||||
* @param in_login_name
|
||||
* @param in_login_key
|
||||
* @param in_is_world_admin
|
||||
* @param ip
|
||||
* @param local
|
||||
*/
|
||||
ClientListEntry(
|
||||
uint32 id,
|
||||
uint32 in_loginserver_id,
|
||||
const char *in_loginserver_name,
|
||||
const char *in_login_name,
|
||||
const char *in_login_key,
|
||||
int16 in_is_world_admin = 0,
|
||||
uint32 ip = 0,
|
||||
uint32 login_server_id,
|
||||
const char *login_server_name,
|
||||
const char *account_name,
|
||||
const char *login_key,
|
||||
int16 is_world_admin = 0,
|
||||
uint32 ip_address = 0,
|
||||
uint8 local = 0
|
||||
);
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* @param iZS
|
||||
* @param scl
|
||||
* @param iOnline
|
||||
*/
|
||||
ClientListEntry(uint32 id, uint32 iAccID, const char* iAccName, MD5& iMD5Pass, int16 iAdmin = AccountStatus::Player);
|
||||
ClientListEntry(uint32 id, ZoneServer* iZS, ServerClientList_Struct* scl, CLE_Status iOnline);
|
||||
ClientListEntry(uint32 id, ZoneServer *z, ServerClientList_Struct *scl, CLE_Status online);
|
||||
~ClientListEntry();
|
||||
bool CheckStale();
|
||||
void Update(ZoneServer* zoneserver, ServerClientList_Struct* scl, CLE_Status iOnline = CLE_Status::InZone);
|
||||
void LSUpdate(ZoneServer* zoneserver);
|
||||
void LSZoneChange(ZoneToZone_Struct* ztz);
|
||||
bool CheckAuth(uint32 loginserver_account_id, const char* key_password);
|
||||
void SetOnline(CLE_Status iOnline = CLE_Status::Online);
|
||||
void SetChar(uint32 iCharID, const char* iCharName);
|
||||
inline CLE_Status Online() { return pOnline; }
|
||||
inline const uint32 GetID() const { return id; }
|
||||
inline const uint32 GetIP() const { return pIP; }
|
||||
inline void SetIP(const uint32& iIP) { pIP = iIP; }
|
||||
inline void KeepAlive() { stale = 0; }
|
||||
inline uint8 GetStaleCounter() const { return stale; }
|
||||
void LeavingZone(ZoneServer* iZS = 0, CLE_Status iOnline = CLE_Status::Offline);
|
||||
void Camp(ZoneServer* iZS = 0);
|
||||
bool CheckStale();
|
||||
void Update(ZoneServer *zoneserver, ServerClientList_Struct *scl, CLE_Status iOnline = CLE_Status::InZone);
|
||||
void LSUpdate(ZoneServer *zoneserver);
|
||||
void LSZoneChange(ZoneToZone_Struct *ztz);
|
||||
bool CheckAuth(uint32 loginserver_account_id, const char *key_password);
|
||||
void SetOnline(CLE_Status iOnline = CLE_Status::Online);
|
||||
void SetChar(uint32 iCharID, const char *iCharName);
|
||||
inline CLE_Status Online() { return m_online; }
|
||||
inline const uint32 GetID() const { return m_id; }
|
||||
inline const uint32 GetIP() const { return m_ip_address; }
|
||||
inline void SetIP(const uint32 &iIP) { m_ip_address = iIP; }
|
||||
inline void KeepAlive() { m_stale = 0; }
|
||||
inline uint8 GetStaleCounter() const { return m_stale; }
|
||||
void LeavingZone(ZoneServer *iZS = 0, CLE_Status iOnline = CLE_Status::Offline);
|
||||
void Camp(ZoneServer *iZS = 0);
|
||||
|
||||
// Login Server stuff
|
||||
inline const char* LoginServer() const { return source_loginserver; }
|
||||
inline uint32 LSID() const { return pLSID; }
|
||||
inline uint32 LSAccountID() const { return pLSID; }
|
||||
inline const char* LSName() const { return loginserver_account_name; }
|
||||
inline int16 WorldAdmin() const { return pworldadmin; }
|
||||
inline const char* GetLSKey() const { return plskey; }
|
||||
inline const CLE_Status GetOnline() const { return pOnline; }
|
||||
inline const char *LoginServer() const { return m_source_loginserver; }
|
||||
inline uint32 LSID() const { return m_login_server_id; }
|
||||
inline uint32 LSAccountID() const { return m_login_server_id; }
|
||||
inline const char *LSName() const { return m_login_account_name; }
|
||||
inline int16 WorldAdmin() const { return m_world_admin; }
|
||||
inline const char *GetLSKey() const { return m_key; }
|
||||
inline const CLE_Status GetOnline() const { return m_online; }
|
||||
|
||||
// Account stuff
|
||||
inline uint32 AccountID() const { return paccountid; }
|
||||
inline const char* AccountName() const { return paccountname; }
|
||||
inline int16 Admin() const { return padmin; }
|
||||
inline void SetAdmin(uint16 iAdmin) { padmin = iAdmin; }
|
||||
inline uint32 AccountID() const { return m_account_id; }
|
||||
inline const char *AccountName() const { return m_account_name; }
|
||||
inline int16 Admin() const { return m_admin; }
|
||||
inline void SetAdmin(uint16 iAdmin) { m_admin = iAdmin; }
|
||||
|
||||
// Character info
|
||||
inline ZoneServer *Server() const { return pzoneserver; }
|
||||
inline void ClearServer() { pzoneserver = 0; }
|
||||
inline uint32 CharID() const { return pcharid; }
|
||||
inline const char *name() const { return pname; }
|
||||
inline uint32 zone() const { return pzone; }
|
||||
inline uint16 instance() const { return pinstance; }
|
||||
inline uint8 level() const { return plevel; }
|
||||
inline uint8 class_() const { return pclass_; }
|
||||
inline uint16 race() const { return prace; }
|
||||
inline uint8 Anon() { return panon; }
|
||||
inline uint8 TellsOff() const { return ptellsoff; }
|
||||
inline uint32 GuildID() const { return pguild_id; }
|
||||
inline uint32 GuildRank() const { return pguild_rank; }
|
||||
inline bool GuildTributeOptIn() const { return pguild_tribute_opt_in; }
|
||||
inline void SetGuild(uint32 guild_id) { pguild_id = guild_id; }
|
||||
inline void SetGuildTributeOptIn(bool opt) { pguild_tribute_opt_in = opt; }
|
||||
inline bool LFG() const { return pLFG; }
|
||||
inline uint8 GetGM() const { return gm; }
|
||||
inline void SetGM(uint8 igm) { gm = igm; }
|
||||
inline void SetZone(uint32 zone) { pzone = zone; }
|
||||
inline bool IsLocalClient() const { return plocal; }
|
||||
inline uint8 GetLFGFromLevel() const { return pLFGFromLevel; }
|
||||
inline uint8 GetLFGToLevel() const { return pLFGToLevel; }
|
||||
inline bool GetLFGMatchFilter() const { return pLFGMatchFilter; }
|
||||
inline const char *GetLFGComments() const { return pLFGComments; }
|
||||
inline uint8 GetClientVersion() { return pClientVersion; }
|
||||
inline ZoneServer *Server() const { return m_zone_server; }
|
||||
inline void ClearServer() { m_zone_server = 0; }
|
||||
inline uint32 CharID() const { return m_char_id; }
|
||||
inline const char *name() const { return m_char_name; }
|
||||
inline uint32 zone() const { return m_zone; }
|
||||
inline uint16 instance() const { return m_instance; }
|
||||
inline uint8 level() const { return m_level; }
|
||||
inline uint8 class_() const { return m_class_; }
|
||||
inline uint16 race() const { return m_race; }
|
||||
inline uint8 Anon() { return m_anon; }
|
||||
inline uint8 TellsOff() const { return m_tells_off; }
|
||||
inline uint32 GuildID() const { return m_guild_id; }
|
||||
inline uint32 GuildRank() const { return m_guild_rank; }
|
||||
inline bool GuildTributeOptIn() const { return m_guild_tribute_opt_in; }
|
||||
inline void SetGuild(uint32 guild_id) { m_guild_id = guild_id; }
|
||||
inline void SetGuildTributeOptIn(bool opt) { m_guild_tribute_opt_in = opt; }
|
||||
inline bool LFG() const { return m_lfg; }
|
||||
inline uint8 GetGM() const { return m_gm; }
|
||||
inline void SetGM(uint8 igm) { m_gm = igm; }
|
||||
inline void SetZone(uint32 zone) { m_zone = zone; }
|
||||
inline bool IsLocalClient() const { return m_is_local; }
|
||||
inline uint8 GetLFGFromLevel() const { return m_lfg_from_level; }
|
||||
inline uint8 GetLFGToLevel() const { return m_lfg_to_level; }
|
||||
inline bool GetLFGMatchFilter() const { return m_lfg_match_filter; }
|
||||
inline const char *GetLFGComments() const { return m_lfg_comments; }
|
||||
inline uint8 GetClientVersion() { return m_client_version; }
|
||||
|
||||
inline bool TellQueueFull() const { return tell_queue.size() >= RuleI(World, TellQueueSize); }
|
||||
inline bool TellQueueEmpty() const { return tell_queue.empty(); }
|
||||
inline void PushToTellQueue(ServerChannelMessage_Struct *scm) { tell_queue.push_back(scm); }
|
||||
inline bool TellQueueFull() const { return m_tell_queue.size() >= RuleI(World, TellQueueSize); }
|
||||
inline bool TellQueueEmpty() const { return m_tell_queue.empty(); }
|
||||
inline void PushToTellQueue(ServerChannelMessage_Struct *scm) { m_tell_queue.push_back(scm); }
|
||||
void ProcessTellQueue();
|
||||
|
||||
void SetPendingDzInvite(ServerPacket* pack) { m_dz_invite.reset(pack->Copy()); };
|
||||
void SetPendingDzInvite(ServerPacket *pack) { m_dz_invite.reset(pack->Copy()); };
|
||||
std::unique_ptr<ServerPacket> GetPendingDzInvite() { return std::move(m_dz_invite); }
|
||||
|
||||
private:
|
||||
void ClearVars(bool iAll = false);
|
||||
void ClearVars(bool iAll = false);
|
||||
|
||||
const uint32 id;
|
||||
uint32 pIP;
|
||||
CLE_Status pOnline;
|
||||
uint8 stale;
|
||||
const uint32 m_id;
|
||||
uint32 m_ip_address;
|
||||
CLE_Status m_online;
|
||||
uint8 m_stale;
|
||||
|
||||
// Login Server stuff
|
||||
char source_loginserver[64]{}; //Loginserver we came from.
|
||||
uint32 pLSID;
|
||||
char loginserver_account_name[32]{};
|
||||
char plskey[16]{};
|
||||
int16 pworldadmin; // Login server's suggested admin status setting
|
||||
bool plocal;
|
||||
char m_source_loginserver[64]{}; //Loginserver we came from.
|
||||
uint32 m_login_server_id;
|
||||
char m_login_account_name[32]{};
|
||||
char m_key[16]{};
|
||||
int16 m_world_admin; // Login server's suggested admin status setting
|
||||
bool m_is_local;
|
||||
|
||||
// Account stuff
|
||||
uint32 paccountid;
|
||||
char paccountname[32]{};
|
||||
int16 padmin{};
|
||||
uint32 m_account_id;
|
||||
char m_account_name[32]{};
|
||||
int16 m_admin{};
|
||||
|
||||
// Character info
|
||||
ZoneServer* pzoneserver{};
|
||||
uint32 pzone{};
|
||||
uint16 pinstance{};
|
||||
uint32 pcharid{};
|
||||
char pname[64]{};
|
||||
uint8 plevel{};
|
||||
uint8 pclass_{};
|
||||
uint16 prace{};
|
||||
uint8 panon{};
|
||||
uint8 ptellsoff{};
|
||||
uint32 pguild_id{};
|
||||
uint32 pguild_rank;
|
||||
bool pguild_tribute_opt_in{};
|
||||
bool pLFG{};
|
||||
uint8 gm{};
|
||||
uint8 pClientVersion{};
|
||||
uint8 pLFGFromLevel{};
|
||||
uint8 pLFGToLevel{};
|
||||
bool pLFGMatchFilter{};
|
||||
char pLFGComments[64]{};
|
||||
ZoneServer *m_zone_server{};
|
||||
uint32 m_zone{};
|
||||
uint16 m_instance{};
|
||||
uint32 m_char_id{};
|
||||
char m_char_name[64]{};
|
||||
uint8 m_level{};
|
||||
uint8 m_class_{};
|
||||
uint16 m_race{};
|
||||
uint8 m_anon{};
|
||||
uint8 m_tells_off{};
|
||||
uint32 m_guild_id{};
|
||||
uint32 m_guild_rank;
|
||||
bool m_guild_tribute_opt_in{};
|
||||
bool m_lfg{};
|
||||
uint8 m_gm{};
|
||||
uint8 m_client_version{};
|
||||
uint8 m_lfg_from_level{};
|
||||
uint8 m_lfg_to_level{};
|
||||
bool m_lfg_match_filter{};
|
||||
char m_lfg_comments[64]{};
|
||||
|
||||
// Tell Queue -- really a vector :D
|
||||
std::vector<ServerChannelMessage_Struct *> tell_queue;
|
||||
std::vector<ServerChannelMessage_Struct *> m_tell_queue;
|
||||
|
||||
std::unique_ptr<ServerPacket> m_dz_invite;
|
||||
};
|
||||
|
||||
+23
-5
@@ -331,8 +331,26 @@ void ClientList::SendCLEList(const int16& admin, const char* to, WorldTCPConnect
|
||||
}
|
||||
|
||||
|
||||
void ClientList::CLEAdd(uint32 iLSID, const char *iLoginServerName, const char* iLoginName, const char* iLoginKey, int16 iWorldAdmin, uint32 ip, uint8 local) {
|
||||
auto tmp = new ClientListEntry(GetNextCLEID(), iLSID, iLoginServerName, iLoginName, iLoginKey, iWorldAdmin, ip, local);
|
||||
void ClientList::CLEAdd(
|
||||
uint32 login_server_id,
|
||||
const char *login_server_name,
|
||||
const char *login_name,
|
||||
const char *login_key,
|
||||
int16 world_admin,
|
||||
uint32 ip_address,
|
||||
uint8 is_local
|
||||
)
|
||||
{
|
||||
auto tmp = new ClientListEntry(
|
||||
GetNextCLEID(),
|
||||
login_server_id,
|
||||
login_server_name,
|
||||
login_name,
|
||||
login_key,
|
||||
world_admin,
|
||||
ip_address,
|
||||
is_local
|
||||
);
|
||||
|
||||
clientlist.Append(tmp);
|
||||
}
|
||||
@@ -457,19 +475,19 @@ void ClientList::CLEKeepAlive(uint32 numupdates, uint32* wid) {
|
||||
}
|
||||
}
|
||||
|
||||
ClientListEntry *ClientList::CheckAuth(uint32 iLSID, const char *iKey)
|
||||
ClientListEntry *ClientList::CheckAuth(uint32 loginserver_account_id, const char *key)
|
||||
{
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
if (iterator.GetData()->CheckAuth(iLSID, iKey)) {
|
||||
if (iterator.GetData()->CheckAuth(loginserver_account_id, key)) {
|
||||
return iterator.GetData();
|
||||
}
|
||||
iterator.Advance();
|
||||
}
|
||||
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ClientList::SendOnlineGuildMembers(uint32 FromID, uint32 GuildID)
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ public:
|
||||
|
||||
void ClientUpdate(ZoneServer* zoneserver, ServerClientList_Struct* scl);
|
||||
void CLERemoveZSRef(ZoneServer* iZS);
|
||||
ClientListEntry* CheckAuth(uint32 iLSID, const char* iKey);
|
||||
ClientListEntry* CheckAuth(uint32 loginserver_account_id, const char* key);
|
||||
ClientListEntry* FindCharacter(const char* name);
|
||||
ClientListEntry* FindCLEByAccountID(uint32 iAccID);
|
||||
ClientListEntry* FindCLEByCharacterID(uint32 iCharID);
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
void DisconnectByIP(uint32 in_ip);
|
||||
void CLCheckStale();
|
||||
void CLEKeepAlive(uint32 numupdates, uint32* wid);
|
||||
void CLEAdd(uint32 iLSID, const char* iLoginServerName, const char* iLoginName, const char* iLoginKey, int16 iWorldAdmin = AccountStatus::Player, uint32 ip = 0, uint8 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);
|
||||
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
|
||||
bool IsAccountInGame(uint32 iLSID);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "zoneserver.h"
|
||||
#include "../common/rulesys.h"
|
||||
#include "../common/repositories/dynamic_zone_lockouts_repository.h"
|
||||
#include <cereal/types/utility.hpp>
|
||||
|
||||
extern ClientList client_list;
|
||||
extern ZSList zoneserver_list;
|
||||
@@ -169,6 +170,33 @@ void DynamicZoneManager::LoadTemplates()
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicZoneManager::SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id)
|
||||
{
|
||||
std::vector<std::pair<uint32_t, std::vector<DynamicZoneMember>>> dzs;
|
||||
dzs.reserve(dynamic_zone_cache.size());
|
||||
|
||||
for (const auto& [dz_id, dz] : dynamic_zone_cache)
|
||||
{
|
||||
dzs.emplace_back(dz_id, dz->GetMembers());
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
{
|
||||
cereal::BinaryOutputArchive archive(ss);
|
||||
archive(dzs);
|
||||
}
|
||||
|
||||
std::string_view sv = ss.view();
|
||||
|
||||
size_t size = sizeof(ServerDzCerealData_Struct) + sv.size();
|
||||
ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, static_cast<uint32_t>(size));
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack.pBuffer);
|
||||
buf->cereal_size = static_cast<uint32_t>(sv.size());
|
||||
memcpy(buf->cereal_data, sv.data(), sv.size());
|
||||
|
||||
zoneserver_list.SendPacket(zone_id, inst_id, &pack);
|
||||
}
|
||||
|
||||
void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack)
|
||||
{
|
||||
switch (pack->opcode)
|
||||
@@ -338,6 +366,15 @@ void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
{
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack->pBuffer);
|
||||
if (buf->zone_id != 0 && !dynamic_zone_cache.empty())
|
||||
{
|
||||
SendBulkMemberStatuses(buf->zone_id, buf->inst_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzUpdateMemberStatus:
|
||||
{
|
||||
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
|
||||
|
||||
@@ -30,6 +30,8 @@ public:
|
||||
std::unordered_map<uint32_t, std::unique_ptr<DynamicZone>> dynamic_zone_cache;
|
||||
|
||||
private:
|
||||
void SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id);
|
||||
|
||||
Timer m_process_throttle_timer{};
|
||||
std::unordered_map<uint32_t, DynamicZoneTemplatesRepository::DynamicZoneTemplates> m_dz_templates;
|
||||
};
|
||||
|
||||
+40
-40
@@ -46,8 +46,8 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p)
|
||||
const WorldConfig *Config = WorldConfig::get();
|
||||
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
|
||||
|
||||
UsertoWorldRequestLegacy_Struct *utwr = (UsertoWorldRequestLegacy_Struct *) p.Data();
|
||||
uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid);
|
||||
UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data();
|
||||
uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid);
|
||||
int16 status = database.GetAccountStatus(id);
|
||||
|
||||
LogDebug(
|
||||
@@ -63,11 +63,11 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p)
|
||||
|
||||
ServerPacket outpack;
|
||||
outpack.opcode = ServerOP_UsertoWorldRespLeg;
|
||||
outpack.size = sizeof(UsertoWorldResponseLegacy_Struct);
|
||||
outpack.size = sizeof(UsertoWorldResponseLegacy);
|
||||
outpack.pBuffer = new uchar[outpack.size];
|
||||
memset(outpack.pBuffer, 0, outpack.size);
|
||||
|
||||
UsertoWorldResponseLegacy_Struct *utwrs = (UsertoWorldResponseLegacy_Struct *) outpack.pBuffer;
|
||||
UsertoWorldResponseLegacy *utwrs = (UsertoWorldResponseLegacy *) outpack.pBuffer;
|
||||
utwrs->lsaccountid = utwr->lsaccountid;
|
||||
utwrs->ToID = utwr->FromID;
|
||||
utwrs->worldid = utwr->worldid;
|
||||
@@ -126,8 +126,8 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p)
|
||||
const WorldConfig *Config = WorldConfig::get();
|
||||
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
|
||||
|
||||
UsertoWorldRequest_Struct *utwr = (UsertoWorldRequest_Struct *) p.Data();
|
||||
uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid);
|
||||
UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data();
|
||||
uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid);
|
||||
int16 status = database.GetAccountStatus(id);
|
||||
|
||||
LogDebug(
|
||||
@@ -143,11 +143,11 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p)
|
||||
|
||||
ServerPacket outpack;
|
||||
outpack.opcode = ServerOP_UsertoWorldResp;
|
||||
outpack.size = sizeof(UsertoWorldResponse_Struct);
|
||||
outpack.size = sizeof(UsertoWorldResponse);
|
||||
outpack.pBuffer = new uchar[outpack.size];
|
||||
memset(outpack.pBuffer, 0, outpack.size);
|
||||
|
||||
UsertoWorldResponse_Struct *utwrs = (UsertoWorldResponse_Struct *) outpack.pBuffer;
|
||||
UsertoWorldResponse *utwrs = (UsertoWorldResponse *) outpack.pBuffer;
|
||||
utwrs->lsaccountid = utwr->lsaccountid;
|
||||
utwrs->ToID = utwr->FromID;
|
||||
strn0cpy(utwrs->login, utwr->login, 64);
|
||||
@@ -208,27 +208,27 @@ void LoginServer::ProcessLSClientAuthLegacy(uint16_t opcode, EQ::Net::Packet &p)
|
||||
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
|
||||
|
||||
try {
|
||||
auto client_authentication_request = p.GetSerialize<ClientAuthLegacy_Struct>(0);
|
||||
auto r = p.GetSerialize<ClientAuthLegacy>(0);
|
||||
|
||||
LogDebug(
|
||||
"Processing Loginserver Auth Legacy | account_id [{0}] account_name [{1}] key [{2}] admin [{3}] ip [{4}] "
|
||||
"local_network [{5}]",
|
||||
client_authentication_request.loginserver_account_id,
|
||||
client_authentication_request.loginserver_account_name,
|
||||
client_authentication_request.key,
|
||||
client_authentication_request.is_world_admin,
|
||||
client_authentication_request.ip,
|
||||
client_authentication_request.is_client_from_local_network
|
||||
"Processing Loginserver Auth Legacy | account_id [{}] account_name [{}] key [{}] admin [{}] ip [{}] "
|
||||
"local_network [{}]",
|
||||
r.loginserver_account_id,
|
||||
r.loginserver_account_name,
|
||||
r.key,
|
||||
r.is_world_admin,
|
||||
r.ip_address,
|
||||
r.is_client_from_local_network
|
||||
);
|
||||
|
||||
client_list.CLEAdd(
|
||||
client_authentication_request.loginserver_account_id,
|
||||
r.loginserver_account_id,
|
||||
"eqemu",
|
||||
client_authentication_request.loginserver_account_name,
|
||||
client_authentication_request.key,
|
||||
client_authentication_request.is_world_admin,
|
||||
client_authentication_request.ip,
|
||||
client_authentication_request.is_client_from_local_network
|
||||
r.loginserver_account_name,
|
||||
r.key,
|
||||
r.is_world_admin,
|
||||
r.ip_address,
|
||||
r.is_client_from_local_network
|
||||
);
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
@@ -242,28 +242,28 @@ void LoginServer::ProcessLSClientAuth(uint16_t opcode, EQ::Net::Packet &p)
|
||||
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
|
||||
|
||||
try {
|
||||
auto client_authentication_request = p.GetSerialize<ClientAuth_Struct>(0);
|
||||
auto r = p.GetSerialize<ClientAuth>(0);
|
||||
|
||||
LogDebug(
|
||||
"Processing Loginserver Auth | account_id [{0}] account_name [{1}] loginserver_name [{2}] key [{3}] "
|
||||
"admin [{4}] ip [{5}] local_network [{6}]",
|
||||
client_authentication_request.loginserver_account_id,
|
||||
client_authentication_request.account_name,
|
||||
client_authentication_request.loginserver_name,
|
||||
client_authentication_request.key,
|
||||
client_authentication_request.is_world_admin,
|
||||
client_authentication_request.ip,
|
||||
client_authentication_request.is_client_from_local_network
|
||||
"Processing Loginserver Auth | account_id [{}] account_name [{}] loginserver_name [{}] key [{}] "
|
||||
"admin [{}] ip [{}] local_network [{}]",
|
||||
r.loginserver_account_id,
|
||||
r.account_name,
|
||||
r.loginserver_name,
|
||||
r.key,
|
||||
r.is_world_admin,
|
||||
r.ip_address,
|
||||
r.is_client_from_local_network
|
||||
);
|
||||
|
||||
client_list.CLEAdd(
|
||||
client_authentication_request.loginserver_account_id,
|
||||
client_authentication_request.loginserver_name,
|
||||
client_authentication_request.account_name,
|
||||
client_authentication_request.key,
|
||||
client_authentication_request.is_world_admin,
|
||||
client_authentication_request.ip,
|
||||
client_authentication_request.is_client_from_local_network
|
||||
r.loginserver_account_id,
|
||||
r.loginserver_name,
|
||||
r.account_name,
|
||||
r.key,
|
||||
r.is_world_admin,
|
||||
r.ip_address,
|
||||
r.is_client_from_local_network
|
||||
);
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
#include "../common/events/player_event_logs.h"
|
||||
#include "../common/skill_caps.h"
|
||||
#include "../common/repositories/character_parcels_repository.h"
|
||||
#include "../common/ip_util.h"
|
||||
|
||||
SkillCaps skill_caps;
|
||||
ZoneStore zone_store;
|
||||
@@ -188,6 +189,11 @@ int main(int argc, char **argv)
|
||||
launcher_list.LoadList();
|
||||
zoneserver_list.Init();
|
||||
|
||||
if (IpUtil::IsPortInUse(Config->WorldIP, Config->WorldTCPPort)) {
|
||||
LogError("World port [{}] already in use", Config->WorldTCPPort);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<EQ::Net::ConsoleServer> console;
|
||||
if (Config->TelnetEnabled) {
|
||||
LogInfo("Console (TCP) listener started on [{}:{}]", Config->TelnetIP, Config->TelnetTCPPort);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "../common/zone_store.h"
|
||||
#include "../common/path_manager.h"
|
||||
#include "../common/database/database_update.h"
|
||||
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||
|
||||
extern ZSList zoneserver_list;
|
||||
extern WorldConfig Config;
|
||||
@@ -101,6 +102,13 @@ bool WorldBoot::HandleCommandInput(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
// check if we ran a valid command, this whole CLI handler needs to be improved at a later time
|
||||
std::string arg1 = argc >= 2 ? argv[1] : "";
|
||||
if (argc >= 2 && !Strings::Contains(arg1, ":")) {
|
||||
std::cout << "Invalid command, use --help to see available commands" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -405,6 +413,11 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
|
||||
LogInfo("Cleaning up instance corpses");
|
||||
database.CleanupInstanceCorpses();
|
||||
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
ZoneStateSpawnsRepository::PurgeInvalidZoneStates(database);
|
||||
ZoneStateSpawnsRepository::PurgeOldZoneStates(database);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,6 @@ WorldDatabase content_db;
|
||||
extern std::vector<RaceClassAllocation> character_create_allocations;
|
||||
extern std::vector<RaceClassCombos> character_create_race_class_combos;
|
||||
|
||||
|
||||
/**
|
||||
* @param account_id
|
||||
* @param out_app
|
||||
* @param client_version_bit
|
||||
*/
|
||||
void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit)
|
||||
{
|
||||
EQ::versions::ClientVersion
|
||||
|
||||
+2
-1
@@ -894,7 +894,8 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
||||
ServerReload::Type::Commands,
|
||||
ServerReload::Type::PerlExportSettings,
|
||||
ServerReload::Type::DataBucketsCache,
|
||||
ServerReload::Type::WorldRepop
|
||||
ServerReload::Type::WorldRepop,
|
||||
ServerReload::Type::WorldWithRespawn
|
||||
};
|
||||
|
||||
// Set requires_zone_booted flag before executing reload logic
|
||||
|
||||
@@ -1480,6 +1480,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
case ServerOP_DzSwapMembers:
|
||||
case ServerOP_DzRemoveAllMembers:
|
||||
case ServerOP_DzGetMemberStatuses:
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
case ServerOP_DzSetSecondsRemaining:
|
||||
case ServerOP_DzSetCompass:
|
||||
case ServerOP_DzSetSafeReturn:
|
||||
|
||||
+27
-18
@@ -2789,32 +2789,35 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
const uint16 entity_id = GetID();
|
||||
|
||||
if (
|
||||
!HasOwner() &&
|
||||
!IsMerc() &&
|
||||
!GetSwarmInfo() &&
|
||||
(!is_merchant || allow_merchant_corpse) &&
|
||||
(
|
||||
!HasOwner() &&
|
||||
!IsMerc() &&
|
||||
!GetSwarmInfo() &&
|
||||
(!is_merchant || allow_merchant_corpse) &&
|
||||
(
|
||||
killer &&
|
||||
(
|
||||
killer->IsClient() ||
|
||||
killer &&
|
||||
(
|
||||
killer->HasOwner() &&
|
||||
killer->GetUltimateOwner()->IsClient()
|
||||
) ||
|
||||
(
|
||||
killer->IsNPC() &&
|
||||
killer->CastToNPC()->GetSwarmInfo() &&
|
||||
killer->CastToNPC()->GetSwarmInfo()->GetOwner() &&
|
||||
killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient()
|
||||
killer->IsClient() ||
|
||||
(
|
||||
killer->HasOwner() &&
|
||||
killer->GetUltimateOwner()->IsClient()
|
||||
) ||
|
||||
(
|
||||
killer->IsNPC() &&
|
||||
killer->CastToNPC()->GetSwarmInfo() &&
|
||||
killer->CastToNPC()->GetSwarmInfo()->GetOwner() &&
|
||||
killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient()
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(
|
||||
killer_mob && is_ldon_treasure
|
||||
)
|
||||
) ||
|
||||
(
|
||||
killer_mob && is_ldon_treasure
|
||||
)
|
||||
)
|
||||
) {
|
||||
|| IsQueuedForCorpse()
|
||||
) {
|
||||
if (killer) {
|
||||
if (killer->GetOwner() != 0 && killer->GetOwner()->IsClient()) {
|
||||
killer = killer->GetOwner();
|
||||
@@ -3038,9 +3041,15 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
if (!other)
|
||||
return;
|
||||
|
||||
if (other->IsDestroying())
|
||||
return;
|
||||
|
||||
if (other == this)
|
||||
return;
|
||||
|
||||
if (other->IsClient() && (other->CastToClient()->IsZoning() || other->CastToClient()->Connected() == false))
|
||||
return;
|
||||
|
||||
if (other->IsTrap())
|
||||
return;
|
||||
|
||||
|
||||
+5
-1
@@ -690,12 +690,16 @@ void Aura::ProcessSpawns()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!e.second->IsOfClientBot()) {
|
||||
if (!e.second->IsClient()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto c = e.second->CastToClient();
|
||||
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool spawned = spawned_for.find(c->GetID()) != spawned_for.end();
|
||||
if (ShouldISpawnFor(c)) {
|
||||
if (!spawned) {
|
||||
|
||||
+190
-400
@@ -281,443 +281,233 @@ void Mob::AddItemBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_a
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_ammo_item) {
|
||||
const auto recommended_level = is_augment ? recommended_level_override : inst->GetItemRecommendedLevel(true);
|
||||
if (is_ammo_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsNPC() || current_level >= recommended_level) {
|
||||
b->HP += item->HP;
|
||||
b->Mana += item->Mana;
|
||||
b->Endurance += item->Endur;
|
||||
const auto recommended_level = is_augment ? recommended_level_override : inst->GetItemRecommendedLevel(true);
|
||||
const bool meets_recommended = (IsNPC() && !RuleB(Items, NPCUseRecommendedLevels)) || current_level >= recommended_level;
|
||||
|
||||
b->AC += item->AC;
|
||||
b->ATK += item->Attack;
|
||||
auto CalcItemBonus = [&](int statValue) -> int {
|
||||
return meets_recommended ? statValue : CalcRecommendedLevelBonus(current_level, recommended_level, statValue);
|
||||
};
|
||||
|
||||
b->STR += (item->AStr + item->HeroicStr);
|
||||
b->STA += (item->ASta + item->HeroicSta);
|
||||
b->DEX += (item->ADex + item->HeroicDex);
|
||||
b->AGI += (item->AAgi + item->HeroicAgi);
|
||||
b->INT += (item->AInt + item->HeroicInt);
|
||||
b->WIS += (item->AWis + item->HeroicWis);
|
||||
b->CHA += (item->ACha + item->HeroicCha);
|
||||
auto CalcCappedItemBonus = [&](int currentStat, int bonus, int cap) -> int {
|
||||
int calc_stat = currentStat + CalcItemBonus(bonus);
|
||||
return IsOfClientBotMerc() ? std::min(cap, calc_stat) : calc_stat;
|
||||
};
|
||||
|
||||
b->MR += (item->MR + item->HeroicMR);
|
||||
b->FR += (item->FR + item->HeroicFR);
|
||||
b->CR += (item->CR + item->HeroicCR);
|
||||
b->PR += (item->PR + item->HeroicPR);
|
||||
b->DR += (item->DR + item->HeroicDR);
|
||||
b->Corrup += (item->SVCorruption + item->HeroicSVCorrup);
|
||||
b->HP += CalcItemBonus(item->HP);
|
||||
b->Mana += CalcItemBonus(item->Mana);
|
||||
b->Endurance += CalcItemBonus(item->Endur);
|
||||
b->AC += CalcItemBonus(item->AC);
|
||||
|
||||
b->STRCapMod += item->HeroicStr;
|
||||
b->STACapMod += item->HeroicSta;
|
||||
b->DEXCapMod += item->HeroicDex;
|
||||
b->AGICapMod += item->HeroicAgi;
|
||||
b->INTCapMod += item->HeroicInt;
|
||||
b->WISCapMod += item->HeroicWis;
|
||||
b->CHACapMod += item->HeroicCha;
|
||||
b->STR += CalcItemBonus(item->AStr + item->HeroicStr);
|
||||
b->STA += CalcItemBonus(item->ASta + item->HeroicSta);
|
||||
b->DEX += CalcItemBonus(item->ADex + item->HeroicDex);
|
||||
b->AGI += CalcItemBonus(item->AAgi + item->HeroicAgi);
|
||||
b->INT += CalcItemBonus(item->AInt + item->HeroicInt);
|
||||
b->WIS += CalcItemBonus(item->AWis + item->HeroicWis);
|
||||
b->CHA += CalcItemBonus(item->ACha + item->HeroicCha);
|
||||
|
||||
b->MRCapMod += item->HeroicMR;
|
||||
b->CRCapMod += item->HeroicFR;
|
||||
b->FRCapMod += item->HeroicCR;
|
||||
b->PRCapMod += item->HeroicPR;
|
||||
b->DRCapMod += item->HeroicDR;
|
||||
b->CorrupCapMod += item->HeroicSVCorrup;
|
||||
b->HeroicSTR += CalcItemBonus(item->HeroicStr);
|
||||
b->HeroicSTA += CalcItemBonus(item->HeroicSta);
|
||||
b->HeroicDEX += CalcItemBonus(item->HeroicDex);
|
||||
b->HeroicAGI += CalcItemBonus(item->HeroicAgi);
|
||||
b->HeroicINT += CalcItemBonus(item->HeroicInt);
|
||||
b->HeroicWIS += CalcItemBonus(item->HeroicWis);
|
||||
b->HeroicCHA += CalcItemBonus(item->HeroicCha);
|
||||
|
||||
b->HeroicSTR += item->HeroicStr;
|
||||
b->HeroicSTA += item->HeroicSta;
|
||||
b->HeroicDEX += item->HeroicDex;
|
||||
b->HeroicAGI += item->HeroicAgi;
|
||||
b->HeroicINT += item->HeroicInt;
|
||||
b->HeroicWIS += item->HeroicWis;
|
||||
b->HeroicCHA += item->HeroicCha;
|
||||
b->STRCapMod += item->HeroicStr;
|
||||
b->STACapMod += item->HeroicSta;
|
||||
b->DEXCapMod += item->HeroicDex;
|
||||
b->AGICapMod += item->HeroicAgi;
|
||||
b->INTCapMod += item->HeroicInt;
|
||||
b->WISCapMod += item->HeroicWis;
|
||||
b->CHACapMod += item->HeroicCha;
|
||||
|
||||
b->HeroicMR += item->HeroicMR;
|
||||
b->HeroicFR += item->HeroicFR;
|
||||
b->HeroicCR += item->HeroicCR;
|
||||
b->HeroicPR += item->HeroicPR;
|
||||
b->HeroicDR += item->HeroicDR;
|
||||
b->HeroicCorrup += item->HeroicSVCorrup;
|
||||
b->MR += CalcItemBonus(item->MR + item->HeroicMR);
|
||||
b->FR += CalcItemBonus(item->FR + item->HeroicFR);
|
||||
b->CR += CalcItemBonus(item->CR + item->HeroicCR);
|
||||
b->PR += CalcItemBonus(item->PR + item->HeroicPR);
|
||||
b->DR += CalcItemBonus(item->DR + item->HeroicDR);
|
||||
b->Corrup += CalcItemBonus(item->SVCorruption + item->HeroicSVCorrup);
|
||||
|
||||
b->HeroicMR += CalcItemBonus(item->HeroicMR);
|
||||
b->HeroicFR += CalcItemBonus(item->HeroicFR);
|
||||
b->HeroicCR += CalcItemBonus(item->HeroicCR);
|
||||
b->HeroicPR += CalcItemBonus(item->HeroicPR);
|
||||
b->HeroicDR += CalcItemBonus(item->HeroicDR);
|
||||
b->HeroicCorrup += CalcItemBonus(item->HeroicSVCorrup);
|
||||
|
||||
b->MRCapMod += item->HeroicMR;
|
||||
b->FRCapMod += item->HeroicFR;
|
||||
b->CRCapMod += item->HeroicCR;
|
||||
b->PRCapMod += item->HeroicPR;
|
||||
b->DRCapMod += item->HeroicDR;
|
||||
b->CorrupCapMod += item->HeroicSVCorrup;
|
||||
|
||||
b->HPRegen += CalcItemBonus(item->Regen);
|
||||
b->ManaRegen += CalcItemBonus(item->ManaRegen);
|
||||
b->EnduranceRegen += CalcItemBonus(item->EnduranceRegen);
|
||||
|
||||
// These have rule-configured caps.
|
||||
b->ATK = CalcCappedItemBonus(b->ATK, item->Attack, RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap);
|
||||
b->DamageShield = CalcCappedItemBonus(b->DamageShield, item->DamageShield, RuleI(Character, ItemDamageShieldCap));
|
||||
b->SpellShield = CalcCappedItemBonus(b->SpellShield, item->SpellShield, RuleI(Character, ItemSpellShieldingCap));
|
||||
b->MeleeMitigation = CalcCappedItemBonus(b->MeleeMitigation, item->Shielding, RuleI(Character, ItemShieldingCap));
|
||||
b->StunResist = CalcCappedItemBonus(b->StunResist, item->StunResist, RuleI(Character, ItemStunResistCap));
|
||||
b->StrikeThrough = CalcCappedItemBonus(b->StrikeThrough, item->StrikeThrough, RuleI(Character, ItemStrikethroughCap));
|
||||
b->AvoidMeleeChance = CalcCappedItemBonus(b->AvoidMeleeChance, item->Avoidance, RuleI(Character, ItemAvoidanceCap));
|
||||
b->HitChance = CalcCappedItemBonus(b->HitChance, item->Accuracy, RuleI(Character, ItemAccuracyCap));
|
||||
b->ProcChance = CalcCappedItemBonus(b->ProcChance, item->CombatEffects, RuleI(Character, ItemCombatEffectsCap));
|
||||
b->DoTShielding = CalcCappedItemBonus(b->DoTShielding, item->DotShielding, RuleI(Character, ItemDoTShieldingCap));
|
||||
b->HealAmt = CalcCappedItemBonus(b->HealAmt, item->HealAmt, RuleI(Character, ItemHealAmtCap));
|
||||
b->SpellDmg = CalcCappedItemBonus(b->SpellDmg, item->SpellDmg, RuleI(Character, ItemSpellDmgCap));
|
||||
b->Clairvoyance = CalcCappedItemBonus(b->Clairvoyance, item->Clairvoyance, RuleI(Character, ItemClairvoyanceCap));
|
||||
b->DSMitigation = CalcCappedItemBonus(b->DSMitigation, item->DSMitigation, RuleI(Character, ItemDSMitigationCap));
|
||||
|
||||
if (b->haste < item->Haste) {
|
||||
b->haste = item->Haste;
|
||||
}
|
||||
|
||||
if (item->ExtraDmgAmt != 0 && item->ExtraDmgSkill <= EQ::skills::HIGHEST_SKILL) {
|
||||
if (item->ExtraDmgSkill == ALL_SKILLS) {
|
||||
for (const auto &skill_id: EQ::skills::GetExtraDamageSkills()) {
|
||||
b->SkillDamageAmount[skill_id] = CalcCappedItemBonus(b->SkillDamageAmount[skill_id], item->ExtraDmgAmt, RuleI(Character, ItemExtraDmgCap));
|
||||
}
|
||||
} else {
|
||||
b->HP += CalcRecommendedLevelBonus(current_level, recommended_level, item->HP);
|
||||
b->Mana += CalcRecommendedLevelBonus(current_level, recommended_level, item->Mana);
|
||||
b->Endurance += CalcRecommendedLevelBonus(current_level, recommended_level, item->Endur);
|
||||
|
||||
b->AC += CalcRecommendedLevelBonus(current_level, recommended_level, item->AC);
|
||||
b->ATK += CalcRecommendedLevelBonus(current_level, recommended_level, item->Attack);
|
||||
|
||||
b->STR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AStr + item->HeroicStr));
|
||||
b->STA += CalcRecommendedLevelBonus(current_level, recommended_level, (item->ASta + item->HeroicSta));
|
||||
b->DEX += CalcRecommendedLevelBonus(current_level, recommended_level, (item->ADex + item->HeroicDex));
|
||||
b->AGI += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AAgi + item->HeroicAgi));
|
||||
b->INT += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AInt + item->HeroicInt));
|
||||
b->WIS += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AWis + item->HeroicWis));
|
||||
b->CHA += CalcRecommendedLevelBonus(current_level, recommended_level, (item->ACha + item->HeroicCha));
|
||||
|
||||
b->MR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->MR + item->HeroicMR));
|
||||
b->FR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->FR + item->HeroicFR));
|
||||
b->CR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->CR + item->HeroicCR));
|
||||
b->PR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->PR + item->HeroicPR));
|
||||
b->DR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->DR + item->HeroicDR));
|
||||
b->Corrup += CalcRecommendedLevelBonus(current_level, recommended_level, (item->SVCorruption + item->HeroicSVCorrup));
|
||||
|
||||
b->STRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicStr);
|
||||
b->STACapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSta);
|
||||
b->DEXCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDex);
|
||||
b->AGICapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicAgi);
|
||||
b->INTCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicInt);
|
||||
b->WISCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicWis);
|
||||
b->CHACapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCha);
|
||||
|
||||
b->MRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicMR);
|
||||
b->CRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicFR);
|
||||
b->FRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCR);
|
||||
b->PRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicPR);
|
||||
b->DRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDR);
|
||||
b->CorrupCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSVCorrup);
|
||||
|
||||
b->HeroicSTR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicStr);
|
||||
b->HeroicSTA += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSta);
|
||||
b->HeroicDEX += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDex);
|
||||
b->HeroicAGI += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicAgi);
|
||||
b->HeroicINT += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicInt);
|
||||
b->HeroicWIS += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicWis);
|
||||
b->HeroicCHA += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCha);
|
||||
|
||||
b->HeroicMR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicMR);
|
||||
b->HeroicFR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicFR);
|
||||
b->HeroicCR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCR);
|
||||
b->HeroicPR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicPR);
|
||||
b->HeroicDR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDR);
|
||||
b->HeroicCorrup += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSVCorrup);
|
||||
b->SkillDamageAmount[item->ExtraDmgSkill] = CalcCappedItemBonus(b->SkillDamageAmount[item->ExtraDmgSkill], item->ExtraDmgAmt, RuleI(Character, ItemExtraDmgCap));
|
||||
}
|
||||
}
|
||||
|
||||
if (b->haste < item->Haste) {
|
||||
b->haste = item->Haste;
|
||||
if (item->Worn.Effect > 0 && item->Worn.Type == EQ::item::ItemEffectWorn) {
|
||||
ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, b, 0, item->Worn.Type);
|
||||
}
|
||||
|
||||
if (item->Focus.Effect > 0 && item->Focus.Type == EQ::item::ItemEffectFocus) {
|
||||
if (
|
||||
IsOfClientBotMerc() ||
|
||||
(IsNPC() && RuleB(Spells, NPC_UseFocusFromItems))
|
||||
) {
|
||||
ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, b, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->Regen != 0) {
|
||||
b->HPRegen += item->Regen;
|
||||
switch (item->BardType) {
|
||||
case EQ::item::ItemTypeAllInstrumentTypes: { // (e.g. Singing Short Sword)
|
||||
if (item->BardValue > b->singingMod) {
|
||||
b->singingMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->brassMod) {
|
||||
b->brassMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->stringedMod) {
|
||||
b->stringedMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->percussionMod) {
|
||||
b->percussionMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->windMod) {
|
||||
b->windMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeSinging: {
|
||||
if (item->BardValue > b->singingMod) {
|
||||
b->singingMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->ManaRegen != 0) {
|
||||
b->ManaRegen += item->ManaRegen;
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeWindInstrument: {
|
||||
if (item->BardValue > b->windMod) {
|
||||
b->windMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->EnduranceRegen != 0) {
|
||||
b->EnduranceRegen += item->EnduranceRegen;
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeStringedInstrument: {
|
||||
if (item->BardValue > b->stringedMod) {
|
||||
b->stringedMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->Attack != 0) {
|
||||
unsigned int cap = RuleI(Character, ItemATKCap);
|
||||
cap += itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap;
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeBrassInstrument: {
|
||||
if (item->BardValue > b->brassMod) {
|
||||
b->brassMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->ATK + item->Attack) > cap
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypePercussionInstrument: {
|
||||
if (item->BardValue > b->percussionMod) {
|
||||
b->percussionMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL) {
|
||||
if (
|
||||
(item->SkillModValue > 0 && b->skillmod[item->SkillModType] < item->SkillModValue) ||
|
||||
(item->SkillModValue < 0 && b->skillmod[item->SkillModType] > item->SkillModValue)
|
||||
) {
|
||||
b->ATK = RuleI(Character, ItemATKCap);
|
||||
} else {
|
||||
b->ATK += item->Attack;
|
||||
}
|
||||
b->skillmod[item->SkillModType] = item->SkillModValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->DamageShield != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)
|
||||
) {
|
||||
b->DamageShield = RuleI(Character, ItemDamageShieldCap);
|
||||
} else {
|
||||
b->DamageShield += item->DamageShield;
|
||||
}
|
||||
if (item->FactionMod1) {
|
||||
if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) {
|
||||
AddItemFactionBonus(item->FactionMod1, item->FactionAmt1);
|
||||
} else if (item->FactionAmt1 < 0 && item->FactionAmt1 < GetItemFactionBonus(item->FactionMod1)) {
|
||||
AddItemFactionBonus(item->FactionMod1, item->FactionAmt1);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->SpellShield != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap)
|
||||
) {
|
||||
b->SpellShield = RuleI(Character, ItemSpellShieldingCap);
|
||||
} else {
|
||||
b->SpellShield += item->SpellShield;
|
||||
}
|
||||
if (item->FactionMod2) {
|
||||
if (item->FactionAmt2 > 0 && item->FactionAmt2 > GetItemFactionBonus(item->FactionMod2)) {
|
||||
AddItemFactionBonus(item->FactionMod2, item->FactionAmt2);
|
||||
} else if (item->FactionAmt2 < 0 && item->FactionAmt2 < GetItemFactionBonus(item->FactionMod2)) {
|
||||
AddItemFactionBonus(item->FactionMod2, item->FactionAmt2);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->Shielding != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap)
|
||||
) {
|
||||
b->MeleeMitigation = RuleI(Character, ItemShieldingCap);
|
||||
} else {
|
||||
b->MeleeMitigation += item->Shielding;
|
||||
}
|
||||
if (item->FactionMod3) {
|
||||
if (item->FactionAmt3 > 0 && item->FactionAmt3 > GetItemFactionBonus(item->FactionMod3)) {
|
||||
AddItemFactionBonus(item->FactionMod3, item->FactionAmt3);
|
||||
} else if (item->FactionAmt3 < 0 && item->FactionAmt3 < GetItemFactionBonus(item->FactionMod3)) {
|
||||
AddItemFactionBonus(item->FactionMod3, item->FactionAmt3);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->StunResist != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap)
|
||||
) {
|
||||
b->StunResist = RuleI(Character, ItemStunResistCap);
|
||||
} else {
|
||||
b->StunResist += item->StunResist;
|
||||
}
|
||||
if (item->FactionMod4) {
|
||||
if (item->FactionAmt4 > 0 && item->FactionAmt4 > GetItemFactionBonus(item->FactionMod4)) {
|
||||
AddItemFactionBonus(item->FactionMod4, item->FactionAmt4);
|
||||
} else if (item->FactionAmt4 < 0 && item->FactionAmt4 < GetItemFactionBonus(item->FactionMod4)) {
|
||||
AddItemFactionBonus(item->FactionMod4, item->FactionAmt4);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->StrikeThrough != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap)
|
||||
) {
|
||||
b->StrikeThrough = RuleI(Character, ItemStrikethroughCap);
|
||||
} else {
|
||||
b->StrikeThrough += item->StrikeThrough;
|
||||
if (!is_augment) {
|
||||
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
|
||||
const auto* augment = inst->GetAugment(i);
|
||||
if (!augment) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->Avoidance != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap)
|
||||
) {
|
||||
b->AvoidMeleeChance = RuleI(Character, ItemAvoidanceCap);
|
||||
} else {
|
||||
b->AvoidMeleeChance += item->Avoidance;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->Accuracy != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap)
|
||||
) {
|
||||
b->HitChance = RuleI(Character, ItemAccuracyCap);
|
||||
} else {
|
||||
b->HitChance += item->Accuracy;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->CombatEffects != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap)
|
||||
) {
|
||||
b->ProcChance = RuleI(Character, ItemCombatEffectsCap);
|
||||
} else {
|
||||
b->ProcChance += item->CombatEffects;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->DotShielding != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap)
|
||||
) {
|
||||
b->DoTShielding = RuleI(Character, ItemDoTShieldingCap);
|
||||
} else {
|
||||
b->DoTShielding += item->DotShielding;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->HealAmt != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap)
|
||||
) {
|
||||
b->HealAmt = RuleI(Character, ItemHealAmtCap);
|
||||
} else {
|
||||
b->HealAmt += item->HealAmt;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->SpellDmg != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap)
|
||||
) {
|
||||
b->SpellDmg = RuleI(Character, ItemSpellDmgCap);
|
||||
} else {
|
||||
b->SpellDmg += item->SpellDmg;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->Clairvoyance != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap)
|
||||
) {
|
||||
b->Clairvoyance = RuleI(Character, ItemClairvoyanceCap);
|
||||
} else {
|
||||
b->Clairvoyance += item->Clairvoyance;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->DSMitigation != 0) {
|
||||
if (
|
||||
IsOfClientBotMerc() &&
|
||||
(b->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap)
|
||||
) {
|
||||
b->DSMitigation = RuleI(Character, ItemDSMitigationCap);
|
||||
} else {
|
||||
b->DSMitigation += item->DSMitigation;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->Worn.Effect > 0 && item->Worn.Type == EQ::item::ItemEffectWorn) {
|
||||
ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, b, 0, item->Worn.Type);
|
||||
}
|
||||
|
||||
if (item->Focus.Effect > 0 && item->Focus.Type == EQ::item::ItemEffectFocus) {
|
||||
if (
|
||||
IsOfClientBotMerc() ||
|
||||
(IsNPC() && RuleB(Spells, NPC_UseFocusFromItems))
|
||||
) {
|
||||
ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, b, 0);
|
||||
}
|
||||
}
|
||||
|
||||
switch (item->BardType) {
|
||||
case EQ::item::ItemTypeAllInstrumentTypes: { // (e.g. Singing Short Sword)
|
||||
if (item->BardValue > b->singingMod) {
|
||||
b->singingMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->brassMod) {
|
||||
b->brassMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->stringedMod) {
|
||||
b->stringedMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->percussionMod) {
|
||||
b->percussionMod = item->BardValue;
|
||||
}
|
||||
|
||||
if (item->BardValue > b->windMod) {
|
||||
b->windMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeSinging: {
|
||||
if (item->BardValue > b->singingMod) {
|
||||
b->singingMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeWindInstrument: {
|
||||
if (item->BardValue > b->windMod) {
|
||||
b->windMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeStringedInstrument: {
|
||||
if (item->BardValue > b->stringedMod) {
|
||||
b->stringedMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypeBrassInstrument: {
|
||||
if (item->BardValue > b->brassMod) {
|
||||
b->brassMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EQ::item::ItemTypePercussionInstrument: {
|
||||
if (item->BardValue > b->percussionMod) {
|
||||
b->percussionMod = item->BardValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL) {
|
||||
if (
|
||||
(item->SkillModValue > 0 && b->skillmod[item->SkillModType] < item->SkillModValue) ||
|
||||
(item->SkillModValue < 0 && b->skillmod[item->SkillModType] > item->SkillModValue)
|
||||
) {
|
||||
b->skillmod[item->SkillModType] = item->SkillModValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->FactionMod1) {
|
||||
if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) {
|
||||
AddItemFactionBonus(item->FactionMod1, item->FactionAmt1);
|
||||
} else if (item->FactionAmt1 < 0 && item->FactionAmt1 < GetItemFactionBonus(item->FactionMod1)) {
|
||||
AddItemFactionBonus(item->FactionMod1, item->FactionAmt1);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->FactionMod2) {
|
||||
if (item->FactionAmt2 > 0 && item->FactionAmt2 > GetItemFactionBonus(item->FactionMod2)) {
|
||||
AddItemFactionBonus(item->FactionMod2, item->FactionAmt2);
|
||||
} else if (item->FactionAmt2 < 0 && item->FactionAmt2 < GetItemFactionBonus(item->FactionMod2)) {
|
||||
AddItemFactionBonus(item->FactionMod2, item->FactionAmt2);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->FactionMod3) {
|
||||
if (item->FactionAmt3 > 0 && item->FactionAmt3 > GetItemFactionBonus(item->FactionMod3)) {
|
||||
AddItemFactionBonus(item->FactionMod3, item->FactionAmt3);
|
||||
} else if (item->FactionAmt3 < 0 && item->FactionAmt3 < GetItemFactionBonus(item->FactionMod3)) {
|
||||
AddItemFactionBonus(item->FactionMod3, item->FactionAmt3);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->FactionMod4) {
|
||||
if (item->FactionAmt4 > 0 && item->FactionAmt4 > GetItemFactionBonus(item->FactionMod4)) {
|
||||
AddItemFactionBonus(item->FactionMod4, item->FactionAmt4);
|
||||
} else if (item->FactionAmt4 < 0 && item->FactionAmt4 < GetItemFactionBonus(item->FactionMod4)) {
|
||||
AddItemFactionBonus(item->FactionMod4, item->FactionAmt4);
|
||||
}
|
||||
}
|
||||
|
||||
if (item->ExtraDmgAmt != 0 && item->ExtraDmgSkill <= EQ::skills::HIGHEST_SKILL) {
|
||||
if (item->ExtraDmgSkill == ALL_SKILLS) {
|
||||
for (const auto& skill_id : EQ::skills::GetExtraDamageSkills()) {
|
||||
if (
|
||||
!IsNPC() &&
|
||||
RuleI(Character, ItemExtraDmgCap) >= 0 &&
|
||||
(b->SkillDamageAmount[skill_id] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)
|
||||
) {
|
||||
b->SkillDamageAmount[skill_id] = RuleI(Character, ItemExtraDmgCap);
|
||||
} else {
|
||||
b->SkillDamageAmount[skill_id] += item->ExtraDmgAmt;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!IsNPC() &&
|
||||
RuleI(Character, ItemExtraDmgCap) >= 0 &&
|
||||
(b->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)
|
||||
) {
|
||||
b->SkillDamageAmount[item->ExtraDmgSkill] = RuleI(Character, ItemExtraDmgCap);
|
||||
} else {
|
||||
b->SkillDamageAmount[item->ExtraDmgSkill] += item->ExtraDmgAmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_augment) {
|
||||
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
|
||||
const auto* augment = inst->GetAugment(i);
|
||||
if (!augment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AddItemBonuses(augment, b, true, false, recommended_level);
|
||||
}
|
||||
AddItemBonuses(augment, b, true, false, recommended_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Mob::AdditiveWornBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_augment) {
|
||||
/*
|
||||
Powerful Non-live like option allows developers to add worn effects on items that
|
||||
|
||||
+385
-201
@@ -2256,7 +2256,7 @@ void Bot::AI_Process()
|
||||
SetAttackingFlag(false);
|
||||
}
|
||||
|
||||
float tar_distance = DistanceSquared(m_Position, tar->GetPosition());
|
||||
float tar_distance = DistanceSquaredNoZ(m_Position, tar->GetPosition());
|
||||
|
||||
// TARGET VALIDATION
|
||||
if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, tar, tar_distance)) {
|
||||
@@ -2296,7 +2296,6 @@ void Bot::AI_Process()
|
||||
CombatRangeInput input = {
|
||||
.target = tar,
|
||||
.target_distance = tar_distance,
|
||||
.behind_mob = behind_mob,
|
||||
.stop_melee_level = stop_melee_level,
|
||||
.p_item = p_item,
|
||||
.s_item = s_item
|
||||
@@ -2368,6 +2367,16 @@ void Bot::AI_Process()
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
HasTargetReflection() &&
|
||||
!IsTaunting() &&
|
||||
!tar->IsFleeing() &&
|
||||
!tar->IsFeared() &&
|
||||
TryEvade(tar)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ENGAGED AT COMBAT RANGE
|
||||
|
||||
// We can fight
|
||||
@@ -2434,7 +2443,11 @@ void Bot::AI_Process()
|
||||
ranged_timer.Start();
|
||||
}
|
||||
else if (!IsBotRanged() && GetLevel() < stop_melee_level) {
|
||||
if (!GetMaxMeleeRange() || !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)) {
|
||||
if (
|
||||
IsTaunting() ||
|
||||
!GetMaxMeleeRange() ||
|
||||
!RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)
|
||||
) {
|
||||
DoClassAttacks(tar);
|
||||
}
|
||||
|
||||
@@ -3065,7 +3078,7 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
|
||||
// For races with a fixed size
|
||||
if (GetRace() == Race::LavaDragon || GetRace() == Race::Wurm || GetRace() == Race::GhostDragon) {
|
||||
// size_mod = 60.0f;
|
||||
size_mod = 60.0f;
|
||||
}
|
||||
else if (size_mod < 6.0f) {
|
||||
size_mod = 8.0f;
|
||||
@@ -3110,91 +3123,39 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
size_mod = (size_mod / 7.0f);
|
||||
}
|
||||
|
||||
o.melee_distance_max = size_mod;
|
||||
o.melee_distance_max = sqrt(size_mod);
|
||||
|
||||
if (!RuleB(Bots, UseFlatNormalMeleeRange)) {
|
||||
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
|
||||
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
|
||||
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
||||
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level;
|
||||
|
||||
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
|
||||
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
|
||||
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
||||
|
||||
switch (GetClass()) {
|
||||
case Class::Warrior:
|
||||
case Class::Paladin:
|
||||
case Class::ShadowKnight:
|
||||
o.melee_distance = (
|
||||
is_two_hander ? o.melee_distance_max * 0.45f
|
||||
: is_shield ? o.melee_distance_max * 0.35f
|
||||
: o.melee_distance_max * 0.40f
|
||||
);
|
||||
|
||||
break;
|
||||
case Class::Necromancer:
|
||||
case Class::Wizard:
|
||||
case Class::Magician:
|
||||
case Class::Enchanter:
|
||||
o.melee_distance = (
|
||||
is_two_hander ? o.melee_distance_max * 0.95f
|
||||
: o.melee_distance_max * 0.75f
|
||||
);
|
||||
|
||||
break;
|
||||
case Class::Rogue:
|
||||
o.melee_distance = (
|
||||
input.behind_mob && is_backstab_weapon
|
||||
? o.melee_distance_max * 0.35f
|
||||
: o.melee_distance_max * 0.50f
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
o.melee_distance = (
|
||||
is_two_hander ? o.melee_distance_max * 0.70f
|
||||
: o.melee_distance_max * 0.50f
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
o.melee_distance = sqrt(o.melee_distance);
|
||||
o.melee_distance_max = sqrt(o.melee_distance_max);
|
||||
if (IsTaunting()) { // Taunting bots
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
|
||||
}
|
||||
else {
|
||||
o.melee_distance_max = sqrt(o.melee_distance_max);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, NormalMeleeRangeDistance);
|
||||
else if (IsBotRanged()) { // Archers/Throwers
|
||||
float min_distance = RuleI(Combat, MinRangedAttackDist);
|
||||
float max_distance = GetBotRangedValue();
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
|
||||
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
|
||||
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
|
||||
o.melee_distance = std::min(max_distance, desired_range);
|
||||
}
|
||||
else if (is_stop_melee_level) { // Casters
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
|
||||
if (o.melee_distance > RuleR(Bots, MaxDistanceForMelee)) {
|
||||
o.melee_distance = RuleR(Bots, MaxDistanceForMelee);
|
||||
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
|
||||
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
|
||||
}
|
||||
|
||||
o.melee_distance_min = o.melee_distance * RuleR(Bots, PercentMinMeleeDistance);
|
||||
|
||||
if (IsTaunting()) {
|
||||
o.melee_distance_min = o.melee_distance * RuleR(Bots, PercentTauntMinMeleeDistance);
|
||||
o.melee_distance = o.melee_distance * RuleR(Bots, TauntNormalMeleeRangeDistance);
|
||||
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
|
||||
}
|
||||
|
||||
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level;
|
||||
|
||||
if (!IsTaunting() && !IsBotRanged() && !is_stop_melee_level && GetMaxMeleeRange()) {
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, PercentMinMaxMeleeRangeDistance);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance);
|
||||
}
|
||||
|
||||
if (is_stop_melee_level && !IsBotRanged()) {
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
o.melee_distance_min = std::max(o.melee_distance, (desired_range / 2));
|
||||
o.melee_distance = std::max((o.melee_distance + 1), desired_range);
|
||||
}
|
||||
|
||||
if (IsBotRanged()) {
|
||||
float min_distance = RuleI(Combat, MinRangedAttackDist);
|
||||
float max_distance = GetBotRangedValue();
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged if set to ranged even if items/ammo aren't correct
|
||||
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
|
||||
o.melee_distance = std::min(max_distance, desired_range);
|
||||
else { // Regular melee
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
|
||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
|
||||
}
|
||||
|
||||
o.at_combat_range = (input.target_distance <= o.melee_distance);
|
||||
@@ -3615,9 +3576,18 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
||||
void Bot::Depop() {
|
||||
WipeHateList();
|
||||
entity_list.RemoveFromHateLists(this);
|
||||
RemoveAllAuras();
|
||||
|
||||
if (HasPet())
|
||||
GetPet()->Depop();
|
||||
Mob* bot_pet = GetPet();
|
||||
|
||||
if (bot_pet) {
|
||||
if (bot_pet->Charmed()) {
|
||||
bot_pet->BuffFadeByEffect(SE_Charm);
|
||||
}
|
||||
else {
|
||||
bot_pet->Depop();
|
||||
}
|
||||
}
|
||||
|
||||
_botOwner = nullptr;
|
||||
_botOwnerCharacterID = 0;
|
||||
@@ -3728,7 +3698,7 @@ bool Bot::Spawn(Client* botCharacterOwner) {
|
||||
SetVerifiedRaid(true);
|
||||
}
|
||||
}
|
||||
else if (auto group = entity_list.GetGroupByMob(this)) {
|
||||
else if (auto group = entity_list.GetGroupByMobName(GetName())) {
|
||||
// Safety Check to confirm we have a valid group
|
||||
auto owner = GetBotOwner();
|
||||
if (owner && !group->IsGroupMember(owner->GetCleanName())) {
|
||||
@@ -3740,13 +3710,6 @@ bool Bot::Spawn(Client* botCharacterOwner) {
|
||||
}
|
||||
}
|
||||
|
||||
MapSpellTypeLevels();
|
||||
|
||||
if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) {
|
||||
OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
|
||||
CheckBotSpells(); //This runs through a serious of checks and outputs any spells that are set to the wrong spell type in the database
|
||||
}
|
||||
|
||||
if (IsBotRanged()) {
|
||||
ChangeBotRangedWeapons(true);
|
||||
}
|
||||
@@ -9766,7 +9729,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
|
||||
)
|
||||
)
|
||||
&&
|
||||
tar->CanBuffStack(spell_id, GetLevel(), true) < 0
|
||||
tar->CanBuffStack(spell_id, GetLevel(), false) < 0
|
||||
) {
|
||||
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
|
||||
return false;
|
||||
@@ -9830,6 +9793,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
|
||||
}
|
||||
|
||||
uint8 bot_class = GetClass();
|
||||
auto spell = spells[spell_id];
|
||||
|
||||
switch (spell_type) {
|
||||
case BotSpellTypes::Buff:
|
||||
@@ -9853,7 +9817,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
|
||||
case BotSpellTypes::SendHome:
|
||||
if (
|
||||
tar == this &&
|
||||
spells[spell_id].target_type == ST_TargetsTarget
|
||||
spell.target_type == ST_TargetsTarget
|
||||
) {
|
||||
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id));
|
||||
return false;
|
||||
@@ -9883,7 +9847,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
|
||||
}
|
||||
|
||||
if (
|
||||
spells[spell_id].target_type == ST_Pet &&
|
||||
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet) &&
|
||||
(
|
||||
!tar->IsPet() ||
|
||||
(
|
||||
@@ -11606,18 +11570,265 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsBotSpellTypeDetrimental(spell_type) && !IsDetrimentalSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsBotSpellTypeBeneficial(spell_type) && !IsBeneficialSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto spell = spells[spell_id];
|
||||
std::string teleport_zone = spell.teleport_zone;
|
||||
|
||||
switch (spell_type) {
|
||||
case BotSpellTypes::Buff:
|
||||
case BotSpellTypes::PetBuffs:
|
||||
if (
|
||||
IsResistanceOnlySpell(spell_id) ||
|
||||
IsDamageShieldOnlySpell(spell_id) ||
|
||||
IsDamageShieldAndResistSpell(spell_id)
|
||||
) {
|
||||
return false;
|
||||
case BotSpellTypes::Nuke:
|
||||
if (IsAnyNukeOrStunSpell(spell_id) && !IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
case BotSpellTypes::RegularHeal:
|
||||
case BotSpellTypes::PetRegularHeals:
|
||||
if (
|
||||
IsAnyHealSpell(spell_id) &&
|
||||
!IsVeryFastHealSpell(spell_id) &&
|
||||
!IsFastHealSpell(spell_id) &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
!IsHealOverTimeSpell(spell_id) &&
|
||||
!IsBuffSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Root:
|
||||
case BotSpellTypes::AERoot:
|
||||
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_Root)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Buff:
|
||||
case BotSpellTypes::PreCombatBuff:
|
||||
case BotSpellTypes::PetBuffs:
|
||||
if (
|
||||
IsBuffSpell(spell_id) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
!IsBardSong(spell_id) &&
|
||||
!IsResistanceOnlySpell(spell_id) &&
|
||||
!IsDamageShieldOnlySpell(spell_id) &&
|
||||
!IsDamageShieldAndResistSpell(spell_id)
|
||||
) {
|
||||
if (
|
||||
spell_type != BotSpellTypes::PetBuffs &&
|
||||
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Escape:
|
||||
if (IsEscapeSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Pet:
|
||||
if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Lifetap:
|
||||
case BotSpellTypes::AELifetap:
|
||||
if (IsDetrimentalSpell(spell_id) && IsLifetapSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Snare:
|
||||
case BotSpellTypes::AESnare:
|
||||
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::DOT:
|
||||
case BotSpellTypes::AEDoT:
|
||||
if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Dispel:
|
||||
case BotSpellTypes::AEDispel:
|
||||
if (IsDispelSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::InCombatBuff:
|
||||
if (
|
||||
!IsBardSong(spell_id) &&
|
||||
(
|
||||
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
|
||||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Mez:
|
||||
case BotSpellTypes::AEMez:
|
||||
if (IsMesmerizeSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Charm:
|
||||
if (IsCharmSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Slow:
|
||||
case BotSpellTypes::AESlow:
|
||||
if (IsDetrimentalSpell(spell_id) && IsSlowSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Debuff:
|
||||
case BotSpellTypes::AEDebuff:
|
||||
if (
|
||||
IsDebuffSpell(spell_id) &&
|
||||
!IsHateReduxSpell(spell_id) &&
|
||||
!IsHateSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Cure:
|
||||
case BotSpellTypes::GroupCures:
|
||||
case BotSpellTypes::PetCures:
|
||||
if (IsCureSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Resurrect:
|
||||
if (IsEffectInSpell(spell_id, SE_Revive)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::HateRedux:
|
||||
if (IsHateReduxSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::InCombatBuffSong:
|
||||
case BotSpellTypes::OutOfCombatBuffSong:
|
||||
case BotSpellTypes::PreCombatBuffSong:
|
||||
if (
|
||||
IsBuffSpell(spell_id) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsBardSong(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Fear:
|
||||
case BotSpellTypes::AEFear:
|
||||
if (IsFearSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Stun:
|
||||
case BotSpellTypes::AEStun:
|
||||
if (IsDetrimentalSpell(spell_id) && IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::HateLine:
|
||||
case BotSpellTypes::AEHateLine:
|
||||
if (IsDetrimentalSpell(spell_id) && IsHateSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::CompleteHeal:
|
||||
case BotSpellTypes::GroupCompleteHeals:
|
||||
case BotSpellTypes::PetCompleteHeals:
|
||||
if (IsCompleteHealSpell(spell_id) || IsGroupCompleteHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::FastHeals:
|
||||
case BotSpellTypes::PetFastHeals:
|
||||
if (IsFastHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::VeryFastHeals:
|
||||
case BotSpellTypes::PetVeryFastHeals:
|
||||
if (IsVeryFastHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::GroupHeals:
|
||||
if (IsRegularGroupHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::HoTHeals:
|
||||
case BotSpellTypes::GroupHoTHeals:
|
||||
case BotSpellTypes::PetHoTHeals:
|
||||
if (IsHealOverTimeSpell(spell_id) || IsGroupHealOverTimeSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AENukes:
|
||||
if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
!IsAERainSpell(spell_id) &&
|
||||
!IsPBAENukeSpell(spell_id) &&
|
||||
!IsStunSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AERains:
|
||||
if (IsAERainNukeSpell(spell_id) && !IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::PBAENuke:
|
||||
if (IsPBAENukeSpell(spell_id) && !IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::ResistBuffs:
|
||||
case BotSpellTypes::PetResistBuffs:
|
||||
if (IsResistanceBuffSpell(spell_id)) {
|
||||
@@ -11627,44 +11838,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
return false;
|
||||
case BotSpellTypes::DamageShields:
|
||||
case BotSpellTypes::PetDamageShields:
|
||||
if (IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::PBAENuke:
|
||||
if (
|
||||
IsPBAENukeSpell(spell_id) &&
|
||||
!IsStunSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AERains:
|
||||
if (
|
||||
IsAERainNukeSpell(spell_id) &&
|
||||
!IsStunSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case BotSpellTypes::AEStun:
|
||||
case BotSpellTypes::Stun:
|
||||
if (IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AENukes:
|
||||
case BotSpellTypes::Nuke:
|
||||
if (!IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Lull:
|
||||
if (IsHarmonySpell(spell_id)) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11672,13 +11846,18 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
case BotSpellTypes::Teleport:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(
|
||||
IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate)
|
||||
)
|
||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Lull:
|
||||
case BotSpellTypes::AELull:
|
||||
if (IsHarmonySpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Succor:
|
||||
if (
|
||||
@@ -11702,25 +11881,21 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Levitate:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Levitate)
|
||||
) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Levitate)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Rune:
|
||||
if (
|
||||
IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) ||
|
||||
IsEffectInSpell(spell_id, SE_Rune)
|
||||
if (IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::WaterBreathing:
|
||||
if (IsEffectInSpell(spell_id, SE_WaterBreathing)) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11728,29 +11903,22 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
case BotSpellTypes::Size:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(
|
||||
IsEffectInSpell(spell_id, SE_ModelSize) ||
|
||||
IsEffectInSpell(spell_id, SE_ChangeHeight)
|
||||
)
|
||||
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Invisibility:
|
||||
if (
|
||||
IsEffectInSpell(spell_id, SE_SeeInvis) ||
|
||||
IsInvisibleSpell(spell_id)
|
||||
if (IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_SeeInvis) ||IsInvisibleSpell(spell_id))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::MovementSpeed:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
||||
) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11758,7 +11926,13 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
case BotSpellTypes::SendHome:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_GateToHomeCity)
|
||||
(
|
||||
IsEffectInSpell(spell_id, SE_GateToHomeCity) ||
|
||||
(
|
||||
teleport_zone.compare("") &&
|
||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -11847,21 +12021,19 @@ void Bot::DoCombatPositioning(
|
||||
bool front_mob
|
||||
) {
|
||||
if (HasTargetReflection()) {
|
||||
if (!IsTaunting() && !tar->IsFeared() && !tar->IsStunned()) {
|
||||
if (TryEvade(tar)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay
|
||||
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range
|
||||
if (tar_distance <= melee_distance_max) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), GetBehindMob(), false)) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), false)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tar_distance < melee_distance_min || (!front_mob && IsTaunting())) { // Back up any bots that are too close
|
||||
else if (
|
||||
tar_distance < melee_distance_min ||
|
||||
(!front_mob && IsTaunting())
|
||||
) { // Back up any bots that are too close or if they're taunting and not in front of the mob
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
@@ -11871,46 +12043,58 @@ void Bot::DoCombatPositioning(
|
||||
}
|
||||
else {
|
||||
if (!tar->IsFeared()) {
|
||||
if (IsTaunting()) { // Taunting adjustments
|
||||
Mob* mob_tar = tar->GetTarget();
|
||||
|
||||
if (!mob_tar) {
|
||||
DoFaceCheckNoJitter(tar);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuleB(Bots, TauntingBotsFollowTopHate)) { // If enabled, taunting bots will stick to top hate
|
||||
if (Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate)) {
|
||||
Goal = mob_tar->GetPosition();
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { // Otherwise, stick to any other bots that are taunting
|
||||
if (mob_tar->IsBot() && mob_tar->CastToBot()->IsTaunting() && (Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))) {
|
||||
Goal = mob_tar->GetPosition();
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tar_distance < melee_distance_min || (GetBehindMob() && !behind_mob) || (IsTaunting() && !front_mob) || !HasRequiredLoSForPositioning(tar)) { // Regular adjustment
|
||||
if (
|
||||
tar_distance < melee_distance_min ||
|
||||
(GetBehindMob() && !behind_mob) ||
|
||||
(IsTaunting() && !front_mob) ||
|
||||
!HasRequiredLoSForPositioning(tar)
|
||||
) { // Regular adjustment
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (tar->IsEnraged() && !IsTaunting() && !stop_melee_level && !behind_mob) { // Move non-taunting melee bots behind target during enrage
|
||||
else if (
|
||||
tar->IsEnraged() &&
|
||||
!IsTaunting() &&
|
||||
!stop_melee_level &&
|
||||
!behind_mob
|
||||
) { // Move non-taunting melee bots behind target during enrage
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (
|
||||
mob_tar->IsBot() &&
|
||||
mob_tar->CastToBot()->IsTaunting() &&
|
||||
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
|
||||
) {
|
||||
Goal = mob_tar->GetPosition();
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -235,7 +235,6 @@ static std::map<uint16, std::string> botSubType_names = {
|
||||
struct CombatRangeInput {
|
||||
Mob* target;
|
||||
float target_distance;
|
||||
bool behind_mob;
|
||||
uint8 stop_melee_level;
|
||||
const EQ::ItemInstance* p_item;
|
||||
const EQ::ItemInstance* s_item;
|
||||
@@ -684,12 +683,9 @@ public:
|
||||
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
|
||||
|
||||
// Spell lists
|
||||
void CheckBotSpells();
|
||||
void MapSpellTypeLevels();
|
||||
const std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>>& GetCommandedSpellTypesMinLevels() { return commanded_spells_min_level; }
|
||||
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
|
||||
static uint16 GetParentSpellType(uint16 spell_type);
|
||||
bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
|
||||
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
|
||||
inline uint16 GetCastedSpellType() const { return _castedSpellType; }
|
||||
void SetCastedSpellType(uint16 spell_type);
|
||||
bool IsValidSpellTypeSubType(uint16 spell_type, uint16 sub_type, uint16 spell_id);
|
||||
@@ -1153,8 +1149,6 @@ protected:
|
||||
std::vector<BotSpells> AIBot_spells_enforced;
|
||||
std::unordered_map<uint16, std::vector<BotSpells_wIndex>> AIBot_spells_by_type;
|
||||
|
||||
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> commanded_spells_min_level;
|
||||
|
||||
std::vector<BotTimer> bot_timers;
|
||||
std::vector<BotBlockedBuffs> bot_blocked_buffs;
|
||||
|
||||
|
||||
+11
-3
@@ -57,6 +57,7 @@
|
||||
#include "water_map.h"
|
||||
#include "worldserver.h"
|
||||
#include "mob.h"
|
||||
#include "bot_database.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -219,6 +220,13 @@ int bot_command_init(void)
|
||||
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
|
||||
std::vector<std::string> orphaned_bot_command_settings;
|
||||
|
||||
if (RuleB(Bots, RunSpellTypeChecksOnBoot)) {
|
||||
LogBotSpellTypeChecks("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
|
||||
database.botdb.CheckBotSpells();
|
||||
}
|
||||
|
||||
database.botdb.MapCommandedSpellTypeMinLevels();
|
||||
|
||||
for (auto bcs_iter : bot_command_settings) {
|
||||
|
||||
auto bcl_iter = bot_command_list.find(bcs_iter.first);
|
||||
@@ -796,10 +804,10 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
|
||||
}
|
||||
}
|
||||
|
||||
auto& spell_map = bot->GetCommandedSpellTypesMinLevels();
|
||||
auto spell_map = database.botdb.GetCommandedSpellTypesMinLevels();
|
||||
|
||||
if (spell_map.empty()) {
|
||||
bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type");
|
||||
bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -810,7 +818,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
|
||||
auto spell_type_itr = spell_map.find(spell_type);
|
||||
auto class_itr = spell_type_itr->second.find(i);
|
||||
const auto& spell_info = class_itr->second;
|
||||
|
||||
|
||||
if (spell_info.min_level < UINT8_MAX) {
|
||||
found = true;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "../common/repositories/bot_pet_buffs_repository.h"
|
||||
#include "../common/repositories/bot_pet_inventories_repository.h"
|
||||
#include "../common/repositories/bot_spell_casting_chances_repository.h"
|
||||
#include "../common/repositories/bot_spells_entries_repository.h"
|
||||
#include "../common/repositories/bot_settings_repository.h"
|
||||
#include "../common/repositories/bot_stances_repository.h"
|
||||
#include "../common/repositories/bot_timers_repository.h"
|
||||
@@ -2521,3 +2522,208 @@ bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BotDatabase::CheckBotSpells() {
|
||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||
uint16 spell_id;
|
||||
SPDat_Spell_Struct spell;
|
||||
|
||||
for (const auto& s : spell_list) {
|
||||
if (!IsValidSpell(s.spell_id)) {
|
||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
spell = spells[s.spell_id];
|
||||
spell_id = spell.id;
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
|
||||
}
|
||||
else {
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id,
|
||||
s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
spell_id,
|
||||
s.npc_spells_id,
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
s.minlevel,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id,
|
||||
s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
spell_id,
|
||||
s.npc_spells_id,
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
s.minlevel,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id,
|
||||
s.maxlevel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
|
||||
|
||||
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
|
||||
uint16 parent_type = Bot::GetParentSpellType(correct_type);
|
||||
|
||||
if (s.type == parent_type || s.type == correct_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (correct_type != parent_type) {
|
||||
correct_type = parent_type;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsPetBotSpellType(s.type)) {
|
||||
correct_type = GetPetBotSpellType(correct_type);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
|
||||
correct_type = Bot::GetParentSpellType(correct_type);
|
||||
}
|
||||
|
||||
if (correct_type == s.type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (correct_type == UINT16_MAX) {
|
||||
LogBotSpellTypeChecks(
|
||||
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
Bot::GetSpellTypeNameByID(s.type),
|
||||
s.type
|
||||
);
|
||||
}
|
||||
else {
|
||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
Bot::GetSpellTypeNameByID(s.type),
|
||||
s.type,
|
||||
Bot::GetSpellTypeNameByID(correct_type),
|
||||
correct_type
|
||||
);
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
|
||||
correct_type,
|
||||
spell_id,
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
Bot::GetSpellTypeNameByID(s.type),
|
||||
s.type,
|
||||
Bot::GetSpellTypeNameByID(correct_type),
|
||||
correct_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BotDatabase::MapCommandedSpellTypeMinLevels() {
|
||||
commanded_spell_type_min_levels.clear();
|
||||
|
||||
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
|
||||
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
|
||||
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if (!Bot::IsValidBotSpellType(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int x = Class::Warrior; x <= Class::Berserker; ++x) {
|
||||
commanded_spell_type_min_levels[i][x] = {UINT8_MAX, "" };
|
||||
}
|
||||
}
|
||||
|
||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||
|
||||
for (const auto& s : spell_list) {
|
||||
if (!IsValidSpell(s.spell_id)) {
|
||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto spell = spells[s.spell_id];
|
||||
|
||||
if (spell.target_type == ST_Self) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t bot_class = s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX;
|
||||
|
||||
if (
|
||||
!EQ::ValueWithin(bot_class, Class::Warrior, Class::Berserker) ||
|
||||
!Bot::IsValidBotSpellType(s.type)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if (s.minlevel > commanded_spell_type_min_levels[i][bot_class].min_level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
i > BotSpellTypes::PARENT_TYPE_END &&
|
||||
i != s.type &&
|
||||
Bot::GetParentSpellType(i) != s.type
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Bot::IsValidSpellTypeBySpellID(i, s.spell_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s.minlevel < commanded_spell_type_min_levels[i][bot_class].min_level) {
|
||||
commanded_spell_type_min_levels[i][bot_class].min_level = s.minlevel;
|
||||
commanded_spell_type_min_levels[i][bot_class].description = StringFormat(
|
||||
"%s [#%u] - Level %u",
|
||||
GetClassIDName(bot_class),
|
||||
bot_class,
|
||||
s.minlevel
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -24,7 +24,7 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "bot_structs.h"
|
||||
|
||||
class Bot;
|
||||
class Client;
|
||||
@@ -130,6 +130,10 @@ public:
|
||||
bool SaveBotSettings(Mob* m);
|
||||
bool DeleteBotSettings(const uint32 bot_id);
|
||||
|
||||
void CheckBotSpells();
|
||||
void MapCommandedSpellTypeMinLevels();
|
||||
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
|
||||
|
||||
/* Bot group functions */
|
||||
bool LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 group_id, std::list<uint32>& group_list);
|
||||
|
||||
@@ -211,6 +215,10 @@ public:
|
||||
|
||||
private:
|
||||
std::string query;
|
||||
|
||||
protected:
|
||||
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> commanded_spell_type_min_levels;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2865,181 +2865,3 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void Bot::CheckBotSpells() {
|
||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||
uint16 spell_id;
|
||||
SPDat_Spell_Struct spell;
|
||||
uint16 correct_type;
|
||||
uint16 parent_type;
|
||||
|
||||
for (const auto& s : spell_list) {
|
||||
if (!IsValidSpell(s.spell_id)) {
|
||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
spell = spells[s.spell_id];
|
||||
spell_id = spell.id;
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
|
||||
}
|
||||
else {
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
, s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, spell_id
|
||||
, s.npc_spells_id
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, s.minlevel
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
, s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, spell_id
|
||||
, s.npc_spells_id
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, s.minlevel
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
, s.maxlevel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
correct_type = GetCorrectBotSpellType(s.type, spell_id);
|
||||
parent_type = GetParentSpellType(correct_type);
|
||||
|
||||
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
|
||||
if (s.type == parent_type || s.type == correct_type) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsPetBotSpellType(s.type)) {
|
||||
correct_type = GetPetBotSpellType(correct_type);
|
||||
}
|
||||
}
|
||||
|
||||
if (correct_type == s.type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (correct_type == UINT16_MAX) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, GetSpellTypeNameByID(s.type)
|
||||
, s.type
|
||||
);
|
||||
}
|
||||
else {
|
||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]"
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, GetSpellTypeNameByID(s.type)
|
||||
, s.type
|
||||
, GetSpellTypeNameByID(correct_type)
|
||||
, correct_type
|
||||
);
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]"
|
||||
, correct_type
|
||||
, spell_id
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, GetSpellTypeNameByID(s.type)
|
||||
, s.type
|
||||
, GetSpellTypeNameByID(correct_type)
|
||||
, correct_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::MapSpellTypeLevels() {
|
||||
commanded_spells_min_level.clear();
|
||||
|
||||
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
|
||||
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
|
||||
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if (!Bot::IsValidBotSpellType(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int x = Class::Warrior; x <= Class::Berserker; ++x) {
|
||||
commanded_spells_min_level[i][x] = { UINT8_MAX, "" };
|
||||
}
|
||||
}
|
||||
|
||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||
|
||||
for (const auto& s : spell_list) {
|
||||
if (!IsValidSpell(s.spell_id)) {
|
||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t spell_type = s.type;
|
||||
int32_t bot_class = s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX;
|
||||
uint8_t min_level = s.minlevel;
|
||||
|
||||
if (
|
||||
!EQ::ValueWithin(bot_class, Class::Warrior, Class::Berserker) ||
|
||||
!Bot::IsValidBotSpellType(spell_type)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& spell_info = commanded_spells_min_level[spell_type][bot_class];
|
||||
|
||||
if (min_level < spell_info.min_level) {
|
||||
spell_info.min_level = min_level;
|
||||
spell_info.description = StringFormat(
|
||||
"%s [#%u]: Level %u",
|
||||
GetClassIDName(bot_class),
|
||||
bot_class,
|
||||
min_level
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../zone.h"
|
||||
#include "../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
} else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneCLI::DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 break_length = 50;
|
||||
int failed_count = 0;
|
||||
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
// boot shell zone for testing
|
||||
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||
zone->StopShutdownTimer();
|
||||
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
|
||||
std::cout << "===========================================\n";
|
||||
std::cout << "⚙\uFE0F> Running DataBuckets Tests...\n";
|
||||
std::cout << "===========================================\n\n";
|
||||
|
||||
Client *client = new Client();
|
||||
|
||||
// Basic Key-Value Set/Get
|
||||
client->DeleteBucket("basic_key");
|
||||
client->SetBucket("basic_key", "simple_value");
|
||||
std::string value = client->GetBucket("basic_key");
|
||||
RunTest("Basic Key-Value Set/Get", "simple_value", value);
|
||||
|
||||
// Overwriting a Key
|
||||
client->SetBucket("basic_key", "new_value");
|
||||
value = client->GetBucket("basic_key");
|
||||
RunTest("Overwriting a Key", "new_value", value);
|
||||
|
||||
// Deleting a Key
|
||||
client->DeleteBucket("basic_key");
|
||||
value = client->GetBucket("basic_key");
|
||||
RunTest("Deleting a Key", "", value);
|
||||
|
||||
// Setting a Key with an Expiration
|
||||
client->SetBucket("expiring_key", "expires_soon", "S1");
|
||||
value = client->GetBucket("expiring_key");
|
||||
RunTest("Setting a Key with an Expiration", "expires_soon", value);
|
||||
|
||||
// Ensure Expired Key is Deleted
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
value = client->GetBucket("expiring_key");
|
||||
RunTest("Ensure Expired Key is Deleted", "", value);
|
||||
|
||||
// Cache Read/Write Consistency
|
||||
client->SetBucket("cache_key", "cached_value");
|
||||
value = client->GetBucket("cache_key");
|
||||
RunTest("Cache Read/Write Consistency", "cached_value", value);
|
||||
|
||||
// Cache Clears on Key Deletion
|
||||
client->DeleteBucket("cache_key");
|
||||
value = client->GetBucket("cache_key");
|
||||
RunTest("Cache Clears on Key Deletion", "", value);
|
||||
|
||||
// Setting a Full JSON String
|
||||
client->SetBucket("json_key", R"({"key1":"value1","key2":"value2"})");
|
||||
value = client->GetBucket("json_key");
|
||||
RunTest("Setting a Full JSON String", R"({"key1":"value1","key2":"value2"})", value);
|
||||
|
||||
// Overwriting JSON with a Simple String
|
||||
client->SetBucket("json_key", "string_value");
|
||||
value = client->GetBucket("json_key");
|
||||
RunTest("Overwriting JSON with a Simple String", "string_value", value);
|
||||
|
||||
// Deleting Non-Existent Key
|
||||
client->DeleteBucket("non_existent_key");
|
||||
value = client->GetBucket("non_existent_key");
|
||||
RunTest("Deleting Non-Existent Key", "", value);
|
||||
|
||||
// Basic Key-Value Storage**
|
||||
client->DeleteBucket("simple_key"); // Reset
|
||||
client->SetBucket("simple_key", "simple_value");
|
||||
value = client->GetBucket("simple_key");
|
||||
RunTest("Basic Key-Value Set/Get", "simple_value", value);
|
||||
|
||||
// Nested Key Storage**
|
||||
client->DeleteBucket("nested");
|
||||
client->SetBucket("nested.test1", "value1");
|
||||
client->SetBucket("nested.test2", "value2");
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Nested Key Set/Get", R"({"test1":"value1","test2":"value2"})", value);
|
||||
|
||||
// Prevent Overwriting Objects**
|
||||
client->DeleteBucket("nested");
|
||||
client->SetBucket("nested.test1.a", "value1");
|
||||
client->SetBucket("nested.test2.a", "value2");
|
||||
client->SetBucket("nested.test2", "new_value"); // Should be **rejected**
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Prevent Overwriting Objects", R"({"test1":{"a":"value1"},"test2":{"a":"value2"}})", value);
|
||||
|
||||
// Deleting a Specific Nested Key**
|
||||
client->DeleteBucket("nested");
|
||||
client->SetBucket("nested.test1", "value1");
|
||||
client->SetBucket("nested.test2", "value2");
|
||||
client->DeleteBucket("nested.test1");
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Delete Nested Key", R"({"test2":"value2"})", value);
|
||||
|
||||
// Deleting the Entire Parent Key**
|
||||
client->DeleteBucket("nested");
|
||||
value = client->GetBucket("nested");
|
||||
RunTest("Delete Parent Key", "", value);
|
||||
|
||||
// Expiration is Ignored for Nested Keys**
|
||||
client->DeleteBucket("exp_test");
|
||||
client->SetBucket("exp_test.nested", "data", "S20"); // Expiration ignored
|
||||
value = client->GetBucket("exp_test");
|
||||
RunTest("Expiration Ignored for Nested Keys", R"({"nested":"data"})", value);
|
||||
|
||||
// Cache Behavior**
|
||||
client->DeleteBucket("cache_test");
|
||||
client->SetBucket("cache_test", "cache_value");
|
||||
value = client->GetBucket("cache_test");
|
||||
RunTest("Cache Read/Write Consistency", "cache_value", value);
|
||||
|
||||
// Ensure Deleting Parent Key Clears Cache**
|
||||
client->DeleteBucket("cache_test");
|
||||
value = client->GetBucket("cache_test");
|
||||
RunTest("Cache Clears on Parent Delete", "", value);
|
||||
|
||||
// Setting an Entire JSON Object**
|
||||
client->DeleteBucket("full_json");
|
||||
client->SetBucket("full_json", R"({"key1":"value1","key2":{"subkey":"subvalue"}})");
|
||||
value = client->GetBucket("full_json");
|
||||
RunTest("Set and Retrieve Full JSON Structure", R"({"key1":"value1","key2":{"subkey":"subvalue"}})", value);
|
||||
|
||||
// Partial Nested Key Deletion within JSON**
|
||||
client->DeleteBucket("full_json");
|
||||
client->SetBucket("full_json", R"({"key1":"value1","key2":{"subkey":"subvalue"}})");
|
||||
client->DeleteBucket("full_json.key2");
|
||||
value = client->GetBucket("full_json");
|
||||
RunTest("Delete Nested Key within JSON", R"({"key1":"value1"})", value);
|
||||
|
||||
// Ensure Object Protection on Overwrite Attempt**
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
client->SetBucket("complex.nested", "overwrite_attempt"); // Should be rejected
|
||||
value = client->GetBucket("complex");
|
||||
RunTest("Ensure Object Protection on Overwrite Attempt", R"({"nested":{"obj1":"data1","obj2":"data2"}})", value);
|
||||
|
||||
// Deleting Non-Existent Key Doesn't Break Existing Data**
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
client->DeleteBucket("does_not_exist"); // Should do nothing
|
||||
value = client->GetBucket("complex");
|
||||
RunTest("Deleting Non-Existent Key Doesn't Break Existing Data", R"({"nested":{"obj1":"data1","obj2":"data2"}})", value);
|
||||
|
||||
// Get nested key value one level up **
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
value = client->GetBucket("complex.nested");
|
||||
RunTest("Get nested key value", R"({"obj1":"data1","obj2":"data2"})", value);
|
||||
|
||||
// Get nested key value deep **
|
||||
client->DeleteBucket("complex");
|
||||
client->SetBucket("complex.nested.obj1", "data1");
|
||||
client->SetBucket("complex.nested.obj2", "data2");
|
||||
value = client->GetBucket("complex.nested.obj2");
|
||||
RunTest("Get nested key value deep", R"(data2)", value);
|
||||
|
||||
// Retrieve Nested Key from Plain String**
|
||||
client->DeleteBucket("plain_string");
|
||||
client->SetBucket("plain_string", "some_value");
|
||||
value = client->GetBucket("plain_string.nested");
|
||||
RunTest("Retrieve Nested Key from Plain String", "", value);
|
||||
|
||||
// Store and Retrieve JSON Array**
|
||||
client->DeleteBucket("json_array");
|
||||
client->SetBucket("json_array", R"(["item1", "item2"])");
|
||||
value = client->GetBucket("json_array");
|
||||
RunTest("Store and Retrieve JSON Array", R"(["item1", "item2"])", value);
|
||||
|
||||
// // Prevent Overwriting Array with Object**
|
||||
// client->DeleteBucket("json_array");
|
||||
// client->SetBucket("json_array", R"(["item1", "item2"])");
|
||||
// client->SetBucket("json_array.item", "new_value"); // Should be rejected
|
||||
// value = client->GetBucket("json_array");
|
||||
// RunTest("Prevent Overwriting Array with Object", R"(["item1", "item2"])", value);
|
||||
|
||||
// Retrieve Non-Existent Nested Key**
|
||||
client->DeleteBucket("nested_partial");
|
||||
client->SetBucket("nested_partial.level1", R"({"exists": "yes"})");
|
||||
value = client->GetBucket("nested_partial.level1.non_existent");
|
||||
RunTest("Retrieve Non-Existent Nested Key", "", value);
|
||||
|
||||
// Overwriting Parent Key Deletes Children**
|
||||
client->DeleteBucket("nested_override");
|
||||
client->SetBucket("nested_override.child", "data");
|
||||
client->SetBucket("nested_override", "new_parent_value"); // Should remove `child`
|
||||
value = client->GetBucket("nested_override");
|
||||
RunTest("Overwriting Parent Key Deletes Children", "new_parent_value", value);
|
||||
|
||||
// Store and Retrieve Empty JSON Object**
|
||||
client->DeleteBucket("empty_json");
|
||||
client->SetBucket("empty_json", R"({})");
|
||||
value = client->GetBucket("empty_json");
|
||||
RunTest("Store and Retrieve Empty JSON Object", R"({})", value);
|
||||
|
||||
// Store and Retrieve JSON String**
|
||||
client->DeleteBucket("json_string");
|
||||
client->SetBucket("json_string", R"("this is a string")");
|
||||
value = client->GetBucket("json_string");
|
||||
RunTest("Store and Retrieve JSON String", R"("this is a string")", value);
|
||||
|
||||
// Deeply Nested Key Retrieval**
|
||||
client->DeleteBucket("deep_nested");
|
||||
client->SetBucket("deep_nested.level1.level2.level3.level4.level5", "final_value");
|
||||
value = client->GetBucket("deep_nested.level1.level2.level3.level4.level5");
|
||||
RunTest("Deeply Nested Key Retrieval", "final_value", value);
|
||||
|
||||
// Setting a Key with an Expiration
|
||||
client->SetBucket("nested_expire.test.test", "shouldnt_expire", "S1");
|
||||
value = client->GetBucket("nested_expire");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
RunTest("Setting a nested key with an expiration protection test", R"({"test":{"test":"shouldnt_expire"}})", value);
|
||||
|
||||
// Delete Deep Nested Key Keeps Parent**
|
||||
// client->DeleteBucket("deep_nested");
|
||||
// client->SetBucket("deep_nested.level1.level2.level3", R"({"key": "value"})");
|
||||
// client->DeleteBucket("deep_nested.level1.level2.level3.key");
|
||||
// value = client->GetBucket("deep_nested.level1.level2.level3");
|
||||
// RunTest("Delete Deep Nested Key Keeps Parent", "{}", value);
|
||||
|
||||
std::cout << "\n===========================================\n";
|
||||
std::cout << "✅ All DataBucket Tests Completed!\n";
|
||||
std::cout << "===========================================\n";
|
||||
}
|
||||
+141
-110
@@ -4,8 +4,76 @@
|
||||
#include "../zone.h"
|
||||
#include "../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
#include "../../common/json/json.hpp"
|
||||
|
||||
extern Zone *zone;
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct HandinEntry {
|
||||
std::string item_id = "0";
|
||||
uint32 count = 0;
|
||||
const EQ::ItemInstance *item = nullptr;
|
||||
bool is_multiquest_item = false; // state
|
||||
};
|
||||
|
||||
struct HandinMoney {
|
||||
uint32 platinum = 0;
|
||||
uint32 gold = 0;
|
||||
uint32 silver = 0;
|
||||
uint32 copper = 0;
|
||||
};
|
||||
|
||||
struct Handin {
|
||||
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
|
||||
HandinMoney money = {}; // money can be removed from this set as successful handins are made
|
||||
};
|
||||
|
||||
struct TestCase {
|
||||
std::string description;
|
||||
Handin hand_in;
|
||||
Handin required;
|
||||
Handin returned;
|
||||
bool handin_check_result;
|
||||
};
|
||||
|
||||
void RunTest(const std::string &test_name, bool expected, bool actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
||||
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
std::string SerializeHandin(const std::map<std::string, uint32> &items, const HandinMoney &money)
|
||||
{
|
||||
json j;
|
||||
j["items"] = items;
|
||||
j["money"] = {
|
||||
{"platinum", money.platinum},
|
||||
{"gold", money.gold},
|
||||
{"silver", money.silver},
|
||||
{"copper", money.copper}
|
||||
};
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
@@ -13,15 +81,6 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 break_length = 50;
|
||||
int failed_count = 0;
|
||||
|
||||
RegisterExecutablePlatform(EQEmuExePlatform::ExePlatformZoneSidecar);
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
LogInfo("Booting test zone for NPC handins");
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||
@@ -30,9 +89,9 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
LogInfo("> Done booting test zone");
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
std::cout << "===========================================\n";
|
||||
std::cout << "⚙\uFE0F> Running Hand-in Tests...\n";
|
||||
std::cout << "===========================================\n\n";
|
||||
|
||||
Client *c = new Client();
|
||||
auto npc_type = content_db.LoadNPCTypesData(754008);
|
||||
@@ -46,36 +105,6 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
|
||||
entity_list.AddNPC(npc);
|
||||
|
||||
LogInfo("> Spawned NPC [{}]", npc->GetCleanName());
|
||||
LogInfo("> Spawned client [{}]", c->GetCleanName());
|
||||
|
||||
struct HandinEntry {
|
||||
std::string item_id = "0";
|
||||
uint32 count = 0;
|
||||
const EQ::ItemInstance *item = nullptr;
|
||||
bool is_multiquest_item = false; // state
|
||||
};
|
||||
|
||||
struct HandinMoney {
|
||||
uint32 platinum = 0;
|
||||
uint32 gold = 0;
|
||||
uint32 silver = 0;
|
||||
uint32 copper = 0;
|
||||
};
|
||||
|
||||
struct Handin {
|
||||
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
|
||||
HandinMoney money = {}; // money can be removed from this set as successful handins are made
|
||||
};
|
||||
|
||||
struct TestCase {
|
||||
std::string description = "";
|
||||
Handin hand_in;
|
||||
Handin required;
|
||||
Handin returned;
|
||||
bool handin_check_result;
|
||||
};
|
||||
|
||||
std::vector<TestCase> test_cases = {
|
||||
TestCase{
|
||||
.description = "Test basic cloth-cap hand-in",
|
||||
@@ -155,7 +184,10 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
.items = {},
|
||||
.money = {.platinum = 100},
|
||||
},
|
||||
.returned = {},
|
||||
.returned = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
@@ -168,7 +200,10 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
.items = {},
|
||||
.money = {.platinum = 100, .gold = 100, .silver = 100, .copper = 100},
|
||||
},
|
||||
.returned = {},
|
||||
.returned = {
|
||||
.items = {},
|
||||
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
TestCase{
|
||||
@@ -217,8 +252,11 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
},
|
||||
.returned = {
|
||||
.items = {
|
||||
HandinEntry{.item_id = "1001", .count = 1},
|
||||
HandinEntry{
|
||||
.item_id = "1001", .count = 0,
|
||||
},
|
||||
},
|
||||
.money = {.platinum = 1},
|
||||
},
|
||||
.handin_check_result = false,
|
||||
},
|
||||
@@ -304,12 +342,7 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
HandinEntry{.item_id = "1007", .count = 1},
|
||||
},
|
||||
.money = {
|
||||
.platinum = 1,
|
||||
.gold = 666,
|
||||
.silver = 234,
|
||||
.copper = 444,
|
||||
},
|
||||
.money = {},
|
||||
},
|
||||
.handin_check_result = true,
|
||||
},
|
||||
@@ -402,8 +435,8 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
},
|
||||
};
|
||||
|
||||
std::map<std::string, uint32> hand_ins;
|
||||
std::map<std::string, uint32> required;
|
||||
std::map<std::string, uint32> hand_ins;
|
||||
std::map<std::string, uint32> required;
|
||||
std::vector<EQ::ItemInstance *> items;
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
@@ -411,14 +444,12 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
// turn this on to see debugging output
|
||||
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
|
||||
for (auto &test_case: test_cases) {
|
||||
for (auto &test: test_cases) {
|
||||
hand_ins.clear();
|
||||
required.clear();
|
||||
items.clear();
|
||||
|
||||
for (auto &hand_in: test_case.hand_in.items) {
|
||||
for (auto &hand_in: test.hand_in.items) {
|
||||
auto item_id = Strings::ToInt(hand_in.item_id);
|
||||
EQ::ItemInstance *inst = database.CreateItem(item_id);
|
||||
if (inst->IsStackable()) {
|
||||
@@ -434,72 +465,76 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
}
|
||||
|
||||
// money
|
||||
if (test_case.hand_in.money.platinum > 0) {
|
||||
hand_ins["platinum"] = test_case.hand_in.money.platinum;
|
||||
if (test.hand_in.money.platinum > 0) {
|
||||
hand_ins["platinum"] = test.hand_in.money.platinum;
|
||||
}
|
||||
if (test_case.hand_in.money.gold > 0) {
|
||||
hand_ins["gold"] = test_case.hand_in.money.gold;
|
||||
if (test.hand_in.money.gold > 0) {
|
||||
hand_ins["gold"] = test.hand_in.money.gold;
|
||||
}
|
||||
if (test_case.hand_in.money.silver > 0) {
|
||||
hand_ins["silver"] = test_case.hand_in.money.silver;
|
||||
if (test.hand_in.money.silver > 0) {
|
||||
hand_ins["silver"] = test.hand_in.money.silver;
|
||||
}
|
||||
if (test_case.hand_in.money.copper > 0) {
|
||||
hand_ins["copper"] = test_case.hand_in.money.copper;
|
||||
if (test.hand_in.money.copper > 0) {
|
||||
hand_ins["copper"] = test.hand_in.money.copper;
|
||||
}
|
||||
|
||||
for (auto &req: test_case.required.items) {
|
||||
for (auto &req: test.required.items) {
|
||||
required[req.item_id] = req.count;
|
||||
}
|
||||
|
||||
// money
|
||||
if (test_case.required.money.platinum > 0) {
|
||||
required["platinum"] = test_case.required.money.platinum;
|
||||
if (test.required.money.platinum > 0) {
|
||||
required["platinum"] = test.required.money.platinum;
|
||||
}
|
||||
if (test_case.required.money.gold > 0) {
|
||||
required["gold"] = test_case.required.money.gold;
|
||||
if (test.required.money.gold > 0) {
|
||||
required["gold"] = test.required.money.gold;
|
||||
}
|
||||
if (test_case.required.money.silver > 0) {
|
||||
required["silver"] = test_case.required.money.silver;
|
||||
if (test.required.money.silver > 0) {
|
||||
required["silver"] = test.required.money.silver;
|
||||
}
|
||||
if (test_case.required.money.copper > 0) {
|
||||
required["copper"] = test_case.required.money.copper;
|
||||
if (test.required.money.copper > 0) {
|
||||
required["copper"] = test.required.money.copper;
|
||||
}
|
||||
|
||||
auto result = npc->CheckHandin(c, hand_ins, required, items);
|
||||
if (result != test_case.handin_check_result) {
|
||||
failed_count++;
|
||||
LogError("FAIL [{}]", test_case.description);
|
||||
// print out the hand-ins
|
||||
LogError("Hand-ins >");
|
||||
for (auto &hand_in: hand_ins) {
|
||||
LogError(" > Item [{}] count [{}]", hand_in.first, hand_in.second);
|
||||
}
|
||||
LogError("Required >");
|
||||
for (auto &req: required) {
|
||||
LogError(" > Item [{}] count [{}]", req.first, req.second);
|
||||
}
|
||||
LogError("Expected [{}] got [{}]", test_case.handin_check_result, result);
|
||||
}
|
||||
else {
|
||||
LogInfo("PASS [{}]", test_case.description);
|
||||
}
|
||||
|
||||
RunTest(test.description, test.handin_check_result, result);
|
||||
|
||||
auto returned = npc->ReturnHandinItems(c);
|
||||
|
||||
// assert that returned items are expected
|
||||
for (auto &item: test_case.returned.items) {
|
||||
auto found = false;
|
||||
for (auto &ret: returned.items) {
|
||||
if (ret.item_id == item.item_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LogError("Returned item [{}] not expected", item.item_id);
|
||||
}
|
||||
std::map<std::string, uint32> returned_items;
|
||||
HandinMoney returned_money{};
|
||||
|
||||
// Serialize returned items
|
||||
for (const auto &ret: returned.items) {
|
||||
// if (ret.item->IsStackable() && ret.item->GetCharges() != ret.count) {
|
||||
// ret.item->SetCharges(ret.count);
|
||||
// }
|
||||
returned_items[ret.item_id] += ret.count;
|
||||
}
|
||||
|
||||
// Serialize returned money
|
||||
returned_money.platinum = returned.money.platinum;
|
||||
returned_money.gold = returned.money.gold;
|
||||
returned_money.silver = returned.money.silver;
|
||||
returned_money.copper = returned.money.copper;
|
||||
|
||||
// Serialize expected and actual return values for comparison
|
||||
std::map<std::string, uint32> expected_returned_items;
|
||||
for (const auto &entry: test.returned.items) {
|
||||
expected_returned_items[entry.item_id] += entry.count;
|
||||
}
|
||||
|
||||
std::string expected_serialized = SerializeHandin(
|
||||
expected_returned_items,
|
||||
test.returned.money
|
||||
);
|
||||
|
||||
std::string actual_serialized = SerializeHandin(returned_items, returned_money);
|
||||
|
||||
// Run serialization check test
|
||||
RunSerializedTest(test.description + " (Return Validation)", expected_serialized, actual_serialized);
|
||||
|
||||
npc->ResetHandin();
|
||||
|
||||
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
|
||||
@@ -508,11 +543,7 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
|
||||
}
|
||||
}
|
||||
|
||||
if (failed_count > 0) {
|
||||
LogError("Failed [{}] tests", failed_count);
|
||||
std::exit(1);
|
||||
}
|
||||
else {
|
||||
LogInfo("All tests passed");
|
||||
}
|
||||
std::cout << "\n===========================================\n";
|
||||
std::cout << "✅ All NPC Hand-in Tests Completed!\n";
|
||||
std::cout << "===========================================\n";
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
|
||||
uint32 break_length = 50;
|
||||
int failed_count = 0;
|
||||
|
||||
RegisterExecutablePlatform(EQEmuExePlatform::ExePlatformZoneSidecar);
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
LogInfo("Booting test zone for NPC handins (MultiQuest)");
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||
@@ -30,9 +24,9 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
LogInfo("> Done booting test zone");
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
std::cout << "===========================================\n";
|
||||
std::cout << "⚙\uFE0F> Running Hand-in Tests (Multi-Quest)...\n";
|
||||
std::cout << "===========================================\n\n";
|
||||
|
||||
Client *c = new Client();
|
||||
auto npc_type = content_db.LoadNPCTypesData(754008);
|
||||
@@ -47,9 +41,6 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
|
||||
entity_list.AddNPC(npc);
|
||||
npc->MultiQuestEnable();
|
||||
|
||||
LogInfo("> Spawned NPC [{}]", npc->GetCleanName());
|
||||
LogInfo("> Spawned client [{}]", c->GetCleanName());
|
||||
|
||||
struct HandinEntry {
|
||||
std::string item_id = "0";
|
||||
uint32 count = 0;
|
||||
@@ -108,12 +99,10 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
|
||||
// turn this on to see debugging output
|
||||
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
|
||||
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||
|
||||
for (auto &test_case: test_cases) {
|
||||
for (auto &test: test_cases) {
|
||||
required.clear();
|
||||
|
||||
for (auto &hand_in: test_case.hand_in.items) {
|
||||
for (auto &hand_in: test.hand_in.items) {
|
||||
hand_ins.clear();
|
||||
items.clear();
|
||||
|
||||
@@ -135,72 +124,43 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
|
||||
}
|
||||
|
||||
// money
|
||||
if (test_case.hand_in.money.platinum > 0) {
|
||||
hand_ins["platinum"] = test_case.hand_in.money.platinum;
|
||||
if (test.hand_in.money.platinum > 0) {
|
||||
hand_ins["platinum"] = test.hand_in.money.platinum;
|
||||
}
|
||||
if (test_case.hand_in.money.gold > 0) {
|
||||
hand_ins["gold"] = test_case.hand_in.money.gold;
|
||||
if (test.hand_in.money.gold > 0) {
|
||||
hand_ins["gold"] = test.hand_in.money.gold;
|
||||
}
|
||||
if (test_case.hand_in.money.silver > 0) {
|
||||
hand_ins["silver"] = test_case.hand_in.money.silver;
|
||||
if (test.hand_in.money.silver > 0) {
|
||||
hand_ins["silver"] = test.hand_in.money.silver;
|
||||
}
|
||||
if (test_case.hand_in.money.copper > 0) {
|
||||
hand_ins["copper"] = test_case.hand_in.money.copper;
|
||||
if (test.hand_in.money.copper > 0) {
|
||||
hand_ins["copper"] = test.hand_in.money.copper;
|
||||
}
|
||||
|
||||
for (auto &req: test_case.required.items) {
|
||||
for (auto &req: test.required.items) {
|
||||
required[req.item_id] = req.count;
|
||||
}
|
||||
|
||||
// money
|
||||
if (test_case.required.money.platinum > 0) {
|
||||
required["platinum"] = test_case.required.money.platinum;
|
||||
if (test.required.money.platinum > 0) {
|
||||
required["platinum"] = test.required.money.platinum;
|
||||
}
|
||||
if (test_case.required.money.gold > 0) {
|
||||
required["gold"] = test_case.required.money.gold;
|
||||
if (test.required.money.gold > 0) {
|
||||
required["gold"] = test.required.money.gold;
|
||||
}
|
||||
if (test_case.required.money.silver > 0) {
|
||||
required["silver"] = test_case.required.money.silver;
|
||||
if (test.required.money.silver > 0) {
|
||||
required["silver"] = test.required.money.silver;
|
||||
}
|
||||
if (test_case.required.money.copper > 0) {
|
||||
required["copper"] = test_case.required.money.copper;
|
||||
if (test.required.money.copper > 0) {
|
||||
required["copper"] = test.required.money.copper;
|
||||
}
|
||||
|
||||
auto result = npc->CheckHandin(c, hand_ins, required, items);
|
||||
if (result != test_case.handin_check_result) {
|
||||
failed_count++;
|
||||
LogError("FAIL [{}]", test_case.description);
|
||||
// print out the hand-ins
|
||||
LogError("Hand-ins >");
|
||||
for (auto &item: npc->GetHandin().items) {
|
||||
LogError(" > Item [{}] count [{}]", item.item_id, item.count);
|
||||
}
|
||||
LogError("Required >");
|
||||
for (auto &req: required) {
|
||||
LogError(" > Item [{}] count [{}]", req.first, req.second);
|
||||
}
|
||||
LogError("Expected [{}] got [{}]", test_case.handin_check_result, result);
|
||||
}
|
||||
else {
|
||||
LogInfo("PASS [{}]", test_case.description);
|
||||
}
|
||||
|
||||
RunTest(test.description, test.handin_check_result, result);
|
||||
|
||||
auto returned = npc->ReturnHandinItems(c);
|
||||
|
||||
// assert that returned items are expected
|
||||
for (auto &item: test_case.returned.items) {
|
||||
auto found = false;
|
||||
for (auto &ret: returned.items) {
|
||||
if (ret.item_id == item.item_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LogError("Returned item [{}] not expected", item.item_id);
|
||||
}
|
||||
}
|
||||
|
||||
npc->ResetHandin();
|
||||
|
||||
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
|
||||
@@ -209,11 +169,7 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
|
||||
}
|
||||
}
|
||||
|
||||
if (failed_count > 0) {
|
||||
LogError("Failed [{}] tests", failed_count);
|
||||
std::exit(1);
|
||||
}
|
||||
else {
|
||||
LogInfo("All tests passed");
|
||||
}
|
||||
std::cout << "\n===========================================\n";
|
||||
std::cout << "✅ All NPC Hand-in Tests Completed (Multi-Quest)!\n";
|
||||
std::cout << "===========================================\n";
|
||||
}
|
||||
|
||||
+140
-37
@@ -708,6 +708,9 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
entity_list.RemoveMobFromCloseLists(this);
|
||||
m_close_mobs.clear();
|
||||
|
||||
if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB (Parcel, EnableParcelMerchants)) {
|
||||
DoParcelCancel();
|
||||
}
|
||||
@@ -965,6 +968,10 @@ bool Client::SaveAA()
|
||||
|
||||
m_pp.aapoints_spent = aa_points_spent + m_epp.expended_aa;
|
||||
|
||||
if (v.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CharacterAlternateAbilitiesRepository::ReplaceMany(database, v);
|
||||
}
|
||||
|
||||
@@ -2542,40 +2549,59 @@ void Client::ChangeLastName(std::string last_name) {
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
|
||||
// Deprecated, this packet does not actually work in ROF2
|
||||
bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname)
|
||||
{
|
||||
// check duplicate name
|
||||
bool used_name = database.IsNameUsed((const char*) in_firstname);
|
||||
if (used_name) {
|
||||
if (!ChangeFirstName(in_firstname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update character_
|
||||
if(!database.UpdateName(GetName(), in_firstname))
|
||||
return false;
|
||||
|
||||
// update pp
|
||||
memset(m_pp.name, 0, sizeof(m_pp.name));
|
||||
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
|
||||
strcpy(name, m_pp.name);
|
||||
Save();
|
||||
|
||||
// send name update packet
|
||||
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
|
||||
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
|
||||
strn0cpy(gmn->gmname,gmname,64);
|
||||
strn0cpy(gmn->gmname,gmname.c_str(),64);
|
||||
strn0cpy(gmn->oldname,GetName(),64);
|
||||
strn0cpy(gmn->newname,in_firstname,64);
|
||||
strn0cpy(gmn->newname,in_firstname.c_str(),64);
|
||||
gmn->unknown[0] = 1;
|
||||
gmn->unknown[1] = 1;
|
||||
gmn->unknown[2] = 1;
|
||||
entity_list.QueueClients(this, outapp, false);
|
||||
safe_delete(outapp);
|
||||
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::ChangeFirstName(const std::string in_firstname)
|
||||
{
|
||||
// check duplicate name
|
||||
bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname);
|
||||
if (used_name || !database.CheckNameFilter(in_firstname, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update character_
|
||||
if(!database.UpdateNameByID(CharacterID(), in_firstname))
|
||||
return false;
|
||||
|
||||
// Send Name Update to Clients
|
||||
SendRename(this, GetName(), in_firstname.c_str());
|
||||
SetName(in_firstname.c_str());
|
||||
|
||||
// update pp
|
||||
memset(m_pp.name, 0, sizeof(m_pp.name));
|
||||
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str());
|
||||
strcpy(name, m_pp.name);
|
||||
Save();
|
||||
|
||||
// Update the active char in account table
|
||||
database.UpdateLiveChar(in_firstname, AccountID());
|
||||
|
||||
// finally, update the /who list
|
||||
UpdateWho();
|
||||
|
||||
// success
|
||||
ClearNameChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4622,7 +4648,7 @@ void Client::KeyRingLoad()
|
||||
const auto &l = KeyringRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_id` = {} ORDER BY `item_id`",
|
||||
"`char_id` = {} ORDER BY `item_id` ASC",
|
||||
character_id
|
||||
)
|
||||
);
|
||||
@@ -4631,21 +4657,15 @@ void Client::KeyRingLoad()
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (const auto &e : l) {
|
||||
for (const auto& e : l) {
|
||||
keyring.emplace_back(e.item_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::KeyRingAdd(uint32 item_id)
|
||||
bool Client::KeyRingAdd(uint32 item_id)
|
||||
{
|
||||
if (!item_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool found = KeyRingCheck(item_id);
|
||||
if (found) {
|
||||
return;
|
||||
if (!item_id || KeyRingCheck(item_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto e = KeyringRepository::NewEntity();
|
||||
@@ -4656,14 +4676,14 @@ void Client::KeyRingAdd(uint32 item_id)
|
||||
e = KeyringRepository::InsertOne(database, e);
|
||||
|
||||
if (!e.id) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
keyring.emplace_back(item_id);
|
||||
|
||||
if (!RuleB(World, UseItemLinksForKeyRing)) {
|
||||
Message(Chat::LightBlue, "Added to keyring.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string &item_link = database.CreateItemLink(item_id);
|
||||
@@ -4675,17 +4695,25 @@ void Client::KeyRingAdd(uint32 item_id)
|
||||
item_link
|
||||
).c_str()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::KeyRingCheck(uint32 item_id)
|
||||
{
|
||||
for (const auto &e : keyring) {
|
||||
if (e == item_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return std::find(keyring.begin(), keyring.end(), item_id) != keyring.end();
|
||||
}
|
||||
|
||||
return false;
|
||||
bool Client::KeyRingClear()
|
||||
{
|
||||
keyring.clear();
|
||||
|
||||
return KeyringRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_id` = {}",
|
||||
CharacterID()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void Client::KeyRingList()
|
||||
@@ -4694,16 +4722,91 @@ void Client::KeyRingList()
|
||||
|
||||
const EQ::ItemData *item = nullptr;
|
||||
|
||||
for (const auto &e : keyring) {
|
||||
for (const uint32& e : keyring) {
|
||||
item = database.GetItem(e);
|
||||
if (item) {
|
||||
const std::string &item_string = RuleB(World, UseItemLinksForKeyRing) ? database.CreateItemLink(e) : item->Name;
|
||||
const std::string& item_string = (
|
||||
RuleB(World, UseItemLinksForKeyRing) ?
|
||||
database.CreateItemLink(e) :
|
||||
item->Name
|
||||
);
|
||||
|
||||
Message(Chat::LightBlue, item_string.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::KeyRingRemove(uint32 item_id)
|
||||
{
|
||||
keyring.erase(
|
||||
std::remove(
|
||||
keyring.begin(),
|
||||
keyring.end(),
|
||||
item_id
|
||||
)
|
||||
);
|
||||
|
||||
return KeyringRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_id` = {} AND `item_id` = {}",
|
||||
CharacterID(),
|
||||
item_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
bool Client::IsNameChangeAllowed() {
|
||||
if (RuleB(Character, AlwaysAllowNameChange)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto k = GetScopedBucketKeys();
|
||||
k.key = "name_change_allowed";
|
||||
|
||||
auto b = DataBucket::GetData(k);
|
||||
if (!b.value.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Client::ClearNameChange() {
|
||||
if (!IsNameChangeAllowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto k = GetScopedBucketKeys();
|
||||
k.key = "name_change_allowed";
|
||||
|
||||
DataBucket::DeleteData(k);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Client::InvokeChangeNameWindow(bool immediate) {
|
||||
if (!IsNameChangeAllowed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy;
|
||||
|
||||
auto outapp = new EQApplicationPacket(packet_op, 0);
|
||||
QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void Client::GrantNameChange() {
|
||||
|
||||
auto k = GetScopedBucketKeys();
|
||||
k.key = "name_change_allowed";
|
||||
k.value = "allowed"; // potentially put a timestamp here
|
||||
DataBucket::SetData(k);
|
||||
|
||||
InvokeChangeNameWindow(true);
|
||||
}
|
||||
|
||||
bool Client::IsPetNameChangeAllowed() {
|
||||
if (RuleB(Pets, AlwaysAllowPetRename)) {
|
||||
return true;
|
||||
|
||||
+43
-5
@@ -327,9 +327,15 @@ public:
|
||||
// void TraderPriceUpdate(const EQApplicationPacket *app);
|
||||
uint8 WithCustomer(uint16 NewCustomer);
|
||||
void KeyRingLoad();
|
||||
void KeyRingAdd(uint32 item_id);
|
||||
bool KeyRingAdd(uint32 item_id);
|
||||
bool KeyRingCheck(uint32 item_id);
|
||||
bool KeyRingClear();
|
||||
bool KeyRingRemove(uint32 item_id);
|
||||
void KeyRingList();
|
||||
bool IsNameChangeAllowed();
|
||||
void InvokeChangeNameWindow(bool immediate = true);
|
||||
bool ClearNameChange();
|
||||
void GrantNameChange();
|
||||
bool IsPetNameChangeAllowed();
|
||||
void GrantPetNameChange();
|
||||
void ClearPetNameChange();
|
||||
@@ -402,6 +408,7 @@ public:
|
||||
void LoadParcels();
|
||||
std::map<uint32, CharacterParcelsRepository::CharacterParcels> GetParcels() { return m_parcels; }
|
||||
int32 FindNextFreeParcelSlot(uint32 char_id);
|
||||
int32 FindNextFreeParcelSlotUsingMemory();
|
||||
void SendParcelIconStatus();
|
||||
|
||||
void SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions action);
|
||||
@@ -440,6 +447,8 @@ public:
|
||||
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
|
||||
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
|
||||
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
|
||||
void CancelBuyerTradeWindow();
|
||||
void CancelTraderTradeWindow();
|
||||
|
||||
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
|
||||
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
|
||||
@@ -506,7 +515,8 @@ public:
|
||||
bool AutoAttackEnabled() const { return auto_attack; }
|
||||
bool AutoFireEnabled() const { return auto_fire; }
|
||||
|
||||
bool ChangeFirstName(const char* in_firstname,const char* gmname);
|
||||
bool ChangeFirstName(const std::string in_firstname,const std::string gmname);
|
||||
bool ChangeFirstName(const std::string in_firstname);
|
||||
|
||||
void Duck();
|
||||
void Stand();
|
||||
@@ -1289,6 +1299,29 @@ public:
|
||||
void SendSpellTypePrompts(bool commanded_types = false, bool client_only_types = false);
|
||||
|
||||
// Task System Methods
|
||||
inline void LoadClientSharedCompletedTasks()
|
||||
{
|
||||
std::string query = fmt::format(R"(
|
||||
SELECT
|
||||
cst.task_id
|
||||
FROM completed_shared_task_members cstm
|
||||
JOIN completed_shared_tasks cst ON cstm.shared_task_id = cst.id
|
||||
WHERE cstm.character_id = {}
|
||||
GROUP BY cst.task_id;
|
||||
)", CharacterID());
|
||||
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_completed_shared_tasks.clear();
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
m_completed_shared_tasks.push_back(std::stoi(row[0]));
|
||||
}
|
||||
};
|
||||
inline std::vector<uint32_t> GetCompletedSharedTasks() const { return m_completed_shared_tasks; };
|
||||
void LoadClientTaskState();
|
||||
void RemoveClientTaskState();
|
||||
void SendTaskActivityComplete(int task_id, int activity_id, int task_index, TaskType task_type, int task_incomplete=1);
|
||||
@@ -1459,7 +1492,10 @@ public:
|
||||
{
|
||||
return (task_state ? task_state->EnabledTaskCount(task_set_id) : -1);
|
||||
}
|
||||
inline bool IsTaskCompleted(int task_id) { return (task_state ? task_state->IsTaskCompleted(task_id) : false); }
|
||||
inline bool IsTaskCompleted(int task_id)
|
||||
{
|
||||
return (task_state ? task_state->IsTaskCompleted(task_id, this) : false);
|
||||
}
|
||||
inline bool AreTasksCompleted(std::vector<int> task_ids)
|
||||
{
|
||||
return (task_state ? task_state->AreTasksCompleted(task_ids) : false);
|
||||
@@ -1872,7 +1908,7 @@ public:
|
||||
void SendEvolvingPacket(int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item);
|
||||
void DoEvolveItemToggle(const EQApplicationPacket* app);
|
||||
void DoEvolveItemDisplayFinalResult(const EQApplicationPacket* app);
|
||||
bool DoEvolveCheckProgression(const EQ::ItemInstance &inst);
|
||||
bool DoEvolveCheckProgression(EQ::ItemInstance &inst);
|
||||
void SendEvolveXPWindowDetails(const EQApplicationPacket* app);
|
||||
void DoEvolveTransferXP(const EQApplicationPacket* app);
|
||||
void SendEvolveXPTransferWindow();
|
||||
@@ -2012,7 +2048,7 @@ private:
|
||||
bool GuildBanker;
|
||||
uint16 duel_target;
|
||||
bool duelaccepted;
|
||||
std::list<uint32> keyring;
|
||||
std::vector<uint32> keyring;
|
||||
bool tellsoff; // GM /toggle
|
||||
bool gm_hide_me;
|
||||
bool LFG;
|
||||
@@ -2290,6 +2326,8 @@ private:
|
||||
bool m_has_quest_compass = false;
|
||||
std::vector<uint32_t> m_dynamic_zone_ids;
|
||||
|
||||
std::vector<uint32_t> m_completed_shared_tasks;
|
||||
|
||||
public:
|
||||
enum BotOwnerOption : size_t {
|
||||
booDeathMarquee,
|
||||
|
||||
+120
-26
@@ -8,7 +8,8 @@
|
||||
#include "worldserver.h"
|
||||
|
||||
extern WorldServer worldserver;
|
||||
extern QueryServ* QServ;
|
||||
extern QueryServ* QServ;
|
||||
const std::string SUB_TYPE_DELIMITER = ".";
|
||||
|
||||
void Client::DoEvolveItemToggle(const EQApplicationPacket *app)
|
||||
{
|
||||
@@ -70,18 +71,19 @@ void Client::SendEvolvingPacket(const int8 action, const CharacterEvolvingItemsR
|
||||
|
||||
void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
{
|
||||
std::vector<const EQ::ItemInstance *> queue{};
|
||||
std::vector<EQ::ItemInstance *> queue{};
|
||||
|
||||
for (auto &[key, inst]: GetInv().GetWorn()) {
|
||||
LogEvolveItemDetail(
|
||||
"CharacterID <green>[{}] found equipped item ID <yellow>[{}]", CharacterID(), inst->GetID());
|
||||
if (!inst->IsEvolving() || !inst->GetEvolveActivated()) {
|
||||
LogEvolveItemDetail(
|
||||
"CharacterID <green>[{}], item ID <yellow>[{}] not an evolving item.", CharacterID(), inst->GetID());
|
||||
"CharacterID <green>[{}], item ID <yellow>[{}] not an evolving item.", CharacterID(), inst->GetID()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst->GetTimers().contains("evolve") && !inst->GetTimers().at("evolve").Check()) {
|
||||
if (inst->GetTimers().contains("evolve") && !inst->GetTimers().at("evolve").Check(false)) {
|
||||
LogEvolveItemDetail(
|
||||
"CharacterID <green>[{}], item ID <yellow>[{}] timer not yet expired. <red>[{}] secs remaining.",
|
||||
CharacterID(),
|
||||
@@ -98,32 +100,40 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
CharacterID(),
|
||||
inst->GetID(),
|
||||
type,
|
||||
sub_type);
|
||||
sub_type
|
||||
);
|
||||
|
||||
auto sub_types = Strings::Split(sub_type, SUB_TYPE_DELIMITER);
|
||||
auto has_sub_type = [&](uint32_t type) {
|
||||
return Strings::Contains(sub_types, std::to_string(type));
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case EvolvingItems::Types::AMOUNT_OF_EXP: {
|
||||
LogEvolveItemDetail("Type <green>[{}] Processing sub_type", type);
|
||||
if (sub_type == EvolvingItems::SubTypes::ALL_EXP ||
|
||||
(sub_type == EvolvingItems::SubTypes::GROUP_EXP && IsGrouped())) {
|
||||
LogEvolveItemDetail("Sub_Type <green>[{}] Processing Item", sub_type);
|
||||
inst->SetEvolveAddToCurrentAmount(exp * RuleR(EvolvingItems, PercentOfGroupExperience) / 100);
|
||||
|
||||
// Determine the evolve amount based on sub_type conditions
|
||||
int evolve_amount = 0;
|
||||
|
||||
if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) ||
|
||||
(has_sub_type(EvolvingItems::SubTypes::GROUP_EXP) && IsGrouped())) {
|
||||
evolve_amount = exp * RuleR(EvolvingItems, PercentOfGroupExperience) / 100;
|
||||
}
|
||||
else if (
|
||||
sub_type == EvolvingItems::SubTypes::ALL_EXP ||
|
||||
(sub_type == EvolvingItems::SubTypes::RAID_EXP && IsRaidGrouped())) {
|
||||
LogEvolveItemDetail("Sub_Type <green>[{}] Processing Item", sub_type);
|
||||
inst->SetEvolveAddToCurrentAmount(exp * RuleR(EvolvingItems, PercentOfRaidExperience) / 100);
|
||||
else if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) ||
|
||||
(has_sub_type(EvolvingItems::SubTypes::RAID_EXP) && IsRaidGrouped())) {
|
||||
evolve_amount = exp * RuleR(EvolvingItems, PercentOfRaidExperience) / 100;
|
||||
}
|
||||
else if (
|
||||
sub_type == EvolvingItems::SubTypes::ALL_EXP || sub_type == EvolvingItems::SubTypes::SOLO_EXP) {
|
||||
LogEvolveItemDetail("Sub_Type <green>[{}] Processing Item", sub_type);
|
||||
inst->SetEvolveAddToCurrentAmount(exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100);
|
||||
else if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) ||
|
||||
has_sub_type(EvolvingItems::SubTypes::SOLO_EXP)) {
|
||||
evolve_amount = exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100;
|
||||
}
|
||||
|
||||
inst->SetEvolveAddToCurrentAmount(evolve_amount);
|
||||
inst->CalculateEvolveProgression();
|
||||
|
||||
auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression(
|
||||
database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression());
|
||||
database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression()
|
||||
);
|
||||
if (!e.id) {
|
||||
break;
|
||||
}
|
||||
@@ -146,7 +156,7 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
}
|
||||
case EvolvingItems::Types::SPECIFIC_MOB_RACE: {
|
||||
LogEvolveItemDetail("Type <green>[{}] Processing sub type", type);
|
||||
if (mob && mob->GetRace() == sub_type) {
|
||||
if (mob && has_sub_type(mob->GetRace())) {
|
||||
LogEvolveItemDetail("Sub_Type <green>[{}] Processing Item", sub_type);
|
||||
inst->SetEvolveAddToCurrentAmount(1);
|
||||
inst->CalculateEvolveProgression();
|
||||
@@ -155,7 +165,8 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
database,
|
||||
inst->GetEvolveUniqueID(),
|
||||
inst->GetEvolveCurrentAmount(),
|
||||
inst->GetEvolveProgression());
|
||||
inst->GetEvolveProgression()
|
||||
);
|
||||
if (!e.id) {
|
||||
break;
|
||||
}
|
||||
@@ -163,7 +174,8 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e);
|
||||
|
||||
LogEvolveItem(
|
||||
"Processing Complete for item id <green>[{1}] Type 3 Specific Mob Race - SubType <yellow>[{0}] "
|
||||
"Processing Complete for item id <green>[{1}] Type 3 Specific Mob Race - SubType "
|
||||
"<yellow>[{0}] "
|
||||
"- Increased count by 1 for <green>[{1}]",
|
||||
sub_type,
|
||||
inst->GetID()
|
||||
@@ -178,7 +190,7 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
}
|
||||
case EvolvingItems::Types::SPECIFIC_ZONE_ID: {
|
||||
LogEvolveItemDetail("Type <green>[{}] Processing sub type", type);
|
||||
if (mob && mob->GetZoneID() == sub_type) {
|
||||
if (mob && has_sub_type(mob->GetZoneID())) {
|
||||
LogEvolveItemDetail("Sub_Type <green>[{}] Processing Item", sub_type);
|
||||
inst->SetEvolveAddToCurrentAmount(1);
|
||||
inst->CalculateEvolveProgression();
|
||||
@@ -187,7 +199,8 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
database,
|
||||
inst->GetEvolveUniqueID(),
|
||||
inst->GetEvolveCurrentAmount(),
|
||||
inst->GetEvolveProgression());
|
||||
inst->GetEvolveProgression()
|
||||
);
|
||||
if (!e.id) {
|
||||
break;
|
||||
}
|
||||
@@ -195,7 +208,8 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e);
|
||||
|
||||
LogEvolveItem(
|
||||
"Processing Complete for item id <green>[{1}] Type 4 Specific Zone ID - SubType <yellow>[{0}] "
|
||||
"Processing Complete for item id <green>[{1}] Type 4 Specific Zone ID - SubType "
|
||||
"<yellow>[{0}] "
|
||||
"- Increased count by 1 for <green>[{1}]",
|
||||
sub_type,
|
||||
inst->GetID()
|
||||
@@ -208,6 +222,44 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
|
||||
break;
|
||||
}
|
||||
case EvolvingItems::Types::NUMBER_OF_KILLS: {
|
||||
LogEvolveItemDetail("Type <green>[{}] Processing sub type", type);
|
||||
if (mob) {
|
||||
if (mob->GetLevel() >= Strings::ToUnsignedInt(sub_types.front()) ||
|
||||
Strings::ToUnsignedInt(sub_types.front()) == 0
|
||||
) {
|
||||
LogEvolveItemDetail("Sub_Type <green>[{}] Processing Item", sub_type);
|
||||
inst->SetEvolveAddToCurrentAmount(1);
|
||||
inst->CalculateEvolveProgression();
|
||||
|
||||
auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression(
|
||||
database,
|
||||
inst->GetEvolveUniqueID(),
|
||||
inst->GetEvolveCurrentAmount(),
|
||||
inst->GetEvolveProgression()
|
||||
);
|
||||
if (!e.id) {
|
||||
break;
|
||||
}
|
||||
|
||||
SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e);
|
||||
|
||||
LogEvolveItem(
|
||||
"Processing Complete for item id <green>[{1}] Type 4 Specific Zone ID - SubType "
|
||||
"<yellow>[{0}] "
|
||||
"- Increased count by 1 for <green>[{1}]",
|
||||
sub_type,
|
||||
inst->GetID()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (inst->GetEvolveProgression() >= 100) {
|
||||
queue.push_back(inst);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
@@ -247,7 +299,7 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app)
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::DoEvolveCheckProgression(const EQ::ItemInstance &inst)
|
||||
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
|
||||
{
|
||||
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
|
||||
return false;
|
||||
@@ -264,6 +316,48 @@ bool Client::DoEvolveCheckProgression(const EQ::ItemInstance &inst)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (RuleB(EvolvingItems, EnableParcelMerchants) &&
|
||||
!RuleB(EvolvingItems, DestroyAugmentsOnEvolve) &&
|
||||
inst.IsAugmented()
|
||||
) {
|
||||
auto const augs = inst.GetAugmentIDs();
|
||||
std::vector<CharacterParcelsRepository::CharacterParcels> parcels;
|
||||
for (auto const &item_id: augs) {
|
||||
if (!item_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CharacterParcelsRepository::CharacterParcels p{};
|
||||
p.char_id = CharacterID();
|
||||
p.from_name = "Evolving Item Sub-System";
|
||||
p.note = fmt::format(
|
||||
"System automatically removed from {} which recently evolved.",
|
||||
inst.GetItem()->Name
|
||||
);
|
||||
p.slot_id = FindNextFreeParcelSlotUsingMemory();
|
||||
p.sent_date = time(nullptr);
|
||||
p.item_id = item_id;
|
||||
p.quantity = 1;
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
|
||||
PlayerEvent::ParcelSend e{};
|
||||
e.from_player_name = p.from_name;
|
||||
e.to_player_name = GetCleanName();
|
||||
e.item_id = p.item_id;
|
||||
e.quantity = 1;
|
||||
e.sent_date = p.sent_date;
|
||||
|
||||
RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e);
|
||||
}
|
||||
|
||||
parcels.push_back(p);
|
||||
}
|
||||
|
||||
CharacterParcelsRepository::InsertMany(database, parcels);
|
||||
SendParcelStatus();
|
||||
SendParcelIconStatus();
|
||||
}
|
||||
|
||||
CheckItemDiscoverability(new_inst->GetID());
|
||||
|
||||
PlayerEvent::EvolveItem e{};
|
||||
|
||||
+37
-13
@@ -826,6 +826,10 @@ void Client::CompleteConnect()
|
||||
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
||||
InvokeChangePetName(false);
|
||||
}
|
||||
|
||||
if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) {
|
||||
InvokeChangeNameWindow(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
|
||||
@@ -4256,7 +4260,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
|
||||
else {
|
||||
OnDisconnect(true);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4548,14 +4552,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) {
|
||||
|
||||
auto p = (ChangePetName_Struct *) app->pBuffer;
|
||||
if (!IsPetNameChangeAllowed()) {
|
||||
p->response_code = ChangePetNameResponse::NotEligible;
|
||||
p->response_code = ChangeNameResponse::Ineligible;
|
||||
QueuePacket(app);
|
||||
return;
|
||||
}
|
||||
|
||||
p->response_code = ChangePetNameResponse::Denied;
|
||||
p->response_code = ChangeNameResponse::Denied;
|
||||
if (ChangePetName(p->new_pet_name)) {
|
||||
p->response_code = ChangePetNameResponse::Accepted;
|
||||
p->response_code = ChangeNameResponse::Accepted;
|
||||
}
|
||||
|
||||
QueuePacket(app);
|
||||
@@ -6776,6 +6780,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
|
||||
|
||||
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
|
||||
{
|
||||
if (app->size == sizeof(AltChangeName_Struct)) {
|
||||
auto p = (AltChangeName_Struct *) app->pBuffer;
|
||||
|
||||
if (!IsNameChangeAllowed()) {
|
||||
p->response_code = ChangeNameResponse::Ineligible;
|
||||
QueuePacket(app);
|
||||
return;
|
||||
}
|
||||
|
||||
p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied;
|
||||
QueuePacket(app);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->size != sizeof(GMName_Struct)) {
|
||||
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
|
||||
return;
|
||||
@@ -7653,7 +7672,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
||||
log.char_id = CharacterID();
|
||||
log.guild_id = GuildID();
|
||||
log.item_id = inst->GetID();
|
||||
log.quantity = inst->GetCharges();
|
||||
log.quantity = 1;
|
||||
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||
log.quantity = inst->GetCharges();
|
||||
}
|
||||
|
||||
if (inst->IsAugmented()) {
|
||||
auto augs = inst->GetAugmentIDs();
|
||||
log.aug_slot_one = augs.at(0);
|
||||
@@ -7737,7 +7760,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
||||
item.guild_id = GuildID();
|
||||
item.area = GuildBankDepositArea;
|
||||
item.item_id = cursor_item->ID;
|
||||
item.quantity = cursor_item_inst->GetCharges();
|
||||
item.quantity = 1;
|
||||
if (cursor_item_inst->GetCharges() > 0 || cursor_item_inst->IsStackable() || cursor_item->MaxCharges > 0) {
|
||||
item.quantity = cursor_item_inst->GetCharges();
|
||||
}
|
||||
|
||||
item.donator = GetCleanName();
|
||||
item.permissions = GuildBankBankerOnly;
|
||||
if (cursor_item_inst->IsAugmented()) {
|
||||
@@ -7821,14 +7848,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
||||
break;
|
||||
}
|
||||
|
||||
if (inst->GetCharges() > 0) {
|
||||
gbwis->Quantity = 1;
|
||||
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||
gbwis->Quantity = inst->GetCharges();
|
||||
}
|
||||
|
||||
if (inst->GetCharges() < 0) {
|
||||
gbwis->Quantity = 1;
|
||||
}
|
||||
|
||||
PushItemOnCursor(*inst.get());
|
||||
SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketLimbo);
|
||||
GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity, this);
|
||||
@@ -15458,7 +15482,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
);
|
||||
Message(
|
||||
Chat::Yellow,
|
||||
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
|
||||
"Direct inventory delivery is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
|
||||
);
|
||||
in->method = BazaarByDirectToInventory;
|
||||
in->sub_action = Failed;
|
||||
@@ -16974,7 +16998,7 @@ void Client::Handle_OP_GuildTributeDonateItem(const EQApplicationPacket *app)
|
||||
|
||||
SendGuildTributeDonateItemReply(in, favor);
|
||||
|
||||
if(player_event_logs.IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM)) {
|
||||
if(inst && player_event_logs.IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM)) {
|
||||
auto e = PlayerEvent::GuildTributeDonateItem{ .item_id = inst->GetID(),
|
||||
.augment_1_id = inst->GetAugmentItemID(0),
|
||||
.augment_2_id = inst->GetAugmentItemID(1),
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "../common/types.h"
|
||||
#include "../common/spdat.h"
|
||||
|
||||
#include <cereal/cereal.hpp>
|
||||
|
||||
#define HIGHEST_RESIST 9 //Max resist type value
|
||||
#define MAX_SPELL_PROJECTILE 10 //Max amount of spell projectiles that can be active by a single mob.
|
||||
|
||||
@@ -272,6 +274,46 @@ struct Buffs_Struct {
|
||||
bool persistant_buff;
|
||||
bool client; //True if the caster is a client
|
||||
bool UpdateClient;
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
|
||||
std::string caster_name_str(caster_name);
|
||||
if (Archive::is_saving::value) {
|
||||
caster_name_str = std::string(caster_name);
|
||||
}
|
||||
|
||||
ar(
|
||||
CEREAL_NVP(spellid),
|
||||
CEREAL_NVP(casterlevel),
|
||||
CEREAL_NVP(casterid),
|
||||
CEREAL_NVP(caster_name_str),
|
||||
CEREAL_NVP(ticsremaining),
|
||||
CEREAL_NVP(counters),
|
||||
CEREAL_NVP(hit_number),
|
||||
CEREAL_NVP(melee_rune),
|
||||
CEREAL_NVP(magic_rune),
|
||||
CEREAL_NVP(dot_rune),
|
||||
CEREAL_NVP(caston_x),
|
||||
CEREAL_NVP(caston_y),
|
||||
CEREAL_NVP(caston_z),
|
||||
CEREAL_NVP(ExtraDIChance),
|
||||
CEREAL_NVP(RootBreakChance),
|
||||
CEREAL_NVP(instrument_mod),
|
||||
CEREAL_NVP(virus_spread_time),
|
||||
CEREAL_NVP(persistant_buff),
|
||||
CEREAL_NVP(client),
|
||||
CEREAL_NVP(UpdateClient)
|
||||
);
|
||||
|
||||
// Copy back into caster_name after deserialization
|
||||
if (Archive::is_loading::value) {
|
||||
strncpy(caster_name, caster_name_str.c_str(), sizeof(caster_name));
|
||||
caster_name[sizeof(caster_name) - 1] = '\0'; // Ensure null termination
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct StatBonuses {
|
||||
|
||||
+1
-1
@@ -1569,7 +1569,7 @@ void Corpse::LootCorpseItem(Client *c, const EQApplicationPacket *app)
|
||||
}
|
||||
}
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) {
|
||||
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) {
|
||||
auto e = PlayerEvent::LootItemEvent{
|
||||
.item_id = inst->GetItem()->ID,
|
||||
.item_name = inst->GetItem()->Name,
|
||||
|
||||
@@ -200,6 +200,7 @@ public:
|
||||
uint32 GetItemIDBySlot(uint16 loot_slot);
|
||||
uint16 GetFirstLootSlotByItemID(uint32 item_id);
|
||||
std::vector<int> GetLootList();
|
||||
inline const LootItems &GetLootItems() { return m_item_list; }
|
||||
void LootCorpseItem(Client *c, const EQApplicationPacket *app);
|
||||
void EndLoot(Client *c, const EQApplicationPacket *app);
|
||||
void MakeLootRequestPackets(Client *c, const EQApplicationPacket *app);
|
||||
|
||||
+145
-41
@@ -27,7 +27,8 @@ void DataBucket::SetData(const std::string &bucket_key, const std::string &bucke
|
||||
void DataBucket::SetData(const DataBucketKey &k_)
|
||||
{
|
||||
DataBucketKey k = k_; // copy the key so we can modify it
|
||||
if (k.key.find(NESTED_KEY_DELIMITER) != std::string::npos) {
|
||||
bool is_nested = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos;
|
||||
if (is_nested) {
|
||||
k.key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front();
|
||||
}
|
||||
|
||||
@@ -63,6 +64,10 @@ void DataBucket::SetData(const DataBucketKey &k_)
|
||||
if (isalpha(k.expires[0]) || isalpha(k.expires[k.expires.length() - 1])) {
|
||||
expires_time_unix = static_cast<int64>(std::time(nullptr)) + Strings::TimeToSeconds(k.expires);
|
||||
}
|
||||
if (is_nested) {
|
||||
LogDataBuckets("Nested keys can't expire; set expiration on the parent key");
|
||||
expires_time_unix = 0;
|
||||
}
|
||||
}
|
||||
|
||||
b.expires = expires_time_unix;
|
||||
@@ -75,26 +80,45 @@ void DataBucket::SetData(const DataBucketKey &k_)
|
||||
std::string existing_value = r.id > 0 ? r.value : "{}";
|
||||
json json_value = json::object();
|
||||
|
||||
try {
|
||||
json_value = json::parse(existing_value);
|
||||
} catch (json::parse_error &e) {
|
||||
LogError("Failed to parse JSON for key [{}]: {}", k_.key, e.what());
|
||||
json_value = json::object(); // Reset to an empty object on error
|
||||
// Check if the JSON is valid
|
||||
if (Strings::IsValidJson(existing_value)) {
|
||||
try {
|
||||
json_value = json::parse(existing_value);
|
||||
} catch (json::parse_error &e) {
|
||||
LogDataBuckets("Failed to parse JSON for key [{}] [{}]", k_.key, e.what());
|
||||
json_value = json::object(); // Reset to an empty object on error
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively merge new key-value pair into the JSON object
|
||||
auto nested_keys = Strings::Split(k_.key, NESTED_KEY_DELIMITER);
|
||||
auto top_key = nested_keys.front();
|
||||
// remove the top-level key
|
||||
nested_keys.erase(nested_keys.begin());
|
||||
|
||||
json *current = &json_value;
|
||||
|
||||
for (size_t i = 0; i < nested_keys.size(); ++i) {
|
||||
const std::string &key_part = nested_keys[i];
|
||||
|
||||
if (i == nested_keys.size() - 1) {
|
||||
|
||||
LogDataBucketsDetail("Setting key [{}] key_part [{}]", k.key, key_part);
|
||||
|
||||
// If the key already exists and is an object or array, prevent overwriting to avoid data loss
|
||||
if (current->contains(key_part) &&
|
||||
((*current)[key_part].is_object() || (*current)[key_part].is_array())) {
|
||||
LogDataBuckets("Attempted to overwrite an existing object or array at key [{}] - skipping", k_.key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the value at the final key
|
||||
(*current)[key_part] = k_.value;
|
||||
} else {
|
||||
// Traverse or create nested objects
|
||||
if (!current->contains(key_part)) {
|
||||
(*current)[key_part] = json::object();
|
||||
LogDataBucketsDetail("Creating nested root key [{}] key_part [{}]", k.key, key_part);
|
||||
} else if (!(*current)[key_part].is_object()) {
|
||||
// If key exists but is not an object, reset to object to avoid conflicts
|
||||
(*current)[key_part] = json::object();
|
||||
@@ -105,7 +129,7 @@ void DataBucket::SetData(const DataBucketKey &k_)
|
||||
|
||||
// Serialize JSON back to string
|
||||
b.value = json_value.dump();
|
||||
b.key_ = nested_keys.front(); // Use the top-level key
|
||||
b.key_ = top_key; // Use the top-level key
|
||||
}
|
||||
|
||||
if (bucket_id) {
|
||||
@@ -142,12 +166,20 @@ DataBucketsRepository::DataBuckets DataBucket::ExtractNestedValue(
|
||||
const std::string &full_key)
|
||||
{
|
||||
auto nested_keys = Strings::Split(full_key, NESTED_KEY_DELIMITER);
|
||||
auto top_key = nested_keys.front();
|
||||
nested_keys.erase(nested_keys.begin());
|
||||
json json_value;
|
||||
|
||||
// Check if the JSON is valid
|
||||
if (!Strings::IsValidJson(bucket.value)) {
|
||||
LogDataBuckets("Invalid JSON for key [{}]", bucket.key_);
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
try {
|
||||
json_value = json::parse(bucket.value); // Parse the JSON
|
||||
} catch (json::parse_error &ex) {
|
||||
LogError("Failed to parse JSON for key [{}]: {}", bucket.key_, ex.what());
|
||||
LogDataBuckets("Failed to parse JSON for key [{}] [{}]", bucket.key_, ex.what());
|
||||
return DataBucketsRepository::NewEntity(); // Return empty entity on parse error
|
||||
}
|
||||
|
||||
@@ -336,44 +368,116 @@ bool DataBucket::GetDataBuckets(Mob *mob)
|
||||
|
||||
bool DataBucket::DeleteData(const DataBucketKey &k)
|
||||
{
|
||||
if (CanCache(k)) {
|
||||
size_t size_before = g_data_bucket_cache.size();
|
||||
bool is_nested_key = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos;
|
||||
|
||||
// delete from cache where contents match
|
||||
g_data_bucket_cache.erase(
|
||||
std::remove_if(
|
||||
g_data_bucket_cache.begin(),
|
||||
g_data_bucket_cache.end(),
|
||||
[&](DataBucketsRepository::DataBuckets &e) {
|
||||
return CheckBucketMatch(e, k);
|
||||
}
|
||||
),
|
||||
g_data_bucket_cache.end()
|
||||
);
|
||||
if (!is_nested_key) {
|
||||
// Update cache
|
||||
if (CanCache(k)) {
|
||||
// delete from cache where contents match
|
||||
g_data_bucket_cache.erase(
|
||||
std::remove_if(
|
||||
g_data_bucket_cache.begin(),
|
||||
g_data_bucket_cache.end(),
|
||||
[&](DataBucketsRepository::DataBuckets &e) {
|
||||
return CheckBucketMatch(e, k);
|
||||
}
|
||||
),
|
||||
g_data_bucket_cache.end()
|
||||
);
|
||||
}
|
||||
|
||||
LogDataBuckets(
|
||||
"Deleting bucket key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}] bot_id [{}] zone_id [{}] instance_id [{}] cache size before [{}] after [{}]",
|
||||
k.key,
|
||||
k.bot_id,
|
||||
k.account_id,
|
||||
k.character_id,
|
||||
k.npc_id,
|
||||
k.bot_id,
|
||||
k.zone_id,
|
||||
k.instance_id,
|
||||
size_before,
|
||||
g_data_bucket_cache.size()
|
||||
// Regular key deletion, no nesting involved
|
||||
return DataBucketsRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format("{} `key` = '{}'", DataBucket::GetScopedDbFilters(k), k.key)
|
||||
);
|
||||
}
|
||||
|
||||
return DataBucketsRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"{} `key` = '{}'",
|
||||
DataBucket::GetScopedDbFilters(k),
|
||||
k.key
|
||||
)
|
||||
);
|
||||
// If it's a nested key, retrieve the top-level JSON object
|
||||
auto top_level_key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front();
|
||||
DataBucketKey top_level_k = k;
|
||||
top_level_k.key = top_level_key;
|
||||
|
||||
auto r = GetData(top_level_k);
|
||||
if (r.id == 0 || r.value.empty() || !Strings::IsValidJson(r.value)) {
|
||||
LogDataBuckets("Attempted to delete nested key [{}] but parent key [{}] does not exist or is invalid JSON", k.key, top_level_key);
|
||||
return false;
|
||||
}
|
||||
|
||||
json json_value;
|
||||
try {
|
||||
json_value = json::parse(r.value);
|
||||
} catch (json::parse_error &ex) {
|
||||
LogDataBuckets("Failed to parse JSON for key [{}] [{}]", top_level_key, ex.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively remove the nested key
|
||||
auto nested_keys = Strings::Split(k.key, NESTED_KEY_DELIMITER);
|
||||
auto top_key = nested_keys.front();
|
||||
nested_keys.erase(nested_keys.begin());
|
||||
json *current = &json_value;
|
||||
|
||||
for (size_t i = 0; i < nested_keys.size(); ++i) {
|
||||
const std::string &key_part = nested_keys[i];
|
||||
|
||||
if (i == nested_keys.size() - 1) {
|
||||
// Last key in the hierarchy - delete it
|
||||
if (current->contains(key_part)) {
|
||||
current->erase(key_part);
|
||||
LogDataBuckets("Deleted nested key [{}] from [{}]", key_part, k.key);
|
||||
} else {
|
||||
LogDataBuckets("Key [{}] not found in JSON - nothing to delete", k.key);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!current->contains(key_part) || !(*current)[key_part].is_object()) {
|
||||
LogDataBuckets("Parent key [{}] does not exist or is not an object", key_part);
|
||||
return false;
|
||||
}
|
||||
current = &(*current)[key_part];
|
||||
}
|
||||
}
|
||||
|
||||
// If the JSON object is now empty, delete the top-level key
|
||||
if (json_value.empty()) {
|
||||
LogDataBuckets("Top-level key [{}] is now empty, deleting entire entry", top_level_key);
|
||||
|
||||
// delete cache
|
||||
if (CanCache(k)) {
|
||||
g_data_bucket_cache.erase(
|
||||
std::remove_if(
|
||||
g_data_bucket_cache.begin(),
|
||||
g_data_bucket_cache.end(),
|
||||
[&](DataBucketsRepository::DataBuckets &e) {
|
||||
return CheckBucketMatch(e, top_level_k);
|
||||
}
|
||||
),
|
||||
g_data_bucket_cache.end()
|
||||
);
|
||||
}
|
||||
|
||||
return DataBucketsRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format("{} `key` = '{}'", DataBucket::GetScopedDbFilters(k), top_level_key)
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise, update the existing JSON without the deleted key
|
||||
r.value = json_value.dump();
|
||||
DataBucketsRepository::UpdateOne(database, r);
|
||||
|
||||
// Update cache
|
||||
if (CanCache(k)) {
|
||||
for (auto &e : g_data_bucket_cache) {
|
||||
if (CheckBucketMatch(e, top_level_k)) {
|
||||
e.value = r.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string DataBucket::GetDataExpires(const DataBucketKey &k)
|
||||
|
||||
+44
-1
@@ -25,6 +25,7 @@
|
||||
#include "worldserver.h"
|
||||
#include "../common/repositories/character_expedition_lockouts_repository.h"
|
||||
#include "../common/repositories/dynamic_zone_lockouts_repository.h"
|
||||
#include <cereal/types/utility.hpp>
|
||||
|
||||
extern WorldServer worldserver;
|
||||
|
||||
@@ -162,14 +163,28 @@ void DynamicZone::CacheAllFromDatabase()
|
||||
}
|
||||
}
|
||||
|
||||
dz->UpdateMembers();
|
||||
zone->dynamic_zone_cache.emplace(dz_id, std::move(dz));
|
||||
}
|
||||
|
||||
if (!zone->dynamic_zone_cache.empty())
|
||||
{
|
||||
RequestMemberStatuses();
|
||||
}
|
||||
|
||||
LogInfo("Loaded [{}] dynamic zone(s)", Strings::Commify(zone->dynamic_zone_cache.size()));
|
||||
LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", zone->dynamic_zone_cache.size(), bench.elapsed());
|
||||
}
|
||||
|
||||
void DynamicZone::RequestMemberStatuses()
|
||||
{
|
||||
ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, sizeof(ServerDzCerealData_Struct));
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack.pBuffer);
|
||||
buf->zone_id = static_cast<uint16_t>(zone->GetZoneID());
|
||||
buf->inst_id = static_cast<uint16_t>(zone->GetInstanceID());
|
||||
|
||||
worldserver.SendPacket(&pack);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
DynamicZone* FindDynamicZone(T pred)
|
||||
{
|
||||
@@ -849,6 +864,34 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
{
|
||||
if (zone)
|
||||
{
|
||||
std::vector<std::pair<uint32_t, std::vector<DynamicZoneMember>>> dzs;
|
||||
dzs.reserve(zone->dynamic_zone_cache.size());
|
||||
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack->pBuffer);
|
||||
EQ::Util::MemoryStreamReader ss(buf->cereal_data, buf->cereal_size);
|
||||
{
|
||||
cereal::BinaryInputArchive archive(ss);
|
||||
archive(dzs);
|
||||
}
|
||||
|
||||
for (const auto& [dz_id, members] : dzs)
|
||||
{
|
||||
if (auto dz = DynamicZone::FindDynamicZoneByID(dz_id))
|
||||
{
|
||||
for (const auto& member : members)
|
||||
{
|
||||
dz->SetInternalMemberStatus(member.id, member.status);
|
||||
}
|
||||
dz->m_has_member_statuses = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzUpdateMemberStatus:
|
||||
{
|
||||
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
|
||||
|
||||
+2
-1
@@ -92,13 +92,13 @@ public:
|
||||
void SendMemberNameToZoneMembers(const std::string& char_name, bool remove);
|
||||
void SendMemberStatusToZoneMembers(const DynamicZoneMember& member);
|
||||
void SetLocked(bool lock, bool update_db = false, DzLockMsg lock_msg = DzLockMsg::None, uint32_t color = Chat::Yellow);
|
||||
void UpdateMembers();
|
||||
|
||||
std::string GetLootEvent(uint32_t id, DzLootEvent::Type type) const;
|
||||
void SetLootEvent(uint32_t id, const std::string& event, DzLootEvent::Type type);
|
||||
|
||||
private:
|
||||
static void StartAllClientRemovalTimers();
|
||||
static void RequestMemberStatuses();
|
||||
|
||||
uint16_t GetCurrentInstanceID() const override;
|
||||
uint16_t GetCurrentZoneID() const override;
|
||||
@@ -125,6 +125,7 @@ private:
|
||||
void SendWorldPlayerInvite(const std::string& inviter, const std::string& swap_name, const std::string& add_name, bool pending = false);
|
||||
void SetUpdatedDuration(uint32_t seconds);
|
||||
void TryAddClient(Client* add_client, const std::string& inviter, const std::string& swap_name, Client* leader = nullptr);
|
||||
void UpdateMembers();
|
||||
|
||||
std::unique_ptr<EQApplicationPacket> CreateExpireWarningPacket(uint32_t minutes_remaining);
|
||||
std::unique_ptr<EQApplicationPacket> CreateInfoPacket(bool clear = false);
|
||||
|
||||
+24
-1
@@ -754,7 +754,7 @@ void Perl__setsky(uint8 new_sky)
|
||||
|
||||
void Perl__setguild(uint32_t guild_id, uint8_t guild_rank_id)
|
||||
{
|
||||
quest_manager.setguild(guild_id, guild_rank_id);
|
||||
quest_manager.SetGuild(guild_id, guild_rank_id);
|
||||
}
|
||||
|
||||
void Perl__createguild(const char* guild_name, const char* leader_name)
|
||||
@@ -5973,6 +5973,28 @@ void Perl__SpawnGrid(uint32 npc_id, float x, float y, float z, float heading, fl
|
||||
quest_manager.SpawnGrid(npc_id, glm::vec4(x, y, z, heading), spacing, spawn_count);
|
||||
}
|
||||
|
||||
bool Perl__handin(perl::reference handin_ref)
|
||||
{
|
||||
perl::hash handin = handin_ref;
|
||||
|
||||
std::map<std::string, uint32> handin_map;
|
||||
|
||||
for (auto e: handin) {
|
||||
if (!e.first) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Strings::EqualFold(e.first, "0")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32 count = static_cast<uint32>(handin.at(e.first));
|
||||
handin_map[e.first] = count;
|
||||
}
|
||||
|
||||
return quest_manager.handin(handin_map);
|
||||
}
|
||||
|
||||
void perl_register_quest()
|
||||
{
|
||||
perl::interpreter perl(PERL_GET_THX);
|
||||
@@ -6698,6 +6720,7 @@ void perl_register_quest()
|
||||
package.add("gmsay", (void(*)(const char*, int, bool))&Perl__gmsay);
|
||||
package.add("gmsay", (void(*)(const char*, int, bool, int))&Perl__gmsay);
|
||||
package.add("gmsay", (void(*)(const char*, int, bool, int, int))&Perl__gmsay);
|
||||
package.add("handin", &Perl__handin);
|
||||
package.add("has_zone_flag", &Perl__has_zone_flag);
|
||||
package.add("hasrecipelearned", &Perl__hasrecipelearned);
|
||||
package.add("hastimer", &Perl__hastimer);
|
||||
|
||||
+3
-3
@@ -2231,7 +2231,7 @@ Raid* EntityList::GetRaidByBotName(const char* name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -2914,7 +2914,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanning_mob->GetID() <= 0) {
|
||||
if (scanning_mob->GetID() <= 0 || scanning_mob->IsZoneController()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2933,7 +2933,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
|
||||
if (mob->GetID() <= 0) {
|
||||
if (mob && (mob->GetID() <= 0 || mob->IsZoneController())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@ void command_guild(Client* c, const Seperator* sep)
|
||||
else {
|
||||
auto guild_name = sep->argplus[3];
|
||||
auto guild_id = guild_mgr.CreateGuild(sep->argplus[3], leader_id);
|
||||
auto leader = entity_list.GetClientByCharID(leader_id);
|
||||
|
||||
|
||||
LogGuilds(
|
||||
"[{}]: Creating guild [{}] with leader [{}] with GM command. It was given id [{}]",
|
||||
@@ -115,7 +117,7 @@ void command_guild(Client* c, const Seperator* sep)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
if (!guild_mgr.SetGuild(leader_id, guild_id, GUILD_LEADER)) {
|
||||
if (!guild_mgr.SetGuild(leader, guild_id, GUILD_LEADER)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
@@ -268,6 +270,7 @@ void command_guild(Client* c, const Seperator* sep)
|
||||
database.GetCharacterID(sep->arg[2])
|
||||
);
|
||||
auto character_name = database.GetCharNameByID(character_id);
|
||||
auto client = entity_list.GetClientByCharID(character_id);
|
||||
if (!character_id || character_name.empty()) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -305,14 +308,14 @@ void command_guild(Client* c, const Seperator* sep)
|
||||
"{} ({}) has {} put into {} ({}).",
|
||||
character_name,
|
||||
character_id,
|
||||
guild_mgr.SetGuild(character_id, guild_id, GUILD_MEMBER) ? "been" : "failed to be",
|
||||
guild_mgr.SetGuild(client, guild_id, GUILD_MEMBER) ? "been" : "failed to be",
|
||||
guild_mgr.GetGuildNameByID(guild_id),
|
||||
guild_id
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
else {
|
||||
guild_mgr.SetGuild(character_id, GUILD_NONE, 0);
|
||||
guild_mgr.SetGuild(client, GUILD_NONE, 0);
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
|
||||
@@ -197,7 +197,7 @@ void command_parcels(Client *c, const Seperator *sep)
|
||||
send_to_client.at(0).character_name.c_str()
|
||||
);
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
|
||||
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
|
||||
PlayerEvent::ParcelSend e{};
|
||||
e.from_player_name = parcel_out.from_name;
|
||||
e.to_player_name = send_to_client.at(0).character_name;
|
||||
@@ -281,7 +281,7 @@ void command_parcels(Client *c, const Seperator *sep)
|
||||
send_to_client.at(0).character_name.c_str()
|
||||
);
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
|
||||
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
|
||||
PlayerEvent::ParcelSend e{};
|
||||
e.from_player_name = parcel_out.from_name;
|
||||
e.to_player_name = send_to_client.at(0).character_name;
|
||||
|
||||
@@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep)
|
||||
std::string new_name = sep->arg[2];
|
||||
std::string old_name = t->GetCleanName();
|
||||
|
||||
if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) {
|
||||
if (t->ChangeFirstName(new_name, c->GetCleanName())) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
@@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
c->Message(Chat::White, "Sending player to char select.");
|
||||
|
||||
t->Kick("Name was changed");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Unable to rename {}. Check that the new name '{}' isn't already taken.",
|
||||
"Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid",
|
||||
old_name,
|
||||
new_name
|
||||
).c_str()
|
||||
|
||||
+35
-36
@@ -1317,42 +1317,6 @@ bool GuildBankManager::SplitStack(uint32 guild_id, uint16 slot_id, uint32 quanti
|
||||
return true;
|
||||
}
|
||||
|
||||
// void GuildBankManager::UpdateItemQuantity(uint32 guildID, uint16 area, uint16 slotID, uint32 quantity)
|
||||
// {
|
||||
// // Helper method for MergeStacks. Assuming all passed parameters are valid.
|
||||
// //
|
||||
// std::string query = StringFormat("UPDATE `guild_bank` SET `qty` = %i "
|
||||
// "WHERE `guildid` = %i AND `area` = %i "
|
||||
// "AND `slot` = %i LIMIT 1",
|
||||
// quantity, guildID, area, slotID);
|
||||
// auto results = database.QueryDatabase(query);
|
||||
// if(!results.Success()) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
// bool GuildBankManager::AllowedToWithdraw(uint32 guild_id, uint16 area, uint16 slot_id, const char *name)
|
||||
// {
|
||||
// auto guild_bank = GetGuildBank(guild_id);
|
||||
// if (!guild_bank) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (area != GuildBankMainArea) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// auto item = &guild_bank->items.main_area[slot_id];
|
||||
// uint8 permissions = item->permissions;
|
||||
//
|
||||
// if (permissions == GuildBankBankerOnly) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
int32 GuildBankManager::NextFreeBankSlot(uint32 guild_id, uint32 area)
|
||||
{
|
||||
auto guild_bank = GetGuildBank(guild_id);
|
||||
@@ -1760,3 +1724,38 @@ void GuildBankManager::SendGuildBankItemUpdate(uint32 guild_id, int32 slot_id, u
|
||||
|
||||
entity_list.QueueClientsGuildBankItemUpdate(&gbius, guild_id);
|
||||
}
|
||||
|
||||
bool ZoneGuildManager::SetGuild(Client *client, uint32 guild_id, uint8 rank)
|
||||
{
|
||||
if (!client || rank > GUILD_MAX_RANK || !GetGuildByGuildID(guild_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rank <= GUILD_RANK_NONE) {
|
||||
rank = GUILD_RECRUIT;
|
||||
}
|
||||
|
||||
const uint32 current_guild_id = client->GuildID();
|
||||
if (current_guild_id == guild_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current_guild_id != guild_id && current_guild_id != GUILD_NONE) {
|
||||
guild_mgr.RemoveMember(client->GuildID(), client->CharacterID(), std::string(client->GetCleanName()));
|
||||
}
|
||||
|
||||
client->SetGuildID(guild_id);
|
||||
client->SetGuildRank(rank);
|
||||
MemberAdd(
|
||||
guild_id,
|
||||
client->CharacterID(),
|
||||
client->GetLevel(),
|
||||
client->GetClass(),
|
||||
rank,
|
||||
client->GetZoneID(),
|
||||
client->GetName()
|
||||
);
|
||||
|
||||
client->SendGuildSpawnAppearance();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
void ListGuilds(Client *c, uint32 guild_id = 0) const;
|
||||
void DescribeGuild(Client *c, uint32 guild_id) const;
|
||||
bool IsActionABankAction(GuildAction action);
|
||||
bool SetGuild(Client *client, uint32 guild_id, uint8 rank);
|
||||
|
||||
uint8 *MakeGuildMembers(uint32 guild_id, const char* prefix_name, uint32& length);
|
||||
void SendToWorldMemberLevelUpdate(uint32 guild_id, uint32 level, std::string player_name);
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ const NPCType *Horse::BuildHorseType(uint16 spell_id)
|
||||
n->npc_id = 0;
|
||||
n->loottable_id = 0;
|
||||
n->texture = e.texture;
|
||||
n->helmtexture = e.texture;
|
||||
n->helmtexture = e.helmtexture == -1 ? e.texture : e.helmtexture;
|
||||
n->runspeed = e.mountspeed;
|
||||
n->light = 0;
|
||||
n->STR = 75;
|
||||
|
||||
+2
-2
@@ -763,7 +763,7 @@ void Client::DropItem(int16 slot_id, bool recurse)
|
||||
|
||||
int i = 0;
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::DROPPED_ITEM)) {
|
||||
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::DROPPED_ITEM)) {
|
||||
auto e = PlayerEvent::DroppedItemEvent{
|
||||
.item_id = inst->GetID(),
|
||||
.augment_1_id = inst->GetAugmentItemID(0),
|
||||
@@ -1655,7 +1655,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
|
||||
|
||||
DeleteItemInInventory(EQ::invslot::slotCursor, 0, true);
|
||||
|
||||
if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
|
||||
if (test_inst && player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
|
||||
auto e = PlayerEvent::DestroyItemEvent{
|
||||
.item_id = test_inst->GetItem()->ID,
|
||||
.item_name = test_inst->GetItem()->Name,
|
||||
|
||||
+19
-1
@@ -23,6 +23,11 @@ void NPC::AddLootTable(uint32 loottable_id, bool is_global)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_resumed_from_zone_suspend) {
|
||||
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_global) {
|
||||
m_loot_copper = 0;
|
||||
m_loot_silver = 0;
|
||||
@@ -276,6 +281,11 @@ void NPC::AddLootDrop(
|
||||
uint32 augment_six
|
||||
)
|
||||
{
|
||||
if (m_resumed_from_zone_suspend) {
|
||||
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item2) {
|
||||
return;
|
||||
}
|
||||
@@ -370,6 +380,10 @@ void NPC::AddLootDrop(
|
||||
if (item2->Slots & slots) {
|
||||
if (equipment[i]) {
|
||||
compitem = database.GetItem(equipment[i]);
|
||||
if (!compitem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item2->AC > compitem->AC || (item2->AC == compitem->AC && item2->HP > compitem->HP)) {
|
||||
// item would be an upgrade
|
||||
// check if we're multi-slot, if yes then we have to keep
|
||||
@@ -380,6 +394,9 @@ void NPC::AddLootDrop(
|
||||
else {
|
||||
// Unequip old item
|
||||
auto *old_item = GetItem(i);
|
||||
if (!old_item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
old_item->equip_slot = EQ::invslot::SLOT_INVALID;
|
||||
|
||||
@@ -500,6 +517,7 @@ void NPC::AddLootDrop(
|
||||
parse->EventNPC(EVENT_LOOT_ADDED, this, nullptr, "", 0, &args);
|
||||
}
|
||||
|
||||
item->lootdrop_id = loot_drop.lootdrop_id;
|
||||
m_loot_items.push_back(item);
|
||||
|
||||
if (found) {
|
||||
@@ -671,7 +689,7 @@ LootItem *NPC::GetItem(int slot_id)
|
||||
end = m_loot_items.end();
|
||||
for (; cur != end; ++cur) {
|
||||
LootItem *item = *cur;
|
||||
if (item->equip_slot == slot_id) {
|
||||
if (item && item->equip_slot == slot_id) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
+53
-11
@@ -1199,16 +1199,6 @@ void Lua_Client::SetStartZone(int zone_id, float x, float y, float z) {
|
||||
self->SetStartZone(zone_id, x, y, z);
|
||||
}
|
||||
|
||||
void Lua_Client::KeyRingAdd(uint32 item) {
|
||||
Lua_Safe_Call_Void();
|
||||
self->KeyRingAdd(item);
|
||||
}
|
||||
|
||||
bool Lua_Client::KeyRingCheck(uint32 item) {
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->KeyRingCheck(item);
|
||||
}
|
||||
|
||||
void Lua_Client::AddPVPPoints(uint32 points) {
|
||||
Lua_Safe_Call_Void();
|
||||
self->AddPVPPoints(points);
|
||||
@@ -3506,6 +3496,24 @@ std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name)
|
||||
return self->GetAccountBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Lua_Client::GrantNameChange()
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->GrantNameChange();
|
||||
}
|
||||
|
||||
bool Lua_Client::IsNameChangeAllowed()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->IsNameChangeAllowed();
|
||||
}
|
||||
|
||||
bool Lua_Client::ClearNameChange()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->ClearNameChange();
|
||||
}
|
||||
|
||||
std::string Lua_Client::GetBandolierName(uint8 bandolier_slot)
|
||||
{
|
||||
Lua_Safe_Call_String();
|
||||
@@ -3548,6 +3556,34 @@ std::string Lua_Client::GetPotionBeltItemName(uint8 slot_id)
|
||||
return self->GetPotionBeltItemName(slot_id);
|
||||
}
|
||||
|
||||
bool Lua_Client::KeyRingAdd(uint32 item) {
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->KeyRingAdd(item);
|
||||
}
|
||||
|
||||
bool Lua_Client::KeyRingCheck(uint32 item) {
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->KeyRingCheck(item);
|
||||
}
|
||||
|
||||
bool Lua_Client::KeyRingClear()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->KeyRingClear();
|
||||
}
|
||||
|
||||
void Lua_Client::KeyRingList()
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->KeyRingList();
|
||||
}
|
||||
|
||||
bool Lua_Client::KeyRingRemove(uint32 item_id)
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->KeyRingRemove(item_id);
|
||||
}
|
||||
|
||||
luabind::scope lua_register_client() {
|
||||
return luabind::class_<Lua_Client, Lua_Mob>("Client")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -3617,6 +3653,7 @@ luabind::scope lua_register_client() {
|
||||
.def("CashReward", &Lua_Client::CashReward)
|
||||
.def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName)
|
||||
.def("GrantPetNameChange", &Lua_Client::GrantPetNameChange)
|
||||
.def("ClearNameChange", &Lua_Client::ClearNameChange)
|
||||
.def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID)
|
||||
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill)
|
||||
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill)
|
||||
@@ -3833,6 +3870,7 @@ luabind::scope lua_register_client() {
|
||||
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints)
|
||||
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility)
|
||||
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility)
|
||||
.def("GrantNameChange", &Lua_Client::GrantNameChange)
|
||||
.def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID)
|
||||
.def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank)
|
||||
.def("HasAugmentEquippedByID", (bool(Lua_Client::*)(uint32))&Lua_Client::HasAugmentEquippedByID)
|
||||
@@ -3863,14 +3901,18 @@ luabind::scope lua_register_client() {
|
||||
.def("IsInAGuild", (bool(Lua_Client::*)(void))&Lua_Client::IsInAGuild)
|
||||
.def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD)
|
||||
.def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding)
|
||||
.def("IsNameChangeAllowed", &Lua_Client::IsNameChangeAllowed)
|
||||
.def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped)
|
||||
.def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting)
|
||||
.def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding)
|
||||
.def("IsTaskActive", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskActive)
|
||||
.def("IsTaskActivityActive", (bool(Lua_Client::*)(int,int))&Lua_Client::IsTaskActivityActive)
|
||||
.def("IsTaskCompleted", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskCompleted)
|
||||
.def("KeyRingAdd", (void(Lua_Client::*)(uint32))&Lua_Client::KeyRingAdd)
|
||||
.def("KeyRingAdd", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingAdd)
|
||||
.def("KeyRingCheck", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingCheck)
|
||||
.def("KeyRingClear", (bool(Lua_Client::*)(void))&Lua_Client::KeyRingClear)
|
||||
.def("KeyRingList", (void(Lua_Client::*)(void))&Lua_Client::KeyRingList)
|
||||
.def("KeyRingRemove", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingRemove)
|
||||
.def("Kick", (void(Lua_Client::*)(void))&Lua_Client::Kick)
|
||||
.def("LearnDisciplines", (uint16(Lua_Client::*)(uint8,uint8))&Lua_Client::LearnDisciplines)
|
||||
.def("LearnRecipe", (void(Lua_Client::*)(uint32))&Lua_Client::LearnRecipe)
|
||||
|
||||
+9
-2
@@ -313,8 +313,6 @@ public:
|
||||
void SetStartZone(int zone_id, float x, float y);
|
||||
void SetStartZone(int zone_id, float x, float y, float z);
|
||||
void SetStartZone(int zone_id, float x, float y, float z, float heading);
|
||||
void KeyRingAdd(uint32 item);
|
||||
bool KeyRingCheck(uint32 item);
|
||||
void AddPVPPoints(uint32 points);
|
||||
void AddCrystals(uint32 radiant_count, uint32 ebon_count);
|
||||
void SetEbonCrystals(uint32 value);
|
||||
@@ -518,6 +516,11 @@ public:
|
||||
uint32 GetPotionBeltItemIcon(uint8 slot_id);
|
||||
uint32 GetPotionBeltItemID(uint8 slot_id);
|
||||
std::string GetPotionBeltItemName(uint8 slot_id);
|
||||
bool KeyRingAdd(uint32 item_id);
|
||||
bool KeyRingCheck(uint32 item_id);
|
||||
bool KeyRingClear();
|
||||
void KeyRingList();
|
||||
bool KeyRingRemove(uint32 item_id);
|
||||
|
||||
// account data buckets
|
||||
void SetAccountBucket(std::string bucket_name, std::string bucket_value);
|
||||
@@ -606,6 +609,10 @@ public:
|
||||
void ShowZoneShardMenu();
|
||||
void GrantPetNameChange();
|
||||
|
||||
void GrantNameChange();
|
||||
bool IsNameChangeAllowed();
|
||||
bool ClearNameChange();
|
||||
|
||||
Lua_Expedition CreateExpedition(luabind::object expedition_info);
|
||||
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players);
|
||||
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages);
|
||||
|
||||
+27
-1
@@ -468,7 +468,7 @@ void lua_set_sky(int sky) {
|
||||
}
|
||||
|
||||
void lua_set_guild(int guild_id, int rank) {
|
||||
quest_manager.setguild(guild_id, rank);
|
||||
quest_manager.SetGuild(guild_id, rank);
|
||||
}
|
||||
|
||||
void lua_create_guild(const char *name, const char *leader) {
|
||||
@@ -5642,6 +5642,31 @@ Lua_Zone lua_get_zone()
|
||||
return Lua_Zone(zone);
|
||||
}
|
||||
|
||||
bool lua_handin(luabind::adl::object handin_table)
|
||||
{
|
||||
std::map<std::string, uint32> handin_map;
|
||||
|
||||
for (luabind::iterator i(handin_table), end; i != end; i++) {
|
||||
std::string key;
|
||||
if (luabind::type(i.key()) == LUA_TSTRING) {
|
||||
key = luabind::object_cast<std::string>(i.key());
|
||||
}
|
||||
else if (luabind::type(i.key()) == LUA_TNUMBER) {
|
||||
key = fmt::format("{}", luabind::object_cast<int>(i.key()));
|
||||
}
|
||||
else {
|
||||
LogError("Handin key type [{}] not supported", luabind::type(i.key()));
|
||||
}
|
||||
|
||||
if (!key.empty()) {
|
||||
handin_map[key] = luabind::object_cast<uint32>(handin_table[i.key()]);
|
||||
LogNpcHandinDetail("Handin key [{}] value [{}]", key, handin_map[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return quest_manager.handin(handin_map);
|
||||
}
|
||||
|
||||
#define LuaCreateNPCParse(name, c_type, default_value) do { \
|
||||
cur = table[#name]; \
|
||||
if(luabind::type(cur) != LUA_TNIL) { \
|
||||
@@ -6450,6 +6475,7 @@ luabind::scope lua_register_general() {
|
||||
luabind::def("spawn_circle", &lua_spawn_circle),
|
||||
luabind::def("spawn_grid", &lua_spawn_grid),
|
||||
luabind::def("get_zone", &lua_get_zone),
|
||||
luabind::def("handin", &lua_handin),
|
||||
/*
|
||||
Cross Zone
|
||||
*/
|
||||
|
||||
@@ -939,6 +939,12 @@ Lua_Spawn Lua_NPC::GetSpawn(lua_State* L)
|
||||
return Lua_Spawn(self->GetSpawn());
|
||||
}
|
||||
|
||||
bool Lua_NPC::IsResumedFromZoneSuspend()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->IsResumedFromZoneSuspend();
|
||||
}
|
||||
|
||||
luabind::scope lua_register_npc() {
|
||||
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -1040,6 +1046,7 @@ luabind::scope lua_register_npc() {
|
||||
.def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist)
|
||||
.def("IsRaidTarget", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRaidTarget)
|
||||
.def("IsRareSpawn", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRareSpawn)
|
||||
.def("IsResumedFromZoneSuspend",(bool(Lua_NPC::*)(void))&Lua_NPC::IsResumedFromZoneSuspend)
|
||||
.def("IsTaunting", (bool(Lua_NPC::*)(void))&Lua_NPC::IsTaunting)
|
||||
.def("IsUnderwaterOnly", (bool(Lua_NPC::*)(void))&Lua_NPC::IsUnderwaterOnly)
|
||||
.def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop)
|
||||
|
||||
@@ -198,6 +198,7 @@ public:
|
||||
);
|
||||
void ReturnHandinItems(Lua_Client c);
|
||||
Lua_Spawn GetSpawn(lua_State* L);
|
||||
bool IsResumedFromZoneSuspend();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+53
-1
@@ -727,6 +727,52 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
|
||||
return self->GetBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Lua_Zone::ClearVariables()
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->ClearVariables();
|
||||
}
|
||||
|
||||
bool Lua_Zone::DeleteVariable(const std::string& variable_name)
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->DeleteVariable(variable_name);
|
||||
}
|
||||
|
||||
std::string Lua_Zone::GetVariable(const std::string& variable_name)
|
||||
{
|
||||
Lua_Safe_Call_String();
|
||||
return self->GetVariable(variable_name);
|
||||
}
|
||||
|
||||
luabind::object Lua_Zone::GetVariables(lua_State* L)
|
||||
{
|
||||
auto t = luabind::newtable(L);
|
||||
if (d_) {
|
||||
auto self = reinterpret_cast<NativeType*>(d_);
|
||||
auto l = self->GetVariables();
|
||||
int i = 1;
|
||||
for (const auto& v : l) {
|
||||
t[i] = v;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void Lua_Zone::SetVariable(const std::string& variable_name, const std::string& variable_value)
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->SetVariable(variable_name, variable_value);
|
||||
}
|
||||
|
||||
bool Lua_Zone::VariableExists(const std::string& variable_name)
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->VariableExists(variable_name);
|
||||
}
|
||||
|
||||
luabind::scope lua_register_zone() {
|
||||
return luabind::class_<Lua_Zone>("Zones")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -737,7 +783,9 @@ luabind::scope lua_register_zone() {
|
||||
.def("CanDoCombat", &Lua_Zone::CanDoCombat)
|
||||
.def("CanLevitate", &Lua_Zone::CanLevitate)
|
||||
.def("ClearSpawnTimers", &Lua_Zone::ClearSpawnTimers)
|
||||
.def("ClearVariables", &Lua_Zone::ClearVariables)
|
||||
.def("DeleteBucket", (void(Lua_Zone::*)(const std::string&))&Lua_Zone::DeleteBucket)
|
||||
.def("DeleteVariable", &Lua_Zone::DeleteVariable)
|
||||
.def("Depop", (void(Lua_Zone::*)(void))&Lua_Zone::Depop)
|
||||
.def("Depop", (void(Lua_Zone::*)(bool))&Lua_Zone::Depop)
|
||||
.def("Despawn", &Lua_Zone::Despawn)
|
||||
@@ -819,6 +867,8 @@ luabind::scope lua_register_zone() {
|
||||
.def("GetZoneType", &Lua_Zone::GetZoneType)
|
||||
.def("GetUnderworld", &Lua_Zone::GetUnderworld)
|
||||
.def("GetUnderworldTeleportIndex", &Lua_Zone::GetUnderworldTeleportIndex)
|
||||
.def("GetVariable", &Lua_Zone::GetVariable)
|
||||
.def("GetVariables", &Lua_Zone::GetVariables)
|
||||
.def("GetWalkSpeed", &Lua_Zone::GetWalkSpeed)
|
||||
.def("GetZoneZType", &Lua_Zone::GetZoneZType)
|
||||
.def("GetZoneTotalBlockedSpells", &Lua_Zone::GetZoneTotalBlockedSpells)
|
||||
@@ -849,6 +899,8 @@ luabind::scope lua_register_zone() {
|
||||
.def("SetInstanceTimer", &Lua_Zone::SetInstanceTimer)
|
||||
.def("SetInstanceTimeRemaining", &Lua_Zone::SetInstanceTimeRemaining)
|
||||
.def("SetIsHotzone", &Lua_Zone::SetIsHotzone)
|
||||
.def("ShowZoneGlobalLoot", &Lua_Zone::ShowZoneGlobalLoot);
|
||||
.def("SetVariable", &Lua_Zone::SetVariable)
|
||||
.def("ShowZoneGlobalLoot", &Lua_Zone::ShowZoneGlobalLoot)
|
||||
.def("VariableExists", &Lua_Zone::VariableExists);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,12 @@ public:
|
||||
void SetInstanceTimeRemaining(uint32 time_remaining);
|
||||
void SetIsHotzone(bool is_hotzone);
|
||||
void ShowZoneGlobalLoot(Lua_Client c);
|
||||
void ClearVariables();
|
||||
bool DeleteVariable(const std::string& variable_name);
|
||||
std::string GetVariable(const std::string& variable_name);
|
||||
luabind::object GetVariables(lua_State* L);
|
||||
void SetVariable(const std::string& variable_name, const std::string& variable_value);
|
||||
bool VariableExists(const std::string& variable_name);
|
||||
|
||||
// data buckets
|
||||
void SetBucket(const std::string& bucket_name, const std::string& bucket_value);
|
||||
|
||||
+30
-22
@@ -306,7 +306,7 @@ int main(int argc, char **argv)
|
||||
|
||||
LogSys.SetDatabase(&database)
|
||||
->SetLogPath(path.GetLogPath())
|
||||
->LoadLogDatabaseSettings()
|
||||
->LoadLogDatabaseSettings(ZoneCLI::RanTestCommand(argc, argv))
|
||||
->SetGMSayHandler(&Zone::GMSayHookCallBackProcess)
|
||||
->StartFileLogs();
|
||||
|
||||
@@ -631,7 +631,7 @@ int main(int argc, char **argv)
|
||||
|
||||
if (zone) {
|
||||
if (!zone->Process()) {
|
||||
Zone::Shutdown();
|
||||
zone->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ int main(int argc, char **argv)
|
||||
safe_delete(Config);
|
||||
|
||||
if (zone != 0) {
|
||||
Zone::Shutdown(true);
|
||||
zone->Shutdown(true);
|
||||
}
|
||||
//Fix for Linux world server problem.
|
||||
safe_delete(task_manager);
|
||||
@@ -687,7 +687,7 @@ int main(int argc, char **argv)
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
Zone::Shutdown(true);
|
||||
zone->Shutdown(true);
|
||||
LogInfo("Shutting down...");
|
||||
LogSys.CloseFileLogs();
|
||||
EQ::EventLoop::Get().Shutdown();
|
||||
@@ -731,37 +731,45 @@ void UpdateWindowTitle(char *iNewTitle)
|
||||
|
||||
bool CheckForCompatibleQuestPlugins()
|
||||
{
|
||||
const std::vector<std::string>& directories = { "lua_modules", "plugins" };
|
||||
const std::vector<std::pair<std::string, bool *>> directories = {
|
||||
{"lua_modules", nullptr},
|
||||
{"plugins", nullptr}
|
||||
};
|
||||
|
||||
bool lua_found = false;
|
||||
bool perl_found = false;
|
||||
|
||||
for (const auto& directory : directories) {
|
||||
for (const auto& file : fs::directory_iterator(path.GetServerPath() + "/" + directory)) {
|
||||
if (file.is_regular_file()) {
|
||||
auto f = file.path().string();
|
||||
if (File::Exists(f)) {
|
||||
auto r = File::GetContents(std::filesystem::path{ f }.string());
|
||||
if (Strings::Contains(r.contents, "CheckHandin")) {
|
||||
if (Strings::EqualFold(directory, "lua_modules")) {
|
||||
lua_found = true;
|
||||
} else if (Strings::EqualFold(directory, "plugins")) {
|
||||
perl_found = true;
|
||||
}
|
||||
try {
|
||||
for (const auto &[directory, flag]: directories) {
|
||||
std::string dir_path = path.GetServerPath() + "/" + directory;
|
||||
if (!File::Exists(dir_path)) { continue; }
|
||||
|
||||
if (lua_found && perl_found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &file: fs::directory_iterator(dir_path)) {
|
||||
if (!file.is_regular_file()) { continue; }
|
||||
|
||||
std::string file_path = file.path().string();
|
||||
if (!File::Exists(file_path)) { continue; }
|
||||
|
||||
auto r = File::GetContents(file_path);
|
||||
if (!Strings::Contains(r.contents, "CheckHandin")) { continue; }
|
||||
|
||||
if (directory == "lua_modules") {
|
||||
lua_found = true;
|
||||
}
|
||||
else {
|
||||
perl_found = true;
|
||||
}
|
||||
|
||||
if (lua_found && perl_found) { return true; }
|
||||
}
|
||||
}
|
||||
} catch (const fs::filesystem_error &ex) {
|
||||
LogError("Failed to check for compatible quest plugins: {}", ex.what());
|
||||
}
|
||||
|
||||
if (!lua_found) {
|
||||
LogError("Failed to find CheckHandin in lua_modules");
|
||||
}
|
||||
|
||||
if (!perl_found) {
|
||||
LogError("Failed to find CheckHandin in plugins");
|
||||
}
|
||||
|
||||
+12
-5
@@ -131,7 +131,8 @@ Mob::Mob(
|
||||
m_scan_close_mobs_timer(6000),
|
||||
m_see_close_mobs_timer(1000),
|
||||
m_mob_check_moving_timer(1000),
|
||||
bot_attack_flag_timer(10000)
|
||||
bot_attack_flag_timer(10000),
|
||||
m_destroying(false)
|
||||
{
|
||||
mMovementManager = &MobMovementManager::Get();
|
||||
mMovementManager->AddMob(this);
|
||||
@@ -531,6 +532,11 @@ Mob::Mob(
|
||||
|
||||
Mob::~Mob()
|
||||
{
|
||||
m_destroying = true;
|
||||
|
||||
entity_list.RemoveMobFromCloseLists(this);
|
||||
m_close_mobs.clear();
|
||||
|
||||
quest_manager.stopalltimers(this);
|
||||
|
||||
mMovementManager->RemoveMob(this);
|
||||
@@ -570,11 +576,8 @@ Mob::~Mob()
|
||||
entity_list.UnMarkNPC(GetID());
|
||||
UninitializeBuffSlots();
|
||||
|
||||
entity_list.RemoveMobFromCloseLists(this);
|
||||
entity_list.RemoveAuraFromMobs(this);
|
||||
|
||||
m_close_mobs.clear();
|
||||
|
||||
ClearDataBucketCache();
|
||||
|
||||
LeaveHealRotationTargetPool();
|
||||
@@ -1453,6 +1456,10 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
ns->spawn.flymode = 0;
|
||||
}
|
||||
|
||||
if (IsZoneController()) {
|
||||
ns->spawn.invis = 255; // gm invis
|
||||
}
|
||||
|
||||
if (RuleB(Character, AllowCrossClassTrainers) && ForWho) {
|
||||
if (ns->spawn.class_ >= Class::WarriorGM && ns->spawn.class_ <= Class::BerserkerGM) {
|
||||
int trainer_class = Class::WarriorGM + (ForWho->GetClass() - 1);
|
||||
@@ -8347,7 +8354,7 @@ int Mob::DispatchZoneControllerEvent(
|
||||
RuleB(Zone, UseZoneController) &&
|
||||
(
|
||||
!IsNPC() ||
|
||||
(IsNPC() && GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID)
|
||||
(IsNPC() && !IsZoneController())
|
||||
)
|
||||
) {
|
||||
auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID);
|
||||
|
||||
@@ -671,6 +671,7 @@ public:
|
||||
((static_cast<float>(current_mana) / max_mana) * 100); }
|
||||
virtual int64 CalcMaxMana();
|
||||
uint32 GetNPCTypeID() const { return npctype_id; }
|
||||
inline bool IsZoneController() const { return npctype_id == ZONE_CONTROLLER_NPC_ID; }
|
||||
void SetNPCTypeID(uint32 npctypeid) { npctype_id = npctypeid; }
|
||||
inline const glm::vec4& GetPosition() const { return m_Position; }
|
||||
inline void SetPosition(const float x, const float y, const float z) { m_Position.x = x; m_Position.y = y; m_Position.z = z; }
|
||||
@@ -1509,6 +1510,7 @@ public:
|
||||
|
||||
void ClearDataBucketCache();
|
||||
bool IsGuildmaster() const;
|
||||
bool IsDestroying() const { return m_destroying; }
|
||||
|
||||
protected:
|
||||
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
|
||||
@@ -1931,6 +1933,7 @@ private:
|
||||
EQ::InventoryProfile m_inv;
|
||||
std::shared_ptr<HealRotation> m_target_of_heal_rotation;
|
||||
bool m_manual_follow;
|
||||
bool m_destroying;
|
||||
|
||||
void SetHeroicStrBonuses(StatBonuses* n);
|
||||
void SetHeroicStaBonuses(StatBonuses* n);
|
||||
|
||||
@@ -626,6 +626,9 @@ inline void NPCCommandsMenu(Client* client, NPC* npc)
|
||||
|
||||
if (npc->GetLoottableID() > 0) {
|
||||
menu_commands += "[" + Saylink::Silent("#npcloot show", "Loot") + "] ";
|
||||
if (npc) {
|
||||
menu_commands += fmt::format(" Item(s) ({}) ", npc->GetLootItems().size());
|
||||
}
|
||||
}
|
||||
|
||||
if (npc->IsProximitySet()) {
|
||||
|
||||
+62
-7
@@ -62,6 +62,7 @@
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#endif
|
||||
|
||||
extern Zone* zone;
|
||||
@@ -131,6 +132,9 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
),
|
||||
attacked_timer(CombatEventTimer_expire),
|
||||
swarm_timer(100),
|
||||
m_corpse_queue_timer(1000),
|
||||
m_corpse_queue_shutoff_timer(30000),
|
||||
m_resumed_from_zone_suspend_shutoff_timer(10000),
|
||||
classattack_timer(1000),
|
||||
monkattack_timer(1000),
|
||||
knightattack_timer(1000),
|
||||
@@ -618,7 +622,49 @@ bool NPC::Process()
|
||||
}
|
||||
}
|
||||
|
||||
// zone state corpse creation timer
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
// creates a corpse if the NPC is queued for corpse creation
|
||||
if (m_corpse_queue_timer.Check()) {
|
||||
if (IsQueuedForCorpse()) {
|
||||
auto decay_timer = m_corpse_decay_time;
|
||||
uint16 corpse_id = GetID();
|
||||
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||
if (c) {
|
||||
c->UnLock();
|
||||
c->SetDecayTimer(decay_timer);
|
||||
}
|
||||
}
|
||||
m_corpse_queue_timer.Disable();
|
||||
m_corpse_queue_shutoff_timer.Disable();
|
||||
}
|
||||
|
||||
// shuts off the corpse queue timer if it is still running
|
||||
if (m_corpse_queue_shutoff_timer.Check()) {
|
||||
m_corpse_queue_timer.Disable();
|
||||
m_corpse_queue_shutoff_timer.Disable();
|
||||
}
|
||||
|
||||
// shuts off the temporary spawn protected state of the NPC
|
||||
if (m_resumed_from_zone_suspend_shutoff_timer.Check()) {
|
||||
m_resumed_from_zone_suspend_shutoff_timer.Disable();
|
||||
SetResumedFromZoneSuspend(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (tic_timer.Check()) {
|
||||
if (RuleB(Zone, StateSavingOnShutdown) && IsQueuedForCorpse()) {
|
||||
auto decay_timer = m_corpse_decay_time;
|
||||
uint16 corpse_id = GetID();
|
||||
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||
if (c) {
|
||||
c->UnLock();
|
||||
c->SetDecayTimer(decay_timer);
|
||||
}
|
||||
}
|
||||
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) {
|
||||
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
|
||||
}
|
||||
@@ -895,13 +941,14 @@ bool NPC::SpawnZoneController()
|
||||
|
||||
npc_type->findable = 0;
|
||||
npc_type->trackable = 0;
|
||||
npc_type->untargetable = 1;
|
||||
|
||||
strcpy(npc_type->special_abilities, "12,1^13,1^14,1^15,1^16,1^17,1^19,1^22,1^24,1^25,1^28,1^31,1^35,1^39,1^42,1");
|
||||
strcpy(npc_type->special_abilities, "1,1,3000,50^12,1^14,1^16,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^26,1^32,1^33,1^35,1^46,1^47,1^48,1^49,1^50,1^52,1^53,1^54,1^55,1^56,1^57,1");
|
||||
|
||||
glm::vec4 point;
|
||||
point.x = 3000;
|
||||
point.y = 1000;
|
||||
point.z = 500;
|
||||
point.x = 30000;
|
||||
point.y = 10000;
|
||||
point.z = -10000;
|
||||
|
||||
auto npc = new NPC(npc_type, nullptr, point, GravityBehavior::Flying);
|
||||
npc->GiveNPCTypeData(npc_type);
|
||||
@@ -4269,7 +4316,7 @@ bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPetOwnerClient() && !IsCharmedPet()) {
|
||||
if (!IsPetOwnerOfClientBot() && !IsCharmedPet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4351,6 +4398,10 @@ bool NPC::CheckHandin(
|
||||
h = m_hand_in;
|
||||
}
|
||||
|
||||
if (IsMultiQuestEnabled()) {
|
||||
LogNpcHandin("{} Multi-Quest hand-in enabled", log_handin_prefix);
|
||||
}
|
||||
|
||||
std::vector<std::pair<const std::map<std::string, uint32>&, Handin&>> datasets = {};
|
||||
|
||||
// if we've already started the hand-in process, we don't want to re-process the hand-in data
|
||||
@@ -4432,7 +4483,7 @@ bool NPC::CheckHandin(
|
||||
|
||||
// multi-quest
|
||||
if (IsMultiQuestEnabled()) {
|
||||
for (auto &h_item: h.items) {
|
||||
for (auto &h_item: m_hand_in.items) {
|
||||
for (const auto &r_item: r.items) {
|
||||
if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) {
|
||||
h_item.is_multiquest_item = true;
|
||||
@@ -4777,7 +4828,11 @@ NPC::Handin NPC::ReturnHandinItems(Client *c)
|
||||
}
|
||||
|
||||
c->PushItemOnCursor(*i.item, true);
|
||||
LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name);
|
||||
LogNpcHandin(
|
||||
"Hand-in failed, returning item [{}] i.is_multiquest_item [{}]",
|
||||
i.item->GetItem()->Name,
|
||||
i.is_multiquest_item
|
||||
);
|
||||
|
||||
returned_handin = true;
|
||||
return true; // Mark this item for removal
|
||||
|
||||
+47
@@ -601,6 +601,41 @@ public:
|
||||
bool HasProcessedHandinReturn() { return m_has_processed_handin_return; }
|
||||
bool HandinStarted() { return m_handin_started; }
|
||||
|
||||
// zone state save
|
||||
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
|
||||
inline bool IsQueuedForCorpse() { return m_queued_for_corpse; }
|
||||
inline uint32_t SetCorpseDecayTime(uint32_t decay_time) { return m_corpse_decay_time = decay_time; }
|
||||
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; }
|
||||
inline bool IsResumedFromZoneSuspend() { return m_resumed_from_zone_suspend; }
|
||||
|
||||
inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
|
||||
int i = 0;
|
||||
for (auto &b: in_buffs) {
|
||||
buffs[i].spellid = b.spellid;
|
||||
buffs[i].casterlevel = b.casterlevel;
|
||||
buffs[i].casterid = b.casterid;
|
||||
strncpy(buffs[i].caster_name, b.caster_name, 64);
|
||||
buffs[i].ticsremaining = b.ticsremaining;
|
||||
buffs[i].counters = b.counters;
|
||||
buffs[i].hit_number = b.hit_number;
|
||||
buffs[i].melee_rune = b.melee_rune;
|
||||
buffs[i].magic_rune = b.magic_rune;
|
||||
buffs[i].dot_rune = b.dot_rune;
|
||||
buffs[i].caston_x = b.caston_x;
|
||||
buffs[i].caston_y = b.caston_y;
|
||||
buffs[i].caston_z = b.caston_z;
|
||||
buffs[i].ExtraDIChance = b.ExtraDIChance;
|
||||
buffs[i].RootBreakChance = b.RootBreakChance;
|
||||
buffs[i].instrument_mod = b.instrument_mod;
|
||||
buffs[i].virus_spread_time = b.virus_spread_time;
|
||||
buffs[i].persistant_buff = b.persistant_buff;
|
||||
buffs[i].client = b.client;
|
||||
buffs[i].UpdateClient = b.UpdateClient;
|
||||
i++;
|
||||
}
|
||||
CalcBonuses();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void HandleRoambox();
|
||||
@@ -622,6 +657,18 @@ protected:
|
||||
uint32 m_loot_platinum;
|
||||
LootItems m_loot_items;
|
||||
|
||||
// zone state
|
||||
bool m_resumed_from_zone_suspend = false;
|
||||
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
|
||||
uint32_t m_corpse_decay_time = 0; // decay time set on zone state restore
|
||||
Timer m_corpse_queue_timer = {}; // this is to check for corpse creation on zone state restore
|
||||
Timer m_corpse_queue_shutoff_timer = {};
|
||||
|
||||
// this is a 30-second timer that protects a NPC from having double assignment of loot
|
||||
// this is to prevent a player from killing a NPC and then zoning out and back in to get loot again
|
||||
// if loot was to be assigned via script again, this protects double assignment for 30 seconds
|
||||
Timer m_resumed_from_zone_suspend_shutoff_timer = {};
|
||||
|
||||
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
|
||||
|
||||
int32 npc_faction_id;
|
||||
|
||||
+28
-8
@@ -58,7 +58,7 @@ void Client::SendBulkParcels()
|
||||
p.second.aug_slot_6
|
||||
));
|
||||
if (inst) {
|
||||
inst->SetCharges(p.second.quantity > 0 ? p.second.quantity : 1);
|
||||
inst->SetCharges(p.second.quantity);
|
||||
inst->SetMerchantCount(1);
|
||||
inst->SetMerchantSlot(p.second.slot_id);
|
||||
if (inst->IsStackable()) {
|
||||
@@ -161,7 +161,7 @@ void Client::SendParcel(Parcel_Struct &parcel_in)
|
||||
p.aug_slot_6
|
||||
));
|
||||
if (inst) {
|
||||
inst->SetCharges(p.quantity > 0 ? p.quantity : 1);
|
||||
inst->SetCharges(p.quantity);
|
||||
inst->SetMerchantCount(1);
|
||||
inst->SetMerchantSlot(p.slot_id);
|
||||
if (inst->IsStackable()) {
|
||||
@@ -272,6 +272,10 @@ void Client::SendParcelStatus()
|
||||
|
||||
void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
{
|
||||
if (IsCasting()) {
|
||||
StopCasting();
|
||||
}
|
||||
|
||||
auto send_to_client = CharacterParcelsRepository::GetParcelCountAndCharacterName(database, parcel_in->send_to);
|
||||
auto merchant = entity_list.GetMob(parcel_in->npc_id);
|
||||
if (!merchant) {
|
||||
@@ -382,7 +386,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
quantity = parcel_in->quantity;
|
||||
}
|
||||
else {
|
||||
quantity = inst->GetCharges() > 0 ? inst->GetCharges() : parcel_in->quantity;
|
||||
quantity = inst->GetCharges() >= 0 ? inst->GetCharges() : parcel_in->quantity;
|
||||
}
|
||||
|
||||
CharacterParcelsRepository::CharacterParcels parcel_out{};
|
||||
@@ -434,13 +438,13 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
cpc.aug_slot_5 = augs.at(4);
|
||||
cpc.aug_slot_6 = augs.at(5);
|
||||
}
|
||||
cpc.quantity = kv.second->GetCharges() > 0 ? kv.second->GetCharges() : 1;
|
||||
cpc.quantity = kv.second->GetCharges() >= 0 ? kv.second->GetCharges() : 1;
|
||||
all_entries.push_back(cpc);
|
||||
}
|
||||
CharacterParcelsContainersRepository::InsertMany(database, all_entries);
|
||||
}
|
||||
|
||||
RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity);
|
||||
RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity);
|
||||
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopSendParcel));
|
||||
QueuePacket(outapp.get());
|
||||
|
||||
@@ -642,9 +646,9 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
if (p != m_parcels.end()) {
|
||||
uint32 item_id = parcel_in.parcel_item_id;
|
||||
uint32 item_quantity = p->second.quantity;
|
||||
if (!item_id || !item_quantity) {
|
||||
if (!item_id) {
|
||||
LogError(
|
||||
"Attempt to retrieve parcel with erroneous item id or quantity for client character id {}.",
|
||||
"Attempt to retrieve parcel with erroneous item id for client character id {}.",
|
||||
CharacterID()
|
||||
);
|
||||
SendParcelRetrieveAck();
|
||||
@@ -884,6 +888,22 @@ void Client::AddParcel(CharacterParcelsRepository::CharacterParcels &parcel)
|
||||
"Unable to send parcel at this time. Please try again later."
|
||||
);
|
||||
SendParcelAck();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int32 Client::FindNextFreeParcelSlotUsingMemory()
|
||||
{
|
||||
auto const results = GetParcels();
|
||||
|
||||
if (results.empty()) {
|
||||
return PARCEL_BEGIN_SLOT;
|
||||
}
|
||||
|
||||
for (uint32 i = PARCEL_BEGIN_SLOT; i <= RuleI(Parcel, ParcelMaxItems); i++) {
|
||||
if (!results.contains(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
+46
-10
@@ -1142,16 +1142,6 @@ void Perl_Client_SetStartZone(Client* self, uint32 zone_id, float x, float y, fl
|
||||
self->SetStartZone(zone_id, x, y, z, heading);
|
||||
}
|
||||
|
||||
void Perl_Client_KeyRingAdd(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items
|
||||
{
|
||||
self->KeyRingAdd(item_id);
|
||||
}
|
||||
|
||||
bool Perl_Client_KeyRingCheck(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items
|
||||
{
|
||||
return self->KeyRingCheck(item_id);
|
||||
}
|
||||
|
||||
void Perl_Client_AddPVPPoints(Client* self, uint32 points) // @categories Currency and Points
|
||||
{
|
||||
self->AddPVPPoints(points);
|
||||
@@ -3271,6 +3261,21 @@ std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string buck
|
||||
return self->GetAccountBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Perl_Client_GrantNameChange(Client* self)
|
||||
{
|
||||
self->GrantNameChange();
|
||||
}
|
||||
|
||||
bool Perl_Client_IsNameChangeAllowed(Client* self)
|
||||
{
|
||||
return self->IsNameChangeAllowed();
|
||||
}
|
||||
|
||||
bool Perl_Client_ClearNameChange(Client* self)
|
||||
{
|
||||
return self->ClearNameChange();
|
||||
}
|
||||
|
||||
std::string Perl_Client_GetBandolierName(Client* self, uint8 bandolier_slot)
|
||||
{
|
||||
return self->GetBandolierName(bandolier_slot);
|
||||
@@ -3306,6 +3311,31 @@ std::string Perl_Client_GetPotionBeltItemName(Client* self, uint8 slot_id)
|
||||
return self->GetPotionBeltItemName(slot_id);
|
||||
}
|
||||
|
||||
bool Perl_Client_KeyRingAdd(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items
|
||||
{
|
||||
return self->KeyRingAdd(item_id);
|
||||
}
|
||||
|
||||
bool Perl_Client_KeyRingCheck(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items
|
||||
{
|
||||
return self->KeyRingCheck(item_id);
|
||||
}
|
||||
|
||||
bool Perl_Client_KeyRingClear(Client* self)
|
||||
{
|
||||
return self->KeyRingClear();
|
||||
}
|
||||
|
||||
void Perl_Client_KeyRingList(Client* self)
|
||||
{
|
||||
self->KeyRingList();
|
||||
}
|
||||
|
||||
bool Perl_Client_KeyRingRemove(Client* self, uint32 item_id)
|
||||
{
|
||||
return self->KeyRingRemove(item_id);
|
||||
}
|
||||
|
||||
void perl_register_client()
|
||||
{
|
||||
perl::interpreter perl(PERL_GET_THX);
|
||||
@@ -3378,6 +3408,7 @@ void perl_register_client()
|
||||
package.add("CashReward", &Perl_Client_CashReward);
|
||||
package.add("ChangeLastName", &Perl_Client_ChangeLastName);
|
||||
package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange);
|
||||
package.add("ClearNameChange", (bool(*)(Client*))&Perl_Client_ClearNameChange);
|
||||
package.add("CharacterID", &Perl_Client_CharacterID);
|
||||
package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill);
|
||||
package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill);
|
||||
@@ -3592,6 +3623,7 @@ void perl_register_client()
|
||||
package.add("GrantAllAAPoints", (void(*)(Client*, uint8, bool))&Perl_Client_GrantAllAAPoints);
|
||||
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int))&Perl_Client_GrantAlternateAdvancementAbility);
|
||||
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int, bool))&Perl_Client_GrantAlternateAdvancementAbility);
|
||||
package.add("GrantNameChange", (void(*)(Client*))&Perl_Client_GrantNameChange);
|
||||
package.add("GuildID", &Perl_Client_GuildID);
|
||||
package.add("GuildRank", &Perl_Client_GuildRank);
|
||||
package.add("HasAugmentEquippedByID", &Perl_Client_HasAugmentEquippedByID);
|
||||
@@ -3622,6 +3654,7 @@ void perl_register_client()
|
||||
package.add("IsInAGuild", &Perl_Client_IsInAGuild);
|
||||
package.add("IsLD", &Perl_Client_IsLD);
|
||||
package.add("IsMedding", &Perl_Client_IsMedding);
|
||||
package.add("IsNameChangeAllowed", (bool(*)(Client*))&Perl_Client_IsNameChangeAllowed);
|
||||
package.add("IsRaidGrouped", &Perl_Client_IsRaidGrouped);
|
||||
package.add("IsSitting", &Perl_Client_IsSitting);
|
||||
package.add("IsStanding", &Perl_Client_IsStanding);
|
||||
@@ -3630,6 +3663,9 @@ void perl_register_client()
|
||||
package.add("IsTaskCompleted", &Perl_Client_IsTaskCompleted);
|
||||
package.add("KeyRingAdd", &Perl_Client_KeyRingAdd);
|
||||
package.add("KeyRingCheck", &Perl_Client_KeyRingCheck);
|
||||
package.add("KeyRingClear", &Perl_Client_KeyRingClear);
|
||||
package.add("KeyRingList", &Perl_Client_KeyRingList);
|
||||
package.add("KeyRingRemove", &Perl_Client_KeyRingRemove);
|
||||
package.add("Kick", &Perl_Client_Kick);
|
||||
package.add("LearnDisciplines", &Perl_Client_LearnDisciplines);
|
||||
package.add("LearnRecipe", &Perl_Client_LearnRecipe);
|
||||
|
||||
@@ -806,6 +806,11 @@ void Perl_NPC_MultiQuestEnable(NPC* self)
|
||||
self->MultiQuestEnable();
|
||||
}
|
||||
|
||||
bool Perl_NPC_IsResumedFromZoneSuspend(NPC* self)
|
||||
{
|
||||
return self->IsResumedFromZoneSuspend();
|
||||
}
|
||||
|
||||
bool Perl_NPC_CheckHandin(
|
||||
NPC* self,
|
||||
Client* c,
|
||||
@@ -983,6 +988,7 @@ void perl_register_npc()
|
||||
package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist);
|
||||
package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget);
|
||||
package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn);
|
||||
package.add("IsResumedFromZoneSuspend", &Perl_NPC_IsResumedFromZoneSuspend);
|
||||
package.add("IsTaunting", &Perl_NPC_IsTaunting);
|
||||
package.add("IsUnderwaterOnly", (bool(*)(NPC*))&Perl_NPC_IsUnderwaterOnly);
|
||||
package.add("MerchantCloseShop", &Perl_NPC_MerchantCloseShop);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user