Compare commits

...

110 Commits

Author SHA1 Message Date
regneq caae34ac5e fixed client lua summonitem.
export npc:CanTalk to lua.
added QuestSlot.
Added a condition for questslot in summonitem function.
2024-04-05 18:01:48 -07:00
regneq 56c7db4cbf [MultiQuest] Add functions to Loot. Import to Lua_NPC. 2024-04-05 12:08:01 -07:00
KayenEQ 043eeced6f [Bug Fix] Client not updating HP bar when an HP Buff with a Heal is applied. (#4237)
* [Bug Fix] HP Bar not updating when applying HP Buff with a heal.

Bug: When an HP buff with a heal effect is applied for first time, the heal portion of the effect heals the client and updates HPs currently server side, but client side the HP bar does not register it as a heal thus you display as less than full HP. However due to server thinking your healed, you are unable to correct it by healing.

Solution: You need to resend the HP update after buff completed and action packet resent.

* add SE_MaxHPChange to fix

would result in same bug
2024-04-02 01:13:29 -05:00
KayenEQ e9a0c79301 [Bug Fix] SPA214 SE_MaxHPChange calculation errors corrected. (#4238)
* [Bug Fix] HP Bar not updating when applying HP Buff with a heal.

Bug: When an HP buff with a heal effect is applied for first time, the heal portion of the effect heals the client and updates HPs currently server side, but client side the HP bar does not register it as a heal thus you display as less than full HP. However due to server thinking your healed, you are unable to correct it by healing.

Solution: You need to resend the HP update after buff completed and action packet resent.

* add SE_MaxHPChange to fix

would result in same bug

* [Bug Fix] SPA214 Percent HP change calculation fix

Fix how spell and item bonuses using SPA 214 are calculated. Will now be calculated consistent with client.

* [Bug Fix] SPA214 SE_MaxHPChange calculation errors corrected.

removed code from other PR
2024-04-02 01:12:55 -05:00
Alex King dc48c45421 [Bug Fix] Fix Bot Creation Issue (#4235)
# Notes
- Creating bots was failing because were checking for `false` on `Database::CheckUsedName()` in `BotDatabase::QueryNameAvailability`.
- `Database::CheckUsedName()` is now `Database::IsNameUsed()` and checks for both bots and character name usages.
- We were checking for `false` which was always happening when there were no entries for the supplied name, meaning we were never allowed to create a bot.
2024-04-02 01:12:08 -05:00
Chris Miles d7a8fb8691 [Fix] Fix manifest for skill caps schema type (#4231) 2024-04-02 01:08:19 -05:00
Chris Miles 3a5381d38a [Crash] Check mob pointer before trying to remove it (#4230) 2024-04-01 18:09:57 -04:00
Alex King e64f03fcc0 [Bug Fix] Fix Lua Crash with Spell Blocked Event (#4236) 2024-04-01 18:06:39 -04:00
Alex King d77966797e [Quest API] Add Spell Blocked Event to Perl/Lua (#4217)
* [Spells] Add Unblockable Spell Table and Spell Blocked Event

- Add `EVENT_SPELL_BLOCKED`, exports `$blocking_spell_id`, `$cast_spell_id`, `$blocking_spell`, and `$cast_spell`.

- Add `event_spell_blocked`, exports `e.blocking_spell_id`, `e.cast_spell_id`, `e.blocking_spell`, and `e.cast_spell`.

- Adds `spells_unblockable` table with a `spell_id` and `is_unblockable` column.
- This table will need to be populated based on known spells that should be unblockable.

- This event will allow operators to perform events when spells are blocked.

* Cleanup

* Cleanup

* Update spells.cpp

* Remove unused repositories.

* Finalize

* Update lua_parser_events.cpp
2024-03-31 22:58:30 -05:00
Akkadius 048aad437b Update pull_request_template.md 2024-03-31 22:55:27 -05:00
hg b2d9de8d96 [Quests] Avoid Player and Bot quests in unloaded zone (#4232)
If a mob has a target when a zone is shutdown it will crash while trying
to dispatch EVENT_TARGET_CHANGE when the Mob destructor cleans up hatelists
if a quest interface isn't loaded for the type (in this case no bot scripts):

 	zone.exe!fmt::v10::format<std::string const &,char const *>(fmt::v10::basic_format_string<char,std::string const &,char const *> fmt={...}, const std::string & <args_0>={...}, const char * && <args_1>=0x0000000000000000) Line 2835	C++
>	zone.exe!QuestParserCollection::GetQIByBotQuest(std::string & filename={...}) Line 1138	C++
 	zone.exe!QuestParserCollection::BotHasQuestSubLocal(QuestEventID event_id=EVENT_TARGET_CHANGE) Line 353	C++
 	zone.exe!QuestParserCollection::BotHasQuestSub(QuestEventID event_id=EVENT_TARGET_CHANGE) Line 389	C++
 	zone.exe!Mob::SetTarget(Mob * mob=0x0000000000000000) Line 5431	C++
 	zone.exe!NPC::SetTarget(Mob * mob=0x0000000000000000) Line 575	C++
 	zone.exe!Mob::RemoveFromHateList(Mob * mob=0x000001bfbdc66040) Line 4894	C++
 	zone.exe!EntityList::RemoveFromTargets(Mob * mob=0x000001bfbdc66040, bool RemoveFromXTargets=true) Line 1530	C++
 	zone.exe!Mob::~Mob() Line 547	C++
 	zone.exe!NPC::~NPC() Line 537	C++
 	zone.exe!NPC::`scalar deleting destructor'(unsigned int)	C++
 	zone.exe!EntityList::RemoveAllMobs() Line 2678	C++
 	zone.exe!EntityList::Clear() Line 3090	C++
 	zone.exe!Zone::~Zone() Line 1103	C++
 	zone.exe!Zone::`scalar deleting destructor'(unsigned int)	C++
 	zone.exe!Zone::Shutdown(bool quiet=false) Line 928	C++

This is caused by the Zone destructor deleting short_name before calling
entity_list.Clear(). With an unloaded quest interface BotHasQuestSubLocal
calls GetQIByBotQuest which gets a null zone->GetShortName() and crashes
while formatting strings.

The immediate regressing commit for this crash is because a check for
zone->IsLoaded() was removed in 74f1eac4 with others that were removed
to fix a regression by #4025. GetQIByBotQuest and GetQIByPlayerQuest
always had this check and should have remained for them.

This restores the zone->IsLoaded() checks for GetQIByBotQuest/PlayerQuest.
The other functions cannot have that check added until the other issues
mentioned in #4149 are addressed.
2024-03-31 22:49:13 -05:00
Fryguy 42bfa4bb2e [Bug Fix] Shared Tasks - charid is now character_id (#4233)
## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)

# Checklist:

- [x] I have performed a self-review of my code. Ensuring variables, functions and methods are named in a human-readable way, comments are added only where naming of variables, functions and methods can't give enough context.
- [x] I have tested my changes
- [x] I own the changes of my code take responsibilities for the potential issues that occur
2024-03-31 22:41:15 -05:00
Chris Miles 72ce7c8e91 Update pull_request_template.md 2024-03-31 15:52:54 -05:00
Chris Miles e306c86875 Create pull_request_template.md 2024-03-31 15:48:33 -05:00
Alex King 3ae7979a67 [Bug Fix] Fix Issue With Bot Raid Aggro (#4222)
# Notes
- Bots were reportedly being bypassed by NPCs due to not taking into consideration their raid group's damage.
- Must have been missed when bot raids were implemented.
2024-03-31 11:56:12 -05:00
Xackery b638795f9b [Feature] Add LuaMod functions for CommonDamage and HealDamage (#4227)
* Add LuaMod functions for CommonDamage and HealDamage

* Remove extra bracket
2024-03-31 11:30:16 -04:00
KayenEQ 9e3bf91374 [Spells] Implemented SPA 463 SE_SHIELD_TARGET (#4224)
SPA 463 SE_SHIELD_TARGET

Live description: "Shields your target, taking a percentage of their damage".

Only example spell on live is an NPC who uses it during a raid event "Laurion's Song" expansion. SPA 54492 'Guardian Stance' Described as 100% Melee Shielding

Example of mechanic. Base value = 70. Caster puts buff on target. Each melee hit Buff Target takes 70% less damage, Buff Caster receives 30% of the melee damage.
Added mechanic to cause buff to fade if target or caster are separated by a distance greater than the casting range of the spell. This allows similar mechanics to the /shield ability, without a range removal mechanic it would be too easy to abuse if put on a player spell. *can not confirm live does this currently

Can not be cast on self.
2024-03-30 13:34:03 -04:00
KayenEQ cd89926435 [Feature] Additive Spell Focus from Worn slot with Limit Checks (#4208)
* [Feature] Additive Spell Focus from Worn slot with limits

New rule (UseAdditiveFocusFromWornSlotWithLimits) allows you place to focus effects in worn slots which will apply the focus additively and perform normal limit checks on those focus. This differs from regular focus behavior that only takes highest value.

This is a new version of an old rule "UseAdditiveFocusFromWornSlot"
which allowed similar behavior but ignored focus limits. Thus hindering its full potential for itemization.

* Update spell_effects.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-03-30 13:23:02 -04:00
Xackery e19f72f021 [Feature] Add RegisterBug LuaMod (#4209)
* Add RegisterBug LuaMod

* Add missing header

* Add missing header to lua_mod

* Fix RegisterBug ignore_default

* Fix ignore_default

* Fix formatting
2024-03-30 11:45:37 -04:00
JJ df1dc5d1e4 [Cleanup] Avoid unnecessary copies in database functions (#4220)
Since `auto` doesn't deduce references, these will most likely create copies having unintended results.
2024-03-29 07:45:41 -04:00
Alex King e11286164f [Bug Fix] Fix Luabind Double Class Register (#4219)
* [Bug Fix] Fix Luabind Double Class Register

# Notes
- These two methods were registering to the same class as another method, causing an error seen by @neckkola and others.

# Error
```
zone: /home/eqemu/source_jas/libs/luabind/src/class_registry.cpp:151: void luabind::detail::class_registry::add_class(const luabind::type_id&, luabind::detail::class_rep*): Assertion `(m_classes.find(info) == m_classes.end()) && "you are trying to register a class twice"' failed.
```

* Update lua_general.cpp
2024-03-28 20:05:55 -04:00
JJ b946b800fb [Cleanup] Reference type in GetRaidLeaderName (#4218)
Minor cleanup from #4054
2024-03-28 19:15:16 -04:00
Alex King ab8ac81df6 [Bug Fix] Fix Group Leadership (#4214)
* [Bug Fix] Fix Group Leadership

# Notes
- We were not sending anything to `group_leaders` table if we did not already have an existing row.

# Video

* Update database.cpp
2024-03-28 15:33:49 -05:00
Alex King 109940fc0c [Quest API] Add Class/Deity/Race Methods to Perl/Lua (#4215)
# Perl
- Add `$client->GetDeityBitmask()`.
- Add `quest::get_class_bitmask(class_id)`.
- Add `quest::get_deity_bitmask(deity_id)`.
- Add `quest::get_race_bitmask(race_id)`.

# Lua
- Add `client:GetDeityBitmask()`.
- Add `eq.get_class_bitmask(class_id)`.
- Add `eq.get_deity_bitmask(deity_id)`.
- Add `eq.get_race_bitmask(race_id)`.

# Notes
- Allows operators to get the class/deity/race bitmask of a class/deity/race by ID.
- Allows operators to get a client's deity bitmask.
2024-03-28 15:32:02 -05:00
Fryguy a87496b0cf [Lua] Add Zone and Language Constants (#4211)
* [LUA] Add Zone and Language Constants

This will add Zone:

```lua
if eq.get_zone_id() == Zone.Qeynos then
   foo()
end
```

It will also add Language:

```lua
if e.language == Language.ElderElvish and e.other:GetLanguageSkill(Language.ElderElvish) >= 100 then
   e.self:Say("You know my language!", Language.ElderElvish);
end
```

These changes should help avoid magic numbers in quests and aide in readability without the need for -- comments to clarify.

* Adjust to lower case
2024-03-28 13:16:41 -04:00
Alex King f905ee70e4 [Bug Fix] Fix Auto Login Issue (#4213)
* [Bug Fix] Fix Auto Login Issue

# Notes
- We were setting `live_name` value regardless of if we were zoning, causing us to automatically log in to the last character we'd logged in to before.

* Remove &
2024-03-28 09:48:17 -04:00
JJ 52417023f8 [Cleanup] Remove unnecessary reference types (#4212)
Minor clean up from #4054 and #4181
2024-03-27 22:17:21 -04:00
KayenEQ 20d9417628 [Spells] SPA148 Stacking Fix (#4206)
Update to SPA148 which acts to block spells buffs that are of lesser value than the current buff for specific effect slots. This effected was preventing detrimental debuffs from being applied that were using same effect slot. This bug affected a very small amount of spell interactions and was fixed on live in 2018.  Example being Vishmitars Corruption (6642) being blocked by SteelOak Skin (5352)

 I confirmed the behavior on live myself. The detrimental buff  if in conflict should now be applied instead of blocked.
2024-03-27 14:39:40 -04:00
Xackery 4692799677 [Bug Fix] Fix event_consider any_cast error (#4210) 2024-03-27 14:31:38 -04:00
Chris Miles cf7f0f4321 [Skill Caps] Further improvements (#4205) 2024-03-24 13:04:26 -04:00
Chris Miles 823a5956de [Hotfix] Fix crash in SendEnterWorld (#4204) 2024-03-23 23:28:29 -05:00
JJ 6a8bd3c5d6 [Bug Fix] Fix fishing chances (#4203)
Fix regression from #4008 where the item count wasn't being incremented
Move the zone table chance to a rule
General cleanup of some initializers and variable types
2024-03-23 23:32:07 -04:00
Alex King 523ba30d81 [Repositories] Convert database.cpp to Repositories (#4054)
* [Repositories] Convert database.cpp to Repositories

- Convert all database.cpp methods to repositories where possible.

* Final push.

* Cleanup

* Cleanup

* Update database.h

* Fix crash

* Update database.cpp
2024-03-23 19:30:56 -05:00
Alex King d7ea290b6b [Skill Caps] Remove from shared memory and simplify (#4069)
* [Skill Caps] Remove from shared memory and simplify

- Removes Skill Caps loading from shared memory and puts it into zone.
- Adds `id` column to `skill_caps`.
- Remove primary keys and use `id` as primary key.
- Add unique index using `skill_id`, `class_id`, `level`, and `cap`.
- Renames `class` to `class_id` in `skill_caps` table.
- Renames `skillID` to `skill_id` in `skill_caps` table.
- Regenerates Skill Caps repository.
- Adds `#reload skill_caps` to reload skill caps in real time.

* Update groups.cpp

* Update groups.cpp
2024-03-23 18:52:40 -05:00
Alex King 036309ebec [Hot Fix] Hot Fix for Group::AddToGroup Hot Fix (#4202)
# Notes
- Typo in previous.
2024-03-23 18:51:43 -05:00
Alex King b400700d81 [Hot Fix] Fix Group::AddToGroup (#4201)
# Notes
- We were not using `r` values at any point.
2024-03-23 18:49:15 -05:00
Alex King 21cec87ac4 [Bug Fix] Fix Bot/Character ID Overlap in Groups (#4093)
* [Bug Fix] Fix Bot/Character ID Overlap in Groups

- Attempt to fix bot/character ID overlap in groups keeping bots with the same unique identifier as players from not spawning on zone.
- Adds `bot_id` to `group_id` to differentiate bots from characters and hopefully alleviate this issue.

* Update base_group_id_repository.h

* Final push
2024-03-23 17:55:03 -05:00
Alex King abdec39cdd [Quest API] Add Archetype Methods to Perl/Lua (#4181)
* [Quest API] Add Archetype Methods to Perl/Lua

- Add `$mob->GetArchetypeName()`.
- Add `$mob->IsIntelligenceCasterClass()`.
- Add `$mob->IsPureMeleeClass()`.
- Add `$mob->IsWisdomCasterClass()`.

- Add `mob:GetArchetypeName()`.
- Add `mob:IsIntelligenceCasterClass()`.
- Add `mob:IsPureMeleeClass()`.
- Add `mob:IsWisdomCasterClass()`.

- Allows operators to use mob archetypes to perform different operations.
- Add a namespace for archetypes instead of constants.
- Utilize `IsIntelligenceCasterClass()`, `IsPureMeleeClass()`, and `IsWisdomCasterClass()` where necessary.
-

* Update mob.cpp
2024-03-23 14:37:35 -05:00
Fryguy d2372de982 [Bug Fix] Radiant/Ebon Crystals should only extract to 1000 (#4195)
* [Bug] Radiant/Ebon Crystals should only extract to 1000

Creating a stack larger than 1000 can cause potential dupe issues further down the stream if other tasks are performed on the stack.

* Use stacksize instead

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-03-23 12:56:59 -05:00
Chris Miles ea9b7841d4 [Release] 22.48.0 (#4200) 2024-03-23 00:24:01 -05:00
Alex King 8826d7b927 [Rules] Add World:Rules Rule (#4194)
* [Rules] Add World:Rules Rule

# Notes
- Adds `World:Rules` rule to take the place of the variables table value.

* Update client.cpp

* Update client.cpp
2024-03-23 00:01:27 -05:00
JJ e4157f0221 [Bug Fix] Fix reusing timers (#4199)
Fixes regression from #4099 where timers that were reused were not getting changed with their updated times
2024-03-23 00:01:15 -05:00
Alex King 66cc947b2a [Quest API] Add Restore Methods for Health, Mana, and Endurance to Perl/Lua (#4179)
* [Quest API] Add Restore Methods for Health, Mana, and Endurance to Perl/Lua

- Add `$mob->RestoreEndurance()`.
- Add `$mob->RestoreHealth()`.
- Add `$mob->RestoreMana()`.

- Add `mob:RestoreEndurance()`.
- Add `mob:RestoreHealth()`.
- Add `mob:RestoreMana()`.

- Allows operators to easily restore a mob to full health, mana, or endurance.
- `RestoreHealth` is just a more verbosely named `Heal`.
- Convert spots in source to use these short hands instead of directly using `SetEndurance(GetMaxEndurance())`, `SetHP(GetMaxHP())`, or `SetMana(GetMaxMana())`.

* Update mob.h

* Update mob.h
2024-03-22 23:53:35 -05:00
hg 5bfd8f5da2 [Tradeskills] Implement learning recipes from books (#4170)
This uses the book scribe button to learn recipes in SoF+ clients. For
Titanium clients the button is not available so a workaround will need
to be added later.

Note live gives no feedback when scribing books (at least those tested).
2024-03-22 23:50:06 -05:00
Chris Miles 96830b4a19 [Loot] Fix issue with nested data being loaded multiple times (#4192)
* [Loot] Fix issue with nested data being loaded multiple times

* Update zone_loot.cpp

* Fix #lootsim printout

* Update loot.cpp
2024-03-22 23:49:23 -05:00
Alex King 4bf60a6522 [Bug Fix] Fix ScaleNPC() in Perl (#4196)
# Notes
- We were not passing `override_special_abilities` in the right parameter slot.
- Default `always_scale` to `true` when using the Perl/Lua method.
2024-03-22 23:36:30 -05:00
JJ 16cb7364e8 [Misc] Windows preprocessor define in crash.cpp (#4191) 2024-03-22 23:27:31 -05:00
Alex King f5050ab5dc [Bug Fix] Fix range_percent (#4197)
# Notes
- This was uninitialized and was getting bogus values.
2024-03-22 16:17:38 -04:00
Alex King e32cbf19ee [Bug Fix] Fix #serverrules Command (#4193)
* [Bug Fix] Fix #serverrules Command

# Notes
- This command was effectively disabled as it was not included nor was the command itself defined within a `command_add`

* Update command.cpp
2024-03-20 03:31:49 -04:00
Alex King ee12a7ad2e [Bug Fix] Fix EVENT_KILLED_MERIT firing before NPC removal (#4185)
* [Bug Fix] Fix EVENT_KILLED_MERIT firing before NPC removal

# Notes
- NPCs were parsing this event too early and anything that checked if they were still alive in `EVENT_KILLED_MERIT` would show them still alive because of this.

# Image

* Code cleanup

* Update client.h

* Add GetRaidOrGroupOrSelf() to Perl/Lua

* Update to luabind::object, fix logic per comments.

* Fix

* Fix per comments.
2024-03-17 17:32:44 -04:00
Alex King e5bdbc4f1e [Bug Fix] Fix Bot Cloning (#4186)
# Notes
- We were not setting `inventories_index` to `0` so it was trying to use the pre-existing unique identifier, causing the query to fail.
2024-03-16 23:09:57 -04:00
Alex King f829a99e6d [Quest API] Add RemoveAlternateCurrencyValue() to Perl/Lua (#4190)
# Perl
- Add `$client->RemoveAlternateCurrencyValue(currency_id, amount)`.

# Lua
- Add `client:RemoveAlternateCurrencyValue(currency_id, amount)`.

# Notes
- Allows operators to more easily remove alternate currencies, returns a `bool`, `false` if failed`, `true` if succeeded.
- Added `Zone::DoesAlternateCurrencyExist` that will reject setting, removing, or adding  to a currency that does not exist.
2024-03-16 23:09:51 -04:00
Alex King 82aa6a1587 [Bug Fix] Fix Proximity Say (#4189)
# Notes
- Without setting `HaveProximitySays` to `true` along with if `n->proximity->say` NPCs will not respond to proximity say stuff.
2024-03-14 20:26:30 -04:00
Alex King 161c13f457 [Quest API] Add Buff Support to Perl/Lua (#4182)
* [Quest API] Add Buff Support to Perl/Lua

- Add `$mob->GetCasterID()`.
- Add `$mob->GetCasterLevel()`.
- Add `$mob->GetCasterName()`.
- Add `$mob->GetCastOnX()`.
- Add `$mob->GetCastOnY()`.
- Add `$mob->GetCastOnZ()`.
- Add `$mob->GetCounters()`.
- Add `$mob->GetDOTRune()`.
- Add `$mob->GetExtraDIChance()`.
- Add `$mob->GetInstrumentModi()`.
- Add `$mob->GetMagicRune()`.
- Add `$mob->GetMeleeRune()`.
- Add `$mob->GetNumberOfHits()`.
- Add `$mob->GetRootBreakChanc()`.
- Add `$mob->GetSpellID()`.
- Add `$mob->GetTicsRemaining()`.
- Add `$mob->GetVirusSpreadTim()`.
- Add `$mob->IsCasterClient()`.
- Add `$mob->IsPersistentBuff()`.
- Add `$mob->SendsClientUpdate()`.

- Add `mob:GetCasterID()`.
- Add `mob:GetCasterLevel()`.
- Add `mob:GetCasterName()`.
- Add `mob:GetCastOnX()`.
- Add `mob:GetCastOnY()`.
- Add `mob:GetCastOnZ()`.
- Add `mob:GetCounters()`.
- Add `mob:GetDOTRune()`.
- Add `mob:GetExtraDIChance()`.
- Add `mob:GetInstrumentModi()`.
- Add `mob:GetMagicRune()`.
- Add `mob:GetMeleeRune()`.
- Add `mob:GetNumberOfHits()`.
- Add `mob:GetRootBreakChanc()`.
- Add `mob:GetSpellID()`.
- Add `mob:GetTicsRemaining()`.
- Add `mob:GetVirusSpreadTim()`.
- Add `mob:IsCasterClient()`.
- Add `mob:IsPersistentBuff()`.
- Add `mob:SendsClientUpdate()`.

- Adds support for `Buffs_Struct` to Perl/Lua.
- Allows operators to read a mob's buff data directly to determine caster, melee rune, etc.

* Fix GetCasterID() to proper data type.

* Remove Lua_Buffs, return table instead.

* Cleanup
2024-03-13 23:38:15 -04:00
Alex King b29c26becb [Bug Fix] Fix GetLeaderName() for Groups (#4184)
* [Bug Fix] Fix GetLeaderName() for Groups

# Notes
- We were getting bogus data in this.
- Made it its own method.

* Remove ExpeditionRequest::GetGroupLeaderName()
2024-03-13 21:52:02 -04:00
Alex King e48dae2392 [Quest API] Add Silent Saylink Methods to Perl/Lua (#4177)
# Perl
- Add `quest::silent_saylink(text)`.
- Add `quest::silent_saylink(text, link_name)`.

# Lua
- Add `eq.silent_say_link(text)`.
- Add `eq.silent_say_link(text, link_name)`.

# Notes
- Allows operators to more easily use silent saylinks without an optional silent parameter in the traditional saylink methods.
- Sets `silent` parameter default to `false` so we do not need to pass `false` when we are not using a a silent saylink.
- Changes all places that used `EQ::SayLinkEngine::GenerateQuestSaylink` to `Saylink::Create` where possible.
- Removed `questmgr` method that is no longer necessary.
- Cleaned up Lua methods to use the strings directly instead of building one out.
2024-03-13 20:27:44 -04:00
Alex King 4b83a96f64 [Bug Fix] Fix Empty Groups When Removing Bots (#4178)
# Notes
- We were checking for the incorrect value, meaning you could end up in an empty group.
2024-03-13 20:27:38 -04:00
Alex King 95cc22ffbb [Quest API] Add GetDeityName() to Perl/Lua (#4180)
# Perl
- Add `$mob->GetDeityName()`.

# Lua
- Add `mob:GetDeityName()`.

# Notes
- Allows operators to get a mob's deity name.
2024-03-13 20:27:31 -04:00
nytmyr 6ca11256c6 [Bots] IsValidTarget Crash Fix (#4187)
* [Bots] IsValidTarget crash fix

This addresses crashes related to IsValidTarget on multiple servers.

Unsure of the exact reason if anyone can explain why changing from const bool to bool in this situation fixes the problem.

Is it because the const is somehow crashing on a bad pointer or is it attempting to be force changed?

* Update bot.cpp

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2024-03-12 12:34:27 -04:00
Alex King d94493468c [Bug Fix] Fix Mob::CalculateDistance(mob) Typo (#4183)
# Notes
- Was using `GetX()` for all parts of the calculation, meaning it would produce incorrect values.
- Part of [this](https://github.com/EQEmu/Server/pull/3455/files#diff-94a8e3b04f001e4f74ce2da8404cbce7653146d963a3b93be6536505035ce96dR602-R607).
2024-03-09 22:20:02 -05:00
Alex King 957b4f8821 [Bug Fix] Fix Crash in ClientList::GetCLEIP (#4173)
* [Bug Fix] Fix Crash in ClientList::GetCLEIP

# Notes
- We were not validating pointer when moving to next iterator.

# Crashes
https://spire.akkadius.com/dev/release/22.46.1?id=19955
https://spire.akkadius.com/dev/release/22.46.1?id=19948
https://spire.akkadius.com/dev/release/22.46.0?id=19945

* Update clientlist.cpp

* Update clientlist.cpp
2024-03-09 19:47:58 -05:00
Alex King 5013459824 [Hot Fix] Add bool return to fix Client::RemoveAAPoints (#4176)
# Notes
- Was missing the `return true;` at the bottom.
2024-03-09 10:07:13 -05:00
Alex King 94af2843e3 [Cleanup] Cleanup Zone Get Methods (#4169)
# Notes
- Utilize `GetZoneVersionWithFallback` to shorten methods and reduce duplicate code.
2024-03-08 21:22:11 -05:00
Alex King 3bfb148bdc [Quest API] Add RemoveAAPoints() and AA Loss Event to Perl/Lua (#4174)
* [Quest API] Add RemoveAAPoints() and AA Loss Event to Perl/Lua

# Perl
- Add `$client->RemoveAAPoints(points)`.
- Add `EVENT_AA_LOSS`, exports `$aa_lost`.

# Lua
- Add `client:RemoveAAPoints(points)`.
- Add `event_aa_loss`, exports `e.aa_lost`.

# Notes
- Allows operators to more easily remove AA Points.
- Has a bool return type that will return false if the player does not have enough AA Points to complete the removal.

* Update client.cpp
2024-03-08 21:20:33 -05:00
MortimerGreenwald 96370e0298 [Bug Fix] An Update to Xtarget to exclude Bot owned Temp/Swarm Pets (#4172)
* An Update to Xtarget to exclude Bot owned Temp/Swarm Pets

* Missing a parentheses

* Cleaned up logic.
2024-03-08 15:29:53 -05:00
nytmyr fef5108b0d [Bots] Move BotGroupSay to Pet Response (#4171)
* [Bots] Move BotGroupSay messages to PetResponse filter to reduce spam

* Cleanup raid checks

* Group cleanup
2024-03-08 11:46:50 -05:00
Alex King 6f883566f6 [Bug Fix] Fix Default Value in rule_values table (#4166)
# Notes
- Some versions of SQL do not allow a default value for text fields.
2024-03-07 16:41:41 -05:00
nytmyr 45b1501c8a [Quest API] Add DeleteBot() to Perl/Lua (#4167)
### Perl
- Add $bot->DeleteBot().
```
sub EVENT_SAY {
	if ($text =~/#deletebot/i && $client->Admin() >= 100) {
		my @bot_list = $entity_list->GetBotList();

		foreach $ent (@bot_list) {
			if ($ent) {
				quest::shout("Deleting " . $ent->GetCleanName());
				$ent->DeleteBot();
				$ent->Camp(0);
			}
		}
	}
}
```
### Lua
- Add bot:DeleteBot().
```
function event_say(e)
	if(e.message:findi("#deletebot")) then
		local bot_list = eq.get_entity_list():GetBotList();

		for ent in bot_list.entries do
			if (ent) then
				e.self:Message(7,"Deleting " .. ent:GetCleanName() .. "");
				ent:DeleteBot();
				ent:Camp(false);
			end
		end
	end
end
```
### Notes
- Allows operators to delete bots.
2024-03-07 06:08:17 -05:00
Akkadius 7e94f0ac72 [Release] 22.47.0 2024-03-05 22:18:16 -06:00
Chris Miles 2aa19f4cae [Release] 22.47.0 (#4164) 2024-03-05 22:04:35 -06:00
Mitch Freeman 805829f15d [Crash Fix] Added a guild_mgr check (#4163)
* CrashFix and Cleanup

* Formatting, add safe_delete

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-03-05 22:00:08 -06:00
Alex King 2c2a8cdb63 [Bug Fix] Fix Character EXP Modifiers default (#4161)
# Notes
- The `-1.0f` was causing these modifiers to be set to `0.0` and when people would enable them they would get no experience.
- We now use the zone based grabbed methods when setting which will default to a value of `1.0f` if there is not a value found.
2024-03-05 21:54:07 -06:00
Chris Miles ee3d02bac6 [Zoning] Zone routing adjustment (#4162) 2024-03-05 21:53:07 -06:00
Alex King b90139bd9a [Feature] Adjust String-based Rules Length (#4138) 2024-03-05 21:52:34 -06:00
catapultam-habeo e6a3d5e1c5 [Bug Fix] Prevent NPE when creating DZ using ad-hoc version IDs (#4141)
* initial commit

* corrected based on hgtw feedback
2024-03-05 18:21:04 -05:00
Alex King 74f1eac401 [Bug Fix] Fix Spawns Not Parsing Quest on Zone Bootup (#4149)
* Update zone.cpp

* Fix

---------

Co-authored-by: Natedog2012 <jwalters_06@yahoo.com>
2024-03-05 18:20:42 -05:00
JJ 1be9b2cdfd [Bug Fix] Fix typo when updating spawn events in spawn condition manager (#4160) 2024-03-05 05:44:38 -05:00
Mitch Freeman add0a8dddf [Bug Fix] Add id to the guild_bank table (#4155)
* Add id to guild_bank table

Add id as a primary key to guild_bank

* Remove content schema update flag
2024-03-04 19:02:35 -05:00
catapultam-habeo 8c226054e7 [Feature] Adds rules to control level requirements for Double Backstab, Assassinate, and Double Bowshot (#29) (#4159)
Co-authored-by: mute <natanx@gmail.com>
2024-03-04 18:41:25 -05:00
Mitch Freeman b4605f77e3 [Crash Fix] Goto Command could crash using Developer Tools (#4158) 2024-03-03 22:33:12 -05:00
Mitch Freeman 74a63daf7e [Crash Fix] Groundspawn Memory Corruption (#4157) 2024-03-03 22:32:29 -05:00
Alex King 8ee7910569 [Quest API] Add IsAlwaysAggro() to Perl/Lua (#4152)
- Add `$mob->IsAlwaysAggro()`.

- Add `mob:IsAlwaysAggro()`.

- Allows operators to determine if a mob is set to always aggro.
2024-03-03 20:40:20 -05:00
Alex King b766a79c11 [Quest API] Add GetHeroicStrikethrough() to Perl/Lua (#4150)
- Add `$mob->GetHeroicStrikethrough()`.

- Add `mob:GetHeroicStrikethrough()`.

- Allows operators to get a mob's Heroic Strikethrough.
2024-03-03 13:05:01 -05:00
Alex King 5e3b6d363a [Quest API] Add IsBoat()/IsControllableBoat() to Perl/Lua (#4151)
- Add `$mob->IsBoat()`.
- Add `$mob->IsControllableBoat()`.

- Add `mob:IsBoat()`.
- Add `mob:IsControllableBoat()`.

- Allows operators to determine if a mob is a boat or a controllable boat.
2024-03-03 12:34:21 -05:00
Alex King b2fc59878a [Quest API] Add IsDestructibleObject() to Perl/Lua (#4153)
# Perl
- Add `$mob-.IsDestructibleObject()`.

# Lua
- Add `mob:IsDestructibleObject()`.

# Notes
- Allows operators to determine if a mob is a destructible object.
2024-03-03 11:43:54 -05:00
JJ 4896688ac5 [Release] 22.46.1 (#4148)
### Fixes

* Change `UnburyCorpse` to use repository methods ([#4147](https://github.com/EQEmu/Server/pull/4147)) @joligario 2024-03-03
2024-03-02 19:56:29 -05:00
JJ 0385ed8526 [Bug Fix] Change UnburyCorpse to use repository methods (#4147) 2024-03-02 19:23:43 -05:00
Chris Miles 9974aaff57 [Release] 22.46.0 (#4146) 2024-03-02 15:21:32 -06:00
Chris Miles b6c3e549da [Zone] Zone Routing Improvements (#4142)
* Routing changes

* Update world_content_service.cpp

* Cleanup routing logic

* Tweaks
2024-03-02 15:19:42 -06:00
Alex King 70ee95efc0 [Quest API] Add Bot Special Attacks for Immune Aggro/Damage (#4108)
* [Quest API] Add Bot Special Attacks for Immune Aggro/Damage

# Notes
- Adds `IMMUNE_AGGRO_BOT` and `IMMUNE_DAMAGE_BOT` for uses in special abilities.

* Cleanup

* Update attack.cpp
2024-03-02 15:19:31 -06:00
Alex King 1d38e473d7 [Bug Fix] GetBotNameByID Temporary Reference Warning (#4145)
# Notes
- We were getting a warning for returning `std::string()` from this method as it's a temporary reference.
- Change from `const std::string&` to `const std::string` to avoid this.
```
/home/eqemu/source/zone/bot_database.cpp: In member function ‘const std::string& BotDatabase::GetBotNameByID(uint32)’:
/home/eqemu/source/zone/bot_database.cpp:2374:25: warning: returning reference to temporary [-Wreturn-local-addr]
 2374 |         return e.bot_id ? e.name : std::string();
```
2024-03-02 15:18:57 -06:00
Alex King 1aa3a4b11a [Bug Fix] Fix Bots/Bot Pets ending up on XTargets (#4132)
* [XTargets]

* Update eqemu_logsys.h

* Update client.cpp

* Update table column

* Undo unnecessary commit
2024-03-02 15:18:37 -06:00
Mitch Freeman 398ecbc8cf [Bug Fix] Update FreeGuildID Routine (#4143)
Updates the routine to determine a free guild id on guild creation
2024-03-02 15:17:28 -06:00
Mitch Freeman c4613e1b0f [Crash Fix] Update to location of qGlobals initialization (#4144) 2024-03-02 15:16:51 -06:00
catapultam-habeo 3003a59955 [Feature] Exempt a zone from IP-limit checks. (#4137)
* Exempt zone from IP checks

* [Feature] Add Support for String-based Rules

# Notes
- Add support for string-based rules.

# Images

* convert to comma-seperated list

* Forgot to convert the zone to a string

* Update lua_general.cpp

* fixed rule name

* use the local string methods instead

* I think this will work as desired without the extra condition

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-03-01 23:11:34 -05:00
Paul Coene 0c582cc4f9 [Bug Fix] Cleanup NPC Mana Tap Logic (#4134)
* [Bug Fix] Cleanup NPC Mana Tap Logic

Mana Tap rule logic was invalid - Cleaned up and simplified, either we care about npc mana or we dont.

* Updated for bypass all rule

* Change so melee targets get blocked even with requiremana rule off

---------

Co-authored-by: Trust <fryguy503@gmail.com>
2024-03-01 22:48:50 -05:00
Mitch Freeman 69c42510ca [Crash Fix] Raid::UpdateGroupAAs (#4139)
Added checks to potentially resolve a crash situation with raids and group AAs.
2024-03-01 22:46:16 -05:00
JJ 79c8858ec8 [Bug Fix] Use std::clamp for Mob::ChangeSize (#4140)
Helper template was not deducing float for lower/upper values allowing invalid sizes
Limit to sane values of 1-255 unrestricted and 3-15 for clients and pets
2024-03-01 22:46:00 -05:00
Alex King c3d8d423fe [Commands] Add #fish Command (#4136)
* [Commands] Add #fish Command

# Notes
- Adds `#fish` command.
- Allows operators to simulate fishing to see what they would get, consumes no bait.

* `use_bait`

* Update fish.cpp
2024-02-29 21:24:16 -05:00
Alex King 1cbda61891 [Bug Fix] Fix issue with NPC Secondary Textures (#4129)
* [Bug Fix] Fix issue with NPC heads

# Notes
- We were overwriting head material within this secondary loop which caused NPC's heads to show their body texture in some places or no texture if their `showhelm` was not flagged.

# Images

* Update mob.cpp

* Update mob.cpp

* Update mob.cpp
2024-02-29 20:45:54 -05:00
JJ 8d12a5b1b1 [Release] 22.45.1 (#4135)
### Character Creation

* Improved Random Name Generator ([#4081](https://github.com/EQEmu/Server/pull/4081)) @catapultam-habeo 2024-02-27

### Code

* Fix Server Rules Documentation Generation ([#4125](https://github.com/EQEmu/Server/pull/4125)) @Kinglykrab 2024-02-26
* Remove unnecessary stoptimer logs ([#4128](https://github.com/EQEmu/Server/pull/4128)) @Kinglykrab 2024-02-28

### Commands

* Add `#forage` command ([#4133](https://github.com/EQEmu/Server/pull/4133)) @joligario 2024-02-29

### Crash

* Fix crash issue during database dump ([#4127](https://github.com/EQEmu/Server/pull/4127)) @Akkadius 2024-02-29

### Crash Fix

* D20 crash if mitigation average resulted in 0 ([#4131](https://github.com/EQEmu/Server/pull/4131)) @nytmyr 2024-02-29

### Fixes

* Fix forage returning first result from table ([#4130](https://github.com/EQEmu/Server/pull/4130)) @nytmyr 2024-02-29
* Who /all displays incorrect guild name ([#4123](https://github.com/EQEmu/Server/pull/4123)) @neckkola 2024-02-25

### Quest API

* Add Pet Owner Methods to Perl/Lua ([#4115](https://github.com/EQEmu/Server/pull/4115)) @Kinglykrab 2024-02-25
2024-02-29 18:22:40 -05:00
JJ 182327b385 [Commands] Add #forage command (#4133) 2024-02-28 20:43:14 -05:00
Chris Miles aa0ca88d9d [Crash] Fix crash issue during database dump (#4127) 2024-02-28 20:36:06 -05:00
nytmyr 103a37e762 [Crash Fix] D20 crash if mitigation average resulted in 0 (#4131) 2024-02-28 20:35:56 -05:00
nytmyr 34f19489d0 [Hotfix] Fix forage returning first result from table (#4130)
Forage was not properly incrementing the total chance of items when more than one was found and would result in the final chance roll being based off the last item found rather than the total. This would cause the first item returned to be chosen in most cases when the chances are the same in the table.
2024-02-28 20:34:40 -05:00
Alex King c001060429 [Cleanup] Remove unnecessary stoptimer logs (#4128)
# Notes
- These logs were for testing and are unnecessary.
- They cause a lot of spam for servers using `Info` logs.
2024-02-27 21:35:51 -05:00
catapultam-habeo 89be55254e [Character Creation] Improved Random Name Generator (#4081)
* test against vanilla branch

* use existing methods to validate name instead of raw sql

* Revert "use existing methods to validate name instead of raw sql"

This reverts commit 43750c6f4f.

* ReserveName doesn't work like that. Oops. Well, check against Name Filter at least.

* That db access condition was wrong.

* that isn't how CheckNameFilter works, either.

* apply editorconfig w/ trivial change

* Actually apply editorconfig changes.
2024-02-26 23:15:40 -06:00
Alex King 2da6190950 [Cleanup] Fix Server Rules Documentation Generation (#4125)
# Notes
- These spaces prevented the docs server rules generator from parsing these rules.
2024-02-26 18:36:12 -05:00
Mitch Freeman d5e024cc02 [Bug Fix] Who /all displays incorrect guild name (#4123)
Issue: guild creation routine does not update world memory holding guild details until the player zones.

Between the creation of a new guild, and a guild member zones, a /who all displays 'Invalid Guild' instead of the actual guild name.
2024-02-25 18:14:28 -05:00
Alex King 35fe38cd09 [Quest API] Add Pet Owner Methods to Perl/Lua (#4115)
* [Quest API] Add Pet Owner Methods to Perl/Lua

- Add `$mob->IsPetOwnerBot()`.
- Add `$mob->IsPetOwnerClient()`.
- Add `$mob->IsPetOwnerNPC()`.

- Add `mob:IsPetOwnerBot()`.
- Add `mob:IsPetOwnerClient()`.
- Add `mob:IsPetOwnerNPC()`.

- Allows operators to use these short hands instead of doing a `GetOwner() && GetOwner()->IsClient()`.

* Update npc.cpp
2024-02-25 00:38:34 -05:00
170 changed files with 7008 additions and 4437 deletions
+169
View File
@@ -1,3 +1,172 @@
## [22.48.0] 3/23/2024
### Bots
* IsValidTarget Crash Fix ([#4187](https://github.com/EQEmu/Server/pull/4187)) @nytmyr 2024-03-12
* Move BotGroupSay to Pet Response ([#4171](https://github.com/EQEmu/Server/pull/4171)) @nytmyr 2024-03-08
### Code
* Cleanup Zone Get Methods ([#4169](https://github.com/EQEmu/Server/pull/4169)) @Kinglykrab 2024-03-09
### Fixes
* An Update to Xtarget to exclude Bot owned Temp/Swarm Pets ([#4172](https://github.com/EQEmu/Server/pull/4172)) @MortimerGreenwald 2024-03-08
* Fix #serverrules Command ([#4193](https://github.com/EQEmu/Server/pull/4193)) @Kinglykrab 2024-03-20
* Fix Bot Cloning ([#4186](https://github.com/EQEmu/Server/pull/4186)) @Kinglykrab 2024-03-17
* Fix Crash in ClientList::GetCLEIP ([#4173](https://github.com/EQEmu/Server/pull/4173)) @Kinglykrab 2024-03-10
* Fix Default Value in `rule_values` table ([#4166](https://github.com/EQEmu/Server/pull/4166)) @Kinglykrab 2024-03-07
* Fix EVENT_KILLED_MERIT firing before NPC removal ([#4185](https://github.com/EQEmu/Server/pull/4185)) @Kinglykrab 2024-03-17
* Fix Empty Groups When Removing Bots ([#4178](https://github.com/EQEmu/Server/pull/4178)) @Kinglykrab 2024-03-14
* Fix GetLeaderName() for Groups ([#4184](https://github.com/EQEmu/Server/pull/4184)) @Kinglykrab 2024-03-14
* Fix Mob::CalculateDistance(mob) Typo ([#4183](https://github.com/EQEmu/Server/pull/4183)) @Kinglykrab 2024-03-10
* Fix Proximity Say ([#4189](https://github.com/EQEmu/Server/pull/4189)) @Kinglykrab 2024-03-15
* Fix ScaleNPC() in Perl ([#4196](https://github.com/EQEmu/Server/pull/4196)) @Kinglykrab 2024-03-23
* Fix range_percent ([#4197](https://github.com/EQEmu/Server/pull/4197)) @Kinglykrab 2024-03-22
* Fix reusing timers ([#4199](https://github.com/EQEmu/Server/pull/4199)) @joligario 2024-03-23
### Hot Fix
* Add bool return to fix Client::RemoveAAPoints ([#4176](https://github.com/EQEmu/Server/pull/4176)) @Kinglykrab 2024-03-09
### Loot
* Fix issue with nested data being loaded multiple times ([#4192](https://github.com/EQEmu/Server/pull/4192)) @Akkadius 2024-03-23
### Misc
* Windows preprocessor define in crash.cpp ([#4191](https://github.com/EQEmu/Server/pull/4191)) @joligario 2024-03-23
### Quest API
* Add Buff Support to Perl/Lua ([#4182](https://github.com/EQEmu/Server/pull/4182)) @Kinglykrab 2024-03-14
* Add DeleteBot() to Perl/Lua ([#4167](https://github.com/EQEmu/Server/pull/4167)) @nytmyr 2024-03-07
* Add GetDeityName() to Perl/Lua ([#4180](https://github.com/EQEmu/Server/pull/4180)) @Kinglykrab 2024-03-14
* Add RemoveAAPoints() and AA Loss Event to Perl/Lua ([#4174](https://github.com/EQEmu/Server/pull/4174)) @Kinglykrab 2024-03-09
* Add RemoveAlternateCurrencyValue() to Perl/Lua ([#4190](https://github.com/EQEmu/Server/pull/4190)) @Kinglykrab 2024-03-17
* Add Restore Methods for Health, Mana, and Endurance to Perl/Lua ([#4179](https://github.com/EQEmu/Server/pull/4179)) @Kinglykrab 2024-03-23
* Add Silent Saylink Methods to Perl/Lua ([#4177](https://github.com/EQEmu/Server/pull/4177)) @Kinglykrab 2024-03-14
### Rules
* Add World:Rules Rule ([#4194](https://github.com/EQEmu/Server/pull/4194)) @Kinglykrab 2024-03-23
### Tradeskills
* Implement learning recipes from books ([#4170](https://github.com/EQEmu/Server/pull/4170)) @hgtw 2024-03-23
## [22.47.0] 3/5/2024
### Crash Fix
* Added a guild_mgr check ([#4163](https://github.com/EQEmu/Server/pull/4163)) @neckkola 2024-03-06
* Goto Command could crash using Developer Tools ([#4158](https://github.com/EQEmu/Server/pull/4158)) @neckkola 2024-03-04
* Groundspawn Memory Corruption ([#4157](https://github.com/EQEmu/Server/pull/4157)) @neckkola 2024-03-04
* Update to location of qGlobals initialization ([#4144](https://github.com/EQEmu/Server/pull/4144)) @neckkola 2024-03-02
### Feature
* Adds rules to control level requirements for Double Backstab, Assassinate, and Double Bowshot (#4159) ([#29](https://github.com/EQEmu/Server/pull/29)) @catapultam-habeo 2024-03-04
* Adjust String-based Rules Length ([#4138](https://github.com/EQEmu/Server/pull/4138)) @Kinglykrab 2024-03-06
* Exempt a zone from IP-limit checks. ([#4137](https://github.com/EQEmu/Server/pull/4137)) @catapultam-habeo 2024-03-02
### Fixes
* Add id to the guild_bank table ([#4155](https://github.com/EQEmu/Server/pull/4155)) @neckkola 2024-03-05
* Fix Bots/Bot Pets ending up on XTargets ([#4132](https://github.com/EQEmu/Server/pull/4132)) @Kinglykrab 2024-03-02
* Fix Character EXP Modifiers default ([#4161](https://github.com/EQEmu/Server/pull/4161)) @Kinglykrab 2024-03-06
* Fix Spawns Not Parsing Quest on Zone Bootup ([#4149](https://github.com/EQEmu/Server/pull/4149)) @Kinglykrab 2024-03-05
* Fix typo when updating spawn events in spawn condition manager ([#4160](https://github.com/EQEmu/Server/pull/4160)) @joligario 2024-03-05
* GetBotNameByID Temporary Reference Warning ([#4145](https://github.com/EQEmu/Server/pull/4145)) @Kinglykrab 2024-03-02
* Prevent NPE when creating DZ using ad-hoc version IDs ([#4141](https://github.com/EQEmu/Server/pull/4141)) @catapultam-habeo 2024-03-05
* Update FreeGuildID Routine ([#4143](https://github.com/EQEmu/Server/pull/4143)) @neckkola 2024-03-02
### Quest API
* Add Bot Special Attacks for Immune Aggro/Damage ([#4108](https://github.com/EQEmu/Server/pull/4108)) @Kinglykrab 2024-03-02
* Add GetHeroicStrikethrough() to Perl/Lua ([#4150](https://github.com/EQEmu/Server/pull/4150)) @Kinglykrab 2024-03-03
* Add IsAlwaysAggro() to Perl/Lua ([#4152](https://github.com/EQEmu/Server/pull/4152)) @Kinglykrab 2024-03-04
* Add IsBoat()/IsControllableBoat() to Perl/Lua ([#4151](https://github.com/EQEmu/Server/pull/4151)) @Kinglykrab 2024-03-03
* Add IsDestructibleObject() to Perl/Lua ([#4153](https://github.com/EQEmu/Server/pull/4153)) @Kinglykrab 2024-03-03
### Zone
* Zone Routing Improvements ([#4142](https://github.com/EQEmu/Server/pull/4142)) @Akkadius 2024-03-02
### Zoning
* Zone routing adjustment ([#4162](https://github.com/EQEmu/Server/pull/4162)) @Akkadius 2024-03-06
## [22.46.1] 3/2/2024
### Fixes
* Change `UnburyCorpse` to use repository methods ([#4147](https://github.com/EQEmu/Server/pull/4147)) @joligario 2024-03-03
## [22.46.0] 3/2/2024
### Commands
* Add #fish Command ([#4136](https://github.com/EQEmu/Server/pull/4136)) @Kinglykrab 2024-03-01
### Crash Fix
* Raid::UpdateGroupAAs ([#4139](https://github.com/EQEmu/Server/pull/4139)) @neckkola 2024-03-02
* Update to location of qGlobals initialization ([#4144](https://github.com/EQEmu/Server/pull/4144)) @neckkola 2024-03-02
### Feature
* Exempt a zone from IP-limit checks. ([#4137](https://github.com/EQEmu/Server/pull/4137)) @catapultam-habeo 2024-03-02
### Fixes
* Cleanup NPC Mana Tap Logic ([#4134](https://github.com/EQEmu/Server/pull/4134)) @noudess 2024-03-02
* Fix Bots/Bot Pets ending up on XTargets ([#4132](https://github.com/EQEmu/Server/pull/4132)) @Kinglykrab 2024-03-02
* Fix issue with NPC Secondary Textures ([#4129](https://github.com/EQEmu/Server/pull/4129)) @Kinglykrab 2024-03-01
* GetBotNameByID Temporary Reference Warning ([#4145](https://github.com/EQEmu/Server/pull/4145)) @Kinglykrab 2024-03-02
* Update FreeGuildID Routine ([#4143](https://github.com/EQEmu/Server/pull/4143)) @neckkola 2024-03-02
* Use std::clamp for Mob::ChangeSize ([#4140](https://github.com/EQEmu/Server/pull/4140)) @joligario 2024-03-02
### Quest API
* Add Bot Special Attacks for Immune Aggro/Damage ([#4108](https://github.com/EQEmu/Server/pull/4108)) @Kinglykrab 2024-03-02
### Zone
* Zone Routing Improvements ([#4142](https://github.com/EQEmu/Server/pull/4142)) @Akkadius 2024-03-02
## [22.45.1] 2/29/2024
### Character Creation
* Improved Random Name Generator ([#4081](https://github.com/EQEmu/Server/pull/4081)) @catapultam-habeo 2024-02-27
### Code
* Fix Server Rules Documentation Generation ([#4125](https://github.com/EQEmu/Server/pull/4125)) @Kinglykrab 2024-02-26
* Remove unnecessary stoptimer logs ([#4128](https://github.com/EQEmu/Server/pull/4128)) @Kinglykrab 2024-02-28
### Commands
* Add `#forage` command ([#4133](https://github.com/EQEmu/Server/pull/4133)) @joligario 2024-02-29
### Crash
* Fix crash issue during database dump ([#4127](https://github.com/EQEmu/Server/pull/4127)) @Akkadius 2024-02-29
### Crash Fix
* D20 crash if mitigation average resulted in 0 ([#4131](https://github.com/EQEmu/Server/pull/4131)) @nytmyr 2024-02-29
### Fixes
* Fix forage returning first result from table ([#4130](https://github.com/EQEmu/Server/pull/4130)) @nytmyr 2024-02-29
* Who /all displays incorrect guild name ([#4123](https://github.com/EQEmu/Server/pull/4123)) @neckkola 2024-02-25
### Quest API
* Add Pet Owner Methods to Perl/Lua ([#4115](https://github.com/EQEmu/Server/pull/4115)) @Kinglykrab 2024-02-25
## [22.45.0] 2/24/2024
### Beacon
+42 -45
View File
@@ -29,6 +29,8 @@
#include "../../common/content/world_content_service.h"
#include "../../common/zone_store.h"
#include "../../common/path_manager.h"
#include "../../common/repositories/skill_caps_repository.h"
#include "../../common/file.h"
EQEmuLogSys LogSys;
WorldContentService content_service;
@@ -164,81 +166,76 @@ void ExportSpells(SharedDatabase *db)
fclose(f);
}
bool SkillUsable(SharedDatabase *db, int skill_id, int class_id)
bool SkillUsable(SharedDatabase* db, int skill_id, int class_id)
{
bool res = false;
std::string query = StringFormat(
"SELECT max(cap) FROM skill_caps WHERE class=%d AND skillID=%d",
class_id, skill_id
const auto& l = SkillCapsRepository::GetWhere(
*db,
fmt::format(
"`class_id` = {} AND `skill_id` = {} ORDER BY `cap` DESC LIMIT 1",
class_id,
skill_id
)
);
auto results = db->QueryDatabase(query);
if (!results.Success()) {
return false;
}
if (results.RowCount() == 0) {
return false;
}
auto row = results.begin();
if (row[0] && Strings::ToInt(row[0]) > 0) {
return true;
}
return false;
return !l.empty();
}
int GetSkill(SharedDatabase *db, int skill_id, int class_id, int level)
uint32 GetSkill(SharedDatabase* db, int skill_id, int class_id, int level)
{
std::string query = StringFormat(
"SELECT cap FROM skill_caps WHERE class=%d AND skillID=%d AND level=%d",
class_id, skill_id, level
const auto& l = SkillCapsRepository::GetWhere(
*db,
fmt::format(
"`class_id` = {} AND `skill_id` = {} AND `level` = {}",
class_id,
skill_id,
level
)
);
auto results = db->QueryDatabase(query);
if (!results.Success()) {
if (l.empty()) {
return 0;
}
if (results.RowCount() == 0) {
return 0;
}
auto e = l.front();
auto row = results.begin();
return Strings::ToInt(row[0]);
return e.cap;
}
void ExportSkillCaps(SharedDatabase *db)
void ExportSkillCaps(SharedDatabase* db)
{
LogInfo("Exporting Skill Caps");
std::string file = fmt::format("{}/export/SkillCaps.txt", path.GetServerPath());
FILE *f = fopen(file.c_str(), "w");
if (!f) {
std::ofstream file(fmt::format("{}/export/SkillCaps.txt", path.GetServerPath()));
if (!file || !file.is_open()) {
LogError("Unable to open export/SkillCaps.txt to write, skipping.");
return;
}
for (int cl = 1; cl <= 16; ++cl) {
for (int skill = 0; skill <= 77; ++skill) {
if (SkillUsable(db, skill, cl)) {
int previous_cap = 0;
for (int level = 1; level <= 100; ++level) {
int cap = GetSkill(db, skill, cl, level);
const uint8 skill_cap_max_level = (
RuleI(Character, SkillCapMaxLevel) > 0 ?
RuleI(Character, SkillCapMaxLevel) :
RuleI(Character, MaxLevel)
);
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) {
for (uint8 skill_id = EQ::skills::Skill1HBlunt; skill_id <= EQ::skills::Skill2HPiercing; skill_id++) {
if (SkillUsable(db, skill_id, class_id)) {
uint32 previous_cap = 0;
for (uint8 level = 1; level <= skill_cap_max_level; level++) {
uint32 cap = GetSkill(db, skill_id, class_id, level);
if (cap < previous_cap) {
cap = previous_cap;
}
fprintf(f, "%d^%d^%d^%d^0\n", cl, skill, level, cap);
file << fmt::format("{}^{}^{}^{}^0", class_id, skill_id, level, cap) << std::endl;
previous_cap = cap;
}
}
}
}
fclose(f);
file.close();
}
void ExportBaseData(SharedDatabase *db)
+11 -9
View File
@@ -83,6 +83,7 @@ SET(common_sources
shared_tasks.cpp
shareddb.cpp
skills.cpp
skill_caps.cpp
spdat.cpp
strings.cpp
struct_strategy.cpp
@@ -492,7 +493,7 @@ SET(repositories
repositories/zone_repository.h
repositories/zone_points_repository.h
)
)
SET(common_headers
additive_lagged_fibonacci_engine.h
@@ -592,7 +593,7 @@ SET(common_headers
ptimer.h
queue.h
races.h
raid.h
raid.h
random.h
rdtsc.h
rulesys.h
@@ -606,6 +607,7 @@ SET(common_headers
shared_tasks.h
shareddb.h
skills.h
skill_caps.h
spdat.h
strings.h
struct_strategy.h
@@ -681,13 +683,13 @@ SOURCE_GROUP(Event FILES
event/event_loop.h
event/timer.h
event/task.h
)
)
SOURCE_GROUP(Json FILES
json/json.h
json/jsoncpp.cpp
json/json-forwards.h
)
)
SOURCE_GROUP(Net FILES
net/console_server.cpp
@@ -724,7 +726,7 @@ SOURCE_GROUP(Net FILES
net/websocket_server.h
net/websocket_server_connection.cpp
net/websocket_server_connection.h
)
)
SOURCE_GROUP(Patches FILES
patches/patches.h
@@ -768,12 +770,12 @@ SOURCE_GROUP(Patches FILES
patches/titanium_limits.cpp
patches/uf.cpp
patches/uf_limits.cpp
)
)
SOURCE_GROUP(StackWalker FILES
StackWalker/StackWalker.h
StackWalker/StackWalker.cpp
)
)
SOURCE_GROUP(Util FILES
util/memory_stream.h
@@ -781,7 +783,7 @@ SOURCE_GROUP(Util FILES
util/directory.h
util/uuid.cpp
util/uuid.h
)
)
INCLUDE_DIRECTORIES(Patches SocketLib StackWalker)
@@ -794,6 +796,6 @@ ENDIF (UNIX)
IF (WIN32 AND EQEMU_BUILD_PCH)
TARGET_PRECOMPILE_HEADERS(common PRIVATE pch/pch.h)
ENDIF()
ENDIF ()
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
+83 -42
View File
@@ -178,12 +178,13 @@ void WorldContentService::ReloadContentFlags()
LogInfo(
"Loaded content flag [{}] [{}]",
f.flag_name,
(f.enabled ? "Enabled" : "Disabled")
(f.enabled ? "enabled" : "disabled")
);
}
SetContentFlags(set_content_flags);
SetContentZones(ZoneRepository::All(*m_content_database));
LoadZones();
LoadStaticGlobalZoneInstances();
}
Database *WorldContentService::GetDatabase() const
@@ -235,19 +236,6 @@ void WorldContentService::SetContentFlag(const std::string &content_flag_name, b
ReloadContentFlags();
}
// SetZones sets the zones for the world content service
// this is used for zone routing middleware
// we pull the zone list from the zone repository and feed from the zone store for now
// we're holding a copy in the content service - but we're talking 250kb of data in memory to handle routing of zoning
WorldContentService *WorldContentService::SetContentZones(const std::vector<BaseZoneRepository::Zone>& zones)
{
m_zones = zones;
LogInfo("Loaded [{}] zones", m_zones.size());
return this;
}
// HandleZoneRoutingMiddleware is meant to handle content and context aware zone routing
//
// example # 1
@@ -260,16 +248,57 @@ WorldContentService *WorldContentService::SetContentZones(const std::vector<Base
// scripts handle all the same way, you don't have to think about instances, the middleware will handle the magic
// the versions of zones are represented by two zone entries that have potentially different min/max expansion and/or different content flags
// we decide to route the client to the correct version of the zone based on the current server side expansion
// example # 2
void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
{
// if we're already in an instance, we don't want to route the player to another instance
if (zc->instanceID > 0) {
auto r = FindZone(zc->zoneID, zc->instanceID);
if (r.zone_id == 0) {
return;
}
zc->instanceID = r.instance.id;
}
// LoadStaticGlobalZoneInstances loads all static global zone instances
// these are zones that are never set to expire and are global
// these are used commonly in v1/v2/v3 versions of the same zone for expansion routing
WorldContentService * WorldContentService::LoadStaticGlobalZoneInstances()
{
m_zone_instances = InstanceListRepository::GetWhere(*GetDatabase(), fmt::format("never_expires = 1 AND is_global = 1"));
LogInfo("Loaded [{}] zone_instances", m_zone_instances.size());
return this;
}
// LoadZones sets the zones for the world content service
// this is used for zone routing middleware
// we pull the zone list from the zone repository and feed from the zone store for now
// we're holding a copy in the content service - but we're talking 250kb of data in memory to handle routing of zoning
WorldContentService * WorldContentService::LoadZones()
{
m_zones = ZoneRepository::All(*GetContentDatabase());
LogInfo("Loaded [{}] zones", m_zones.size());
return this;
}
// FindZone is critical to the zone routing middleware and any logic that needs to route players to the correct zone
// era contextual routing, multiple version of zones, etc
WorldContentService::FindZoneResult WorldContentService::FindZone(uint32 zone_id, uint32 instance_id)
{
// if there's an active dynamic instance, we don't need to route
if (instance_id > 0) {
auto inst = InstanceListRepository::FindOne(*GetDatabase(), instance_id);
if (inst.id != 0 && !inst.is_global && !inst.never_expires) {
return WorldContentService::FindZoneResult{
.zone_id = 0,
};
}
}
for (auto &z: m_zones) {
if (z.zoneidnumber == zc->zoneID) {
if (z.zoneidnumber == zone_id) {
auto f = ContentFlags{
.min_expansion = z.min_expansion,
.max_expansion = z.max_expansion,
@@ -286,33 +315,45 @@ void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
z.long_name
);
auto instances = InstanceListRepository::GetWhere(
*GetDatabase(),
fmt::format(
"zone = {} AND version = {} AND never_expires = 1 AND is_global = 1",
z.zoneidnumber,
z.version
)
// first pass, explicit match on public static global zone instances
for (auto &i: m_zone_instances) {
if (i.zone == zone_id && i.version == z.version) {
LogInfo(
"Routed player to instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
i.id,
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
i.notes
);
return WorldContentService::FindZoneResult{
.zone_id = static_cast<uint32>(z.zoneidnumber),
.instance = i,
.zone = z
};
}
}
LogInfo(
"Routed player to non-instance zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
z.note
);
if (!instances.empty()) {
auto instance = instances.front();
zc->instanceID = instance.id;
LogInfo(
"Routed player to instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
instance.id,
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
instance.notes
);
break;
}
return WorldContentService::FindZoneResult{
.zone_id = static_cast<uint32>(z.zoneidnumber),
.instance = InstanceListRepository::NewEntity(),
.zone = z
};
}
}
}
return WorldContentService::FindZoneResult{.zone_id = 0};
}
+12 -1
View File
@@ -5,6 +5,7 @@
#include <vector>
#include "../repositories/content_flags_repository.h"
#include "../repositories/zone_repository.h"
#include "../repositories/instance_list_repository.h"
class Database;
@@ -169,7 +170,14 @@ public:
void SetContentFlag(const std::string &content_flag_name, bool enabled);
void HandleZoneRoutingMiddleware(ZoneChange_Struct *zc);
WorldContentService * SetContentZones(const std::vector<ZoneRepository::Zone>& zones);
struct FindZoneResult {
uint32 zone_id = 0;
InstanceListRepository::InstanceList instance;
ZoneRepository::Zone zone;
};
FindZoneResult FindZone(uint32 zone_id, uint32 instance_id);
private:
int current_expansion{};
std::vector<ContentFlagsRepository::ContentFlags> content_flags;
@@ -180,6 +188,9 @@ private:
// holds a record of the zone table from the database
std::vector<ZoneRepository::Zone> m_zones = {};
WorldContentService *LoadStaticGlobalZoneInstances();
std::vector<InstanceListRepository::InstanceList> m_zone_instances;
WorldContentService * LoadZones();
};
extern WorldContentService content_service;
+1 -1
View File
@@ -15,7 +15,7 @@
#include <cstdio>
#include <vector>
#if WINDOWS
#ifdef _WINDOWS
#define popen _popen
#endif
+1011 -1285
View File
File diff suppressed because it is too large Load Diff
+145 -139
View File
@@ -18,8 +18,8 @@
#ifndef EQEMU_DATABASE_H
#define EQEMU_DATABASE_H
#define AUTHENTICATION_TIMEOUT 60
#define INVALID_ID 0xFFFFFFFF
#define AUTHENTICATION_TIMEOUT 60
#define INVALID_ID 0xFFFFFFFF
#include "global_define.h"
#include "eqemu_logsys.h"
@@ -38,8 +38,7 @@
class MySQLRequestResult;
class Client;
namespace EQ
{
namespace EQ {
class InventoryProfile;
}
@@ -52,10 +51,11 @@ struct npcDecayTimes_Struct {
struct VarCache_Struct {
std::map<std::string, std::string> m_cache;
uint32 last_update;
uint32 last_update;
VarCache_Struct() : last_update(0) { }
void Add(const std::string &key, const std::string &value) { m_cache[key] = value; }
const std::string *Get(const std::string &key) {
void Add(const std::string& key, const std::string& value) { m_cache[key] = value; }
const std::string* Get(const std::string& key)
{
auto it = m_cache.find(key);
return (it != m_cache.end() ? &it->second : nullptr);
}
@@ -76,37 +76,33 @@ class PTimerList;
#define SQL(...) #__VA_ARGS__
class LogSettings;
class Database : public DBcore {
public:
Database();
Database(const char* host, const char* user, const char* passwd, const char* database,uint32 port);
bool Connect(const char* host, const char* user, const char* passwd, const char* database, uint32 port, std::string connection_label = "default");
Database(
const std::string& host,
const std::string& user,
const std::string& password,
const std::string& database,
uint32 port
);
bool Connect(
const std::string& host,
const std::string& user,
const std::string& password,
const std::string& database,
uint32 port,
std::string connection_label = "default"
);
~Database();
/* Character Creation */
bool CreateCharacter(
uint32 account_id,
char *name,
uint16 gender,
uint16 race,
uint16 class_,
uint8 str,
uint8 sta,
uint8 cha,
uint8 dex,
uint8 int_,
uint8 agi,
uint8 wis,
uint8 face
);
bool DeleteCharacter(char *character_name);
bool MoveCharacterToZone(const char *charname, uint32 zone_id);
bool DeleteCharacter(const std::string& name);
bool MoveCharacterToZone(const std::string& name, uint32 zone_id);
bool MoveCharacterToZone(uint32 character_id, uint32 zone_id);
bool ReserveName(uint32 account_id, char *name);
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct *pp);
bool UpdateName(const char *oldname, const char *newname);
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 CopyCharacter(
const std::string& source_character_name,
const std::string& destination_character_name,
@@ -114,161 +110,171 @@ public:
);
/* General Information Queries */
bool AddBannedIP(const std::string& banned_ip, const std::string& notes); //Add IP address to the banned_ips table.
bool AddToNameFilter(const std::string& name);
bool CheckBannedIPs(const std::string& login_ip); //Check incoming connection against banned IP table.
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 AddBannedIP(std::string banned_ip, std::string notes); //Add IP address to the banned_ips table.
bool AddToNameFilter(std::string name);
bool CheckBannedIPs(std::string login_ip); //Check incoming connection against banned IP table.
bool CheckGMIPs(std::string login_ip, uint32 account_id);
bool CheckNameFilter(std::string name, bool surname = false);
bool CheckUsedName(std::string name);
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
uint32 GetAccountIDByChar(uint32 character_id);
uint32 GetAccountIDByName(const std::string& account_name, const std::string& loginserver, int16* status = 0, uint32* lsid = 0);
uint32 GetCharacterID(const std::string& name);
uint32 GetGuildIDByCharID(uint32 character_id);
uint32 GetGroupIDByCharID(uint32 character_id);
uint32 GetRaidIDByCharID(uint32 character_id);
uint32 GetAccountIDByChar(const char* charname, uint32* oCharID = 0);
uint32 GetAccountIDByChar(uint32 char_id);
uint32 GetAccountIDByName(std::string account_name, std::string loginserver, int16* status = 0, uint32* lsid = 0);
uint32 GetCharacterID(const std::string& name);
uint32 GetCharacterInfo(std::string character_name, uint32 *account_id, uint32 *zone_id, uint32 *instance_id);
uint32 GetGuildIDByCharID(uint32 char_id);
uint32 GetGroupIDByCharID(uint32 char_id);
uint32 GetRaidIDByCharID(uint32 char_id);
void GetAccountName(uint32 accountid, char* name, uint32* oLSAccountID = 0);
void GetCharName(uint32 char_id, char* name);
std::string GetCharNameByID(uint32 char_id);
std::string GetNPCNameByID(uint32 npc_id);
std::string GetCleanNPCNameByID(uint32 npc_id);
void LoginIP(uint32 account_id, std::string login_ip);
const std::string GetAccountName(uint32 account_id, uint32* lsaccount_id = 0);
const std::string GetCharName(uint32 character_id);
const std::string GetCharNameByID(uint32 character_id);
const std::string GetNPCNameByID(uint32 npc_id);
const std::string GetCleanNPCNameByID(uint32 npc_id);
void LoginIP(uint32 account_id, const std::string& login_ip);
/* Instancing */
bool AddClientToInstance(uint16 instance_id, uint32 character_id);
bool CheckInstanceByCharID(uint16 instance_id, uint32 character_id);
bool CheckInstanceExists(uint16 instance_id);
bool CheckInstanceExpired(uint16 instance_id);
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
bool GetUnusedInstanceID(uint16 &instance_id);
bool GetUnusedInstanceID(uint16& instance_id);
bool IsGlobalInstance(uint16 instance_id);
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
bool RemoveClientsFromInstance(uint16 instance_id);
bool VerifyInstanceAlive(uint16 instance_id, uint32 character_id);
bool VerifyZoneInstance(uint32 zone_id, uint16 instance_id);
uint16 GetInstanceID(uint32 zone, uint32 charid, int16 version);
uint16 GetInstanceID(uint32 zone, uint32 character_id, int16 version);
std::vector<uint16> GetInstanceIDs(uint32 zone_id, uint32 character_id);
uint8_t GetInstanceVersion(uint16 instance_id);
uint32 GetTimeRemainingInstance(uint16 instance_id, bool &is_perma);
uint32 GetTimeRemainingInstance(uint16 instance_id, bool& is_perma);
uint32 GetInstanceZoneID(uint16 instance_id);
void AssignGroupToInstance(uint32 gid, uint32 instance_id);
void AssignRaidToInstance(uint32 rid, uint32 instance_id);
void AssignGroupToInstance(uint32 group_id, uint32 instance_id);
void AssignRaidToInstance(uint32 raid_id, uint32 instance_id);
void DeleteInstance(uint16 instance_id);
void FlagInstanceByGroupLeader(uint32 zone_id, int16 version, uint32 charid, uint32 group_id);
void FlagInstanceByRaidLeader(uint32 zone_id, int16 version, uint32 charid, uint32 raid_id);
void GetCharactersInInstance(uint16 instance_id, std::list<uint32> &character_ids);
void FlagInstanceByGroupLeader(uint32 zone_id, int16 version, uint32 character_id, uint32 group_id);
void FlagInstanceByRaidLeader(uint32 zone_id, int16 version, uint32 character_id, uint32 raid_id);
void GetCharactersInInstance(uint16 instance_id, std::list<uint32>& character_ids);
void PurgeExpiredInstances();
void SetInstanceDuration(uint16 instance_id, uint32 new_duration);
void CleanupInstanceCorpses();
/* Adventure related. */
void UpdateAdventureStatsEntry(uint32 char_id, uint8 theme, bool win = false, bool remove = false);
bool GetAdventureStats(uint32 char_id, AdventureStats_Struct *as);
void UpdateAdventureStatsEntry(uint32 character_id, uint8 theme_id, bool is_win = false, bool is_remove = false);
bool GetAdventureStats(uint32 character_id, AdventureStats_Struct* as);
/* Account Related */
const std::string GetLiveChar(uint32 account_id);
bool SetAccountStatus(const std::string& account_name, int16 status);
bool SetLocalPassword(uint32 account_id, const std::string& password);
bool UpdateLiveChar(const std::string& name, uint32 account_id);
int16 CheckStatus(uint32 account_id);
void SetAccountCRCField(uint32 account_id, const std::string& field_name, uint64 checksum);
uint32 CheckLogin(const std::string& name, const std::string& password, const std::string& loginserver, int16* status = 0);
uint32 CreateAccount(
const std::string& name,
const std::string& password,
int16 status,
const std::string& loginserver,
uint32 lsaccount_id
);
uint32 GetAccountIDFromLSID(
const std::string& in_loginserver_id,
uint32 in_loginserver_account_id,
char* in_account_name = 0,
int16* in_status = 0
);
bool DeleteAccount(const char *name, const char* loginserver);
bool GetLiveChar(uint32 account_id, char* cname);
bool SetAccountStatus(const char* name, int16 status);
bool SetAccountStatus(const std::string& account_name, int16 status);
bool SetLocalPassword(uint32 accid, const char* password);
bool UpdateLiveChar(char* charname, uint32 account_id);
uint8 GetAgreementFlag(uint32 account_id);
void SetAgreementFlag(uint32 account_id);
int16 CheckStatus(uint32 account_id);
int GetIPExemption(const std::string& account_ip);
void SetIPExemption(const std::string& account_ip, int exemption_amount);
void SetAccountCRCField(uint32 account_id, std::string field_name, uint64 checksum);
uint32 CheckLogin(const char* name, const char* password, const char *loginserver, int16* oStatus = 0);
uint32 CreateAccount(const char* name, const char* password, int16 status, const char* loginserver, uint32 lsaccount_id);
uint32 GetAccountIDFromLSID(const std::string& in_loginserver_id, uint32 in_loginserver_account_id, char* in_account_name = 0, int16* in_status = 0);
uint8 GetAgreementFlag(uint32 account_id);
void GetAccountFromID(uint32 id, char* oAccountName, int16* oStatus);
void SetAgreementFlag(uint32 account_id);
int GetIPExemption(std::string account_ip);
void SetIPExemption(std::string account_ip, int exemption_amount);
int GetInstanceID(uint32 char_id, uint32 zone_id);
int GetInstanceID(uint32 character_id, uint32 zone_id);
/* Groups */
std::string GetGroupLeaderForLogin(std::string character_name);
char* GetGroupLeadershipInfo(uint32 gid, char* leaderbuf, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, char *mentoree = nullptr, int *mentor_percent = nullptr, GroupLeadershipAA_Struct* GLAA = nullptr);
uint32 GetGroupID(const char* name);
void ClearGroup(uint32 gid = 0);
void ClearGroupLeader(uint32 gid = 0);
void SetGroupID(const char* name, uint32 id, uint32 charid, uint32 ismerc = false);
void SetGroupLeaderName(uint32 gid, const char* name);
std::string GetGroupLeaderForLogin(const std::string& character_name);
char* GetGroupLeadershipInfo(
uint32 group_id,
char* leaderbuf,
char* maintank = nullptr,
char* assist = nullptr,
char* puller = nullptr,
char* marknpc = nullptr,
char* mentoree = nullptr,
int* mentor_percent = nullptr,
GroupLeadershipAA_Struct* GLAA = nullptr
);
std::string GetGroupLeaderName(uint32 group_id);
uint32 GetGroupID(const std::string& name);
void ClearGroup(uint32 group_id = 0);
void ClearGroupLeader(uint32 group_id = 0);
void SetGroupLeaderName(uint32 group_id, const std::string& name);
/* Raids */
const std::string GetRaidLeaderName(uint32 raid_id);
uint32 GetRaidID(const std::string& name);
void ClearRaid(uint32 raid_id = 0);
void ClearRaidDetails(uint32 raid_id = 0);
void ClearRaidLeader(uint32 group_id = std::numeric_limits<uint32>::max(), uint32 raid_id = 0);
void GetGroupLeadershipInfo(
uint32 group_id,
uint32 raid_id,
char* maintank = nullptr,
char* assist = nullptr,
char* puller = nullptr,
char* marknpc = nullptr,
char* mentoree = nullptr,
int* mentor_percent = nullptr,
GroupLeadershipAA_Struct* GLAA = nullptr
);
void GetRaidLeadershipInfo(
uint32 raid_id,
char* maintank = nullptr,
char* assist = nullptr,
char* puller = nullptr,
char* marknpc = nullptr,
RaidLeadershipAA_Struct* RLAA = nullptr
);
void SetRaidGroupLeaderInfo(uint32 group_id, uint32 raid_id);
const char *GetRaidLeaderName(uint32 rid);
uint32 GetRaidID(const char* name);
void ClearRaid(uint32 rid = 0);
void ClearRaidDetails(uint32 rid = 0);
void ClearRaidLeader(uint32 gid = 0xFFFFFFFF, uint32 rid = 0);
void GetGroupLeadershipInfo(uint32 gid, uint32 rid, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, char *mentoree = nullptr, int *mentor_percent = nullptr, GroupLeadershipAA_Struct* GLAA = nullptr);
void GetRaidLeadershipInfo(uint32 rid, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, RaidLeadershipAA_Struct* RLAA = nullptr);
void SetRaidGroupLeaderInfo(uint32 gid, uint32 rid);
void PurgeAllDeletedDataBuckets();
void PurgeAllDeletedDataBuckets();
/* Database Variables */
bool GetVariable(const std::string& name, std::string& value);
bool SetVariable(const std::string& name, const std::string& value);
bool LoadVariables();
bool GetVariable(std::string varname, std::string &varvalue);
bool SetVariable(const std::string& varname, const std::string &varvalue);
bool LoadVariables();
uint8 GetPEQZone(uint32 zone_id, uint32 version);
uint32 GetServerType();
void AddReport(const std::string& who, const std::string& against, const std::string& lines);
struct TimeOfDay_Struct LoadTime(time_t& realtime);
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
void ClearMerchantTemp();
void ClearPTimers(uint32 character_id);
void SetFirstLogon(uint32 character_id, uint8 first_logon);
void SetLFG(uint32 character_id, bool is_lfg);
void SetLFP(uint32 character_id, bool is_lfp);
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
/* General Queries */
int64 CountInvSnapshots();
void ClearInvSnapshots(bool from_now = false);
bool GetZoneGraveyard(const uint32 graveyard_id, uint32* graveyard_zoneid = 0, float* graveyard_x = 0, float* graveyard_y = 0, float* graveyard_z = 0, float* graveyard_heading = 0);
bool LoadPTimers(uint32 charid, PTimerList &into);
uint8 GetPEQZone(uint32 zone_id, uint32 version);
uint8 GetMinStatus(uint32 zone_id, uint32 instance_version);
uint8 GetRaceSkill(uint8 skillid, uint8 in_race);
uint8 GetServerType();
uint8 GetSkillCap(uint8 skillid, uint8 in_race, uint8 in_class, uint16 in_level);
void AddReport(std::string who, std::string against, std::string lines);
struct TimeOfDay_Struct LoadTime(time_t &realtime);
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
void ClearMerchantTemp();
void ClearPTimers(uint32 charid);
void SetFirstLogon(uint32 CharID, uint8 firstlogon);
void SetLFG(uint32 CharID, bool LFG);
void SetLFP(uint32 CharID, bool LFP);
void SetLoginFlags(uint32 CharID, bool LFP, bool LFG, uint8 firstlogon);
int CountInvSnapshots();
void ClearInvSnapshots(bool from_now = false);
void SourceDatabaseTableFromUrl(std::string table_name, std::string url);
void SourceSqlFromUrl(std::string url);
void SourceDatabaseTableFromUrl(const std::string& table_name, const std::string& url);
void SourceSqlFromUrl(const std::string& url);
private:
Mutex Mvarcache;
Mutex Mvarcache;
VarCache_Struct varcache;
/* Groups, utility methods. */
void ClearAllGroupLeaders();
void ClearAllGroups();
void ClearAllGroupLeaders();
void ClearAllGroups();
/* Raid, utility methods. */
void ClearAllRaids();
+6 -1
View File
@@ -575,7 +575,12 @@ void DatabaseDumpService::RemoveSqlBackup()
{
std::string file = fmt::format("{}.sql", GetDumpFileNameWithPath());
if (File::Exists(file)) {
std::filesystem::remove(file);
try {
std::filesystem::remove(file);
}
catch (std::exception &e) {
LogError("std::filesystem::remove err [{}]", e.what());
}
}
RemoveCredentialsFile();
@@ -5410,6 +5410,65 @@ ADD COLUMN `augment_five` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_fou
ADD COLUMN `augment_six` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_five`;
)",
.content_schema_update = true
},
ManifestEntry{
.version = 9265,
.description = "2024_03_03_add_id_to_guild_bank.sql",
.check = "SHOW COLUMNS FROM `guild_bank` LIKE 'id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `guild_bank`
ADD COLUMN `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD PRIMARY KEY (`id`);
)",
},
ManifestEntry{
.version = 9266,
.description = "2024_03_02_rule_values_rule_value_length.sql",
.check = "SHOW COLUMNS FROM `rule_values` LIKE 'rule_value'",
.condition = "contains",
.match = "varchar(30)",
.sql = R"(
ALTER TABLE `rule_values`
MODIFY COLUMN `rule_value` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `rule_name`;
)"
},
ManifestEntry{
.version = 9267,
.description = "2024_02_18_group_id_bot_id.sql",
.check = "SHOW COLUMNS FROM `group_id` LIKE 'bot_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `group_id`
CHANGE COLUMN `groupid` `group_id` int(11) UNSIGNED NOT NULL DEFAULT 0 FIRST,
CHANGE COLUMN `charid` `character_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `group_id`,
CHANGE COLUMN `ismerc` `merc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `name`,
ADD COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
MODIFY COLUMN `name` varchar(64) NOT NULL DEFAULT '' AFTER `character_id`,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`group_id`, `character_id`, `bot_id`, `merc_id`) USING BTREE;
ALTER TABLE `group_id`
MODIFY COLUMN `character_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `name`;
)"
},
ManifestEntry{
.version = 9268,
.description = "2024_03_23_skill_caps.sql",
.check = "SHOW COLUMNS FROM `skill_caps` LIKE 'skill_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `skill_caps`
CHANGE COLUMN `skillID` `skill_id` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 FIRST,
CHANGE COLUMN `class` `class_id` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `skill_id`,
ADD COLUMN `id` int(3) UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`) USING BTREE,
ADD INDEX `level_skill_cap`(`skill_id`, `class_id`, `level`, `cap`);
)",
.content_schema_update = true,
}
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
+10 -5
View File
@@ -421,20 +421,25 @@ void Database::AssignGroupToInstance(uint32 group_id, uint32 instance_id)
auto zone_id = GetInstanceZoneID(instance_id);
auto version = GetInstanceVersion(instance_id);
auto l = GroupIdRepository::GetWhere(
const auto& l = GroupIdRepository::GetWhere(
*this,
fmt::format(
"groupid = {}",
"`group_id` = {}",
group_id
)
);
if (l.empty()) {
return;
}
for (const auto& e : l) {
if (!GetInstanceID(zone_id, e.charid, version)) {
AddClientToInstance(instance_id, e.charid);
if (!e.character_id) {
continue;
}
if (!GetInstanceID(zone_id, e.character_id, version)) {
AddClientToInstance(instance_id, e.character_id);
}
}
}
@@ -504,7 +509,7 @@ void Database::FlagInstanceByRaidLeader(uint32 zone_id, int16 version, uint32 ch
return;
}
auto raid_leader_id = GetCharacterID(GetRaidLeaderName(raid_id));
auto raid_leader_id = GetCharacterID(GetRaidLeaderName(raid_id).c_str());
auto raid_leader_instance_id = GetInstanceID(zone_id, raid_leader_id, version);
if (!raid_leader_instance_id) {
+2
View File
@@ -698,6 +698,8 @@ const std::map<uint32, std::string>& EQ::constants::GetSpecialAbilityMap()
{ IMMUNE_OPEN, "Immune to Open" },
{ IMMUNE_ASSASSINATE, "Immune to Assassinate" },
{ IMMUNE_HEADSHOT, "Immune to Headshot" },
{ IMMUNE_AGGRO_BOT, "Immune to Bot Aggro" },
{ IMMUNE_DAMAGE_BOT, "Immune to Bot Damage" },
};
return special_ability_map;
+5 -1
View File
@@ -89,6 +89,8 @@ namespace EQ
using RoF2::invslot::SLOT_INVALID;
using RoF2::invslot::SLOT_BEGIN;
using RoF2::invslot::SLOT_QUEST;
using Titanium::invslot::SLOT_TRADESKILL_EXPERIMENT_COMBINE;
const int16 SLOT_AUGMENT_GENERIC_RETURN = 1001; // clients don't appear to use this method... (internal inventory return value)
@@ -656,7 +658,9 @@ enum {
IMMUNE_OPEN = 53,
IMMUNE_ASSASSINATE = 54,
IMMUNE_HEADSHOT = 55,
MAX_SPECIAL_ATTACK = 56
IMMUNE_AGGRO_BOT = 56,
IMMUNE_DAMAGE_BOT = 57,
MAX_SPECIAL_ATTACK = 58
};
+5
View File
@@ -130,6 +130,11 @@ enum CrystalReclaimTypes
Radiant = 4,
};
namespace ItemStackSizeConstraint {
constexpr int16 Minimum = 1;
constexpr int16 Maximum = 1000;
}
///////////////////////////////////////////////////////////////////////////////
+2
View File
@@ -141,6 +141,7 @@ namespace Logs {
Zoning,
EqTime,
Corpses,
XTargets,
MaxCategoryID /* Don't Remove this */
};
@@ -241,6 +242,7 @@ namespace Logs {
"Zoning",
"EqTime",
"Corpses",
"XTargets"
};
}
+10
View File
@@ -834,6 +834,16 @@
OutF(LogSys, Logs::Detail, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogXTargets(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::XTargets))\
OutF(LogSys, Logs::General, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogXTargetsDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::XTargets))\
OutF(LogSys, Logs::Detail, Logs::XTargets, __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__);\
+5
View File
@@ -82,6 +82,10 @@ Zone extensions and features
#define QUEST_GLOBAL_DIRECTORY "global"
#endif
// Number of quest items a Quest NPC can hold
#define MAX_NPC_QUEST_INVENTORY 24
//the min ratio at which a mob's speed is reduced
#define FLEE_HP_MINSPEED 22
//number of tics to try to run straight away before looking again
@@ -111,6 +115,7 @@ Zone extensions and features
#define SKILL_MAX_LEVEL 75
/*
Zone Numerical configuration
+7 -34
View File
@@ -342,41 +342,14 @@ bool BaseGuildManager::_StoreGuildDB(uint32 guild_id)
return true;
}
uint32 BaseGuildManager::_GetFreeGuildID()
{
GuildsRepository::DeleteWhere(*m_db, "`name` = ''");
GuildsRepository::Guilds out;
out.id = 0;
out.leader = 0;
out.minstatus = 0;
out.tribute = 0;
out.name = "";
out.motd = "";
out.motd_setter = "";
out.url = "";
out.channel = "";
auto last_insert_id = GuildsRepository::InsertOne(*m_db, out);
if (last_insert_id.id > 0) {
LogGuilds("Located a free guild ID [{}] in the database", last_insert_id.id);
return last_insert_id.id;
}
LogGuilds("Unable to find a free guild ID in the database");
return GUILD_NONE;
}
uint32 BaseGuildManager::CreateGuild(std::string name, uint32 leader_char_id)
{
uint32 guild_id = UpdateDbCreateGuild(name, leader_char_id);
if (guild_id == GUILD_NONE) {
return (GUILD_NONE);
}
//RefreshGuild(guild_id);
//SendGuildRefresh(guild_id, true, false, false, false);
//SendCharRefresh(GUILD_NONE, guild_id, leader_char_id);
uint32 guild_id = UpdateDbCreateGuild(name, leader_char_id);
if (guild_id == GUILD_NONE) {
return (GUILD_NONE);
}
return guild_id;
return guild_id;
}
bool BaseGuildManager::DeleteGuild(uint32 guild_id)
@@ -539,8 +512,8 @@ bool BaseGuildManager::SetPublicNote(uint32 charid, std::string public_note)
uint32 BaseGuildManager::UpdateDbCreateGuild(std::string name, uint32 leader)
{
auto new_id = _GetFreeGuildID();
if (new_id == GUILD_NONE) {
auto new_id = GuildsRepository::GetMaxId(*m_db) + 1;
if (!new_id) {
return GUILD_NONE;
}
-1
View File
@@ -208,7 +208,6 @@ class BaseGuildManager
bool _StoreGuildDB(uint32 guild_id);
GuildInfo* _CreateGuild(uint32 guild_id, std::string guild_name, uint32 leader_char_id, uint8 minstatus, std::string guild_motd, std::string motd_setter, std::string Channel, std::string URL, uint32 favour);
uint32 _GetFreeGuildID();
GuildsRepository::Guilds CreateGuildRepoFromGuildInfo(uint32 guild_id, BaseGuildManager::GuildInfo& in);
};
#endif /*GUILD_BASE_H_*/
+2
View File
@@ -25,6 +25,8 @@ struct LootItem {
uint16 trivial_max_level;
uint16 npc_min_level;
uint16 npc_max_level;
uint8 quest;
uint8 pet;
};
typedef std::list<LootItem*> LootItems;
+2
View File
@@ -165,6 +165,8 @@ namespace RoF2
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 SLOT_QUEST = 9999;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
+2
View File
@@ -162,6 +162,8 @@ namespace RoF
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 SLOT_QUEST = 9999;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
+2
View File
@@ -152,6 +152,8 @@ namespace SoD
const int16 SLOT_TRADESKILL_EXPERIMENT_COMBINE = 1000;
const int16 SLOT_QUEST = 9999;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
+2
View File
@@ -152,6 +152,8 @@ namespace SoF
const int16 SLOT_TRADESKILL_EXPERIMENT_COMBINE = 1000;
const int16 SLOT_QUEST = 9999;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
+2
View File
@@ -151,6 +151,8 @@ namespace Titanium
const int16 SLOT_TRADESKILL_EXPERIMENT_COMBINE = 1000;
const int16 SLOT_QUEST = 9999;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
+2
View File
@@ -152,6 +152,8 @@ namespace UF
const int16 SLOT_TRADESKILL_EXPERIMENT_COMBINE = 1000;
const int16 SLOT_QUEST = 9999;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
+1 -1
View File
@@ -1379,7 +1379,7 @@ uint32 GetPlayerRaceValue(uint16 race_id) {
}
}
uint32 GetPlayerRaceBit(uint16 race_id) {
uint16 GetPlayerRaceBit(uint16 race_id) {
switch (race_id) {
case HUMAN:
return PLAYER_RACE_HUMAN_BIT;
+1 -1
View File
@@ -124,7 +124,7 @@ bool IsPlayerRace(uint16 race_id);
const std::string GetPlayerRaceAbbreviation(uint16 race_id);
uint32 GetPlayerRaceValue(uint16 race_id);
uint32 GetPlayerRaceBit(uint16 race_id);
uint16 GetPlayerRaceBit(uint16 race_id);
uint16 GetRaceIDFromPlayerRaceValue(uint32 player_race_value);
uint16 GetRaceIDFromPlayerRaceBit(uint32 player_race_bit);
+44
View File
@@ -44,7 +44,51 @@ public:
*/
// Custom extended repository methods here
static int16 GetAccountStatus(Database& db, const uint32 account_id)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT `status`, TIMESTAMPDIFF(SECOND, NOW(), `suspendeduntil`) FROM `{}` WHERE `{}` = {}",
TableName(),
PrimaryKey(),
account_id
)
);
if (!results.Success() || !results.RowCount()) {
return 0;
}
auto row = results.begin();
int16 status = static_cast<int16>(Strings::ToInt(row[0]));
int date_diff = 0;
if (row[1]) {
date_diff = Strings::ToInt(row[1]);
}
if (date_diff > 0) {
status = -1;
}
return status;
}
static bool UpdatePassword(Database& db, const uint32 account_id, const std::string& password)
{
auto results = db.QueryDatabase(
fmt::format(
"UPDATE `{}` SET `password` = MD5('{}') WHERE `{}` = {}",
TableName(),
password,
PrimaryKey(),
account_id
)
);
return results.Success();
}
};
#endif //EQEMU_ACCOUNT_REPOSITORY_H
@@ -44,7 +44,65 @@ public:
*/
// Custom extended repository methods here
static void UpdateAdventureStatsEntry(Database& db, uint32 character_id, uint8 theme_id, bool is_win, bool is_remove)
{
std::string field;
switch (theme_id) {
case LDoNThemes::GUK: {
field = "guk_";
break;
}
case LDoNThemes::MIR: {
field = "mir_";
break;
}
case LDoNThemes::MMC: {
field = "mmc_";
break;
}
case LDoNThemes::RUJ: {
field = "ruj_";
break;
}
case LDoNThemes::TAK: {
field = "tak_";
break;
}
}
field += is_win ? "wins" : "losses";
auto e = FindOne(db, character_id);
if (!e.player_id && !is_remove) {
const std::string& query = fmt::format(
"INSERT INTO `{}` SET `{}` = 1, `{}` = {}",
TableName(),
field,
PrimaryKey(),
character_id
);
db.QueryDatabase(query);
return;
}
const std::string& field_operation = is_remove ? "-" : "+";
const std::string& query = fmt::format(
"UPDATE `{}` SET `{}` = {} {} 1 WHERE `{}` = {}",
TableName(),
field,
field,
field_operation,
PrimaryKey(),
character_id
);
db.QueryDatabase(query);
}
};
#endif //EQEMU_ADVENTURE_STATS_REPOSITORY_H
@@ -22,7 +22,7 @@ public:
int32_t accid;
std::string ip;
int32_t count;
std::string lastused;
time_t lastused;
};
static std::string PrimaryKey()
@@ -46,7 +46,7 @@ public:
"accid",
"ip",
"count",
"lastused",
"UNIX_TIMESTAMP(lastused)",
};
}
@@ -130,7 +130,7 @@ public:
e.accid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.ip = row[1] ? row[1] : "";
e.count = row[2] ? static_cast<int32_t>(atoi(row[2])) : 1;
e.lastused = row[3] ? row[3] : std::time(nullptr);
e.lastused = strtoll(row[3] ? row[3] : "-1", nullptr, 10);
return e;
}
@@ -167,7 +167,7 @@ public:
v.push_back(columns[0] + " = " + std::to_string(e.accid));
v.push_back(columns[1] + " = '" + Strings::Escape(e.ip) + "'");
v.push_back(columns[2] + " = " + std::to_string(e.count));
v.push_back(columns[3] + " = '" + Strings::Escape(e.lastused) + "'");
v.push_back(columns[3] + " = FROM_UNIXTIME(" + (e.lastused > 0 ? std::to_string(e.lastused) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -192,7 +192,7 @@ public:
v.push_back(std::to_string(e.accid));
v.push_back("'" + Strings::Escape(e.ip) + "'");
v.push_back(std::to_string(e.count));
v.push_back("'" + Strings::Escape(e.lastused) + "'");
v.push_back("FROM_UNIXTIME(" + (e.lastused > 0 ? std::to_string(e.lastused) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -225,7 +225,7 @@ public:
v.push_back(std::to_string(e.accid));
v.push_back("'" + Strings::Escape(e.ip) + "'");
v.push_back(std::to_string(e.count));
v.push_back("'" + Strings::Escape(e.lastused) + "'");
v.push_back("FROM_UNIXTIME(" + (e.lastused > 0 ? std::to_string(e.lastused) : "null") + ")");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -262,7 +262,7 @@ public:
e.accid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.ip = row[1] ? row[1] : "";
e.count = row[2] ? static_cast<int32_t>(atoi(row[2])) : 1;
e.lastused = row[3] ? row[3] : std::time(nullptr);
e.lastused = strtoll(row[3] ? row[3] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@@ -290,7 +290,7 @@ public:
e.accid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.ip = row[1] ? row[1] : "";
e.count = row[2] ? static_cast<int32_t>(atoi(row[2])) : 1;
e.lastused = row[3] ? row[3] : std::time(nullptr);
e.lastused = strtoll(row[3] ? row[3] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@@ -368,7 +368,7 @@ public:
v.push_back(std::to_string(e.accid));
v.push_back("'" + Strings::Escape(e.ip) + "'");
v.push_back(std::to_string(e.count));
v.push_back("'" + Strings::Escape(e.lastused) + "'");
v.push_back("FROM_UNIXTIME(" + (e.lastused > 0 ? std::to_string(e.lastused) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -394,7 +394,7 @@ public:
v.push_back(std::to_string(e.accid));
v.push_back("'" + Strings::Escape(e.ip) + "'");
v.push_back(std::to_string(e.count));
v.push_back("'" + Strings::Escape(e.lastused) + "'");
v.push_back("FROM_UNIXTIME(" + (e.lastused > 0 ? std::to_string(e.lastused) : "null") + ")");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -19,34 +19,37 @@
class BaseGroupIdRepository {
public:
struct GroupId {
int32_t groupid;
int32_t charid;
uint32_t group_id;
std::string name;
int8_t ismerc;
uint32_t character_id;
uint32_t bot_id;
uint32_t merc_id;
};
static std::string PrimaryKey()
{
return std::string("groupid");
return std::string("group_id");
}
static std::vector<std::string> Columns()
{
return {
"groupid",
"charid",
"group_id",
"name",
"ismerc",
"character_id",
"bot_id",
"merc_id",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"groupid",
"charid",
"group_id",
"name",
"ismerc",
"character_id",
"bot_id",
"merc_id",
};
}
@@ -87,10 +90,11 @@ public:
{
GroupId e{};
e.groupid = 0;
e.charid = 0;
e.name = "";
e.ismerc = 0;
e.group_id = 0;
e.name = "";
e.character_id = 0;
e.bot_id = 0;
e.merc_id = 0;
return e;
}
@@ -101,7 +105,7 @@ public:
)
{
for (auto &group_id : group_ids) {
if (group_id.groupid == group_id_id) {
if (group_id.group_id == group_id_id) {
return group_id;
}
}
@@ -127,10 +131,11 @@ public:
if (results.RowCount() == 1) {
GroupId e{};
e.groupid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.charid = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.name = row[2] ? row[2] : "";
e.ismerc = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.group_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.name = row[1] ? row[1] : "";
e.character_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.bot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.merc_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
return e;
}
@@ -164,10 +169,11 @@ public:
auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.groupid));
v.push_back(columns[1] + " = " + std::to_string(e.charid));
v.push_back(columns[2] + " = '" + Strings::Escape(e.name) + "'");
v.push_back(columns[3] + " = " + std::to_string(e.ismerc));
v.push_back(columns[0] + " = " + std::to_string(e.group_id));
v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'");
v.push_back(columns[2] + " = " + std::to_string(e.character_id));
v.push_back(columns[3] + " = " + std::to_string(e.bot_id));
v.push_back(columns[4] + " = " + std::to_string(e.merc_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -175,7 +181,7 @@ public:
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.groupid
e.group_id
)
);
@@ -189,10 +195,11 @@ public:
{
std::vector<std::string> v;
v.push_back(std::to_string(e.groupid));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.group_id));
v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -203,7 +210,7 @@ public:
);
if (results.Success()) {
e.groupid = results.LastInsertedID();
e.group_id = results.LastInsertedID();
return e;
}
@@ -222,10 +229,11 @@ public:
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.groupid));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.group_id));
v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -259,10 +267,11 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
GroupId e{};
e.groupid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.charid = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.name = row[2] ? row[2] : "";
e.ismerc = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.group_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.name = row[1] ? row[1] : "";
e.character_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.bot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.merc_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@@ -287,10 +296,11 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
GroupId e{};
e.groupid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.charid = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.name = row[2] ? row[2] : "";
e.ismerc = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.group_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.name = row[1] ? row[1] : "";
e.character_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.bot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.merc_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@@ -365,10 +375,11 @@ public:
{
std::vector<std::string> v;
v.push_back(std::to_string(e.groupid));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.group_id));
v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -391,10 +402,11 @@ public:
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.groupid));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.group_id));
v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back(std::to_string(e.ismerc));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.merc_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -105,7 +105,7 @@ public:
e.gid = 0;
e.leadername = "";
e.marknpc = "";
e.leadershipaa = 0;
e.leadershipaa = "";
e.maintank = "";
e.assist = "";
e.puller = "";
@@ -150,7 +150,7 @@ public:
e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.leadername = row[1] ? row[1] : "";
e.marknpc = row[2] ? row[2] : "";
e.leadershipaa = row[3] ? row[3] : 0;
e.leadershipaa = row[3] ? row[3] : "";
e.maintank = row[4] ? row[4] : "";
e.assist = row[5] ? row[5] : "";
e.puller = row[6] ? row[6] : "";
@@ -302,7 +302,7 @@ public:
e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.leadername = row[1] ? row[1] : "";
e.marknpc = row[2] ? row[2] : "";
e.leadershipaa = row[3] ? row[3] : 0;
e.leadershipaa = row[3] ? row[3] : "";
e.maintank = row[4] ? row[4] : "";
e.assist = row[5] ? row[5] : "";
e.puller = row[6] ? row[6] : "";
@@ -335,7 +335,7 @@ public:
e.gid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.leadername = row[1] ? row[1] : "";
e.marknpc = row[2] ? row[2] : "";
e.leadershipaa = row[3] ? row[3] : 0;
e.leadershipaa = row[3] ? row[3] : "";
e.maintank = row[4] ? row[4] : "";
e.assist = row[5] ? row[5] : "";
e.puller = row[6] ? row[6] : "";
@@ -19,8 +19,9 @@
class BaseSkillCapsRepository {
public:
struct SkillCaps {
uint8_t skillID;
uint8_t class_;
uint32_t id;
uint8_t skill_id;
uint8_t class_id;
uint8_t level;
uint32_t cap;
uint8_t class_;
@@ -28,14 +29,15 @@ public:
static std::string PrimaryKey()
{
return std::string("skillID");
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"skillID",
"`class`",
"id",
"skill_id",
"class_id",
"level",
"cap",
"class_",
@@ -45,8 +47,9 @@ public:
static std::vector<std::string> SelectColumns()
{
return {
"skillID",
"`class`",
"id",
"skill_id",
"class_id",
"level",
"cap",
"class_",
@@ -90,11 +93,12 @@ public:
{
SkillCaps e{};
e.skillID = 0;
e.class_ = 0;
e.level = 0;
e.cap = 0;
e.class_ = 0;
e.id = 0;
e.skill_id = 0;
e.class_id = 0;
e.level = 0;
e.cap = 0;
e.class_ = 0;
return e;
}
@@ -105,7 +109,7 @@ public:
)
{
for (auto &skill_caps : skill_capss) {
if (skill_caps.skillID == skill_caps_id) {
if (skill_caps.id == skill_caps_id) {
return skill_caps;
}
}
@@ -131,11 +135,12 @@ public:
if (results.RowCount() == 1) {
SkillCaps e{};
e.skillID = row[0] ? static_cast<uint8_t>(strtoul(row[0], nullptr, 10)) : 0;
e.class_ = row[1] ? static_cast<uint8_t>(strtoul(row[1], nullptr, 10)) : 0;
e.level = row[2] ? static_cast<uint8_t>(strtoul(row[2], nullptr, 10)) : 0;
e.cap = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.class_ = row[4] ? static_cast<uint8_t>(strtoul(row[4], nullptr, 10)) : 0;
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.skill_id = row[1] ? static_cast<uint8_t>(strtoul(row[1], nullptr, 10)) : 0;
e.class_id = row[2] ? static_cast<uint8_t>(strtoul(row[2], nullptr, 10)) : 0;
e.level = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.cap = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.class_ = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 0;
return e;
}
@@ -169,11 +174,11 @@ public:
auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.skillID));
v.push_back(columns[1] + " = " + std::to_string(e.class_));
v.push_back(columns[2] + " = " + std::to_string(e.level));
v.push_back(columns[3] + " = " + std::to_string(e.cap));
v.push_back(columns[4] + " = " + std::to_string(e.class_));
v.push_back(columns[1] + " = " + std::to_string(e.skill_id));
v.push_back(columns[2] + " = " + std::to_string(e.class_id));
v.push_back(columns[3] + " = " + std::to_string(e.level));
v.push_back(columns[4] + " = " + std::to_string(e.cap));
v.push_back(columns[5] + " = " + std::to_string(e.class_));
auto results = db.QueryDatabase(
fmt::format(
@@ -181,7 +186,7 @@ public:
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.skillID
e.id
)
);
@@ -195,8 +200,9 @@ public:
{
std::vector<std::string> v;
v.push_back(std::to_string(e.skillID));
v.push_back(std::to_string(e.class_));
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.skill_id));
v.push_back(std::to_string(e.class_id));
v.push_back(std::to_string(e.level));
v.push_back(std::to_string(e.cap));
v.push_back(std::to_string(e.class_));
@@ -210,7 +216,7 @@ public:
);
if (results.Success()) {
e.skillID = results.LastInsertedID();
e.id = results.LastInsertedID();
return e;
}
@@ -229,8 +235,9 @@ public:
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.skillID));
v.push_back(std::to_string(e.class_));
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.skill_id));
v.push_back(std::to_string(e.class_id));
v.push_back(std::to_string(e.level));
v.push_back(std::to_string(e.cap));
v.push_back(std::to_string(e.class_));
@@ -267,11 +274,12 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
SkillCaps e{};
e.skillID = row[0] ? static_cast<uint8_t>(strtoul(row[0], nullptr, 10)) : 0;
e.class_ = row[1] ? static_cast<uint8_t>(strtoul(row[1], nullptr, 10)) : 0;
e.level = row[2] ? static_cast<uint8_t>(strtoul(row[2], nullptr, 10)) : 0;
e.cap = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.class_ = row[4] ? static_cast<uint8_t>(strtoul(row[4], nullptr, 10)) : 0;
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.skill_id = row[1] ? static_cast<uint8_t>(strtoul(row[1], nullptr, 10)) : 0;
e.class_id = row[2] ? static_cast<uint8_t>(strtoul(row[2], nullptr, 10)) : 0;
e.level = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.cap = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.class_ = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@@ -296,11 +304,12 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
SkillCaps e{};
e.skillID = row[0] ? static_cast<uint8_t>(strtoul(row[0], nullptr, 10)) : 0;
e.class_ = row[1] ? static_cast<uint8_t>(strtoul(row[1], nullptr, 10)) : 0;
e.level = row[2] ? static_cast<uint8_t>(strtoul(row[2], nullptr, 10)) : 0;
e.cap = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.class_ = row[4] ? static_cast<uint8_t>(strtoul(row[4], nullptr, 10)) : 0;
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.skill_id = row[1] ? static_cast<uint8_t>(strtoul(row[1], nullptr, 10)) : 0;
e.class_id = row[2] ? static_cast<uint8_t>(strtoul(row[2], nullptr, 10)) : 0;
e.level = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.cap = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.class_ = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@@ -375,8 +384,9 @@ public:
{
std::vector<std::string> v;
v.push_back(std::to_string(e.skillID));
v.push_back(std::to_string(e.class_));
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.skill_id));
v.push_back(std::to_string(e.class_id));
v.push_back(std::to_string(e.level));
v.push_back(std::to_string(e.cap));
v.push_back(std::to_string(e.class_));
@@ -402,8 +412,9 @@ public:
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.skillID));
v.push_back(std::to_string(e.class_));
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.skill_id));
v.push_back(std::to_string(e.class_id));
v.push_back(std::to_string(e.level));
v.push_back(std::to_string(e.cap));
v.push_back(std::to_string(e.class_));
@@ -23,7 +23,7 @@ public:
std::string varname;
std::string value;
std::string information;
std::string ts;
time_t ts;
};
static std::string PrimaryKey()
@@ -49,7 +49,7 @@ public:
"varname",
"value",
"information",
"ts",
"UNIX_TIMESTAMP(ts)",
};
}
@@ -135,7 +135,7 @@ public:
e.varname = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.information = row[3] ? row[3] : "";
e.ts = row[4] ? row[4] : std::time(nullptr);
e.ts = strtoll(row[4] ? row[4] : "-1", nullptr, 10);
return e;
}
@@ -172,7 +172,7 @@ public:
v.push_back(columns[1] + " = '" + Strings::Escape(e.varname) + "'");
v.push_back(columns[2] + " = '" + Strings::Escape(e.value) + "'");
v.push_back(columns[3] + " = '" + Strings::Escape(e.information) + "'");
v.push_back(columns[4] + " = '" + Strings::Escape(e.ts) + "'");
v.push_back(columns[4] + " = FROM_UNIXTIME(" + (e.ts > 0 ? std::to_string(e.ts) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -198,7 +198,7 @@ public:
v.push_back("'" + Strings::Escape(e.varname) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'");
v.push_back("'" + Strings::Escape(e.information) + "'");
v.push_back("'" + Strings::Escape(e.ts) + "'");
v.push_back("FROM_UNIXTIME(" + (e.ts > 0 ? std::to_string(e.ts) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -232,7 +232,7 @@ public:
v.push_back("'" + Strings::Escape(e.varname) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'");
v.push_back("'" + Strings::Escape(e.information) + "'");
v.push_back("'" + Strings::Escape(e.ts) + "'");
v.push_back("FROM_UNIXTIME(" + (e.ts > 0 ? std::to_string(e.ts) : "null") + ")");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -270,7 +270,7 @@ public:
e.varname = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.information = row[3] ? row[3] : "";
e.ts = row[4] ? row[4] : std::time(nullptr);
e.ts = strtoll(row[4] ? row[4] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@@ -299,7 +299,7 @@ public:
e.varname = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : "";
e.information = row[3] ? row[3] : "";
e.ts = row[4] ? row[4] : std::time(nullptr);
e.ts = strtoll(row[4] ? row[4] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@@ -378,7 +378,7 @@ public:
v.push_back("'" + Strings::Escape(e.varname) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'");
v.push_back("'" + Strings::Escape(e.information) + "'");
v.push_back("'" + Strings::Escape(e.ts) + "'");
v.push_back("FROM_UNIXTIME(" + (e.ts > 0 ? std::to_string(e.ts) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -405,7 +405,7 @@ public:
v.push_back("'" + Strings::Escape(e.varname) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'");
v.push_back("'" + Strings::Escape(e.information) + "'");
v.push_back("'" + Strings::Escape(e.ts) + "'");
v.push_back("FROM_UNIXTIME(" + (e.ts > 0 ? std::to_string(e.ts) : "null") + ")");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -59,6 +59,24 @@ public:
return NewEntity();
}
// insert with ON DUPLICATE KEY UPDATE to leave rows that exist unchanged
static int InsertUpdateMany(Database& db, const std::vector<CharRecipeList>& entries)
{
std::vector<std::string> values;
values.reserve(entries.size());
for (const auto& e: entries)
{
values.emplace_back(fmt::format("({},{},{})", e.char_id, e.recipe_id, e.madecount));
}
auto results = db.QueryDatabase(fmt::format(
"INSERT INTO {0} (char_id, recipe_id, madecount) VALUES {1} ON DUPLICATE KEY UPDATE {2}={2}",
TableName(), fmt::join(values, ","), PrimaryKey()));
return results.Success() ? results.RowsAffected() : 0;
}
};
#endif //EQEMU_CHAR_RECIPE_LIST_REPOSITORY_H
@@ -213,23 +213,23 @@ public:
const glm::vec4& position
)
{
auto results = db.QueryDatabase(
fmt::format(
"UPDATE `{}` SET `is_buried` = 0, `zone_id` = {}, `instance_id` = {}, `x` = {:.2f}, `y` = {:.2f}, `z` = {:.2f}, `heading` = {:.2f}, `time_of_death` = {}, `was_at_graveyard` = 0 WHERE `{}` = {}",
TableName(),
zone_id,
instance_id,
position.x,
position.y,
position.z,
position.w,
std::time(nullptr),
PrimaryKey(),
corpse_id
)
);
auto corpse = FindOne(db, corpse_id);
return results.Success() ? results.RowsAffected() : 0;
if (corpse.id == 0) {
return 0;
}
corpse.is_buried = 0;
corpse.zone_id = zone_id;
corpse.instance_id = instance_id;
corpse.x = position.x;
corpse.y = position.y;
corpse.z = position.z;
corpse.heading = position.w;
corpse.time_of_death = time(nullptr);
corpse.was_at_graveyard = 0;
return UpdateOne(db, corpse);
}
};
@@ -74,9 +74,11 @@ public:
};
}
const auto& m = l.front();
return EXPModifier{
.aa_modifier = l[0].aa_modifier,
.exp_modifier = l[0].exp_modifier
.aa_modifier = m.aa_modifier,
.exp_modifier = m.exp_modifier
};
}
+9 -1
View File
@@ -44,7 +44,15 @@ public:
*/
// Custom extended repository methods here
static void ClearAllGroups(Database& db)
{
db.QueryDatabase(
fmt::format(
"DELETE FROM `{}`",
TableName()
)
);
}
};
#endif //EQEMU_GROUP_ID_REPOSITORY_H
@@ -44,7 +44,15 @@ public:
*/
// Custom extended repository methods here
static void ClearAllGroupLeaders(Database& db)
{
db.QueryDatabase(
fmt::format(
"DELETE FROM `{}`",
TableName()
)
);
}
};
#endif //EQEMU_GROUP_LEADERS_REPOSITORY_H
@@ -191,4 +191,5 @@ public:
return UpdateOne(db, m);
}
};
#endif //EQEMU_GUILD_MEMBERS_REPOSITORY_H
@@ -44,7 +44,30 @@ public:
*/
// Custom extended repository methods here
static int64 CountInventorySnapshots(Database& db)
{
const std::string& query = "SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a GROUP BY `charid`, `time_index`) b";
auto results = db.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return -1;
}
auto row = results.begin();
const int64 count = Strings::ToBigInt(row[0]);
if (count > std::numeric_limits<int>::max()) {
return -2;
}
if (count < 0) {
return -3;
}
return count;
}
};
#endif //EQEMU_INVENTORY_SNAPSHOTS_REPOSITORY_H
@@ -66,6 +66,16 @@ public:
return results.Success() ? results.RowsAffected() : 0;
}
static void ClearAllRaidDetails(Database& db)
{
db.QueryDatabase(
fmt::format(
"DELETE FROM `{}`",
TableName()
)
);
}
};
#endif //EQEMU_RAID_DETAILS_REPOSITORY_H
@@ -1,297 +0,0 @@
#ifndef EQEMU_RAID_LEADERS_REPOSITORY_H
#define EQEMU_RAID_LEADERS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
class RaidLeadersRepository {
public:
struct RaidLeaders {
int gid;
int rid;
std::string marknpc;
std::string maintank;
std::string assist;
std::string puller;
std::string leadershipaa;
std::string mentoree;
int mentor_percent;
};
static std::string PrimaryKey()
{
return std::string("");
}
static std::vector<std::string> Columns()
{
return {
"gid",
"rid",
"marknpc",
"maintank",
"assist",
"puller",
"leadershipaa",
"mentoree",
"mentor_percent",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string InsertColumnsRaw()
{
std::vector<std::string> insert_columns;
for (auto &column : Columns()) {
if (column == PrimaryKey()) {
continue;
}
insert_columns.push_back(column);
}
return std::string(Strings::Implode(", ", insert_columns));
}
static std::string TableName()
{
return std::string("raid_leaders");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
ColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
InsertColumnsRaw()
);
}
static RaidLeaders NewEntity()
{
RaidLeaders entry{};
entry.gid = 0;
entry.rid = 0;
entry.marknpc = "";
entry.maintank = "";
entry.assist = "";
entry.puller = "";
entry.leadershipaa = 0;
entry.mentoree = "";
entry.mentor_percent = 0;
return entry;
}
static RaidLeaders GetRaidLeadersEntry(
const std::vector<RaidLeaders> &raid_leaderss,
int raid_leaders_id
)
{
for (auto &raid_leaders : raid_leaderss) {
if (raid_leaders. == raid_leaders_id) {
return raid_leaders;
}
}
return NewEntity();
}
static RaidLeaders FindOne(
int raid_leaders_id
)
{
auto results = database.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
BaseSelect(),
raid_leaders_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
RaidLeaders entry{};
entry.gid = atoi(row[0]);
entry.rid = atoi(row[1]);
entry.marknpc = row[2];
entry.maintank = row[3];
entry.assist = row[4];
entry.puller = row[5];
entry.leadershipaa = row[6];
entry.mentoree = row[7];
entry.mentor_percent = atoi(row[8]);
return entry;
}
return NewEntity();
}
static int DeleteOne(
int raid_leaders_id
)
{
auto results = database.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
raid_leaders_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
RaidLeaders raid_leaders_entry
)
{
std::vector<std::string> update_values;
auto columns = Columns();
update_values.push_back(columns[0] + " = " + std::to_string(raid_leaders_entry.gid));
update_values.push_back(columns[1] + " = " + std::to_string(raid_leaders_entry.rid));
update_values.push_back(columns[2] + " = '" + Strings::Escape(raid_leaders_entry.marknpc) + "'");
update_values.push_back(columns[3] + " = '" + Strings::Escape(raid_leaders_entry.maintank) + "'");
update_values.push_back(columns[4] + " = '" + Strings::Escape(raid_leaders_entry.assist) + "'");
update_values.push_back(columns[5] + " = '" + Strings::Escape(raid_leaders_entry.puller) + "'");
update_values.push_back(columns[6] + " = '" + Strings::Escape(raid_leaders_entry.leadershipaa) + "'");
update_values.push_back(columns[7] + " = '" + Strings::Escape(raid_leaders_entry.mentoree) + "'");
update_values.push_back(columns[8] + " = " + std::to_string(raid_leaders_entry.mentor_percent));
auto results = database.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", update_values),
PrimaryKey(),
raid_leaders_entry.
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static RaidLeaders InsertOne(
RaidLeaders raid_leaders_entry
)
{
std::vector<std::string> insert_values;
insert_values.push_back(std::to_string(raid_leaders_entry.gid));
insert_values.push_back(std::to_string(raid_leaders_entry.rid));
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.marknpc) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.maintank) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.assist) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.puller) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.leadershipaa) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.mentoree) + "'");
insert_values.push_back(std::to_string(raid_leaders_entry.mentor_percent));
auto results = database.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", insert_values)
)
);
if (results.Success()) {
raid_leaders_entry.id = results.LastInsertedID();
return raid_leaders_entry;
}
raid_leaders_entry = InstanceListRepository::NewEntity();
return raid_leaders_entry;
}
static int InsertMany(
std::vector<RaidLeaders> raid_leaders_entries
)
{
std::vector<std::string> insert_chunks;
for (auto &raid_leaders_entry: raid_leaders_entries) {
std::vector<std::string> insert_values;
insert_values.push_back(std::to_string(raid_leaders_entry.gid));
insert_values.push_back(std::to_string(raid_leaders_entry.rid));
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.marknpc) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.maintank) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.assist) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.puller) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.leadershipaa) + "'");
insert_values.push_back("'" + Strings::Escape(raid_leaders_entry.mentoree) + "'");
insert_values.push_back(std::to_string(raid_leaders_entry.mentor_percent));
insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")");
}
std::vector<std::string> insert_values;
auto results = database.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<RaidLeaders> All()
{
std::vector<RaidLeaders> all_entries;
auto results = database.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
RaidLeaders entry{};
entry.gid = atoi(row[0]);
entry.rid = atoi(row[1]);
entry.marknpc = row[2];
entry.maintank = row[3];
entry.assist = row[4];
entry.puller = row[5];
entry.leadershipaa = row[6];
entry.mentoree = row[7];
entry.mentor_percent = atoi(row[8]);
all_entries.push_back(entry);
}
return all_entries;
}
};
#endif //EQEMU_RAID_LEADERS_REPOSITORY_H
+11 -1
View File
@@ -60,7 +60,7 @@ public:
);
return results.Success() ? results.RowsAffected() : 0;
}
static int UpdateRaidAssister(
Database& db,
int32_t raid_id,
@@ -98,5 +98,15 @@ public:
return results.Success() ? results.RowsAffected() : 0;
}
static void ClearAllRaids(Database& db)
{
db.QueryDatabase(
fmt::format(
"DELETE FROM `{}`",
TableName()
)
);
}
};
#endif //EQEMU_RAID_MEMBERS_REPOSITORY_H
+35 -5
View File
@@ -34,8 +34,8 @@ const char *RuleManager::s_categoryNames[_CatCount + 1] = {
"InvalidCategory"
};
const RuleManager::RuleInfo RuleManager::s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount + 1] = {
/* this is done in three steps so we can reliably get to them by index*/
const RuleManager::RuleInfo RuleManager::s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount + StringRuleCount + 1] = {
/* this is done in three steps, so we can reliably get to them by index*/
#define RULE_INT(category_name, rule_name, default_value, notes) \
{ #category_name ":" #rule_name, Category__##category_name, IntRule, Int__##rule_name, notes },
#include "ruletypes.h"
@@ -45,6 +45,9 @@ const RuleManager::RuleInfo RuleManager::s_RuleInfo[IntRuleCount + RealRuleCount
#define RULE_BOOL(category_name, rule_name, default_value, notes) \
{ #category_name ":" #rule_name, Category__##category_name, BoolRule, Bool__##rule_name, notes },
#include "ruletypes.h"
#define RULE_STRING(category_name, rule_name, default_value, notes) \
{ #category_name ":" #rule_name, Category__##category_name, StringRule, String__##rule_name, notes },
#include "ruletypes.h"
{ "Invalid Rule", _CatCount, IntRule }
};
@@ -114,6 +117,9 @@ bool RuleManager::GetRule(const std::string &rule_name, std::string &rule_value)
case BoolRule:
rule_value = m_RuleBoolValues[index] ? "true" : "false";
break;
case StringRule:
rule_value = m_RuleStringValues[index];
break;
}
return true;
@@ -152,6 +158,10 @@ bool RuleManager::SetRule(const std::string &rule_name, const std::string &rule_
m_RuleBoolValues[index] = static_cast<uint32>(Strings::ToBool(rule_value));
LogRules("Set rule [{}] to value [{}]", rule_name, m_RuleBoolValues[index] == 1 ? "true" : "false");
break;
case StringRule:
m_RuleStringValues[index] = rule_value;
LogRules("Set rule [{}] to value [{}]", rule_name, rule_value);
break;
}
if (db_save) {
@@ -215,11 +225,13 @@ std::string RuleManager::_GetRuleName(RuleType type, uint16 index) {
return s_RuleInfo[index + IntRuleCount].name;
case BoolRule:
return s_RuleInfo[index + IntRuleCount + RealRuleCount].name;
case StringRule:
return s_RuleInfo[index + IntRuleCount + RealRuleCount + StringRuleCount].name;
default:
break;
}
return s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount].name;
return s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount + StringRuleCount].name;
}
//assumes index is valid!
@@ -231,11 +243,13 @@ const std::string &RuleManager::_GetRuleNotes(RuleType type, uint16 index) {
return s_RuleInfo[index + IntRuleCount].notes;
case BoolRule:
return s_RuleInfo[index + IntRuleCount + RealRuleCount].notes;
case StringRule:
return s_RuleInfo[index + IntRuleCount + RealRuleCount + StringRuleCount].notes;
default:
break;
}
return s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount].notes;
return s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount + StringRuleCount].notes;
}
bool RuleManager::LoadRules(Database *db, const std::string &rule_set_name, bool reload) {
@@ -343,6 +357,10 @@ void RuleManager::SaveRules(Database *db, const std::string &rule_set_name) {
for (i = 0; i < BoolRuleCount; i++) {
_SaveRule(db, BoolRule, i);
}
for (i = 0; i < StringRuleCount; i++) {
_SaveRule(db, StringRule, i);
}
}
void RuleManager::_SaveRule(Database *db, RuleType type, uint16 index) {
@@ -367,6 +385,9 @@ void RuleManager::_SaveRule(Database *db, RuleType type, uint16 index) {
case BoolRule:
rule_value = m_RuleBoolValues[index] ? "true" : "false";
break;
case StringRule:
rule_value = m_RuleStringValues[index];
break;
}
const auto& rule_notes = _GetRuleNotes(type, index);
@@ -446,6 +467,10 @@ bool RuleManager::UpdateInjectedRules(Database *db, const std::string &rule_set_
rule_data[r.name].first = fmt::format("{}", m_RuleBoolValues[r.rule_index] ? "true" : "false");
rule_data[r.name].second = &r.notes;
break;
case StringRule:
rule_data[r.name].first = m_RuleStringValues[r.rule_index];
rule_data[r.name].second = &r.notes;
break;
default:
break;
}
@@ -552,7 +577,7 @@ bool RuleManager::RestoreRuleNotes(Database *db)
}
}
return s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount];
return s_RuleInfo[IntRuleCount + RealRuleCount + BoolRuleCount + StringRuleCount];
}(e.rule_name);
if (Strings::Contains(rule.name, e.rule_name)) {
@@ -617,3 +642,8 @@ bool RuleManager::GetBoolRule(RuleManager::BoolType t) const
{
return m_RuleBoolValues[t] == 1;
}
std::string RuleManager::GetStringRule(RuleManager::StringType t) const
{
return m_RuleStringValues[t];
}
+45 -25
View File
@@ -23,6 +23,7 @@
* - RuleI(category, rule) -> fetch an integer rule's value
* - RuleR(category, rule) -> fetch a real (float) rule's value
* - RuleB(category, rule) -> fetch a boolean/flag rule's value
* - RuleS(category, rule) -> fetch a string rule's value
*
*/
@@ -35,6 +36,8 @@
RuleManager::Instance()->GetRealRule( RuleManager::Real__##rule_name )
#define RuleB(category_name, rule_name) \
RuleManager::Instance()->GetBoolRule( RuleManager::Bool__##rule_name )
#define RuleS(category_name, rule_name) \
RuleManager::Instance()->GetStringRule( RuleManager::String__##rule_name )
#include <vector>
@@ -81,6 +84,17 @@ public:
static const int BoolRuleCount = static_cast<int>(_BoolRuleCount);
typedef enum {
#define RULE_STRING(category_name, rule_name, default_value, notes) \
String__##rule_name,
#include "ruletypes.h"
_StringRuleCount
} StringType;
static const int StringRuleCount = static_cast<int>(_StringRuleCount);
typedef enum {
#define RULE_CATEGORY(category_name) \
Category__##category_name,
@@ -99,45 +113,49 @@ public:
static const IntType InvalidInt = _IntRuleCount;
static const RealType InvalidReal = _RealRuleCount;
static const BoolType InvalidBool = _BoolRuleCount;
static const StringType InvalidString = _StringRuleCount;
static const CategoryType InvalidCategory = _CatCount;
static const uint32 RulesCount = IntRuleCount + RealRuleCount + BoolRuleCount;
static const uint32 RulesCount = IntRuleCount + RealRuleCount + BoolRuleCount + StringRuleCount;
//fetch routines, you should generally use the Rule* macros instead of this
int GetIntRule(IntType t) const;
float GetRealRule(RealType t) const;
bool GetBoolRule(BoolType t) const;
std::string GetStringRule(StringType t) const;
//management routines
static std::string GetRuleName(IntType t) { return s_RuleInfo[static_cast<int>(t)].name; }
static std::string GetRuleName(RealType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount].name; }
static std::string GetRuleName(BoolType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount + RealRuleCount].name; }
static const std::string &GetRuleNotes(IntType t) { return s_RuleInfo[static_cast<int>(t)].notes; }
static const std::string &GetRuleNotes(RealType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount].notes; }
static const std::string &GetRuleNotes(BoolType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount + RealRuleCount].notes; }
static std::string GetRuleName(StringType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount + RealRuleCount + StringRuleCount].name; }
static const std::string& GetRuleNotes(IntType t) { return s_RuleInfo[static_cast<int>(t)].notes; }
static const std::string& GetRuleNotes(RealType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount].notes; }
static const std::string& GetRuleNotes(BoolType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount + RealRuleCount].notes; }
static const std::string& GetRuleNotes(StringType t) { return s_RuleInfo[static_cast<int>(t) + IntRuleCount + RealRuleCount + StringRuleCount].notes; }
static uint32 CountRules() { return RulesCount; }
static CategoryType FindCategory(const std::string &category_name);
bool ListRules(const std::string &category_name, std::vector <std::string> &l);
bool ListCategories(std::vector <std::string> &l);
bool GetRule(const std::string &rule_name, std::string &rule_value);
static CategoryType FindCategory(const std::string& category_name);
bool ListRules(const std::string& category_name, std::vector<std::string>& l);
bool ListCategories(std::vector<std::string>& l);
bool GetRule(const std::string& rule_name, std::string& rule_value);
bool SetRule(
const std::string &rule_name,
const std::string &rule_value,
Database *db = nullptr,
const std::string& rule_name,
const std::string& rule_value,
Database* db = nullptr,
bool db_save = false,
bool reload = false
);
int GetActiveRulesetID() const { return m_activeRuleset; }
std::string GetActiveRuleset() const { return m_activeName; }
static bool ListRulesets(Database *db, std::map<int, std::string> &l);
static bool ListRulesets(Database* db, std::map<int, std::string>& l);
void ResetRules(bool reload = false);
bool LoadRules(Database *db, const std::string &rule_set_name, bool reload = false);
void SaveRules(Database *db, const std::string &rule_set_name);
bool UpdateInjectedRules(Database *db, const std::string &rule_set_name, bool quiet_update = false);
bool UpdateOrphanedRules(Database *db, bool quiet_update = false);
bool RestoreRuleNotes(Database *db);
bool LoadRules(Database* db, const std::string& rule_set_name, bool reload = false);
void SaveRules(Database* db, const std::string& rule_set_name);
bool UpdateInjectedRules(Database* db, const std::string& rule_set_name, bool quiet_update = false);
bool UpdateOrphanedRules(Database* db, bool quiet_update = false);
bool RestoreRuleNotes(Database* db);
private:
RuleManager();
@@ -147,21 +165,23 @@ private:
int m_activeRuleset;
std::string m_activeName;
int m_RuleIntValues[IntRuleCount];
float m_RuleRealValues[RealRuleCount];
uint32 m_RuleBoolValues[BoolRuleCount];
int m_RuleIntValues[IntRuleCount];
float m_RuleRealValues[RealRuleCount];
uint32 m_RuleBoolValues[BoolRuleCount];
std::string m_RuleStringValues[StringRuleCount];
typedef enum {
IntRule,
RealRule,
BoolRule
BoolRule,
StringRule
} RuleType;
static bool _FindRule(const std::string &rule_name, RuleType &type_into, uint16 &index_into);
static bool _FindRule(const std::string& rule_name, RuleType& type_into, uint16& index_into);
static std::string _GetRuleName(RuleType type, uint16 index);
static const std::string &_GetRuleNotes(RuleType type, uint16 index);
static int _FindOrCreateRuleset(Database *db, const std::string &rule_set_name);
void _SaveRule(Database *db, RuleType type, uint16 index);
static const std::string& _GetRuleNotes(RuleType type, uint16 index);
static int _FindOrCreateRuleset(Database* db, const std::string& rule_set_name);
void _SaveRule(Database* db, RuleType type, uint16 index);
static const char* s_categoryNames[];
typedef struct {
+17 -4
View File
@@ -28,6 +28,9 @@
#ifndef RULE_BOOL
#define RULE_BOOL(cat, rule, default_value, notes)
#endif
#ifndef RULE_STRING
#define RULE_STRING(cat, rule, default_value, notes)
#endif
#ifndef RULE_CATEGORY_END
#define RULE_CATEGORY_END()
#endif
@@ -48,10 +51,10 @@ RULE_BOOL(Character, DeathKeepLevel, false, "Players can not drop below 0% exper
RULE_BOOL(Character, UseDeathExpLossMult, false, "Setting to control whether DeathExpLossMultiplier or the code default is used: (Level x Level / 18.0) x 12000")
RULE_BOOL(Character, UseOldRaceRezEffects, false, "Older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore")
RULE_INT(Character, CorpseDecayTime, 604800000, "Time after which the corpse decays (milliseconds) DEFAULT: 604800000 (7 Days)")
RULE_INT( Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, CorpseResTime, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT( Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
RULE_INT( Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
RULE_INT(Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items")
RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
@@ -324,6 +327,9 @@ RULE_INT(World, MaximumQuestErrors, 30, "Changes the maximum number of quest err
RULE_INT(World, BootHour, 0, "Sets the in-game hour world will set when it first boots. 0-24 are valid options, where 0 disables this rule")
RULE_BOOL(World, UseItemLinksForKeyRing, false, "Uses item links for Key Ring Listing instead of item name")
RULE_BOOL(World, UseOldShadowKnightClassExport, true, "Disable to have Shadowknight show as Shadow Knight (live-like)")
RULE_STRING(World, IPExemptionZones, "", "Comma-delimited list of zones to exclude from IP-limit checks. Empty string to disable.")
RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to have this be used instead of variables table 'motd' value")
RULE_STRING(World, Rules, "", "Server Rules, change from empty to have this be used instead of variables table 'rules' value, lines are pipe (|) separated, example: A|B|C")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
@@ -349,6 +355,7 @@ RULE_INT(Zone, GlobalLootMultiplier, 1, "Sets Global Loot drop multiplier for da
RULE_BOOL(Zone, KillProcessOnDynamicShutdown, true, "When process has booted a zone and has hit its zone shut down timer, it will hard kill the process to free memory back to the OS")
RULE_INT(Zone, SpawnEventMin, 3, "When strict is set in spawn_events, specifies the max EQ minutes into the trigger hour a spawn_event will fire. Going below 3 may cause the spawn_event to not fire.")
RULE_INT(Zone, ForageChance, 25, "Chance of foraging from zone table vs global table")
RULE_INT(Zone, FishingChance, 399, "Chance of fishing from zone table vs global table")
RULE_BOOL(Zone, AllowCrossZoneSpellsOnBots, false, "Set to true to allow cross zone spells (cast/remove) to affect bots")
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")
@@ -444,7 +451,8 @@ RULE_BOOL(Spells, Jun182014HundredHandsRevamp, false, "This should be true for i
RULE_BOOL(Spells, SwarmPetTargetLock, false, "Use old method of swarm pets target locking till target dies then despawning")
RULE_BOOL(Spells, NPC_UseFocusFromSpells, true, "Allow NPC to use most spell derived focus effects")
RULE_BOOL(Spells, NPC_UseFocusFromItems, false, "Allow NPC to use most item derived focus effects")
RULE_BOOL(Spells, UseAdditiveFocusFromWornSlot, false, "Allows an additive focus effect to be calculated from worn slot")
RULE_BOOL(Spells, UseAdditiveFocusFromWornSlot, false, "Allows an additive focus effect to be calculated from worn slot. Does not apply limits checks. Can only have one additive focus rule be true.")
RULE_BOOL(Spells, UseAdditiveFocusFromWornSlotWithLimits, false, "Allows an additive focus effect to be calculated from worn slot. Applies normal limit checks. Can only have one additive focus rule be true.")
RULE_BOOL(Spells, AlwaysSendTargetsBuffs, false, "Ignore Leadership Alternate Abilities level if true")
RULE_BOOL(Spells, FlatItemExtraSpellAmt, false, "Allow SpellDmg stat to affect all spells, regardless of cast time/cooldown/etc")
RULE_BOOL(Spells, IgnoreSpellDmgLvlRestriction, false, "Ignore the 5 level spread on applying SpellDmg")
@@ -495,6 +503,7 @@ RULE_BOOL(Spells, EvacClearCharmPet, false, "Enable to have evac in zone clear c
RULE_BOOL(Spells, ManaTapsRequireNPCMana, false, "Enabling will require target to have mana to tap. Default off as many npc's are caster class with 0 mana and need fixed.")
RULE_INT(Spells, HarmTouchCritRatio, 200, "Harmtouch crit bonus, on top of BaseCritRatio")
RULE_BOOL(Spells, UseClassicSpellFocus, false, "Enabling will tell the server to handle random focus damage as classic spell imports lack the limit values.")
RULE_BOOL(Spells, ManaTapsOnAnyClass, false, "Enabling this will allow you to cast mana taps on any class, this will bypass ManaTapsRequireNPCMana rule.")
RULE_CATEGORY_END()
RULE_CATEGORY(Combat)
@@ -521,6 +530,7 @@ RULE_REAL(Combat, BaseProcChance, 0.035, "Base chance for procs")
RULE_REAL(Combat, ProcDexDivideBy, 11000, "Divisor for the probability of a proc increased by dexterity")
RULE_INT(Combat, MinRangedAttackDist, 25, "Minimum Distance to use Ranged Attacks")
RULE_BOOL(Combat, ArcheryBonusRequiresStationary, true, "does the 2x archery bonus chance require a stationary npc")
RULE_INT(Combat, ArcheryBonusLevelRequirement, 51, "Level requirement when the 2x archery bonus will be enabled. The default is 51.")
RULE_REAL(Combat, ArcheryNPCMultiplier, 1.0, "Value is multiplied by the regular dmg to get the archery dmg")
RULE_BOOL(Combat, AssistNoTargetSelf, true, "When assisting a target that does not have a target: true = target self, false = leave target as was before assist (false = live like)")
RULE_INT(Combat, MaxRampageTargets, 3, "Maximum number of people hit with rampage")
@@ -569,10 +579,12 @@ RULE_BOOL(Combat, NPCsUseFrontalStunImmunityClasses, false, "Enable or disable N
RULE_INT(Combat, FrontalStunImmunityRaces, 512, "Bitmask for Races than have frontal stun immunity, Ogre (512) only by default.")
RULE_BOOL(Combat, NPCsUseFrontalStunImmunityRaces, true, "Enable or disable NPCs using frontal stun immunity Races from Combat:FrontalStunImmunityRaces, true by default.")
RULE_BOOL(Combat, AssassinateOnlyHumanoids, true, "Enable or disable Assassinate only being allowed on Humanoids, true by default.")
RULE_INT(Combat, AssassinateLevelRequirement, 60, "Level requirement to enable assassinate attempts on backstabs. The default is 60.")
RULE_BOOL(Combat, HeadshotOnlyHumanoids, true, "Enable or disable Headshot only being allowed on Humanoids, true by default.")
RULE_BOOL(Combat, EnableWarriorShielding, true, "Enable or disable Warrior Shielding Ability (/shield), true by default.")
RULE_BOOL(Combat, BackstabIgnoresElemental, false, "Enable or disable Elemental weapon damage affecting backstab damage, false by default.")
RULE_BOOL(Combat, BackstabIgnoresBane, false, "Enable or disable Bane weapon damage affecting backstab damage, false by default.")
RULE_INT(Combat, DoubleBackstabLevelRequirement, 55, "Level requirement to enable double backstab attempts. The default is 55.")
RULE_BOOL(Combat, SummonMeleeRange, true, "Enable or disable summoning of a player when already in melee range of the summoner.")
RULE_BOOL(Combat, WaterMatchRequiredForAutoFireLoS, true, "Enable/Disable the requirement of both the attacker/victim being both in or out of water for AutoFire LoS to pass.")
RULE_INT(Combat, ExtraAllowedKickClassesBitmask, 0, "Bitmask for allowing extra classes beyond Warrior, Ranger, Beastlord, and Berserker to kick, No Extra Classes (0) by default")
@@ -950,4 +962,5 @@ RULE_CATEGORY_END()
#undef RULE_INT
#undef RULE_REAL
#undef RULE_BOOL
#undef RULE_STRING
#undef RULE_CATEGORY_END
+4 -3
View File
@@ -348,7 +348,7 @@ std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message)
if (ch != startpos)
{
std::string str(startpos, ch - startpos);
new_message += EQ::SayLinkEngine::GenerateQuestSaylink(str, false, str);
new_message += Saylink::Create(str);
}
in_bracket_state = false;
}
@@ -417,11 +417,12 @@ SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string sayli
return {};
}
std::string Saylink::Create(const std::string &saylink_text, bool silent, const std::string &link_name)
std::string Saylink::Create(const std::string& saylink_text, bool silent, const std::string& link_name)
{
return EQ::SayLinkEngine::GenerateQuestSaylink(saylink_text, silent, (link_name.empty() ? saylink_text : link_name));
}
std::string Saylink::Silent(const std::string &saylink_text, const std::string &link_name) {
std::string Saylink::Silent(const std::string& saylink_text, const std::string& link_name)
{
return EQ::SayLinkEngine::GenerateQuestSaylink(saylink_text, true, (link_name.empty() ? saylink_text : link_name));
}
+1 -1
View File
@@ -130,7 +130,7 @@ namespace EQ
class Saylink {
public:
static std::string Create(const std::string &saylink_text, bool silent, const std::string &link_name = "");
static std::string Create(const std::string &saylink_text, bool silent = false, const std::string &link_name = "");
static std::string Silent(const std::string &saylink_text, const std::string &link_name = "");
};
+1
View File
@@ -273,6 +273,7 @@
#define ServerOP_ReloadFactions 0x4126
#define ServerOP_ReloadLoot 0x4127
#define ServerOP_ReloadBaseData 0x4128
#define ServerOP_ReloadSkillCaps 0x4129
#define ServerOP_CZDialogueWindow 0x4500
#define ServerOP_CZLDoNUpdate 0x4501
+1 -1
View File
@@ -60,7 +60,7 @@ SharedTaskRequest SharedTask::GetRequestCharacters(Database &db, uint32_t reques
request.group_type = SharedTaskRequestGroupType::Group;
auto characters = CharacterDataRepository::GetWhere(
db, fmt::format(
"id IN (select charid from group_id where groupid = (select groupid from group_id where charid = {}))",
"id IN (select character_id from group_id where group_id = (select group_id from group_id where character_id = {}))",
requested_character_id
)
);
+4 -134
View File
@@ -45,6 +45,7 @@
#include "repositories/loottable_repository.h"
#include "repositories/character_item_recast_repository.h"
#include "repositories/character_corpses_repository.h"
#include "repositories/skill_caps_repository.h"
namespace ItemField
{
@@ -793,8 +794,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
}
if (cv_conflict) {
char char_name[64] = "";
GetCharName(char_id, char_name);
const std::string& char_name = GetCharName(char_id);
LogError("ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])",
char_name,
char_id,
@@ -1626,136 +1626,6 @@ bool SharedDatabase::GetCommandSubSettings(std::vector<CommandSubsettingsReposit
return true;
}
bool SharedDatabase::LoadSkillCaps(const std::string &prefix) {
skill_caps_mmf.reset(nullptr);
try {
const auto Config = EQEmuConfig::get();
EQ::IPCMutex mutex("skill_caps");
mutex.Lock();
std::string file_name = fmt::format("{}/{}{}", path.GetSharedMemoryPath(), prefix, std::string("skill_caps"));
LogInfo("Loading [{}]", file_name);
skill_caps_mmf = std::make_unique<EQ::MemoryMappedFile>(file_name);
LogInfo("Loaded skill caps via shared memory");
mutex.Unlock();
} catch(std::exception &ex) {
LogError("Error loading skill caps: {}", ex.what());
return false;
}
return true;
}
void SharedDatabase::LoadSkillCaps(void *data) {
const uint32 class_count = Class::PLAYER_CLASS_COUNT;
const uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1;
const uint32 level_count = HARD_LEVEL_CAP + 1;
uint16 *skill_caps_table = static_cast<uint16*>(data);
const std::string query = "SELECT skillID, class, level, cap FROM skill_caps ORDER BY skillID, class, level";
auto results = QueryDatabase(query);
if (!results.Success()) {
LogError("Error loading skill caps from database: {}", results.ErrorMessage().c_str());
return;
}
for(auto& row = results.begin(); row != results.end(); ++row) {
const uint8 skillID = Strings::ToUnsignedInt(row[0]);
const uint8 class_ = Strings::ToUnsignedInt(row[1]) - 1;
const uint8 level = Strings::ToUnsignedInt(row[2]);
const uint16 cap = Strings::ToUnsignedInt(row[3]);
if(skillID >= skill_count || class_ >= class_count || level >= level_count)
continue;
const uint32 index = (((class_ * skill_count) + skillID) * level_count) + level;
skill_caps_table[index] = cap;
}
}
uint16 SharedDatabase::GetSkillCap(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const
{
if(!skill_caps_mmf) {
return 0;
}
if(Class_ == 0)
return 0;
int SkillMaxLevel = RuleI(Character, SkillCapMaxLevel);
if(SkillMaxLevel < 1) {
SkillMaxLevel = RuleI(Character, MaxLevel);
}
const uint32 class_count = Class::PLAYER_CLASS_COUNT;
const uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1;
const uint32 level_count = HARD_LEVEL_CAP + 1;
if(Class_ > class_count || static_cast<uint32>(Skill) > skill_count || Level > level_count) {
return 0;
}
if(Level > static_cast<uint8>(SkillMaxLevel)){
Level = static_cast<uint8>(SkillMaxLevel);
}
const uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count) + Level;
const uint16 *skill_caps_table = static_cast<uint16*>(skill_caps_mmf->Get());
return skill_caps_table[index];
}
uint8 SharedDatabase::GetTrainLevel(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const
{
if(!skill_caps_mmf) {
return 0;
}
if(Class_ == 0)
return 0;
int SkillMaxLevel = RuleI(Character, SkillCapMaxLevel);
if (SkillMaxLevel < 1) {
SkillMaxLevel = RuleI(Character, MaxLevel);
}
const uint32 class_count = Class::PLAYER_CLASS_COUNT;
const uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1;
const uint32 level_count = HARD_LEVEL_CAP + 1;
if(Class_ > class_count || static_cast<uint32>(Skill) > skill_count || Level > level_count) {
return 0;
}
uint8 ret = 0;
if(Level > static_cast<uint8>(SkillMaxLevel)) {
const uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count);
const uint16 *skill_caps_table = static_cast<uint16*>(skill_caps_mmf->Get());
for(uint8 x = 0; x < Level; x++){
if(skill_caps_table[index + x]){
ret = x;
break;
}
}
}
else
{
const uint32 index = ((((Class_ - 1) * skill_count) + Skill) * level_count);
const uint16 *skill_caps_table = static_cast<uint16*>(skill_caps_mmf->Get());
for(int x = 0; x < SkillMaxLevel; x++){
if(skill_caps_table[index + x]){
ret = x;
break;
}
}
}
if(ret > GetSkillCap(Class_, Skill, Level))
ret = static_cast<uint8>(GetSkillCap(Class_, Skill, Level));
return ret;
}
void SharedDatabase::LoadDamageShieldTypes(SPDat_Spell_Struct* sp, int32 iMaxSpellID) {
const std::string query = StringFormat("SELECT `spellid`, `type` FROM `damageshieldtypes` WHERE `spellid` > 0 "
"AND `spellid` <= %i", iMaxSpellID);
@@ -1984,9 +1854,9 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
sp[tempid].min_range = Strings::ToFloat(row[231]);
sp[tempid].no_remove = Strings::ToBool(row[232]);
sp[tempid].damage_shield_type = 0;
}
}
LoadDamageShieldTypes(sp, max_spells);
LoadDamageShieldTypes(sp, max_spells);
}
void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* message) {
-8
View File
@@ -160,14 +160,6 @@ public:
uint32 GetSharedItemsCount() { return m_shared_items_count; }
uint32 GetItemsCount();
/**
* skills
*/
void LoadSkillCaps(void *data);
bool LoadSkillCaps(const std::string &prefix);
uint16 GetSkillCap(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const;
uint8 GetTrainLevel(uint8 Class_, EQ::skills::SkillType Skill, uint8 Level) const;
/**
* spells
*/
+91
View File
@@ -0,0 +1,91 @@
#include "skill_caps.h"
SkillCaps *SkillCaps::SetContentDatabase(Database *db)
{
m_content_database = db;
return this;
}
SkillCapsRepository::SkillCaps SkillCaps::GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level)
{
if (!IsPlayerClass(class_id)) {
return SkillCapsRepository::NewEntity();
}
for (const auto &e: m_skill_caps) {
if (
e.class_id == class_id &&
e.level == level &&
static_cast<EQ::skills::SkillType>(e.skill_id) == skill_id
) {
return e;
}
}
return SkillCapsRepository::NewEntity();
}
uint8 SkillCaps::GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level)
{
if (
!IsPlayerClass(class_id) ||
class_id > Class::PLAYER_CLASS_COUNT ||
static_cast<uint32>(skill_id) > (EQ::skills::HIGHEST_SKILL + 1)
) {
return 0;
}
const uint8 skill_cap_max_level = (
RuleI(Character, SkillCapMaxLevel) > 0 ?
RuleI(Character, SkillCapMaxLevel) :
RuleI(Character, MaxLevel)
);
const uint8 max_level = level > skill_cap_max_level ? level : skill_cap_max_level;
for (const auto &e: m_skill_caps) {
for (uint8 current_level = 1; current_level <= max_level; current_level++) {
if (
e.class_id == class_id &&
static_cast<EQ::skills::SkillType>(e.skill_id) == skill_id &&
e.level == current_level
) {
return current_level;
}
}
}
return 0;
}
void SkillCaps::LoadSkillCaps()
{
const auto &l = SkillCapsRepository::All(*m_content_database);
m_skill_caps.reserve(l.size());
for (const auto &e: l) {
if (
e.level < 1 ||
!IsPlayerClass(e.class_id) ||
static_cast<EQ::skills::SkillType>(e.skill_id) >= EQ::skills::SkillCount
) {
continue;
}
m_skill_caps.emplace_back(e);
}
LogInfo(
"Loaded [{}] Skill Cap Entr{}",
l.size(),
l.size() != 1 ? "ies" : "y"
);
}
void SkillCaps::ReloadSkillCaps()
{
ClearSkillCaps();
LoadSkillCaps();
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef CODE_SKILL_CAPS_H
#define CODE_SKILL_CAPS_H
#include "repositories/skill_caps_repository.h"
#include "types.h"
#include "classes.h"
#include "skills.h"
class SkillCaps {
public:
inline void ClearSkillCaps() { m_skill_caps.clear(); }
SkillCapsRepository::SkillCaps GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level);
uint8 GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level);
void LoadSkillCaps();
void ReloadSkillCaps();
SkillCaps *SetContentDatabase(Database *db);
private:
Database *m_content_database{};
std::vector<SkillCapsRepository::SkillCaps> m_skill_caps = {};
};
extern SkillCaps skill_caps;
#endif //CODE_SKILL_CAPS_H
+1 -1
View File
@@ -18,10 +18,10 @@
*/
#include "skills.h"
#include "classes.h"
#include <string.h>
bool EQ::skills::IsTradeskill(SkillType skill)
{
switch (skill) {
-1
View File
@@ -26,7 +26,6 @@
#include <map>
#include <vector>
namespace EQ
{
namespace skills {
+1 -1
View File
@@ -1257,7 +1257,7 @@ typedef enum {
#define SE_Ff_Override_NotFocusable 460 // implemented, @Fc, Allow spell to be focused event if flagged with 'not_focusable' in spell table, base: 1
#define SE_ImprovedDamage2 461 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct
#define SE_FcDamageAmt2 462 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt
//#define SE_Shield_Target 463 //
#define SE_Shield_Target 463 // implemented, Base1 % damage shielded on target
#define SE_PC_Pet_Rampage 464 // implemented - Base1 % chance to do rampage for base2 % of damage each melee round
#define SE_PC_Pet_AE_Rampage 465 // implemented - Base1 % chance to do AE rampage for base2 % of damage each melee round
#define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit.
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.45.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.48.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9264
#define CURRENT_BINARY_DATABASE_VERSION 9268
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9043
#endif
+225 -891
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "22.45.0",
"version": "22.48.0",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+28
View File
@@ -0,0 +1,28 @@
# Description
Please include a summary of the changes and the related issue (Why is this change necessary). Please also include relevant motivation and context. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
# Testing
Attach images and describe testing done to validate functionality.
Clients tested:
# Checklist
- [ ] I have tested my changes
- [ ] I have performed a self-review of my code. Ensuring variables, functions and methods are named in a human-readable way, comments are added only where naming of variables, functions and methods can't give enough context.
- [ ] I have made corresponding changes to the documentation (if applicable, if not delete this line)
- [ ] I own the changes of my code and take responsibility for the potential issues that occur
- [ ] If my changes make database schema changes, I have tested the changes on a local database (attach image). Updated version.h CURRENT_BINARY_DATABASE_VERSION to the new version. (Delete this if not applicable)
-2
View File
@@ -4,13 +4,11 @@ SET(shared_memory_sources
items.cpp
main.cpp
spells.cpp
skill_caps.cpp
)
SET(shared_memory_headers
items.h
spells.h
skill_caps.h
)
ADD_EXECUTABLE(shared_memory ${shared_memory_sources} ${shared_memory_headers})
+1 -17
View File
@@ -28,7 +28,6 @@
#include "../common/eqemu_exception.h"
#include "../common/strings.h"
#include "items.h"
#include "skill_caps.h"
#include "spells.h"
#include "../common/content/world_content_service.h"
#include "../common/zone_store.h"
@@ -183,7 +182,6 @@ int main(int argc, char **argv)
bool load_all = true;
bool load_items = false;
bool load_loot = false;
bool load_skill_caps = false;
bool load_spells = false;
if (argc > 1) {
@@ -197,11 +195,7 @@ int main(int argc, char **argv)
break;
case 's':
if (strcasecmp("skill_caps", argv[i]) == 0) {
load_skill_caps = true;
load_all = false;
}
else if (strcasecmp("spells", argv[i]) == 0) {
if (strcasecmp("spells", argv[i]) == 0) {
load_spells = true;
load_all = false;
}
@@ -236,16 +230,6 @@ int main(int argc, char **argv)
}
}
if (load_all || load_skill_caps) {
LogInfo("Loading skill caps");
try {
LoadSkillCaps(&content_db, hotfix_name);
} catch (std::exception &ex) {
LogError("{}", ex.what());
return 1;
}
}
if (load_all || load_spells) {
LogInfo("Loading spells");
try {
-45
View File
@@ -1,45 +0,0 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2013 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 "skill_caps.h"
#include "../common/global_define.h"
#include "../common/shareddb.h"
#include "../common/ipc_mutex.h"
#include "../common/memory_mapped_file.h"
#include "../common/eqemu_exception.h"
#include "../common/classes.h"
#include "../common/features.h"
void LoadSkillCaps(SharedDatabase *database, const std::string &prefix) {
EQ::IPCMutex mutex("skill_caps");
mutex.Lock();
uint32 class_count = Class::PLAYER_CLASS_COUNT;
uint32 skill_count = EQ::skills::HIGHEST_SKILL + 1;
uint32 level_count = HARD_LEVEL_CAP + 1;
uint32 size = (class_count * skill_count * level_count * sizeof(uint16));
auto Config = EQEmuConfig::get();
std::string file_name = Config->SharedMemDir + prefix + std::string("skill_caps");
EQ::MemoryMappedFile mmf(file_name, size);
mmf.ZeroFile();
void *ptr = mmf.Get();
database->LoadSkillCaps(ptr);
mutex.Unlock();
}
-29
View File
@@ -1,29 +0,0 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2013 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
*/
#ifndef __EQEMU_SHARED_MEMORY_SKILL_CAPS_H
#define __EQEMU_SHARED_MEMORY_SKILL_CAPS_H
#include <string>
#include "../common/eqemu_config.h"
class SharedDatabase;
void LoadSkillCaps(SharedDatabase *database, const std::string &prefix);
#endif
@@ -280,7 +280,7 @@ foreach my $table_to_generate (@tables) {
# column names (string)
$column_names_quoted .= sprintf("\t\t\t\"%s\",\n", format_column_name_for_mysql($column_name));
if ($data_type =~ /datetime/) {
if ($data_type =~ /datetime|timestamp/) {
$select_column_names_quoted .= sprintf("\t\t\t\"UNIX_TIMESTAMP(%s)\",\n", format_column_name_for_mysql($column_name));
}
else {
@@ -293,7 +293,7 @@ foreach my $table_to_generate (@tables) {
if ($data_type =~ /int|float|double|decimal/) {
$query_value = sprintf('" + std::to_string(e.%s));', $column_name_formatted);
}
elsif ($data_type =~ /datetime/) {
elsif ($data_type =~ /datetime|timestamp/) {
$query_value = sprintf('FROM_UNIXTIME(" + (e.%s > 0 ? std::to_string(e.%s) : "null") + ")");', $column_name_formatted, $column_name_formatted);
}
@@ -309,7 +309,7 @@ foreach my $table_to_generate (@tables) {
if ($data_type =~ /int|float|double|decimal/) {
$value = sprintf('std::to_string(e.%s)', $column_name_formatted);
}
elsif ($data_type =~ /datetime/) {
elsif ($data_type =~ /datetime|timestamp/) {
$value = sprintf('"FROM_UNIXTIME(" + (e.%s > 0 ? std::to_string(e.%s) : "null") + ")"', $column_name_formatted, $column_name_formatted);
}
@@ -332,7 +332,7 @@ foreach my $table_to_generate (@tables) {
$all_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? strtoll(row[%s], nullptr, 10) : %s;\n", $column_name_formatted, $index, $index, $default_value);
$find_one_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? strtoll(row[%s], nullptr, 10) : %s;\n", $column_name_formatted, $index, $index, $default_value);
}
elsif ($data_type =~ /datetime/) {
elsif ($data_type =~ /datetime|timestamp/) {
$all_entries .= sprintf("\t\t\te.%-${longest_column_length}s = strtoll(row[%s] ? row[%s] : \"-1\", nullptr, 10);\n", $column_name_formatted, $index, $index);
$find_one_entries .= sprintf("\t\t\te.%-${longest_column_length}s = strtoll(row[%s] ? row[%s] : \"-1\", nullptr, 10);\n", $column_name_formatted, $index, $index);
}
@@ -591,7 +591,7 @@ sub translate_mysql_data_type_to_c
elsif ($mysql_data_type =~ /double/) {
$struct_data_type = 'double';
}
elsif ($mysql_data_type =~ /datetime/) {
elsif ($mysql_data_type =~ /datetime|timestamp/) {
$struct_data_type = 'time_t';
}
+112 -82
View File
@@ -52,6 +52,10 @@
#include "../common/repositories/player_event_logs_repository.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/events/player_event_logs.h"
#include "../common/content/world_content_service.h"
#include "../common/repositories/group_id_repository.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/skill_caps.h"
#include <iostream>
#include <iomanip>
@@ -189,9 +193,10 @@ bool Client::CanTradeFVNoDropItem()
void Client::SendEnterWorld(std::string name)
{
char char_name[64] = { 0 };
if (is_player_zoning && database.GetLiveChar(GetAccountID(), char_name)) {
if(database.GetAccountIDByChar(char_name) != GetAccountID()) {
std::string live_name {};
if (is_player_zoning) {
live_name = database.GetLiveChar(GetAccountID());
if(database.GetAccountIDByChar(live_name) != GetAccountID()) {
eqs->Close();
return;
} else {
@@ -199,8 +204,8 @@ void Client::SendEnterWorld(std::string name)
}
}
auto outapp = new EQApplicationPacket(OP_EnterWorld, strlen(char_name) + 1);
memcpy(outapp->pBuffer,char_name,strlen(char_name)+1);
auto outapp = new EQApplicationPacket(OP_EnterWorld, live_name.length() + 1);
memcpy(outapp->pBuffer, live_name.c_str(), live_name.length() + 1);
QueuePacket(outapp);
safe_delete(outapp);
}
@@ -599,79 +604,63 @@ bool Client::HandleNameApprovalPacket(const EQApplicationPacket *app)
}
bool Client::HandleGenerateRandomNamePacket(const EQApplicationPacket *app) {
// creates up to a 10 char name
char vowels[18]="aeiouyaeiouaeioe";
char cons[48]="bcdfghjklmnpqrstvwxzybcdgklmnprstvwbcdgkpstrkd";
char rndname[17]="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
char paircons[33]="ngrkndstshthphsktrdrbrgrfrclcr";
int rndnum=emu_random.Int(0, 75),n=1;
bool dlc=false;
bool vwl=false;
bool dbl=false;
if (rndnum>63)
{ // rndnum is 0 - 75 where 64-75 is cons pair, 17-63 is cons, 0-16 is vowel
rndnum=(rndnum-61)*2; // name can't start with "ng" "nd" or "rk"
rndname[0]=paircons[rndnum];
rndname[1]=paircons[rndnum+1];
n=2;
}
else if (rndnum>16)
{
rndnum-=17;
rndname[0]=cons[rndnum];
}
else
{
rndname[0]=vowels[rndnum];
vwl=true;
}
int namlen=emu_random.Int(5, 10);
for (int i=n;i<namlen;i++)
{
dlc=false;
if (vwl) //last char was a vowel
{ // so pick a cons or cons pair
rndnum=emu_random.Int(0, 62);
if (rndnum>46)
{ // pick a cons pair
if (i>namlen-3) // last 2 chars in name?
{ // name can only end in cons pair "rk" "st" "sh" "th" "ph" "sk" "nd" or "ng"
rndnum=emu_random.Int(0, 7)*2;
}
else
{ // pick any from the set
rndnum=(rndnum-47)*2;
}
rndname[i]=paircons[rndnum];
rndname[i+1]=paircons[rndnum+1];
dlc=true; // flag keeps second letter from being doubled below
i+=1;
}
else
{ // select a single cons
rndname[i]=cons[rndnum];
char newName[17] = {0};
bool unique = false;
while (!unique) {
std::string cons = "bcdfghjklmnpqrstvwxyz";
std::string vows = "aeou";
std::string allVows = "aeiou";
std::vector<std::string> endPhon = {"a", "e", "i", "o", "u", "os", "as", "us", "is", "y", "an", "en", "in", "on", "un"};
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> lenDist(5, 10);
std::uniform_int_distribution<int> firstCharDist(0, 1);
std::uniform_int_distribution<int> consDist(0, cons.size() - 1);
std::uniform_int_distribution<int> vowDist(0, vows.size() - 1);
std::uniform_int_distribution<int> allVowDist(0, allVows.size() - 1);
std::uniform_int_distribution<int> endPhonDist(0, endPhon.size() - 1);
int len = 0;
memset(newName, 0, sizeof(newName));
if (firstCharDist(gen) == 0) {
newName[len++] = vows[vowDist(gen)];
newName[len++] = cons[consDist(gen)];
} else {
newName[len++] = cons[consDist(gen)];
newName[len++] = allVows[allVowDist(gen)];
}
newName[0] = toupper(newName[0]);
while (len < lenDist(gen) - 1) {
if (len % 2 == 0) {
newName[len++] = cons[consDist(gen)];
} else {
newName[len++] = allVows[allVowDist(gen)];
}
}
else
{ // select a vowel
rndname[i]=vowels[emu_random.Int(0, 16)];
std::string end = endPhon[endPhonDist(gen)];
for (char c : end) {
if (len < 10) newName[len++] = c;
}
vwl=!vwl;
if (!dbl && !dlc)
{ // one chance at double letters in name
if (!emu_random.Int(0, i+9)) // chances decrease towards end of name
{
rndname[i+1]=rndname[i];
dbl=true;
i+=1;
if (database.CheckNameFilter(newName)) {
std::string query = StringFormat("SELECT `name` FROM `character_data` WHERE `name` = '%s'", newName);
auto res = database.QueryDatabase(query);
if (res.Success() && res.RowCount() == 0) {
unique = true;
}
}
}
rndname[0]=toupper(rndname[0]);
NameGeneration_Struct* ngs = (NameGeneration_Struct*)app->pBuffer;
memset(ngs->name,0,64);
strcpy(ngs->name,rndname);
memset(ngs->name, 0, 64);
strcpy(ngs->name, newName);
QueuePacket(app);
return true;
@@ -779,21 +768,50 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
auto ew = (EnterWorld_Struct *) app->pBuffer;
strn0cpy(char_name, ew->name, sizeof(char_name));
uint32 temporary_account_id = 0;
charid = database.GetCharacterInfo(char_name, &temporary_account_id, &zone_id, &instance_id);
if (!charid) {
const auto& l = CharacterDataRepository::GetWhere(
database,
fmt::format(
"`name` = '{}' LIMIT 1",
Strings::Escape(char_name)
)
);
if (l.empty()) {
LogInfo("Could not get CharInfo for [{}]", char_name);
eqs->Close();
return true;
}
auto r = content_service.FindZone(zone_id, instance_id);
if (r.zone_id && r.instance.id != instance_id) {
LogInfo(
"Zone [{}] has been remapped to instance_id [{}] from instance_id [{}] for client [{}]",
r.zone.short_name,
r.instance.id,
instance_id,
char_name
);
instance_id = r.instance.id;
}
const auto& e = l.front();
// Make sure this account owns this character
if (temporary_account_id != account_id) {
LogInfo("Account [{}] does not own the character named [{}] from account [{}]", account_id, char_name, temporary_account_id);
if (e.account_id != account_id) {
LogInfo(
"Account [{}] does not own the character named [{}] from account [{}]",
account_id,
char_name,
e.account_id
);
eqs->Close();
return true;
}
charid = e.id;
zone_id = e.zone_id;
instance_id = e.zone_instance;
// This can probably be moved outside and have another method return requested info (don't forget to remove the #include "../common/shareddb.h" above)
// (This is a literal translation of the original process..I don't see why it can't be changed to a single-target query over account iteration)
if (!is_player_zoning) {
@@ -884,7 +902,14 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
}
if(!is_player_zoning) {
database.SetGroupID(char_name, 0, charid);
GroupIdRepository::DeleteWhere(
database,
fmt::format(
"`character_id` = {} AND `name` = '{}'",
charid,
Strings::Escape(char_name)
)
);
database.SetLoginFlags(charid, false, false, 1);
} else {
auto group_id = database.GetGroupID(char_name);
@@ -903,14 +928,19 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
}
auto outapp = new EQApplicationPacket(OP_MOTD);
std::string motd_message;
if (database.GetVariable("MOTD", motd_message)) {
outapp->size = motd_message.length() + 1;
std::string motd = RuleS(World, MOTD);
if (!motd.empty()) {
outapp->size = motd.length() + 1;
outapp->pBuffer = new uchar[outapp->size];
memset(outapp->pBuffer, 0, outapp->size);
strcpy((char*)outapp->pBuffer, motd_message.c_str());
strcpy((char*) outapp->pBuffer, motd.c_str());
} else if (database.GetVariable("MOTD", motd)) {
outapp->size = motd.length() + 1;
outapp->pBuffer = new uchar[outapp->size];
memset(outapp->pBuffer, 0, outapp->size);
strcpy((char*) outapp->pBuffer, motd.c_str());
} else { // Null Message of the Day. :)
outapp->size = 1;
outapp->size = 1;
outapp->pBuffer = new uchar[outapp->size];
outapp->pBuffer[0] = 0;
}
@@ -2105,7 +2135,7 @@ void Client::SetClassStartingSkills(PlayerProfile_Struct *pp)
i == EQ::skills::SkillAlcoholTolerance || i == EQ::skills::SkillBindWound)
continue;
pp->skills[i] = content_db.GetSkillCap(pp->class_, (EQ::skills::SkillType)i, 1);
pp->skills[i] = skill_caps.GetSkillCap(pp->class_, (EQ::skills::SkillType)i, 1).cap;
}
}
+1 -1
View File
@@ -344,7 +344,7 @@ bool ClientListEntry::CheckAuth(uint32 loginserver_account_id, const char *key_p
paccountid = database.CreateAccount(
loginserver_account_name,
0,
std::string(),
default_account_status,
source_loginserver,
LSID()
+17
View File
@@ -94,8 +94,25 @@ void ClientList::GetCLEIP(uint32 in_ip) {
int count = 0;
iterator.Reset();
const auto& zones = Strings::Split(RuleS(World, IPExemptionZones), ",");
while (iterator.MoreElements()) {
cle = iterator.GetData();
if (!zones.empty() && cle->zone()) {
auto it = std::ranges::find_if(
zones,
[cle](const auto& z) {
return Strings::ToUnsignedInt(z) == cle->zone();
}
);
if (it != zones.end()) {
iterator.Advance();
continue;
}
}
if (
cle->GetIP() == in_ip &&
(
+1 -2
View File
@@ -57,8 +57,7 @@ struct EQ::Net::ConsoleLoginStatus CheckLogin(const std::string &username, const
return ret;
}
char account_name[64];
database.GetAccountName(ret.account_id, account_name);
const std::string& account_name = database.GetAccountName(ret.account_id);
ret.account_name = account_name;
ret.status = database.CheckStatus(ret.account_id);
+2
View File
@@ -142,6 +142,7 @@ std::vector<Reload> reload_types = {
Reload{.command = "base_data", .opcode = ServerOP_ReloadBaseData, .desc = "Base Data"},
Reload{.command = "blocked_spells", .opcode = ServerOP_ReloadBlockedSpells, .desc = "Blocked Spells"},
Reload{.command = "commands", .opcode = ServerOP_ReloadCommands, .desc = "Commands"},
Reload{.command = "content_flags", .opcode = ServerOP_ReloadContentFlags, .desc = "Content Flags"},
Reload{.command = "data_buckets_cache", .opcode = ServerOP_ReloadDataBucketsCache, .desc = "Data Buckets Cache"},
Reload{.command = "doors", .opcode = ServerOP_ReloadDoors, .desc = "Doors"},
Reload{.command = "dztemplates", .opcode = ServerOP_ReloadDzTemplates, .desc = "Dynamic Zone Templates"},
@@ -155,6 +156,7 @@ std::vector<Reload> reload_types = {
Reload{.command = "opcodes", .opcode = ServerOP_ReloadOpcodes, .desc = "Opcodes"},
Reload{.command = "perl_export", .opcode = ServerOP_ReloadPerlExportSettings, .desc = "Perl Event Export Settings"},
Reload{.command = "rules", .opcode = ServerOP_ReloadRules, .desc = "Rules"},
Reload{.command = "skill_caps", .opcode = ServerOP_ReloadSkillCaps, .desc = "Skill Caps"},
Reload{.command = "static", .opcode = ServerOP_ReloadStaticZoneData, .desc = "Static Zone Data"},
Reload{.command = "tasks", .opcode = ServerOP_ReloadTasks, .desc = "Tasks"},
Reload{.command = "titles", .opcode = ServerOP_ReloadTitles, .desc = "Titles"},
+9 -1
View File
@@ -86,8 +86,9 @@
#include "world_boot.h"
#include "../common/path_manager.h"
#include "../common/events/player_event_logs.h"
#include "../common/skill_caps.h"
SkillCaps skill_caps;
ZoneStore zone_store;
ClientList client_list;
GroupLFPList LFPGroupList;
@@ -188,6 +189,13 @@ int main(int argc, char **argv)
RegisterConsoleFunctions(console);
}
content_service.SetDatabase(&database)
->SetContentDatabase(&content_db)
->SetExpansionContext()
->ReloadContentFlags();
skill_caps.SetContentDatabase(&content_db)->LoadSkillCaps();
std::unique_ptr<EQ::Net::ServertalkServer> server_connection;
server_connection = std::make_unique<EQ::Net::ServertalkServer>();
+11 -1
View File
@@ -230,13 +230,23 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
case ServerOP_GuildChannel:
case ServerOP_GuildURL:
case ServerOP_GuildMemberRemove:
case ServerOP_GuildMemberAdd:
case ServerOP_GuildSendGuildList:
case ServerOP_GuildMembersList:
{
zoneserver_list.SendPacketToBootedZones(pack);
break;
}
case ServerOP_GuildMemberAdd:
{
auto in = (ServerOP_GuildMessage_Struct *)pack->pBuffer;
auto guild = GetGuildByGuildID(in->guild_id);
if (!guild) {
BaseGuildManager::RefreshGuild(in->guild_id);
}
zoneserver_list.SendPacketToBootedZones(pack);
break;
}
default:
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
break;
-4
View File
@@ -292,10 +292,6 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
LogError("Error: Could not load item data. But ignoring");
}
if (!content_db.LoadSkillCaps(std::string(hotfix_name))) {
LogError("Error: Could not load skill cap data. But ignoring");
}
guild_mgr.LoadGuilds();
guild_mgr.LoadTributes();
+7 -6
View File
@@ -49,6 +49,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/patches/patches.h"
#include "../zone/data_bucket.h"
#include "../common/repositories/guild_tributes_repository.h"
#include "../common/skill_caps.h"
extern ClientList client_list;
extern GroupLFPList LFPGroupList;
@@ -1100,7 +1101,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
}
auto smotd = (ServerMotd_Struct*) pack->pBuffer;
database.SetVariable("MOTD", smotd->motd);
RuleManager::Instance()->SetRule("MOTD", smotd->motd, &database, true, true);
zoneserver_list.SendPacket(pack);
break;
}
@@ -1436,6 +1437,11 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_ReloadSkillCaps: {
zoneserver_list.SendPacket(pack);
skill_caps.ReloadSkillCaps();
break;
}
case ServerOP_ReloadRules: {
zoneserver_list.SendPacket(pack);
RuleManager::Instance()->LoadRules(&database, "default", true);
@@ -1496,11 +1502,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
LogInfo("Error: Could not load item data. But ignoring");
}
LogInfo("Loading skill caps");
if (!content_db.LoadSkillCaps(hotfix_name)) {
LogInfo("Error: Could not load skill cap data. But ignoring");
}
zoneserver_list.SendPacket(pack);
break;
}
+3
View File
@@ -51,6 +51,7 @@ SET(zone_sources
loot.cpp
lua_bot.cpp
lua_bit.cpp
lua_buff.cpp
lua_corpse.cpp
lua_client.cpp
lua_door.cpp
@@ -105,6 +106,7 @@ SET(zone_sources
pathfinder_null.cpp
pathing.cpp
perl_bot.cpp
perl_buff.cpp
perl_client.cpp
perl_doors.cpp
perl_entity.cpp
@@ -207,6 +209,7 @@ SET(zone_headers
horse.h
lua_bot.h
lua_bit.h
lua_buff.h
lua_client.h
lua_corpse.h
lua_door.h
+25 -15
View File
@@ -621,28 +621,38 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack)
// NPC *npc1, *npc2;
int reverse;
if(!zone->CanDoCombat())
return false;
// some special cases
if(!target)
return false;
if(this == target) // you can attack yourself
return true;
if(target->GetSpecialAbility(NO_HARM_FROM_CLIENT)){
if (!zone->CanDoCombat()) {
return false;
}
if (target->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT) && IsClient())
// some special cases
if (!target) {
return false;
}
if (target->GetSpecialAbility(IMMUNE_DAMAGE_NPC) && IsNPC())
return false;
if (this == target) { // you can attack yourself
return true;
}
if (target->IsHorse())
if (target->GetSpecialAbility(NO_HARM_FROM_CLIENT)) {
return false;
}
if (IsBot() && target->GetSpecialAbility(IMMUNE_DAMAGE_BOT)) {
return false;
}
if (IsClient() && target->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT)) {
return false;
}
if (IsNPC() && target->GetSpecialAbility(IMMUNE_DAMAGE_NPC)) {
return false;
}
if (target->IsHorse()) {
return false;
}
// can't damage own pet (applies to everthing)
Mob *target_owner = target->GetOwner();
+1 -3
View File
@@ -57,9 +57,7 @@ EQ::Net::WebsocketLoginStatus CheckLogin(
return ret;
}
char account_name[64];
database.GetAccountName(static_cast<uint32>(ret.account_id), account_name);
ret.account_name = account_name;
ret.account_name = database.GetAccountName(static_cast<uint32>(ret.account_id));
ret.logged_in = true;
ret.status = database.CheckStatus(ret.account_id);
return ret;
+141 -49
View File
@@ -1005,7 +1005,7 @@ double Mob::RollD20(int offense, int mitigation)
auto atk_roll = zone->random.Roll0(offense + 5);
auto def_roll = zone->random.Roll0(mitigation + 5);
int avg = (offense + mitigation + 10) / 2;
int avg = std::max(1, (offense + mitigation + 10) / 2);
int index = std::max(0, (atk_roll - def_roll) + (avg / 2));
index = EQ::Clamp((index * 20) / avg, 0, 19);
@@ -1687,13 +1687,6 @@ bool Mob::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
}
}
//used by complete heal and #heal
void Mob::Heal()
{
SetMaxHP();
SendHPUpdate();
}
void Client::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special)
{
if (dead || IsCorpse())
@@ -2618,12 +2611,6 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
}
if (m.member) {
m.member->RecordKilledNPCEvent(this);
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_KILLED_MERIT)) {
parse->EventNPC(EVENT_KILLED_MERIT, this, m.member, "killed", 0);
}
if (RuleB(NPC, EnableMeritBasedFaction)) {
m.member->SetFactionLevel(
m.member->CharacterID(),
@@ -2683,18 +2670,11 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
}
}
/* Send the EVENT_KILLED_MERIT event and update kill tasks
* for all group members */
/* Update kill tasks for all group members */
for (const auto& m : killer_group->members) {
if (m && m->IsClient()) {
Client* c = m->CastToClient();
c->RecordKilledNPCEvent(this);
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_KILLED_MERIT)) {
parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0);
}
if (RuleB(NPC, EnableMeritBasedFaction)) {
c->SetFactionLevel(
c->CharacterID(),
@@ -2756,13 +2736,6 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
}
}
/* Send the EVENT_KILLED_MERIT event */
give_exp_client->RecordKilledNPCEvent(this);
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_KILLED_MERIT)) {
parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0);
}
if (RuleB(NPC, EnableMeritBasedFaction)) {
give_exp_client->SetFactionLevel(
give_exp_client->CharacterID(),
@@ -2843,6 +2816,9 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
entity_list.RemoveFromAutoXTargets(this);
// Here we create the corpse.
DeleteInvalidQuestLoot();
corpse = new Corpse(
this,
&m_loot_items,
@@ -2991,6 +2967,17 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
m_combat_record.Stop();
if (give_exp_client && !IsCorpse()) {
const auto& v = give_exp_client->GetRaidOrGroupOrSelf(true);
for (const auto& m : v) {
m->CastToClient()->RecordKilledNPCEvent(this);
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_KILLED_MERIT)) {
parse->EventNPC(EVENT_KILLED_MERIT, this, m, "killed", 0);
}
}
}
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_DEATH_COMPLETE)) {
const auto& export_string = fmt::format(
"{} {} {} {} {} {} {} {} {}",
@@ -3091,26 +3078,37 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
TryTriggerOnCastRequirement();
}
if (IsClient() && !IsAIControlled())
if (IsClient() && !IsAIControlled()) {
return;
}
if (IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO))
if (IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) {
return;
}
if (GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC())
if (other->IsBot() && GetSpecialAbility(IMMUNE_AGGRO_BOT)) {
return;
}
if (GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient())
if (other->IsClient() && GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) {
return;
}
if (IsValidSpell(spell_id) && IsNoDetrimentalSpellAggroSpell(spell_id))
if (other->IsNPC() && GetSpecialAbility(IMMUNE_AGGRO_NPC)) {
return;
}
if (other == myowner)
if (IsValidSpell(spell_id) && IsNoDetrimentalSpellAggroSpell(spell_id)) {
return;
}
if (other->GetSpecialAbility(IMMUNE_AGGRO_ON))
if (other == myowner) {
return;
}
if (other->GetSpecialAbility(IMMUNE_AGGRO_ON)) {
return;
}
if (GetSpecialAbility(NPC_TUNNELVISION)) {
int tv_mod = GetSpecialAbilityParam(NPC_TUNNELVISION, 0);
@@ -3194,8 +3192,9 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
// owner must get on list, but he's not actually gained any hate yet
if (
!owner->GetSpecialAbility(IMMUNE_AGGRO) &&
!(GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && owner->IsClient()) &&
!(GetSpecialAbility(IMMUNE_AGGRO_NPC) && owner->IsNPC())
!(owner->IsBot() && GetSpecialAbility(IMMUNE_AGGRO_BOT)) &&
!(owner->IsClient() && GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) &&
!(owner->IsNPC() && GetSpecialAbility(IMMUNE_AGGRO_NPC))
) {
if (owner->IsClient() && !CheckAggro(owner)) {
owner->CastToClient()->AddAutoXTarget(this);
@@ -3209,8 +3208,9 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (
!mypet->IsFamiliar() &&
!mypet->GetSpecialAbility(IMMUNE_AGGRO) &&
!(mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && IsClient()) &&
!(mypet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && IsNPC())
!(IsBot() && mypet->GetSpecialAbility(IMMUNE_AGGRO_BOT)) &&
!(IsClient() && mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) &&
!(IsNPC() && mypet->GetSpecialAbility(IMMUNE_AGGRO_NPC))
) {
mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
@@ -3219,8 +3219,9 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (
myowner->IsAIControlled() &&
!myowner->GetSpecialAbility(IMMUNE_AGGRO) &&
!(GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && myowner->IsClient()) &&
!(GetSpecialAbility(IMMUNE_AGGRO_NPC) && myowner->IsNPC())
!(myowner->IsBot() && GetSpecialAbility(IMMUNE_AGGRO_BOT)) &&
!(myowner->IsClient() && GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) &&
!(myowner->IsNPC() && GetSpecialAbility(IMMUNE_AGGRO_NPC))
) {
myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
@@ -3966,6 +3967,18 @@ bool Mob::CheckDoubleAttack()
}
void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks special) {
#ifdef LUA_EQEMU
int64 lua_ret = 0;
bool ignore_default = false;
lua_ret = LuaParser::Instance()->CommonDamage(this, attacker, damage, spell_id, static_cast<int>(skill_used), avoidable, buffslot, iBuffTic, static_cast<int>(special), ignore_default);
if (lua_ret != 0) {
damage = lua_ret;
}
if (ignore_default) {
//return lua_ret;
}
#endif
// This method is called with skill_used=ABJURE for Damage Shield damage.
bool FromDamageShield = (skill_used == EQ::skills::SkillAbjuration);
bool ignore_invul = false;
@@ -4060,8 +4073,9 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
!pet->GetSpecialAbility(IMMUNE_AGGRO) &&
!pet->IsEngaged() &&
attacker &&
!(pet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) &&
!(pet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) &&
!(attacker->IsBot() && pet->GetSpecialAbility(IMMUNE_AGGRO_BOT)) &&
!(attacker->IsClient() && pet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) &&
!(attacker->IsNPC() && pet->GetSpecialAbility(IMMUNE_AGGRO_NPC)) &&
attacker != this &&
!attacker->IsCorpse() &&
!pet->IsGHeld() &&
@@ -4692,6 +4706,19 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
void Mob::HealDamage(uint64 amount, Mob* caster, uint16 spell_id)
{
#ifdef LUA_EQEMU
uint64 lua_ret = 0;
bool ignore_default = false;
lua_ret = LuaParser::Instance()->HealDamage(this, caster, amount, spell_id, ignore_default);
if (lua_ret != 0) {
amount = lua_ret;
}
if (ignore_default) {
//return lua_ret;
}
#endif
int64 maxhp = GetMaxHP();
int64 curhp = GetHP();
uint64 acthealed = 0;
@@ -6194,7 +6221,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac
if (headshot > 0) {
hit.damage_done = headshot;
}
else if (GetClass() == Class::Ranger && GetLevel() > 50) { // no double dmg on headshot
else if (GetClass() == Class::Ranger && GetLevel() >= RuleI(Combat, ArcheryBonusLevelRequirement)) { // no double dmg on headshot
if ((defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) || !RuleB(Combat, ArcheryBonusRequiresStationary)) {
hit.damage_done *= 2;
MessageString(Chat::MeleeCrit, BOW_DOUBLE_DAMAGE);
@@ -6286,9 +6313,24 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac
hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + defender->GetPositionalDmgTakenAmt(this);
if (defender->GetShielderID()) {
DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill);
hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage
if (defender->GetShielderID() || defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]) {
bool use_shield_ability = true;
//If defender is being shielded by an ability AND has a shield spell effect buff use highest mitigation value.
if ((defender->GetShieldTargetMitigation() && defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]) &&
(defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] >= defender->GetShieldTargetMitigation())){
bool use_shield_ability = false;
}
//use targeted /shield ability values
if (defender->GetShielderID() && use_shield_ability) {
DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill);
hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage
}
//use spell effect SPA 463 values
else if (defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]){
DoShieldDamageOnShielderSpellEffect(defender, hit.damage_done, hit.skill);
hit.damage_done -= hit.damage_done * defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] / 100;
}
}
CheckNumHitsRemaining(NumHit::OutgoingHitSuccess);
@@ -6331,7 +6373,57 @@ void Mob::DoShieldDamageOnShielder(Mob *shield_target, int64 hit_damage_done, EQ
hit_damage_done -= hit_damage_done * mitigation / 100;
shielder->Damage(this, hit_damage_done, SPELL_UNKNOWN, skillInUse, true, -1, false, m_specialattacks);
shielder->CheckNumHitsRemaining(NumHit::OutgoingHitSuccess);
}
void Mob::DoShieldDamageOnShielderSpellEffect(Mob* shield_target, int64 hit_damage_done, EQ::skills::SkillType skillInUse)
{
if (!shield_target) {
return;
}
/*
SPA 463 SE_SHIELD_TARGET
Live description: "Shields your target, taking a percentage of their damage".
Only example spell on live is an NPC who uses it during a raid event "Laurion's Song" expansion. SPA 54492 'Guardian Stance' Described as 100% Melee Shielding
Example of mechanic. Base value = 70. Caster puts buff on target. Each melee hit Buff Target takes 70% less damage, Buff Caster receives 30% of the melee damage.
Added mechanic to cause buff to fade if target or caster are seperated by a distance greater than the casting range of the spell. This allows similiar mechanics
to the /shield ability, without a range removal mechanic it would be too easy to abuse if put on a player spell. *can not confirm live does this currently
Can not be cast on self.
*/
if (shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT])
{
if (shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] >= 0)
{
Mob *shielder = entity_list.GetMob(shield_target->buffs[shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]].casterid);
if (!shielder) {
shield_target->BuffFadeBySlot(shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]);
return;
}
int shield_spell_id = shield_target->buffs[shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]].spellid;
if (!IsValidSpell(shield_spell_id)) {
return;
}
float max_range = spells[shield_spell_id].range;
if (spells[shield_spell_id].aoe_range > max_range) {
max_range = spells[shield_spell_id].aoe_range;
}
max_range += 5.0f; //small buffer in case casted at exactly max range.
if (shield_target->CalculateDistance(shielder->GetX(), shielder->GetY(), shielder->GetZ()) > max_range) {
shield_target->BuffFadeBySlot(shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]);
return;
}
int mitigation = 100 - shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT];
hit_damage_done -= hit_damage_done * mitigation / 100;
shielder->Damage(this, hit_damage_done, SPELL_UNKNOWN, skillInUse, true, -1, false, m_specialattacks);
}
}
}
void Mob::CommonBreakInvisibleFromCombat()
+24 -1
View File
@@ -2090,7 +2090,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
if (focus)
{
if (WornType){
if (RuleB(Spells, UseAdditiveFocusFromWornSlot)) {
if (RuleB(Spells, UseAdditiveFocusFromWornSlotWithLimits)) {
new_bonus->FocusEffectsWornWithLimits[focus] = spells[spell_id].effect_id[i];
}
else if (RuleB(Spells, UseAdditiveFocusFromWornSlot)) {
new_bonus->FocusEffectsWorn[focus] += spells[spell_id].base_value[i];
}
}
@@ -3298,6 +3301,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
break;
}
case SE_Shield_Target:
{
if (new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] < effect_value) {
new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] = effect_value;
new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] = buffslot;
}
break;
}
case SE_TriggerMeleeThreshold:
new_bonus->TriggerMeleeThreshold = true;
break;
@@ -4989,6 +5001,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
case SE_MaxHPChange:
if (negate_spellbonus) { spellbonuses.MaxHPChange = effect_value; }
if (negate_aabonus) { aabonuses.MaxHPChange = effect_value; }
if (negate_aabonus) { aabonuses.MaxHP = effect_value; }
if (negate_itembonus) { itembonuses.MaxHPChange = effect_value; }
break;
@@ -5916,6 +5929,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
if (negate_itembonus) { itembonuses.SkillProcSuccess[e] = effect_value; }
if (negate_aabonus) { aabonuses.SkillProcSuccess[e] = effect_value; }
}
break;
}
case SE_SkillProcAttempt: {
@@ -5925,6 +5939,15 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
if (negate_itembonus) { itembonuses.SkillProc[e] = effect_value; }
if (negate_aabonus) { aabonuses.SkillProc[e] = effect_value; }
}
break;
}
case SE_Shield_Target: {
if (negate_spellbonus) {
spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] = effect_value;
spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] = effect_value;
}
break;
}
}
}
+179 -121
View File
@@ -27,6 +27,7 @@
#include "../common/repositories/bot_starting_items_repository.h"
#include "../common/data_verification.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/skill_caps.h"
// This constructor is used during the bot create command
Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1) {
@@ -1171,7 +1172,7 @@ uint16 Bot::GetPrimarySkillValue() {
}
uint16 Bot::MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const {
return(content_db.GetSkillCap(class_, skillid, level));
return skill_caps.GetSkillCap(class_, skillid, level).cap;
}
uint32 Bot::GetTotalATK() {
@@ -2287,7 +2288,7 @@ bool Bot::TryMeditate() {
if (!IsMoving() && !spellend_timer.Enabled()) {
if (GetTarget() && AI_EngagedCastCheck()) {
BotMeditate(false);
} else if (GetArchetype() == ARCHETYPE_CASTER) {
} else if (GetArchetype() == Archetype::Caster) {
BotMeditate(true);
}
@@ -2684,16 +2685,19 @@ bool Bot::IsValidTarget(
return false;
}
const bool valid_target_state = (
HOLDING ||
bool invalid_target_state = false;
if (HOLDING ||
!tar->IsNPC() ||
tar->IsMezzed() ||
lo_distance > leash_distance ||
tar_distance > leash_distance
);
const bool valid_target = !GetAttackingFlag() && !CheckLosFN(tar) && !leash_owner->CheckLosFN(tar);
tar_distance > leash_distance ||
(!GetAttackingFlag() && !CheckLosFN(tar) && !leash_owner->CheckLosFN(tar)) ||
!IsAttackAllowed(tar)
) {
invalid_target_state = true;
}
if (valid_target_state || valid_target || !IsAttackAllowed(tar)) {
if (invalid_target_state) {
// Normally, we wouldn't want to do this without class checks..but, too many issues can arise if we let enchanter animation pets run rampant
if (HasPet()) {
GetPet()->RemoveFromHateList(tar);
@@ -2752,7 +2756,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner)
}
}
if (GetArchetype() == ARCHETYPE_CASTER) {
if (GetArchetype() == Archetype::Caster) {
BotMeditate(true);
}
}
@@ -3340,7 +3344,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) {
}
if (bot_spawn_limit >= 0 && spawned_bots_count >= bot_spawn_limit) {
database.SetGroupID(b->GetCleanName(), 0, b->GetBotID());
Group::RemoveFromGroup(b);
g->UpdatePlayer(bot_owner);
continue;
}
@@ -3352,7 +3356,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) {
bot_spawn_limit_class >= 0 &&
spawned_bot_count_class >= bot_spawn_limit_class
) {
database.SetGroupID(b->GetCleanName(), 0, b->GetBotID());
Group::RemoveFromGroup(b);
g->UpdatePlayer(bot_owner);
continue;
}
@@ -3372,7 +3376,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) {
}
if (!bot_owner->HasGroup()) {
database.SetGroupID(b->GetCleanName(), 0, b->GetBotID());
Group::RemoveFromGroup(b);
}
}
}
@@ -3629,8 +3633,8 @@ bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) {
bot->SetFollowID(0);
if (group->DelMember(bot)) {
group->DelMemberOOZ(bot->GetName());
database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID());
if (group->GroupCount() < 1) {
Group::RemoveFromGroup(bot);
if (group->GroupCount() < 2) {
group->DisbandGroup();
}
}
@@ -3643,7 +3647,7 @@ bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) {
group->members[i]->SetFollowID(0);
}
group->DisbandGroup();
database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID());
Group::RemoveFromGroup(bot);
}
Result = true;
}
@@ -5186,31 +5190,26 @@ void Bot::ProcessBotOwnerRefDelete(Mob* botOwner) {
}
}
int64 Bot::CalcMaxMana() {
switch(GetCasterClass()) {
case 'I':
max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + aabonuses.Mana + GroupLeadershipAAManaEnhancement());
max_mana += itembonuses.heroic_max_mana;
case 'W': {
max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + aabonuses.Mana + GroupLeadershipAAManaEnhancement());
max_mana += itembonuses.heroic_max_mana;
break;
}
case 'N': {
max_mana = 0;
break;
}
default: {
LogDebug("Invalid Class [{}] in CalcMaxMana", GetCasterClass());
max_mana = 0;
break;
}
int64 Bot::CalcMaxMana()
{
if (IsIntelligenceCasterClass() || IsWisdomCasterClass()) {
max_mana = (
GenerateBaseManaPoints() +
itembonuses.Mana +
spellbonuses.Mana +
aabonuses.Mana +
GroupLeadershipAAManaEnhancement()
);
max_mana += itembonuses.heroic_max_mana;
} else {
max_mana = 0;
}
if (current_mana > max_mana)
if (current_mana > max_mana) {
current_mana = max_mana;
else if (max_mana < 0)
} else if (max_mana < 0) {
max_mana = 0;
}
return max_mana;
}
@@ -5525,87 +5524,107 @@ bool Bot::DoCastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot
return Result;
}
int32 Bot::GenerateBaseManaPoints() {
int32 bot_mana = 0;
int32 WisInt = 0;
int32 Bot::GenerateBaseManaPoints()
{
int32 bot_mana = 0;
int32 WisInt = 0;
int32 MindLesserFactor, MindFactor;
int wisint_mana = 0;
int base_mana = 0;
int ConvertedWisInt = 0;
switch(GetCasterClass()) {
case 'I':
WisInt = INT;
if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
if (WisInt > 100) {
ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100);
if (WisInt > 201)
ConvertedWisInt -= ((WisInt - 201) * 5 / 4);
}
else
ConvertedWisInt = WisInt;
int wisint_mana = 0;
int base_mana = 0;
int ConvertedWisInt = 0;
if (GetLevel() < 41) {
wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000);
base_mana = (GetLevel() * 15);
} else if (GetLevel() < 81) {
wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100));
base_mana = (600 + ((GetLevel() - 40) * 30));
} else {
wisint_mana = (9 * ConvertedWisInt);
base_mana = (1800 + ((GetLevel() - 80) * 18));
if (IsIntelligenceCasterClass()) {
WisInt = INT;
if (
GetOwner() &&
GetOwner()->CastToClient() &&
GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD &&
RuleB(Character, SoDClientUseSoDHPManaEnd)
) {
if (WisInt > 100) {
ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100);
if (WisInt > 201) {
ConvertedWisInt -= ((WisInt - 201) * 5 / 4);
}
bot_mana = (base_mana + wisint_mana);
} else {
if (((WisInt - 199) / 2) > 0)
MindLesserFactor = ((WisInt - 199) / 2);
else
MindLesserFactor = 0;
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100)
bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
else
bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
ConvertedWisInt = WisInt;
}
break;
case 'W':
WisInt = WIS;
if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
if (WisInt > 100) {
ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100);
if (WisInt > 201)
ConvertedWisInt -= ((WisInt - 201) * 5 / 4);
} else
ConvertedWisInt = WisInt;
if (GetLevel() < 41) {
wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000);
base_mana = (GetLevel() * 15);
} else if (GetLevel() < 81) {
wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100));
base_mana = (600 + ((GetLevel() - 40) * 30));
} else {
wisint_mana = (9 * ConvertedWisInt);
base_mana = (1800 + ((GetLevel() - 80) * 18));
}
bot_mana = (base_mana + wisint_mana);
if (GetLevel() < 41) {
wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000);
base_mana = (GetLevel() * 15);
} else if (GetLevel() < 81) {
wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100));
base_mana = (600 + ((GetLevel() - 40) * 30));
} else {
if (((WisInt - 199) / 2) > 0)
MindLesserFactor = ((WisInt - 199) / 2);
else
MindLesserFactor = 0;
MindFactor = (WisInt - MindLesserFactor);
if (WisInt > 100)
bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
else
bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
wisint_mana = (9 * ConvertedWisInt);
base_mana = (1800 + ((GetLevel() - 80) * 18));
}
break;
default:
bot_mana = 0;
break;
bot_mana = (base_mana + wisint_mana);
} else {
if (((WisInt - 199) / 2) > 0) {
MindLesserFactor = ((WisInt - 199) / 2);
} else {
MindLesserFactor = 0;
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
} else {
bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
}
} else if (IsWisdomCasterClass()) {
WisInt = WIS;
if (
GetOwner() &&
GetOwner()->CastToClient() &&
GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD &&
RuleB(Character, SoDClientUseSoDHPManaEnd)
) {
if (WisInt > 100) {
ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100);
if (WisInt > 201) {
ConvertedWisInt -= ((WisInt - 201) * 5 / 4);
}
} else {
ConvertedWisInt = WisInt;
}
if (GetLevel() < 41) {
wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000);
base_mana = (GetLevel() * 15);
} else if (GetLevel() < 81) {
wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100));
base_mana = (600 + ((GetLevel() - 40) * 30));
} else {
wisint_mana = (9 * ConvertedWisInt);
base_mana = (1800 + ((GetLevel() - 80) * 18));
}
bot_mana = (base_mana + wisint_mana);
} else {
if (((WisInt - 199) / 2) > 0) {
MindLesserFactor = ((WisInt - 199) / 2);
} else {
MindLesserFactor = 0;
}
MindFactor = (WisInt - MindLesserFactor);
if (WisInt > 100) {
bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
} else {
bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
}
} else {
bot_mana = 0;
}
max_mana = bot_mana;
return bot_mana;
}
@@ -6216,11 +6235,10 @@ int64 Bot::CalcMaxHP() {
uint32 nd = 10000;
bot_hp += (GenerateBaseHitPoints() + itembonuses.HP);
bot_hp += itembonuses.heroic_max_hp;
nd += aabonuses.MaxHP;
nd += aabonuses.MaxHP + spellbonuses.MaxHPChange + itembonuses.MaxHPChange;
bot_hp = ((float)bot_hp * (float)nd / (float)10000);
bot_hp += (spellbonuses.HP + aabonuses.HP);
bot_hp += GroupLeadershipAAHealthEnhancement();
bot_hp += (bot_hp * ((spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f));
max_hp = bot_hp;
if (current_hp > max_hp)
current_hp = max_hp;
@@ -6582,14 +6600,14 @@ void Bot::ProcessBotGroupInvite(Client* c, std::string const& botName) {
entity_list.AddGroup(g);
database.SetGroupLeaderName(g->GetID(), c->GetName());
g->SaveGroupLeaderAA();
database.SetGroupID(c->GetName(), g->GetID(), c->CharacterID());
database.SetGroupID(invitedBot->GetCleanName(), g->GetID(), invitedBot->GetBotID());
g->AddToGroup(c);
g->AddToGroup(invitedBot);
} else {
delete g;
}
} else {
if (AddBotToGroup(invitedBot, c->GetGroup())) {
database.SetGroupID(invitedBot->GetCleanName(), c->GetGroup()->GetID(), invitedBot->GetBotID());
c->GetGroup()->AddToGroup(invitedBot);
}
}
} else if (invitedBot->HasGroup()) {
@@ -6740,7 +6758,7 @@ void Bot::CalcBotStats(bool showtext) {
SetLevel(GetBotOwner()->GetLevel());
for (int sindex = 0; sindex <= EQ::skills::HIGHEST_SKILL; ++sindex) {
skills[sindex] = content_db.GetSkillCap(GetClass(), (EQ::skills::SkillType)sindex, GetLevel());
skills[sindex] = skill_caps.GetSkillCap(GetClass(), (EQ::skills::SkillType)sindex, GetLevel()).cap;
}
taunt_timer.Start(1000);
@@ -7640,18 +7658,58 @@ void Bot::SetDefaultBotStance() {
_botStance = defaultStance;
}
void Bot::BotGroupSay(Mob *speaker, const char *msg, ...) {
void Bot::BotGroupSay(Mob* speaker, const char* msg, ...) {
char buf[1000];
va_list ap;
va_start(ap, msg);
vsnprintf(buf, 1000, msg, ap);
va_end(ap);
if (speaker->HasGroup()) {
Group *g = speaker->GetGroup();
if (g)
g->GroupMessage(speaker->CastToMob(), Language::CommonTongue, Language::MaxValue, buf);
} else
speaker->Say("%s", buf);
if (speaker->IsRaidGrouped()) {
Raid* r = entity_list.GetRaidByBotName(speaker->GetName());
if (r) {
for (const auto& m : r->members) {
if (m.member && !m.is_bot) {
m.member->FilteredMessageString(
speaker,
Chat::PetResponse,
FilterSocials,
GENERIC_SAY,
speaker->GetCleanName(),
buf
);
}
}
}
}
else if (speaker->HasGroup()) {
Group* g = speaker->GetGroup();
if (g) {
for (auto& m : g->members) {
if (m && !m->IsBot()) {
m->FilteredMessageString(
speaker,
Chat::PetResponse,
FilterSocials,
GENERIC_SAY,
speaker->GetCleanName(),
buf
);
}
}
}
}
else {
//speaker->Say("%s", buf);
speaker->GetOwner()->FilteredMessageString(
speaker,
Chat::PetResponse,
FilterSocials,
GENERIC_SAY,
speaker->GetCleanName(),
buf
);
}
}
bool Bot::UseDiscipline(uint32 spell_id, uint32 target) {
+6 -33
View File
@@ -184,35 +184,11 @@ bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& availa
if (
bot_name.empty() ||
bot_name.size() > 60 ||
!database.CheckUsedName(bot_name)
database.IsNameUsed(bot_name)
) {
return false;
}
const auto& bot_data = BotDataRepository::GetWhere(
database,
fmt::format(
"`name` LIKE '{}' LIMIT 1",
bot_name
)
);
if (!bot_data.empty()) {
return false;
}
const auto& character_data = CharacterDataRepository::GetWhere(
database,
fmt::format(
"`name` LIKE '{}' LIMIT 1",
bot_name
)
);
if (!character_data.empty()) {
return false;
}
available_flag = true;
return true;
@@ -1788,7 +1764,8 @@ bool BotDatabase::CreateCloneBotInventory(const uint32 bot_id, const uint32 clon
}
for (auto& e : l) {
e.bot_id = clone_id;
e.inventories_index = 0;
e.bot_id = clone_id;
}
return BotInventoriesRepository::InsertMany(database, l);
@@ -1921,18 +1898,14 @@ bool BotDatabase::LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 g
const auto& l = GroupIdRepository::GetWhere(
database,
fmt::format(
"`groupid` = {} AND `name` IN (SELECT `name` FROM `bot_data` WHERE `owner_id` = {})",
"`group_id` = {} AND `bot_id` != 0 AND `name` IN (SELECT `name` FROM `bot_data` WHERE `owner_id` = {})",
group_id,
owner_id
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
group_list.push_back(e.charid);
group_list.emplace_back(e.bot_id);
}
return true;
@@ -2367,7 +2340,7 @@ const uint8 BotDatabase::GetBotLevelByID(const uint32 bot_id)
return e.bot_id ? e.level : 0;
}
const std::string& BotDatabase::GetBotNameByID(const uint32 bot_id)
const std::string BotDatabase::GetBotNameByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
+1 -1
View File
@@ -164,7 +164,7 @@ public:
const uint8 GetBotGenderByID(const uint32 bot_id);
std::vector<uint32> GetBotIDsByCharacterID(const uint32 character_id, uint8 class_id = Class::None);
const uint8 GetBotLevelByID(const uint32 bot_id);
const std::string& GetBotNameByID(const uint32 bot_id);
const std::string GetBotNameByID(const uint32 bot_id);
const uint16 GetBotRaceByID(const uint32 bot_id);
class fail {
+1 -1
View File
@@ -98,7 +98,7 @@ void Raid::HandleBotGroupDisband(uint32 owner, uint32 gid)
auto r_group_members = GetRaidGroupMembers(GetGroup(b->GetName()));
auto g = new Group(b);
entity_list.AddGroup(g);
database.SetGroupID(b->GetCleanName(), g->GetID(), b->GetBotID());
g->AddToGroup(b);
database.SetGroupLeaderName(g->GetID(), b->GetName());
for (auto m: r_group_members) {
+4 -4
View File
@@ -591,7 +591,7 @@ bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
//Only check archetype if spell is not a group spell
//Hybrids get all buffs
switch (tar->GetArchetype()) {
case ARCHETYPE_CASTER:
case Archetype::Caster:
//TODO: probably more caster specific spell effects in here
if (
(
@@ -606,7 +606,7 @@ bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
continue;
}
break;
case ARCHETYPE_MELEE:
case Archetype::Melee:
if (
(
IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) ||
@@ -899,7 +899,7 @@ bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
switch (tar->GetArchetype())
{
case ARCHETYPE_CASTER:
case Archetype::Caster:
//TODO: probably more caster specific spell effects in here
if (IsEffectInSpell(s.SpellId, SE_AttackSpeed) || IsEffectInSpell(s.SpellId, SE_ATK) ||
IsEffectInSpell(s.SpellId, SE_STR) || IsEffectInSpell(s.SpellId, SE_ReverseDS))
@@ -907,7 +907,7 @@ bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
continue;
}
break;
case ARCHETYPE_MELEE:
case Archetype::Melee:
if (IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(s.SpellId, SE_ManaPool) ||
IsEffectInSpell(s.SpellId, SE_CastingLevel) || IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) ||
IsEffectInSpell(s.SpellId, SE_CurrentMana))
+256 -68
View File
@@ -57,6 +57,7 @@ extern volatile bool RunLoops;
#include "queryserv.h"
#include "mob_movement_manager.h"
#include "cheat_manager.h"
#include "lua_parser.h"
#include "../common/repositories/character_alternate_abilities_repository.h"
#include "../common/repositories/account_flags_repository.h"
@@ -68,10 +69,12 @@ extern volatile bool RunLoops;
#include "../common/repositories/discovered_items_repository.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/keyring_repository.h"
#include "../common/repositories/tradeskill_recipe_repository.h"
#include "../common/events/player_events.h"
#include "../common/events/player_event_logs.h"
#include "dialogue_window.h"
#include "../common/zone_store.h"
#include "../common/skill_caps.h"
extern QueryServ* QServ;
@@ -284,6 +287,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
PendingSacrifice = false;
controlling_boat_id = 0;
controlled_mob_id = 0;
qGlobals = nullptr;
if (!RuleB(Character, PerCharacterQglobalMaxLevel) && !RuleB(Character, PerCharacterBucketMaxLevel)) {
SetClientMaxLevel(0);
@@ -311,7 +315,6 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
aa_los_them_mob = nullptr;
los_status = false;
los_status_facing = false;
qGlobals = nullptr;
HideCorpseMode = HideCorpseNone;
PendingGuildInvitation = false;
@@ -2225,8 +2228,8 @@ void Client::ChangeLastName(std::string last_name) {
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
{
// check duplicate name
bool usedname = database.CheckUsedName((const char*) in_firstname);
if (!usedname) {
bool used_name = database.IsNameUsed((const char*) in_firstname);
if (used_name) {
return false;
}
@@ -2294,29 +2297,23 @@ void Client::ReadBook(BookRequest_Struct *book) {
BookText_Struct *out = (BookText_Struct *) outapp->pBuffer;
out->window = book->window;
if (ClientVersion() >= EQ::versions::ClientVersion::SoF) {
// SoF+ need to look up book type for the output message.
const EQ::ItemInstance *inst = nullptr;
if (book->invslot <= EQ::invbag::GENERAL_BAGS_END)
{
inst = m_inv[book->invslot];
}
if(inst)
out->type = inst->GetItem()->Book;
else
out->type = book->type;
}
else {
out->type = book->type;
}
out->type = book->type;
out->invslot = book->invslot;
out->target_id = book->target_id;
out->can_cast = 0; // todo: implement
out->can_scribe = 0; // todo: implement
out->can_scribe = false;
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && book->invslot <= EQ::invbag::GENERAL_BAGS_END)
{
const EQ::ItemInstance* inst = m_inv[book->invslot];
if (inst && inst->GetItem())
{
auto recipe = TradeskillRecipeRepository::GetWhere(content_db,
fmt::format("learned_by_item_id = {} LIMIT 1", inst->GetItem()->ID));
out->type = inst->GetItem()->Book;
out->can_scribe = !recipe.empty();
}
}
memcpy(out->booktext, booktxt2.c_str(), length);
@@ -2761,31 +2758,48 @@ void Client::CheckLanguageSkillIncrease(uint8 language_id, uint8 teacher_skill)
}
}
bool Client::HasSkill(EQ::skills::SkillType skill_id) const {
return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id));
}
bool Client::CanHaveSkill(EQ::skills::SkillType skill_id) const {
if (ClientVersion() < EQ::versions::ClientVersion::RoF2 && class_ == Class::Berserker && skill_id == EQ::skills::Skill1HPiercing)
skill_id = EQ::skills::Skill2HPiercing;
return(content_db.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)) > 0);
//if you don't have it by max level, then odds are you never will?
}
uint16 Client::MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const {
if (ClientVersion() < EQ::versions::ClientVersion::RoF2 && class_ == Class::Berserker && skillid == EQ::skills::Skill1HPiercing)
skillid = EQ::skills::Skill2HPiercing;
return(content_db.GetSkillCap(class_, skillid, level));
}
uint8 Client::SkillTrainLevel(EQ::skills::SkillType skillid, uint16 class_)
bool Client::HasSkill(EQ::skills::SkillType skill_id) const
{
if (ClientVersion() < EQ::versions::ClientVersion::RoF2 && class_ == Class::Berserker && skillid == EQ::skills::Skill1HPiercing)
skillid = EQ::skills::Skill2HPiercing;
return GetSkill(skill_id) > 0 && CanHaveSkill(skill_id);
}
return(content_db.GetTrainLevel(class_, skillid, RuleI(Character, MaxLevel)));
bool Client::CanHaveSkill(EQ::skills::SkillType skill_id) const
{
if (
ClientVersion() < EQ::versions::ClientVersion::RoF2 &&
class_ == Class::Berserker &&
skill_id == EQ::skills::Skill1HPiercing
) {
skill_id = EQ::skills::Skill2HPiercing;
}
return skill_caps.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)).cap > 0;
}
uint16 Client::MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const
{
if (
ClientVersion() < EQ::versions::ClientVersion::RoF2 &&
class_id == Class::Berserker &&
skill_id == EQ::skills::Skill1HPiercing
) {
skill_id = EQ::skills::Skill2HPiercing;
}
return skill_caps.GetSkillCap(class_id, skill_id, level).cap;
}
uint8 Client::SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id)
{
if (
ClientVersion() < EQ::versions::ClientVersion::RoF2 &&
class_id == Class::Berserker &&
skill_id == EQ::skills::Skill1HPiercing
) {
skill_id = EQ::skills::Skill2HPiercing;
}
return skill_caps.GetTrainLevel(class_id, skill_id, RuleI(Character, MaxLevel));
}
uint16 Client::GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill)
@@ -3791,14 +3805,12 @@ void Client::GetRaidAAs(RaidLeadershipAA_Struct *into) const {
void Client::EnteringMessages(Client* client)
{
std::string rules;
if (database.GetVariable("Rules", rules)) {
uint8 flag = database.GetAgreementFlag(client->AccountID());
std::string rules = RuleS(World, Rules);
if (!rules.empty() || database.GetVariable("Rules", rules)) {
const uint8 flag = database.GetAgreementFlag(client->AccountID());
if (!flag) {
auto rules_link = Saylink::Silent(
"#serverrules",
"rules"
);
const std::string& rules_link = Saylink::Silent("#serverrules", "rules");
client->Message(
Chat::White,
@@ -3815,9 +3827,9 @@ void Client::EnteringMessages(Client* client)
void Client::SendRules()
{
std::string rules;
std::string rules = RuleS(World, Rules);
if (!database.GetVariable("Rules", rules)) {
if (rules.empty() && !database.GetVariable("Rules", rules)) {
return;
}
@@ -4449,7 +4461,7 @@ bool Client::GroupFollow(Client* inviter) {
}
//now we have a group id, can set inviter's id
database.SetGroupID(inviter->GetName(), group->GetID(), inviter->CharacterID(), false);
group->AddToGroup(inviter);
database.SetGroupLeaderName(group->GetID(), inviter->GetName());
group->UpdateGroupAAs();
@@ -6285,7 +6297,17 @@ void Client::SendZonePoints()
zp->zpe[i].z = data->target_z;
zp->zpe[i].heading = data->target_heading;
zp->zpe[i].zoneid = data->target_zone_id;
zp->zpe[i].zoneinstance = data->target_zone_instance;
// if the target zone is the same as the current zone, use the instance of the current zone
// if we don't use the same instance_id that the client was sent, the client will forcefully
// issue a zone change request when they should be simply moving to a different point in the same zone
// because the client will think the zone point target is different from the current instance
auto target_instance = data->target_zone_instance;
if (data->target_zone_id == zone->GetZoneID() && data->target_zone_instance == 0) {
target_instance = zone->GetInstanceID();
}
zp->zpe[i].zoneinstance = target_instance;
i++;
}
iterator.Advance();
@@ -6610,13 +6632,72 @@ void Client::SendAltCurrencies() {
void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount)
{
if (!zone->DoesAlternateCurrencyExist(currency_id)) {
return;
}
const uint32 current_amount = alternate_currency[currency_id];
const bool is_gain = new_amount > current_amount;
const uint32 change_amount = is_gain ? (new_amount - current_amount) : (current_amount - new_amount);
if (!change_amount) {
return;
}
alternate_currency[currency_id] = new_amount;
database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_amount);
SendAlternateCurrencyValue(currency_id);
QuestEventID event_id = is_gain ? EVENT_ALT_CURRENCY_GAIN : EVENT_ALT_CURRENCY_LOSS;
if (parse->PlayerHasQuestSub(event_id)) {
const std::string &export_string = fmt::format(
"{} {} {}",
currency_id,
change_amount,
new_amount
);
parse->EventPlayer(event_id, this, export_string, 0);
}
}
bool Client::RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount)
{
if (!amount || !zone->DoesAlternateCurrencyExist(currency_id)) {
return false;
}
const uint32 current_amount = alternate_currency[currency_id];
if (current_amount < amount) {
return false;
}
const uint32 new_amount = (current_amount - amount);
alternate_currency[currency_id] = new_amount;
if (parse->PlayerHasQuestSub(EVENT_ALT_CURRENCY_LOSS)) {
const std::string &export_string = fmt::format(
"{} {} {}",
currency_id,
amount,
new_amount
);
parse->EventPlayer(EVENT_ALT_CURRENCY_LOSS, this, export_string, 0);
}
return true;
}
int Client::AddAlternateCurrencyValue(uint32 currency_id, int amount, bool is_scripted)
{
if (!zone->DoesAlternateCurrencyExist(currency_id)) {
return 0;
}
/* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */
if (is_scripted) {
/* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */
@@ -6655,7 +6736,6 @@ int Client::AddAlternateCurrencyValue(uint32 currency_id, int amount, bool is_sc
SendAlternateCurrencyValue(currency_id);
QuestEventID event_id = amount > 0 ? EVENT_ALT_CURRENCY_GAIN : EVENT_ALT_CURRENCY_LOSS;
if (parse->PlayerHasQuestSub(event_id)) {
const std::string &export_string = fmt::format(
"{} {} {}",
@@ -6696,6 +6776,10 @@ void Client::SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null)
uint32 Client::GetAlternateCurrencyValue(uint32 currency_id) const
{
if (!zone->DoesAlternateCurrencyExist(currency_id)) {
return 0;
}
auto iter = alternate_currency.find(currency_id);
return iter == alternate_currency.end() ? 0 : (*iter).second;
@@ -6765,23 +6849,36 @@ void Client::UpdateClientXTarget(Client *c)
// IT IS NOT SAFE TO CALL THIS IF IT'S NOT INITIAL AGGRO
void Client::AddAutoXTarget(Mob *m, bool send)
{
if (m->IsBot() || ((m->IsPet() || m->IsTempPet()) && m->IsPetOwnerBot())) {
return;
}
m_activeautohatermgr->increment_count(m);
if (!XTargettingAvailable() || !XTargetAutoAddHaters || IsXTarget(m))
if (!XTargettingAvailable() || !XTargetAutoAddHaters || IsXTarget(m)) {
return;
}
for(int i = 0; i < GetMaxXTargets(); ++i)
{
if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0))
{
for (int i = 0; i < GetMaxXTargets(); ++i) {
if (XTargets[i].Type == Auto && XTargets[i].ID == 0) {
XTargets[i].ID = m->GetID();
if (send) // if we don't send we're bulk sending updates later on
if (send) { // if we don't send we're bulk sending updates later on
SendXTargetPacket(i, m);
else
} else {
XTargets[i].dirty = true;
}
break;
}
}
LogXTargets(
"Adding [{}] to [{}] ({}) XTargets",
m->GetCleanName(),
GetCleanName(),
GetID()
);
}
void Client::RemoveXTarget(Mob *m, bool OnlyAutoSlots)
@@ -6790,15 +6887,23 @@ void Client::RemoveXTarget(Mob *m, bool OnlyAutoSlots)
// now we may need to clean up our CurrentTargetNPC entries
for (int i = 0; i < GetMaxXTargets(); ++i) {
if (XTargets[i].Type == CurrentTargetNPC && XTargets[i].ID == m->GetID()) {
XTargets[i].Type = Auto;
XTargets[i].ID = 0;
XTargets[i].Type = Auto;
XTargets[i].ID = 0;
XTargets[i].dirty = true;
}
}
auto r = GetRaid();
if (r) {
r->UpdateRaidXTargets();
}
LogXTargets(
"Removing [{}] from [{}] ({}) XTargets",
m->GetCleanName(),
GetCleanName(),
GetID()
);
}
void Client::UpdateXTargetType(XTargetType Type, Mob *m, const char *Name)
@@ -9140,6 +9245,7 @@ void Client::ShowDevToolsMenu()
menu_reload_six += " | " + Saylink::Silent("#reload quest", "Quests");
menu_reload_seven += Saylink::Silent("#reload rules", "Rules");
menu_reload_seven += " | " + Saylink::Silent("#reload skill_caps", "Skill Caps");
menu_reload_seven += " | " + Saylink::Silent("#reload static", "Static Zone Data");
menu_reload_seven += " | " + Saylink::Silent("#reload tasks", "Tasks");
@@ -11267,6 +11373,16 @@ void Client::SendReloadCommandMessages() {
).c_str()
);
auto skill_caps_link = Saylink::Silent("#reload skill_caps");
Message(
Chat::White,
fmt::format(
"Usage: {} - Reloads Skill Caps globally",
skill_caps_link
).c_str()
);
auto static_link = Saylink::Silent("#reload static");
Message(
@@ -11442,6 +11558,39 @@ void Client::SetLockSavePosition(bool lock_save_position)
Client::m_lock_save_position = lock_save_position;
}
void Client::SetAAPoints(uint32 points)
{
const uint32 current_points = m_pp.aapoints;
m_pp.aapoints = points;
QuestEventID event_id = points > current_points ? EVENT_AA_GAIN : EVENT_AA_LOSS;
const uint32 change = event_id == EVENT_AA_GAIN ? points - current_points : current_points - points;
if (parse->PlayerHasQuestSub(event_id)) {
parse->EventPlayer(event_id, this, std::to_string(change), 0);
}
SendAlternateAdvancementStats();
}
bool Client::RemoveAAPoints(uint32 points)
{
if (m_pp.aapoints < points) {
return false;
}
m_pp.aapoints -= points;
if (parse->PlayerHasQuestSub(EVENT_AA_LOSS)) {
parse->EventPlayer(EVENT_AA_LOSS, this, std::to_string(points), 0);
}
SendAlternateAdvancementStats();
return true;
}
void Client::AddAAPoints(uint32 points)
{
m_pp.aapoints += points;
@@ -11499,6 +11648,14 @@ void Client::RegisterBug(BugReport_Struct* r) {
b.bug_report = r->bug_report;
b.system_info = r->system_info;
#ifdef LUA_EQEMU
bool ignore_default = false;
LuaParser::Instance()->RegisterBug(this, b, ignore_default);
if (ignore_default) {
return;
}
#endif
auto n = BugReportsRepository::InsertOne(database, b);
if (!n.id) {
Message(Chat::White, "Failed to created your bug report."); // Client sends success message
@@ -11648,7 +11805,7 @@ void Client::MaxSkills()
auto current_skill_value = (
EQ::skills::IsSpecializedSkill(s.first) ?
MAX_SPECIALIZED_SKILL :
content_db.GetSkillCap(GetClass(), s.first, GetLevel())
skill_caps.GetSkillCap(GetClass(), s.first, GetLevel()).cap
);
if (GetSkill(s.first) < current_skill_value) {
@@ -12194,3 +12351,34 @@ int Client::GetEXPPercentage()
return static_cast<int>(std::round(scaled * 100.0 / 330.0)); // unscaled pct
}
std::vector<Mob*> Client::GetRaidOrGroupOrSelf(bool clients_only)
{
std::vector<Mob*> v;
if (IsRaidGrouped()) {
Raid* r = GetRaid();
if (r) {
for (const auto& m : r->members) {
if (m.member && (!m.is_bot || !clients_only)) {
v.emplace_back(m.member);
}
}
}
} else if (IsGrouped()) {
Group* g = GetGroup();
if (g) {
for (const auto& m : g->members) {
if (m && (m->IsClient() || !clients_only)) {
v.emplace_back(m);
}
}
}
} else {
v.emplace_back(this);
}
return v;
}
+10 -5
View File
@@ -272,6 +272,8 @@ public:
int GetQuiverHaste(int delay);
void DoAttackRounds(Mob *target, int hand, bool IsFromSpell = false);
std::vector<Mob*> GetRaidOrGroupOrSelf(bool clients_only = false);
void AI_Init();
void AI_Start(uint32 iMoveDelay = 0);
void AI_Stop();
@@ -348,6 +350,7 @@ public:
int GetRecipeMadeCount(uint32 recipe_id);
bool HasRecipeLearned(uint32 recipe_id);
bool CanIncreaseTradeskill(EQ::skills::SkillType tradeskill);
void ScribeRecipes(uint32_t item_id) const;
bool GetRevoked() const { return revoked; }
void SetRevoked(bool rev) { revoked = rev; }
@@ -812,9 +815,9 @@ public:
void SetHoTT(uint32 mobid);
void ShowSkillsWindow();
uint16 MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const;
inline uint16 MaxSkill(EQ::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); }
uint8 SkillTrainLevel(EQ::skills::SkillType skillid, uint16 class_);
uint16 MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const;
inline uint16 MaxSkill(EQ::skills::SkillType skill_id) const { return MaxSkill(skill_id, GetClass(), GetLevel()); }
uint8 SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id);
void MaxSkills();
void SendTradeskillSearchResults(const std::string &query, unsigned long objtype, unsigned long someid);
@@ -933,8 +936,9 @@ public:
void ResetAlternateAdvancementTimers();
void ResetOnDeathAlternateAdvancement();
void SetAAPoints(uint32 points) { m_pp.aapoints = points; SendAlternateAdvancementStats(); }
void SetAAPoints(uint32 points);
void AddAAPoints(uint32 points);
bool RemoveAAPoints(uint32 points);
int GetAAPoints() { return m_pp.aapoints; }
int GetSpentAA() { return m_pp.aapoints_spent; }
uint32 GetRequiredAAExperience();
@@ -1090,7 +1094,7 @@ public:
void SetPEQZoneFlag(uint32 zone_id);
bool CanFish();
void GoFish();
void GoFish(bool guarantee = false, bool use_bait = true);
void ForageItem(bool guarantee = false);
//Calculate vendor price modifier based on CHA: (reverse==selling)
float CalcPriceMod(Mob* other = 0, bool reverse = false);
@@ -1539,6 +1543,7 @@ public:
void SendAltCurrencies();
void SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount);
int AddAlternateCurrencyValue(uint32 currency_id, int amount, bool is_scripted = false);
bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount);
void SendAlternateCurrencyValues();
void SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null = true);
uint32 GetAlternateCurrencyValue(uint32 currency_id) const;
+86 -93
View File
@@ -322,11 +322,12 @@ int64 Client::CalcMaxHP()
//but the actual effect sent on live causes the client
//to apply it to (basehp + itemhp).. I will oblige to the client's whims over
//the aa description
nd += aabonuses.MaxHP; //Natural Durability, Physical Enhancement, Planar Durability
nd += aabonuses.MaxHP + spellbonuses.MaxHPChange + itembonuses.MaxHPChange; //Natural Durability, Physical Enhancement, Planar Durability (MaxHP and MaxHPChange are SPA214)
max_hp = (float)max_hp * (float)nd / (float)10000; //this is to fix the HP-above-495k issue
max_hp += spellbonuses.HP + aabonuses.HP;
max_hp += GroupLeadershipAAHealthEnhancement();
max_hp += max_hp * ((spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f);
if (current_hp > max_hp) {
current_hp = max_hp;
}
@@ -523,28 +524,26 @@ int32 Client::GetRawItemAC()
int64 Client::CalcMaxMana()
{
switch (GetCasterClass()) {
case 'I':
case 'W': {
max_mana = (CalcBaseMana() + itembonuses.Mana + spellbonuses.Mana + aabonuses.Mana + GroupLeadershipAAManaEnhancement());
break;
}
case 'N': {
max_mana = 0;
break;
}
default: {
LogSpells("Invalid Class [{}] in CalcMaxMana", GetCasterClass());
max_mana = 0;
break;
}
if (IsIntelligenceCasterClass() || IsWisdomCasterClass()) {
max_mana = (
CalcBaseMana() +
itembonuses.Mana +
spellbonuses.Mana +
aabonuses.Mana +
GroupLeadershipAAManaEnhancement()
);
} else {
max_mana = 0;
}
if (max_mana < 0) {
max_mana = 0;
}
if (current_mana > max_mana) {
current_mana = max_mana;
}
int mana_perc_cap = spellbonuses.ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP];
if (mana_perc_cap) {
int curMana_cap = (max_mana * mana_perc_cap) / 100;
@@ -552,96 +551,90 @@ int64 Client::CalcMaxMana()
current_mana = curMana_cap;
}
}
LogSpells("for [{}] returning [{}]", GetName(), max_mana);
return max_mana;
}
int64 Client::CalcBaseMana()
{
int ConvertedWisInt = 0;
int MindLesserFactor, MindFactor;
int WisInt = 0;
int64 base_mana = 0;
int wisint_mana = 0;
int64 max_m = 0;
switch (GetCasterClass()) {
case 'I':
WisInt = GetINT();
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
ConvertedWisInt = WisInt;
int over200 = WisInt;
if (WisInt > 100) {
if (WisInt > 200) {
over200 = (WisInt - 200) / -2 + WisInt;
}
ConvertedWisInt = (3 * over200 - 300) / 2 + over200;
}
auto base_data = zone->GetBaseData(GetLevel(), GetClass());
if (base_data.level == GetLevel()) {
max_m = base_data.mana + (ConvertedWisInt * base_data.mana_fac) + itembonuses.heroic_max_mana;
int ConvertedWisInt = 0;
int MindLesserFactor, MindFactor;
int WisInt = 0;
int64 base_mana = 0;
int wisint_mana = 0;
int64 max_m = 0;
if (IsIntelligenceCasterClass()) {
WisInt = GetINT();
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
ConvertedWisInt = WisInt;
int over200 = WisInt;
if (WisInt > 100) {
if (WisInt > 200) {
over200 = (WisInt - 200) / -2 + WisInt;
}
ConvertedWisInt = (3 * over200 - 300) / 2 + over200;
}
else {
if ((( WisInt - 199 ) / 2) > 0) {
MindLesserFactor = ( WisInt - 199 ) / 2;
}
else {
MindLesserFactor = 0;
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
max_m = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
}
else {
max_m = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
auto base_data = zone->GetBaseData(GetLevel(), GetClass());
if (base_data.level == GetLevel()) {
max_m = base_data.mana + (ConvertedWisInt * base_data.mana_fac) + itembonuses.heroic_max_mana;
}
break;
case 'W':
WisInt = GetWIS();
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
ConvertedWisInt = WisInt;
int over200 = WisInt;
if (WisInt > 100) {
if (WisInt > 200) {
over200 = (WisInt - 200) / -2 + WisInt;
}
ConvertedWisInt = (3 * over200 - 300) / 2 + over200;
}
auto base_data = zone->GetBaseData(GetLevel(), GetClass());
if (base_data.level == GetLevel()) {
max_m = base_data.mana + (ConvertedWisInt * base_data.mana_fac) + itembonuses.heroic_max_mana;
}
} else {
if (((WisInt - 199) / 2) > 0) {
MindLesserFactor = (WisInt - 199) / 2;
} else {
MindLesserFactor = 0;
}
else {
if ((( WisInt - 199 ) / 2) > 0) {
MindLesserFactor = ( WisInt - 199 ) / 2;
}
else {
MindLesserFactor = 0;
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
max_m = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
}
else {
max_m = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
max_m = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
} else {
max_m = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
break;
case 'N': {
max_m = 0;
break;
}
} else if (IsWisdomCasterClass()) {
WisInt = GetWIS();
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
ConvertedWisInt = WisInt;
int over200 = WisInt;
if (WisInt > 100) {
if (WisInt > 200) {
over200 = (WisInt - 200) / -2 + WisInt;
}
ConvertedWisInt = (3 * over200 - 300) / 2 + over200;
}
default: {
LogDebug("Invalid Class [{}] in CalcMaxMana", GetCasterClass());
max_m = 0;
break;
auto base_data = zone->GetBaseData(GetLevel(), GetClass());
if (base_data.level == GetLevel()) {
max_m = base_data.mana + (ConvertedWisInt * base_data.mana_fac) + itembonuses.heroic_max_mana;
}
} else {
if (((WisInt - 199) / 2) > 0) {
MindLesserFactor = (WisInt - 199) / 2;
} else {
MindLesserFactor = 0;
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
max_m = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
} else {
max_m = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
}
} else {
max_m = 0;
}
#if EQDEBUG >= 11
LogDebug("Client::CalcBaseMana() called for [{}] - returning [{}]", GetName(), max_m);
#endif
return max_m;
}
+22 -9
View File
@@ -1530,8 +1530,9 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
}
} //else, somebody from our group is already here...
if (!group)
database.SetGroupID(GetName(), 0, CharacterID(), false); //cannot re-establish group, kill it
if (!group) { //cannot re-establish group, kill it
Group::RemoveFromGroup(this);
}
}
else { //no group id
@@ -4168,10 +4169,11 @@ void Client::Handle_OP_BookButton(const EQApplicationPacket* app)
BookButton_Struct* book = reinterpret_cast<BookButton_Struct*>(app->pBuffer);
const EQ::ItemInstance* const inst = GetInv().GetItem(book->invslot);
if (inst && inst->GetItem()->Book)
if (inst && inst->GetItem())
{
// todo: if scribe book learn recipes and delete book from inventory
// todo: if cast book use its spell on target and delete book from inventory (unless reusable?)
// todo: cast spell button (unknown if anything on live uses this)
ScribeRecipes(inst->GetItem()->ID);
DeleteItemInInventory(book->invslot, 1, true);
}
EQApplicationPacket outapp(OP_FinishWindow, 0);
@@ -5595,9 +5597,20 @@ void Client::Handle_OP_CrystalCreate(const EQApplicationPacket *app)
}
// Prevent the client from creating more than they have.
const uint32 amount = EQ::ClampUpper(quantity, current_quantity);
uint32 amount = EQ::ClampUpper(quantity, current_quantity);
const uint32 item_id = is_radiant ? RuleI(Zone, RadiantCrystalItemID) : RuleI(Zone, EbonCrystalItemID);
const auto item = database.GetItem(item_id);
// Prevent pulling more than max stack size or 1,000 (if stackable), whichever is lesser
const uint32 max_reclaim_amount = EQ::Clamp(
item && item->Stackable ? item->StackSize : ItemStackSizeConstraint::Minimum,
ItemStackSizeConstraint::Minimum,
ItemStackSizeConstraint::Maximum
);
if (amount > max_reclaim_amount) {
amount = max_reclaim_amount;
}
const bool success = SummonItem(item_id, amount);
if (!success) {
return;
@@ -6862,7 +6875,7 @@ void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
Client *c = entity_list.GetClientByName(gmn->oldname);
LogInfo("GM([{}]) changeing players name. Old:[{}] New:[{}]", GetName(), gmn->oldname, gmn->newname);
const bool used_name = database.CheckUsedName(gmn->newname);
const bool used_name = database.IsNameUsed(gmn->newname);
if (!c) {
Message(Chat::Red, fmt::format("{} not found for name change. Operation failed!", gmn->oldname).c_str());
return;
@@ -6873,7 +6886,7 @@ void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
return;
}
if (!used_name) {
if (used_name) {
Message(Chat::Red, fmt::format("{} is already in use. Operation failed!", gmn->newname).c_str());
return;
}
@@ -7177,7 +7190,7 @@ void Client::Handle_OP_GroupCancelInvite(const EQApplicationPacket *app)
if (!GetMerc())
{
database.SetGroupID(GetName(), 0, CharacterID(), false);
Group::RemoveFromGroup(this);
}
return;
}
+6 -6
View File
@@ -1064,9 +1064,9 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I
);
SpellOnTarget(resurrection_sickness_spell_id, this);
} else if (SpellID == SPELL_DIVINE_REZ) {
SetHP(GetMaxHP());
SetMana(GetMaxMana());
SetEndurance(GetMaxEndurance());
RestoreHealth();
RestoreMana();
RestoreEndurance();
} else {
SetHP(GetMaxHP() / 20);
SetMana(GetMaxMana() / 20);
@@ -2177,9 +2177,9 @@ void Client::HandleRespawnFromHover(uint32 Option)
FastQueuePacket(&outapp);
CalcBonuses();
SetHP(GetMaxHP());
SetMana(GetMaxMana());
SetEndurance(GetMaxEndurance());
RestoreHealth();
RestoreMana();
RestoreEndurance();
m_Position.x = chosen->x;
m_Position.y = chosen->y;
+6
View File
@@ -131,8 +131,10 @@ int command_init(void)
command_add("feature", "Change your or your target's feature's temporarily", AccountStatus::QuestTroupe, command_feature) ||
command_add("size", "Change your targets size (alias of #feature size)", AccountStatus::QuestTroupe, command_feature) ||
command_add("find", "Search command used to find various things", AccountStatus::Guide, command_find) ||
command_add("fish", "Fish for an item", AccountStatus::QuestTroupe, command_fish) ||
command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", AccountStatus::QuestTroupe, command_fixmob) ||
command_add("flagedit", "Edit zone flags on your target. Use #flagedit help for more info.", AccountStatus::GMAdmin, command_flagedit) ||
command_add("forage", "Forage an item", AccountStatus::QuestTroupe, command_forage) ||
command_add("gearup", "Developer tool to quickly equip yourself or your target", AccountStatus::GMMgmt, command_gearup) ||
command_add("giveitem", "[itemid] [charges] - Summon an item onto your target's cursor. Charges are optional.", AccountStatus::GMMgmt, command_giveitem) ||
command_add("givemoney", "[Platinum] [Gold] [Silver] [Copper] - Gives specified amount of money to you or your player target", AccountStatus::GMMgmt, command_givemoney) ||
@@ -206,6 +208,7 @@ int command_init(void)
command_add("scribespells", "[Max level] [Min level] - Scribe all spells for you or your player target that are usable by them, up to level specified. (may freeze client for a few seconds)", AccountStatus::GMLeadAdmin, command_scribespells) ||
command_add("sendzonespawns", "Refresh spawn list for all clients in zone", AccountStatus::GMLeadAdmin, command_sendzonespawns) ||
command_add("sensetrap", "Analog for ldon sense trap for the newer clients since we still don't have it working.", AccountStatus::Player, command_sensetrap) ||
command_add("serverrules", "Show server rules", AccountStatus::Player, command_serverrules) ||
command_add("set", "Set command used to set various things", AccountStatus::Guide, command_set) ||
command_add("show", "Show command used to show various things", AccountStatus::Guide, command_show) ||
command_add("shutdown", "Shut this zone process down", AccountStatus::GMLeadAdmin, command_shutdown) ||
@@ -822,8 +825,10 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/faction.cpp"
#include "gm_commands/feature.cpp"
#include "gm_commands/find.cpp"
#include "gm_commands/fish.cpp"
#include "gm_commands/fixmob.cpp"
#include "gm_commands/flagedit.cpp"
#include "gm_commands/forage.cpp"
#include "gm_commands/gearup.cpp"
#include "gm_commands/giveitem.cpp"
#include "gm_commands/givemoney.cpp"
@@ -893,6 +898,7 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/scribespells.cpp"
#include "gm_commands/sendzonespawns.cpp"
#include "gm_commands/sensetrap.cpp"
#include "gm_commands/serverrules.cpp"
#include "gm_commands/set.cpp"
#include "gm_commands/show.cpp"
#include "gm_commands/shutdown.cpp"
+2
View File
@@ -83,8 +83,10 @@ void command_faction(Client *c, const Seperator *sep);
void command_faction_association(Client *c, const Seperator *sep);
void command_feature(Client *c, const Seperator *sep);
void command_find(Client *c, const Seperator *sep);
void command_fish(Client* c, const Seperator* sep);
void command_fixmob(Client *c, const Seperator *sep);
void command_flagedit(Client *c, const Seperator *sep);
void command_forage(Client* c, const Seperator* sep);
void command_gearup(Client *c, const Seperator *sep);
void command_giveitem(Client *c, const Seperator *sep);
void command_givemoney(Client *c, const Seperator *sep);
+14 -7
View File
@@ -19,9 +19,11 @@
#define HEAD_POSITION 0.9f //ratio of GetSize() where NPCs see from
#define SEE_POSITION 0.5f //ratio of GetSize() where NPCs try to see for LOS
#define ARCHETYPE_HYBRID 1
#define ARCHETYPE_CASTER 2
#define ARCHETYPE_MELEE 3
namespace Archetype {
constexpr uint8 Hybrid = 1;
constexpr uint8 Caster = 2;
constexpr uint8 Melee = 3;
};
#define CON_GREEN 2
#define CON_LIGHTBLUE 18
@@ -285,7 +287,7 @@ struct StatBonuses {
int32 AC;
int64 HP;
int64 HPRegen;
int64 MaxHP;
int64 MaxHP; //same bonus as MaxHPChange when applied to spells and item bonuses
int64 ManaRegen;
int64 EnduranceRegen;
int64 Mana;
@@ -411,7 +413,7 @@ struct StatBonuses {
int32 MeleeLifetap; //i
int32 Vampirism; //i
int32 HealRate; // Spell effect that influences effectiveness of heals
int32 MaxHPChange; // Spell Effect
int32 MaxHPChange; // percent change in hit points (aabonuses use variable MaxHP)
int16 SkillDmgTaken[EQ::skills::HIGHEST_SKILL + 2]; // All Skills + -1
int32 HealAmt; // Item Effect
int32 SpellDmg; // Item Effect
@@ -443,7 +445,8 @@ struct StatBonuses {
int32 SongRange; // increases range of beneficial bard songs
uint32 HPToManaConvert; // Uses HP to cast spells at specific conversion
int32 FocusEffects[HIGHEST_FOCUS+1]; // Stores the focus effectid for each focustype you have.
int16 FocusEffectsWorn[HIGHEST_FOCUS+1]; // Optional to allow focus effects to be applied additively from worn slot
int16 FocusEffectsWorn[HIGHEST_FOCUS+1]; // Optional to allow focus effects to be applied additively from worn slot, limits do not apply
int32 FocusEffectsWornWithLimits[HIGHEST_FOCUS + 1];// Optional to allow focus effects to be applied additively from worn slot, limits apply
bool NegateEffects; // Check if you contain a buff with negate effect. (only spellbonuses)
int32 SkillDamageAmount2[EQ::skills::HIGHEST_SKILL + 2]; // Adds skill specific damage
uint32 NegateAttacks[3]; // 0 = bool HasEffect 1 = Buff Slot 2 = Max damage absorbed per hit
@@ -509,6 +512,7 @@ struct StatBonuses {
uint8 invisibility; // invisibility level
uint8 invisibility_verse_undead; // IVU level
uint8 invisibility_verse_animal; // IVA level
int32 ShieldTargetSpa[2]; // [0] base = % mitigation amount, [1] buff slot
// AAs
int32 TrapCircumvention; // reduce chance to trigger a trap.
@@ -654,6 +658,8 @@ namespace SBIndex {
constexpr uint16 COMBAT_PROC_SPELL_ID = 1; // SPA
constexpr uint16 COMBAT_PROC_RATE_MOD = 2; // SPA
constexpr uint16 COMBAT_PROC_REUSE_TIMER = 3; // SPA
constexpr uint16 SHIELD_TARGET_MITIGATION_PERCENT = 0; // SPA 463
constexpr uint16 SHIELD_TARGET_BUFFSLOT = 1; // SPA 463
};
@@ -826,7 +832,8 @@ struct ExtraAttackOptions {
armor_pen_percent(0.0f), armor_pen_flat(0),
crit_percent(1.0f), crit_flat(0.0f),
hate_percent(1.0f), hate_flat(0), hit_chance(0),
melee_damage_bonus_flat(0), skilldmgtaken_bonus_flat(0)
melee_damage_bonus_flat(0), skilldmgtaken_bonus_flat(0),
range_percent(0)
{ }
float damage_percent;
+1 -1
View File
@@ -334,7 +334,7 @@ void DialogueWindow::Render(Client *c, std::string markdown)
if (responses.size() > 1) {
for (auto &r: responses) {
bracket_responses.emplace_back(
fmt::format("[{}]", Saylink::Create(r, false))
fmt::format("[{}]", Saylink::Create(r))
);
}
}
+9
View File
@@ -73,6 +73,15 @@ Doors::Doors(const DoorsRepository::Doors &door) :
m_door_param = door.door_param;
m_size = door.size;
m_invert_state = door.invert_state;
// if the target zone is the same as the current zone, use the instance of the current zone
// if we don't use the same instance_id that the client was sent, the client will forcefully
// issue a zone change request when they should be simply moving to a different point in the same zone
// because the client will think the zone point target is different from the current instance
if (door.dest_zone == zone->GetShortName() && m_destination_instance_id == 0) {
m_destination_instance_id = zone->GetInstanceID();
}
m_destination_instance_id = door.dest_instance;
m_is_ldon_door = door.is_ldon_door;
m_dz_switch_id = door.dz_switch_id;
+28
View File
@@ -56,6 +56,7 @@ void perl_register_doors();
void perl_register_expedition();
void perl_register_expedition_lock_messages();
void perl_register_bot();
void perl_register_buff();
#endif // EMBPERL_XS_CLASSES
#endif // EMBPERL_XS
@@ -200,6 +201,8 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_ENTITY_VARIABLE_DELETE",
"EVENT_ENTITY_VARIABLE_SET",
"EVENT_ENTITY_VARIABLE_UPDATE",
"EVENT_AA_LOSS",
"EVENT_SPELL_BLOCKED",
// Add new events before these or Lua crashes
"EVENT_SPELL_EFFECT_BOT",
@@ -1174,6 +1177,7 @@ void PerlembParser::MapFunctions()
perl_register_expedition();
perl_register_expedition_lock_messages();
perl_register_bot();
perl_register_buff();
#endif // EMBPERL_XS_CLASSES
}
@@ -1934,6 +1938,25 @@ void PerlembParser::ExportEventVariables(
break;
}
case EVENT_SPELL_BLOCKED: {
Seperator sep(data);
const uint32 blocking_spell_id = Strings::ToUnsignedInt(sep.arg[0]);
const uint32 cast_spell_id = Strings::ToUnsignedInt(sep.arg[1]);
ExportVar(package_name.c_str(), "blocking_spell_id", blocking_spell_id);
ExportVar(package_name.c_str(), "cast_spell_id", cast_spell_id);
if (IsValidSpell(blocking_spell_id)) {
ExportVar(package_name.c_str(), "blocking_spell", "Spell", (void*) &spells[blocking_spell_id]);
}
if (IsValidSpell(cast_spell_id)) {
ExportVar(package_name.c_str(), "cast_spell", "Spell", (void*) &spells[cast_spell_id]);
}
break;
}
//tradeskill events
case EVENT_COMBINE_SUCCESS:
case EVENT_COMBINE_FAILURE: {
@@ -2273,6 +2296,11 @@ void PerlembParser::ExportEventVariables(
break;
}
case EVENT_AA_LOSS: {
ExportVar(package_name.c_str(), "aa_lost", data);
break;
}
case EVENT_AA_EXP_GAIN: {
ExportVar(package_name.c_str(), "aa_exp_gained", data);
break;
+45 -18
View File
@@ -380,12 +380,12 @@ void Perl__settimer(std::string timer_name, uint32 seconds)
void Perl__settimer(std::string timer_name, uint32 seconds, Mob* m)
{
quest_manager.settimer(timer_name, seconds);
quest_manager.settimer(timer_name, seconds, m);
}
void Perl__settimer(std::string timer_name, uint32 seconds, EQ::ItemInstance* inst)
{
quest_manager.settimer(timer_name, seconds);
quest_manager.settimerMS(timer_name, seconds * 1000, inst);
}
void Perl__settimerMS(std::string timer_name, uint32 milliseconds)
@@ -1579,9 +1579,7 @@ std::string Perl__GetCharactersInInstance(uint16 instance_id)
char_id_string = fmt::format("{} player(s) in instance: ", character_ids.size());
auto iter = character_ids.begin();
while (iter != character_ids.end()) {
char char_name[64];
database.GetCharName(*iter, char_name);
char_id_string += char_name;
char_id_string += database.GetCharName(*iter);
char_id_string += "(";
char_id_string += itoa(*iter);
char_id_string += ")";
@@ -1654,20 +1652,19 @@ void Perl__FlagInstanceByRaidLeader(uint32 zone, uint16 version)
quest_manager.FlagInstanceByRaidLeader(zone, version);
}
std::string Perl__saylink(const char* text)
std::string Perl__saylink(std::string text)
{
// const cast is safe since, target api doesn't modify it
return quest_manager.saylink(const_cast<char*>(text), false, text);
return Saylink::Create(text);
}
std::string Perl__saylink(const char* text, bool silent)
std::string Perl__saylink(std::string text, bool silent)
{
return quest_manager.saylink(const_cast<char*>(text), silent, text);
return Saylink::Create(text, silent);
}
std::string Perl__saylink(const char* text, bool silent, const char* link_name)
std::string Perl__saylink(std::string text, bool silent, std::string link_name)
{
return quest_manager.saylink(const_cast<char*>(text), silent, link_name);
return Saylink::Create(text, silent, link_name);
}
std::string Perl__getcharnamebyid(uint32 char_id)
@@ -5833,6 +5830,31 @@ uint16 Perl__GetBotRaceByID(uint32 bot_id)
return database.botdb.GetBotRaceByID(bot_id);
}
std::string Perl__silent_saylink(std::string text)
{
return Saylink::Silent(text);
}
std::string Perl__silent_saylink(std::string text, std::string link_name)
{
return Saylink::Silent(text, link_name);
}
uint16 Perl__get_class_bitmask(uint8 class_id)
{
return GetPlayerClassBit(class_id);
}
uint32 Perl__get_deity_bitmask(uint16 deity_id)
{
return static_cast<uint32>(EQ::deity::GetDeityBitmask(static_cast<EQ::deity::DeityType>(deity_id)));
}
uint16 Perl__get_race_bitmask(uint16 race_id)
{
return GetPlayerRaceBit(race_id);
}
void perl_register_quest()
{
perl::interpreter perl(PERL_GET_THX);
@@ -6479,9 +6501,11 @@ void perl_register_quest()
package.add("getconsiderlevelname", &Perl__getconsiderlevelname);
package.add("gethexcolorcode", &Perl__gethexcolorcode);
package.add("getcurrencyid", &Perl__getcurrencyid);
package.add("get_class_bitmask", &Perl__get_class_bitmask);
package.add("get_data", &Perl__get_data);
package.add("get_data_expires", &Perl__get_data_expires);
package.add("get_data_remaining", &Perl__get_data_remaining);
package.add("get_deity_bitmask", &Perl__get_deity_bitmask);
package.add("get_dz_task_id", &Perl__get_dz_task_id);
package.add("getexpmodifierbycharid", (double(*)(uint32, uint32))&Perl__getexpmodifierbycharid);
package.add("getexpmodifierbycharid", (double(*)(uint32, uint32, int16))&Perl__getexpmodifierbycharid);
@@ -6514,6 +6538,7 @@ void perl_register_quest()
package.add("getgroupidbycharid", &Perl__getgroupidbycharid);
package.add("getinventoryslotname", &Perl__getinventoryslotname);
package.add("getraididbycharid", &Perl__getraididbycharid);
package.add("get_race_bitmask", &Perl__get_race_bitmask);
package.add("get_recipe_component_item_ids", &Perl__GetRecipeComponentItemIDs);
package.add("get_recipe_container_item_ids", &Perl__GetRecipeContainerItemIDs);
package.add("get_recipe_fail_item_ids", &Perl__GetRecipeFailItemIDs);
@@ -6644,9 +6669,9 @@ void perl_register_quest()
package.add("say", (void(*)(const char*, uint8, int))&Perl__say);
package.add("say", (void(*)(const char*, uint8, int, int))&Perl__say);
package.add("say", (void(*)(const char*, uint8, int, int, int))&Perl__say);
package.add("saylink", (std::string(*)(const char*))&Perl__saylink);
package.add("saylink", (std::string(*)(const char*, bool))&Perl__saylink);
package.add("saylink", (std::string(*)(const char*, bool, const char*))&Perl__saylink);
package.add("saylink", (std::string(*)(std::string))&Perl__saylink);
package.add("saylink", (std::string(*)(std::string, bool))&Perl__saylink);
package.add("saylink", (std::string(*)(std::string, bool, std::string))&Perl__saylink);
package.add("scribespells", (int(*)(int))&Perl__scribespells);
package.add("scribespells", (int(*)(int, int))&Perl__scribespells);
package.add("secondstotime", &Perl__secondstotime);
@@ -6684,9 +6709,9 @@ void perl_register_quest()
package.add("settarget", &Perl__settarget);
package.add("settime", (void(*)(int, int))&Perl__settime);
package.add("settime", (void(*)(int, int, bool))&Perl__settime);
package.add("settimer", (void(*)(std::string, uint32))&Perl__settimer),
package.add("settimer", (void(*)(std::string, uint32, EQ::ItemInstance*))&Perl__settimer),
package.add("settimer", (void(*)(std::string, uint32, Mob*))&Perl__settimer),
package.add("settimer", (void(*)(std::string, uint32))&Perl__settimer);
package.add("settimer", (void(*)(std::string, uint32, EQ::ItemInstance*))&Perl__settimer);
package.add("settimer", (void(*)(std::string, uint32, Mob*))&Perl__settimer);
package.add("settimerMS", (void(*)(std::string, uint32))&Perl__settimerMS);
package.add("settimerMS", (void(*)(std::string, uint32, EQ::ItemInstance*))&Perl__settimerMS);
package.add("settimerMS", (void(*)(std::string, uint32, Mob*))&Perl__settimerMS);
@@ -6698,6 +6723,8 @@ void perl_register_quest()
package.add("signal", (void(*)(int, int))&Perl__signal);
package.add("signalwith", (void(*)(int, int))&Perl__signalwith);
package.add("signalwith", (void(*)(int, int, int))&Perl__signalwith);
package.add("silent_saylink", (std::string(*)(std::string))&Perl__silent_saylink);
package.add("silent_saylink", (std::string(*)(std::string, std::string))&Perl__silent_saylink);
package.add("snow", &Perl__snow);
package.add("spawn", &Perl__spawn);
package.add("spawn2", &Perl__spawn2);
+14 -5
View File
@@ -2152,7 +2152,7 @@ Group *EntityList::GetGroupByLeaderName(const char *leader)
iterator = group_list.begin();
while (iterator != group_list.end()) {
if (!strcmp((*iterator)->GetLeaderName(), leader))
if (!strcmp((*iterator)->GetLeaderName().c_str(), leader))
return *iterator;
++iterator;
}
@@ -2675,6 +2675,10 @@ void EntityList::RemoveAllMobs()
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (!it->second) {
++it;
continue;
}
safe_delete(it->second);
free_ids.push(it->first);
it = mob_list.erase(it);
@@ -2812,6 +2816,9 @@ bool EntityList::RemoveMob(uint16 delete_id)
auto it = mob_list.find(delete_id);
if (it != mob_list.end()) {
if (!it->second) {
return false;
}
if (npc_list.count(delete_id)) {
entity_list.RemoveNPC(delete_id);
}
@@ -4379,8 +4386,9 @@ void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy)
if (n->GetSwarmInfo()->owner_id == owner->GetID()) {
if (
!n->GetSpecialAbility(IMMUNE_AGGRO) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient()) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC())
!(other->IsBot() && n->GetSpecialAbility(IMMUNE_AGGRO_BOT)) &&
!(other->IsClient() && n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) &&
!(other->IsNPC() && n->GetSpecialAbility(IMMUNE_AGGRO_NPC))
) {
n->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
@@ -4405,8 +4413,9 @@ void EntityList::AddTempPetsToHateListOnOwnerDamage(Mob *owner, Mob* attacker, i
attacker != n &&
!n->IsEngaged() &&
!n->GetSpecialAbility(IMMUNE_AGGRO) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) &&
!(attacker->IsBot() && n->GetSpecialAbility(IMMUNE_AGGRO_BOT)) &&
!(attacker->IsClient() && n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) &&
!(attacker->IsNPC() && n->GetSpecialAbility(IMMUNE_AGGRO_NPC)) &&
!attacker->IsTrap() &&
!attacker->IsCorpse()
) {

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