TrinityCore
SkillDiscovery.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "SkillDiscovery.h"
19#include "DatabaseEnv.h"
20#include "DB2Stores.h"
21#include "Log.h"
22#include "Player.h"
23#include "Random.h"
24#include "SpellMgr.h"
25#include "SpellInfo.h"
26#include "World.h"
27#include <map>
28#include <sstream>
29
31{
32 uint32 spellId; // discavered spell
33 uint32 reqSkillValue; // skill level limitation
34 float chance; // chance
35
37 : spellId(0), reqSkillValue(0), chance(0) { }
38
39 SkillDiscoveryEntry(uint32 _spellId, uint32 req_skill_val, float _chance)
40 : spellId(_spellId), reqSkillValue(req_skill_val), chance(_chance) { }
41};
42
43typedef std::list<SkillDiscoveryEntry> SkillDiscoveryList;
44typedef std::unordered_map<int32, SkillDiscoveryList> SkillDiscoveryMap;
45
47
49{
50 uint32 oldMSTime = getMSTime();
51
52 SkillDiscoveryStore.clear(); // need for reload
53
54 // 0 1 2 3
55 QueryResult result = WorldDatabase.Query("SELECT spellId, reqSpell, reqSkillValue, chance FROM skill_discovery_template");
56
57 if (!result)
58 {
59 TC_LOG_INFO("server.loading", ">> Loaded 0 skill discovery definitions. DB table `skill_discovery_template` is empty.");
60 return;
61 }
62
63 uint32 count = 0;
64
65 std::ostringstream ssNonDiscoverableEntries;
66 std::set<uint32> reportedReqSpells;
67
68 do
69 {
70 Field* fields = result->Fetch();
71
72 uint32 spellId = fields[0].GetUInt32();
73 int32 reqSkillOrSpell = fields[1].GetInt32();
74 uint32 reqSkillValue = fields[2].GetUInt16();
75 float chance = fields[3].GetFloat();
76
77 if (chance <= 0) // chance
78 {
79 ssNonDiscoverableEntries << "spellId = " << spellId << " reqSkillOrSpell = " << reqSkillOrSpell
80 << " reqSkillValue = " << reqSkillValue << " chance = " << chance << "(chance problem)\n";
81 continue;
82 }
83
84 if (reqSkillOrSpell > 0) // spell case
85 {
86 uint32 absReqSkillOrSpell = uint32(reqSkillOrSpell);
87 SpellInfo const* reqSpellInfo = sSpellMgr->GetSpellInfo(absReqSkillOrSpell, DIFFICULTY_NONE);
88 if (!reqSpellInfo)
89 {
90 if (reportedReqSpells.find(absReqSkillOrSpell) == reportedReqSpells.end())
91 {
92 TC_LOG_ERROR("sql.sql", "Spell (ID: {}) has a non-existing spell (ID: {}) in `reqSpell` field in the `skill_discovery_template` table.", spellId, reqSkillOrSpell);
93 reportedReqSpells.insert(absReqSkillOrSpell);
94 }
95 continue;
96 }
97
98 // mechanic discovery
99 if (reqSpellInfo->Mechanic != MECHANIC_DISCOVERY &&
100 // explicit discovery ability
101 !reqSpellInfo->IsExplicitDiscovery())
102 {
103 if (reportedReqSpells.find(absReqSkillOrSpell) == reportedReqSpells.end())
104 {
105 TC_LOG_ERROR("sql.sql", "Spell (ID: {}) does not have any MECHANIC_DISCOVERY (28) value in the Mechanic field in spell.dbc"
106 " nor 100% chance random discovery ability, but is listed for spellId {} (and maybe more) in the `skill_discovery_template` table.",
107 absReqSkillOrSpell, spellId);
108 reportedReqSpells.insert(absReqSkillOrSpell);
109 }
110 continue;
111 }
112
113 SkillDiscoveryStore[reqSkillOrSpell].push_back(SkillDiscoveryEntry(spellId, reqSkillValue, chance));
114 }
115 else if (reqSkillOrSpell == 0) // skill case
116 {
117 SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
118
119 if (bounds.first == bounds.second)
120 {
121 TC_LOG_ERROR("sql.sql", "Spell (ID: {}) is not listed in `SkillLineAbility.dbc`, but listed with `reqSpell`= 0 in the `skill_discovery_template` table.", spellId);
122 continue;
123 }
124
125 for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
126 SkillDiscoveryStore[-int32(_spell_idx->second->SkillLine)].push_back(SkillDiscoveryEntry(spellId, reqSkillValue, chance));
127 }
128 else
129 {
130 TC_LOG_ERROR("sql.sql", "Spell (ID: {}) has a negative value in `reqSpell` field in the `skill_discovery_template` table.", spellId);
131 continue;
132 }
133
134 ++count;
135 }
136 while (result->NextRow());
137
138 if (!ssNonDiscoverableEntries.str().empty())
139 TC_LOG_ERROR("sql.sql", "Some items can't be successfully discovered, their chance field value is < 0.000001 in the `skill_discovery_template` DB table. List:\n{}", ssNonDiscoverableEntries.str());
140
141 // report about empty data for explicit discovery spells
142 for (SpellNameEntry const* spellNameEntry : sSpellNameStore)
143 {
144 SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spellNameEntry->ID, DIFFICULTY_NONE);
145 if (!spellEntry)
146 continue;
147
148 // skip not explicit discovery spells
149 if (!spellEntry->IsExplicitDiscovery())
150 continue;
151
152 if (SkillDiscoveryStore.find(int32(spellEntry->Id)) == SkillDiscoveryStore.end())
153 TC_LOG_ERROR("sql.sql", "Spell (ID: {}) has got 100% chance random discovery ability, but does not have data in the `skill_discovery_template` table.", spellEntry->Id);
154 }
155
156 TC_LOG_INFO("server.loading", ">> Loaded {} skill discovery definitions in {} ms.", count, GetMSTimeDiffToNow(oldMSTime));
157}
158
160{
161 // explicit discovery spell chances (always success if case exist)
162 // in this case we have both skill and spell
163 SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));
164 if (tab == SkillDiscoveryStore.end())
165 return 0;
166
167 SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
168 uint32 skillvalue = bounds.first != bounds.second ? player->GetSkillValue(bounds.first->second->SkillLine) : uint32(0);
169
170 float full_chance = 0;
171 for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
172 if (item_iter->reqSkillValue <= skillvalue)
173 if (!player->HasSpell(item_iter->spellId))
174 full_chance += item_iter->chance;
175
176 float rate = full_chance / 100.0f;
177 float roll = rand_chance() * rate; // roll now in range 0..full_chance
178
179 for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
180 {
181 if (item_iter->reqSkillValue > skillvalue)
182 continue;
183
184 if (player->HasSpell(item_iter->spellId))
185 continue;
186
187 if (item_iter->chance > roll)
188 return item_iter->spellId;
189
190 roll -= item_iter->chance;
191 }
192
193 return 0;
194}
195
197{
198 SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));
199 if (tab == SkillDiscoveryStore.end())
200 return true;
201
202 for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
203 if (!player->HasSpell(item_iter->spellId))
204 return false;
205
206 return true;
207}
208
209bool HasDiscoveredAnySpell(uint32 spellId, Player* player)
210{
211 SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));
212 if (tab == SkillDiscoveryStore.end())
213 return false;
214
215 for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
216 if (player->HasSpell(item_iter->spellId))
217 return true;
218
219 return false;
220}
221
223{
224 uint32 skillvalue = skillId ? player->GetSkillValue(skillId) : uint32(0);
225
226 // check spell case
227 SkillDiscoveryMap::const_iterator tab = SkillDiscoveryStore.find(int32(spellId));
228
229 if (tab != SkillDiscoveryStore.end())
230 {
231 for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
232 {
233 if (roll_chance_f(item_iter->chance * sWorld->getRate(RATE_SKILL_DISCOVERY)) &&
234 item_iter->reqSkillValue <= skillvalue &&
235 !player->HasSpell(item_iter->spellId))
236 return item_iter->spellId;
237 }
238
239 return 0;
240 }
241
242 if (!skillId)
243 return 0;
244
245 // check skill line case
246 tab = SkillDiscoveryStore.find(-(int32)skillId);
247 if (tab != SkillDiscoveryStore.end())
248 {
249 for (SkillDiscoveryList::const_iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter)
250 {
251 if (roll_chance_f(item_iter->chance * sWorld->getRate(RATE_SKILL_DISCOVERY)) &&
252 item_iter->reqSkillValue <= skillvalue &&
253 !player->HasSpell(item_iter->spellId))
254 return item_iter->spellId;
255 }
256
257 return 0;
258 }
259
260 return 0;
261}
DB2Storage< SpellNameEntry > sSpellNameStore("SpellName.db2", &SpellNameLoadInfo::Instance)
@ DIFFICULTY_NONE
Definition: DBCEnums.h:874
std::shared_ptr< ResultSet > QueryResult
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
Definition: DatabaseEnv.cpp:20
int32_t int32
Definition: Define.h:138
uint32_t uint32
Definition: Define.h:142
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition: Log.h:159
float rand_chance()
Definition: Random.cpp:81
bool roll_chance_f(float chance)
Definition: Random.h:53
@ MECHANIC_DISCOVERY
std::list< SkillDiscoveryEntry > SkillDiscoveryList
static SkillDiscoveryMap SkillDiscoveryStore
uint32 GetExplicitDiscoverySpell(uint32 spellId, Player *player)
uint32 GetSkillDiscoverySpell(uint32 skillId, uint32 spellId, Player *player)
std::unordered_map< int32, SkillDiscoveryList > SkillDiscoveryMap
bool HasDiscoveredAllSpells(uint32 spellId, Player *player)
bool HasDiscoveredAnySpell(uint32 spellId, Player *player)
void LoadSkillDiscoveryTable()
#define sSpellMgr
Definition: SpellMgr.h:849
std::pair< SkillLineAbilityMap::const_iterator, SkillLineAbilityMap::const_iterator > SkillLineAbilityMapBounds
Definition: SpellMgr.h:621
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition: Timer.h:57
uint32 getMSTime()
Definition: Timer.h:33
Class used to access individual fields of database query result.
Definition: Field.h:90
uint16 GetUInt16() const
Definition: Field.cpp:46
float GetFloat() const
Definition: Field.cpp:94
uint32 GetUInt32() const
Definition: Field.cpp:62
int32 GetInt32() const
Definition: Field.cpp:70
uint16 GetSkillValue(uint32 skill) const
Definition: Player.cpp:6052
bool HasSpell(uint32 spell) const override
Definition: Player.cpp:3792
bool IsExplicitDiscovery() const
Definition: SpellInfo.cpp:1470
uint32 const Id
Definition: SpellInfo.h:325
uint32 Mechanic
Definition: SpellInfo.h:329
#define sWorld
Definition: World.h:931
@ RATE_SKILL_DISCOVERY
Definition: World.h:472
SkillDiscoveryEntry(uint32 _spellId, uint32 req_skill_val, float _chance)