/* * Copyright (C) 2012-2020 CypherCore * * 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, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; 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, see . */ using Framework.Constants; using Framework.Database; using Game.DataStorage; using Game.Entities; using System.Collections.Generic; using System.Linq; using System.Text; namespace Game.Spells { public class SkillDiscovery { public static void LoadSkillDiscoveryTable() { uint oldMSTime = Time.GetMSTime(); SkillDiscoveryStorage.Clear(); // need for reload // 0 1 2 3 SQLResult result = DB.World.Query("SELECT spellId, reqSpell, reqSkillValue, chance FROM skill_discovery_template"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 skill discovery definitions. DB table `skill_discovery_template` is empty."); return; } uint count = 0; StringBuilder ssNonDiscoverableEntries = new(); List reportedReqSpells = new(); do { uint spellId = result.Read(0); int reqSkillOrSpell = result.Read(1); uint reqSkillValue = result.Read(2); float chance = result.Read(3); if (chance <= 0) // chance { ssNonDiscoverableEntries.AppendFormat("spellId = {0} reqSkillOrSpell = {1} reqSkillValue = {2} chance = {3} (chance problem)\n", spellId, reqSkillOrSpell, reqSkillValue, chance); continue; } if (reqSkillOrSpell > 0) // spell case { uint absReqSkillOrSpell = (uint)reqSkillOrSpell; SpellInfo reqSpellInfo = Global.SpellMgr.GetSpellInfo(absReqSkillOrSpell, Difficulty.None); if (reqSpellInfo == null) { if (!reportedReqSpells.Contains(absReqSkillOrSpell)) { Log.outError(LogFilter.Sql, "Spell (ID: {0}) have not existed spell (ID: {1}) in `reqSpell` field in `skill_discovery_template` table", spellId, reqSkillOrSpell); reportedReqSpells.Add(absReqSkillOrSpell); } continue; } // mechanic discovery if (reqSpellInfo.Mechanic != Mechanics.Discovery && // explicit discovery ability !reqSpellInfo.IsExplicitDiscovery()) { if (!reportedReqSpells.Contains(absReqSkillOrSpell)) { Log.outError(LogFilter.Sql, "Spell (ID: {0}) not have MECHANIC_DISCOVERY (28) value in Mechanic field in spell.dbc" + " and not 100%% chance random discovery ability but listed for spellId {1} (and maybe more) in `skill_discovery_template` table", absReqSkillOrSpell, spellId); reportedReqSpells.Add(absReqSkillOrSpell); } continue; } SkillDiscoveryStorage.Add(reqSkillOrSpell, new SkillDiscoveryEntry(spellId, reqSkillValue, chance)); } else if (reqSkillOrSpell == 0) // skill case { var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spellId); if (bounds.Empty()) { Log.outError(LogFilter.Sql, "Spell (ID: {0}) not listed in `SkillLineAbility.dbc` but listed with `reqSpell`=0 in `skill_discovery_template` table", spellId); continue; } foreach (var _spell_idx in bounds) SkillDiscoveryStorage.Add(-(int)_spell_idx.SkillLine, new SkillDiscoveryEntry(spellId, reqSkillValue, chance)); } else { Log.outError(LogFilter.Sql, "Spell (ID: {0}) have negative value in `reqSpell` field in `skill_discovery_template` table", spellId); continue; } ++count; } while (result.NextRow()); if (ssNonDiscoverableEntries.Length != 0) Log.outError(LogFilter.Sql, "Some items can't be successfully discovered: have in chance field value < 0.000001 in `skill_discovery_template` DB table . List:\n{0}", ssNonDiscoverableEntries.ToString()); // report about empty data for explicit discovery spells foreach (SpellNameRecord spellNameEntry in CliDB.SpellNameStorage.Values) { SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(spellNameEntry.Id, Difficulty.None); if (spellEntry == null) continue; // skip not explicit discovery spells if (!spellEntry.IsExplicitDiscovery()) continue; if (!SkillDiscoveryStorage.ContainsKey((int)spellEntry.Id)) Log.outError(LogFilter.Sql, "Spell (ID: {0}) is 100% chance random discovery ability but not have data in `skill_discovery_template` table", spellEntry.Id); } Log.outInfo(LogFilter.ServerLoading, "Loaded {0} skill discovery definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } public static uint GetExplicitDiscoverySpell(uint spellId, Player player) { // explicit discovery spell chances (always success if case exist) // in this case we have both skill and spell var tab = SkillDiscoveryStorage.LookupByKey((int)spellId); if (tab.Empty()) return 0; var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spellId); uint skillvalue = !bounds.Empty() ? (uint)player.GetSkillValue((SkillType)bounds.FirstOrDefault().SkillLine) : 0; float full_chance = 0; foreach (var item_iter in tab) if (item_iter.reqSkillValue <= skillvalue) if (!player.HasSpell(item_iter.spellId)) full_chance += item_iter.chance; float rate = full_chance / 100.0f; float roll = (float)RandomHelper.randChance() * rate; // roll now in range 0..full_chance foreach (var item_iter in tab) { if (item_iter.reqSkillValue > skillvalue) continue; if (player.HasSpell(item_iter.spellId)) continue; if (item_iter.chance > roll) return item_iter.spellId; roll -= item_iter.chance; } return 0; } public static bool HasDiscoveredAllSpells(uint spellId, Player player) { var tab = SkillDiscoveryStorage.LookupByKey((int)spellId); if (tab.Empty()) return true; foreach (var item_iter in tab) if (!player.HasSpell(item_iter.spellId)) return false; return true; } public static uint GetSkillDiscoverySpell(uint skillId, uint spellId, Player player) { uint skillvalue = skillId != 0 ? (uint)player.GetSkillValue((SkillType)skillId) : 0; // check spell case var tab = SkillDiscoveryStorage.LookupByKey((int)spellId); if (!tab.Empty()) { foreach (var item_iter in tab) { if (RandomHelper.randChance(item_iter.chance * WorldConfig.GetFloatValue(WorldCfg.RateSkillDiscovery)) && item_iter.reqSkillValue <= skillvalue && !player.HasSpell(item_iter.spellId)) return item_iter.spellId; } return 0; } if (skillId == 0) return 0; // check skill line case tab = SkillDiscoveryStorage.LookupByKey(-(int)skillId); if (!tab.Empty()) { foreach (var item_iter in tab) { if (RandomHelper.randChance(item_iter.chance * WorldConfig.GetFloatValue(WorldCfg.RateSkillDiscovery)) && item_iter.reqSkillValue <= skillvalue && !player.HasSpell(item_iter.spellId)) return item_iter.spellId; } return 0; } return 0; } static MultiMap SkillDiscoveryStorage = new(); } public class SkillDiscoveryEntry { public SkillDiscoveryEntry(uint _spellId = 0, uint req_skill_val = 0, float _chance = 0) { spellId = _spellId; reqSkillValue = req_skill_val; chance = _chance; } public uint spellId; // discavered spell public uint reqSkillValue; // skill level limitation public float chance; // chance } }