Modcraft - The community dedicated to quality WoW modding!
Wrath of the Lich King Modding => Serverside Modding => Topic started by: Phabulous on August 30, 2011, 05:38:34 am
-
Ello Modcraft,
If anyone would be so kind as to help me, I'm trying my darndest to enable players to learn spells Cata-style, in that they learn a spell once and it scales as they level.
I think I MIGHT be able to retro-fit something like this from Skyfire to work, but my skills in C++ don't extend past modest boss encounters.
Could anyone assist me or point me to someone who could? (http://https)
-
I think, from a realistic point of view, your best of to let players auto-learn spellranks.
Example:
Warrior level 50 learns mortal strike, and on level 53, the second rank of mortal strike is available. Normally the player would need to visit his/her class trainer to get this second rank, but you could teach it automatically on level-up.
Do your research, check how functions like Player::giveLevel work and Player::addSpell
-
I haven't been able to find a pointer to check if a player has already learned a rank of a spell. I can add Mortal Strike to a warrior who reaches an appropriate level, but I haven't figured out how to check if the previous rank of a spell has already been learned before auto-learning the next rank. I appreciate the help but I'd also appreciate another nudge.
-
I haven't been able to find a pointer to check if a player has already learned a rank of a spell. I can add Mortal Strike to a warrior who reaches an appropriate level, but I haven't figured out how to check if the previous rank of a spell has already been learned before auto-learning the next rank. I appreciate the help but I'd also appreciate another nudge.
Create a pointer to a unit which is a trainer that will be used to get the available spells from. So my advice is to make a global trainer which can learn every spell and create a Unit* pointer to him.
Then you can use unit->GetTrainerSpells() to get all the spells he learns, and then in a for loop you can check if each spell is available to be learned by player->GetTrainerSpellState.
Something like:
TrainerSpellData const& trainer_spells = unit->GetTrainerSpells();
for (TrainerSpellMap::const_iterator itr = trainer_spells.spellList.begin(); itr != trainer_spells.spellList.end(); ++itr)
{
// spell not found, cheat?
TrainerSpell const* trainer_spell = trainer_spells->Find(itr->first);
if (!trainer_spell)
return;
// learn nothing else then spells that are green(available) at the trainer.
if (_player->GetTrainerSpellState(trainer_spell) != TRAINER_SPELL_GREEN)
return;
// then if thats good u can learn the spell here
}
I only wrote this out of the top of my head, without an IDE so beware for errors, its just for pointing in the right direction.
-
You may as well have a look at Player::GetTrainerSpellState(trainer_spell). That will look somewhere in Player, where you already are, if a spell is available. Therefore, there needs to be information inside of Player, if a spell is learnable. You shouldn't need to have a virtual trainer for doing this, being only indirection from Player to Trainer to Player.
-
You may as well have a look at Player::GetTrainerSpellState(trainer_spell). That will look somewhere in Player, where you already are, if a spell is available. Therefore, there needs to be information inside of Player, if a spell is learnable. You shouldn't need to have a virtual trainer for doing this, being only indirection from Player to Trainer to Player.
Interesting, i didn't know that function existed, yet you still need a source of potential spells. It's unrealistic to do a class and level check of every spell known in the dbcs. A virtual trainer isn't the best solution either but im not sure there is a way to get a class-only spellmap without checking every spell in the dbc. Please correct me if im wrong.
-
Well, as it seems after actually looking at trinity core, this is indeed the only way to do this. Sadly.
They implement trainers as having a list of trainable stuff _each_, not per trainer type. (seems bad design to me).
The checking if it's available to the player, is done by checking if the player knows that particular spell being learned and matching the parameters given in the trainer data.
Therefore, the way to go is inserting a dummy trainer per class, but never spawning it.
QueryResult result = WorldDatabase.Query("SELECT b.entry, a.spell, a.spellcost, a.reqskill, a.reqskillvalue, a.reqlevel FROM npc_trainer AS a "
"INNER JOIN npc_trainer AS b ON a.entry = -(b.spell) "
"UNION SELECT * FROM npc_trainer WHERE spell > 0");
This query as well as
if (entry >= TRINITY_TRAINER_START_REF)
return;
CreatureInfo const* cInfo = GetCreatureTemplate(entry);
if (!cInfo)
{
sLog->outErrorDb("Table `npc_trainer` contains an entry for a non-existing creature template (Entry: %u), ignoring", entry);
return;
}
if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
{
sLog->outErrorDb("Table `npc_trainer` contains an entry for a creature template (Entry: %u) without trainer flag, ignoring", entry);
return;
}
SpellEntry const *spellinfo = sSpellStore.LookupEntry(spell);
if (!spellinfo)
{
sLog->outErrorDb("Table `npc_trainer` contains an entry (Entry: %u) for a non-existing spell (Spell: %u), ignoring", entry, spell);
return;
}
if (!SpellMgr::IsSpellValid(spellinfo))
{
sLog->outErrorDb("Table `npc_trainer` contains an entry (Entry: %u) for a broken spell (Spell: %u), ignoring", entry, spell);
return;
}
if (GetTalentSpellCost(spell))
{
sLog->outErrorDb("Table `npc_trainer` contains an entry (Entry: %u) for a non-existing spell (Spell: %u) which is a talent, ignoring", entry, spell);
return;
}
needs to be fulfilled.
You sadly need to assemble lists of all learnable spells per class yourself. You may copy that together from all trainers having the same type.
You then _not_ create a unit for this but you only ask const TrainerSpellData* sObjectMgr->GetNpcTrainerSpells(dummy_trainer_id_for_class);
There, you now iterate over the entries as Laniax said.
you should end up with something like
void Player::updateSpellsOnLevelup() const
{
const uint32 dummy_trainer_id_for_class (something());
TrainerSpellData const* trainer_spells
(sObjectMgr->GetNpcTrainerSpells(dummy_trainer_id_for_class));
for ( TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin()
; itr != trainer_spells->spellList.end()
; ++itr
)
{
uint32 spellId (itr->first); // was this spell id? not sure. else get from trainer_spell.
TrainerSpell const* trainer_spell = trainer_spells->Find (itr->first);
if (!trainer_spell)
continue;
if (GetTrainerSpellState (trainer_spell) != TRAINER_SPELL_GREEN)
continue;
WorldPacket data (SMSG_PLAY_SPELL_VISUAL, 12);
data << uint64 (GetGUID());
data << uint32 (0x016A); // index from SpellVisualKit.dbc
SendPacket(&data);
// learn explicitly or cast explicitly
if (trainer_spell->IsCastable())
CastSpell(this, trainer_spell->spell, true);
else
learnSpell(spellId, false);
}
}
-
Well, as it seems after actually looking at trinity core, this is indeed the only way to do this. Sadly.
They implement trainers as having a list of trainable stuff _each_, not per trainer type. (seems bad design to me).
The checking if it's available to the player, is done by checking if the player knows that particular spell being learned and matching the parameters given in the trainer data.
Therefore, the way to go is inserting a dummy trainer per class, but never spawning it.
QueryResult result = WorldDatabase.Query("SELECT b.entry, a.spell, a.spellcost, a.reqskill, a.reqskillvalue, a.reqlevel FROM npc_trainer AS a "
"INNER JOIN npc_trainer AS b ON a.entry = -(b.spell) "
"UNION SELECT * FROM npc_trainer WHERE spell > 0");
This query as well as
if (entry >= TRINITY_TRAINER_START_REF)
return;
CreatureInfo const* cInfo = GetCreatureTemplate(entry);
if (!cInfo)
{
sLog->outErrorDb("Table `npc_trainer` contains an entry for a non-existing creature template (Entry: %u), ignoring", entry);
return;
}
if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
{
sLog->outErrorDb("Table `npc_trainer` contains an entry for a creature template (Entry: %u) without trainer flag, ignoring", entry);
return;
}
SpellEntry const *spellinfo = sSpellStore.LookupEntry(spell);
if (!spellinfo)
{
sLog->outErrorDb("Table `npc_trainer` contains an entry (Entry: %u) for a non-existing spell (Spell: %u), ignoring", entry, spell);
return;
}
if (!SpellMgr::IsSpellValid(spellinfo))
{
sLog->outErrorDb("Table `npc_trainer` contains an entry (Entry: %u) for a broken spell (Spell: %u), ignoring", entry, spell);
return;
}
if (GetTalentSpellCost(spell))
{
sLog->outErrorDb("Table `npc_trainer` contains an entry (Entry: %u) for a non-existing spell (Spell: %u) which is a talent, ignoring", entry, spell);
return;
}
needs to be fulfilled.
You sadly need to assemble lists of all learnable spells per class yourself. You may copy that together from all trainers having the same type.
You then _not_ create a unit for this but you only ask const TrainerSpellData* sObjectMgr->GetNpcTrainerSpells(dummy_trainer_id_for_class);
There, you now iterate over the entries as Laniax said.
you should end up with something like
void Player::updateSpellsOnLevelup() const
{
const uint32 dummy_trainer_id_for_class (something());
TrainerSpellData const* trainer_spells
(sObjectMgr->GetNpcTrainerSpells(dummy_trainer_id_for_class));
for ( TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin()
; itr != trainer_spells->spellList.end()
; ++itr
)
{
uint32 spellId (itr->first); // was this spell id? not sure. else get from trainer_spell.
TrainerSpell const* trainer_spell = trainer_spells->Find (itr->first);
if (!trainer_spell)
continue;
if (GetTrainerSpellState (trainer_spell) != TRAINER_SPELL_GREEN)
continue;
WorldPacket data (SMSG_PLAY_SPELL_VISUAL, 12);
data << uint64 (GetGUID());
data << uint32 (0x016A); // index from SpellVisualKit.dbc
SendPacket(&data);
// learn explicitly or cast explicitly
if (trainer_spell->IsCastable())
CastSpell(this, trainer_spell->spell, true);
else
learnSpell(spellId, false);
}
}
Then you have to create a virtual trainer for every class ingame. (thats 10 classes). Trinity bad designed trainer system does give us the opportunity to put all these classes inside 1 trainer. Not only will you end up with only 1 virtual trainer (instead of 10) but it is also already made for you. If were doing it a hacky way, atleast hack it 'cleanly'.
A global trainer, which is a trainer that can be used by every class. Now im not sure how they check the class condition in the trainer (haven't really checked the source) but you might want to include a classcheck in the core.
I'm not linking to other forums that are full of trolls, but this link should help you:
http://tinyurl.com/3k9hlqr (http://tinyurl.com/3k9hlqr" onclick="window.open(this.href);return false;)