[7776] Completed implementation of CMSG_SPELLCLICK

For vehicles, you have to add the correct SPELL_AURA_CONTROL_VEHICLE spells to
npc_spellclick_spells, otherwise you won't be able to use them
This commit is contained in:
arrai 2009-04-29 17:44:39 +02:00
parent 6e87802fa5
commit fefe56e3c5
19 changed files with 225 additions and 39 deletions

View file

@ -23,7 +23,7 @@ DROP TABLE IF EXISTS `db_version`;
CREATE TABLE `db_version` (
`version` varchar(120) default NULL,
`creature_ai_version` varchar(120) default NULL,
`required_7720_01_mangos_mangos_string` bit(1) default NULL
`required_7776_01_mangos_npc_spellclick_spells` bit(1) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes';
--

View file

@ -0,0 +1,8 @@
ALTER TABLE db_version CHANGE COLUMN required_7720_01_mangos_mangos_string required_7776_01_mangos_npc_spellclick_spells bit;
CREATE TABLE `npc_spellclick_spells` (
`npc_entry` INT UNSIGNED NOT NULL COMMENT 'reference to creature_template',
`spell_id` INT UNSIGNED NOT NULL COMMENT 'spell which should be casted ',
`quest_id` INT UNSIGNED NOT NULL COMMENT 'reference to quest_template',
`cast_flags` TINYINT UNSIGNED NOT NULL COMMENT 'first bit defines caster: 1=player, 0=creature; second bit defines target, same mapping as caster bit'
) ENGINE = MYISAM DEFAULT CHARSET=utf8;

View file

@ -177,6 +177,7 @@ pkgdata_DATA = \
7706_01_mangos_command.sql \
7714_01_mangos_command.sql \
7720_01_mangos_mangos_string.sql \
7776_01_mangos_npc_spellclick_spells.sql \
README
## Additional files to include when running 'make dist'
@ -334,4 +335,5 @@ EXTRA_DIST = \
7706_01_mangos_command.sql \
7714_01_mangos_command.sql \
7720_01_mangos_mangos_string.sql \
7776_01_mangos_npc_spellclick_spells.sql \
README

View file

@ -420,6 +420,7 @@ ChatCommand * ChatHandler::getCommandTable()
{ "page_text", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadPageTextsCommand, "", NULL },
{ "pickpocketing_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesPickpocketingCommand,"",NULL},
{ "points_of_interest", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadPointsOfInterestCommand, "",NULL},
{ "npc_spellclick_spells", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellClickSpellsCommand, "",NULL},
{ "prospecting_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesProspectingCommand,"", NULL },
{ "quest_mail_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesQuestMailCommand, "", NULL },
{ "quest_end_scripts", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadQuestEndScriptsCommand, "", NULL },

View file

@ -345,6 +345,7 @@ class ChatHandler
bool HandleReloadNpcVendorCommand(const char* args);
bool HandleReloadPageTextsCommand(const char* args);
bool HandleReloadPointsOfInterestCommand(const char* args);
bool HandleReloadSpellClickSpellsCommand(const char* args);
bool HandleReloadQuestAreaTriggersCommand(const char* args);
bool HandleReloadQuestEndScriptsCommand(const char* args);
bool HandleReloadQuestStartScriptsCommand(const char* args);

View file

@ -208,7 +208,7 @@ void Group::ConvertToRaid()
// update quest related GO states (quest activity dependent from raid membership)
for(member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr)
if(Player* player = objmgr.GetPlayer(citr->guid))
player->UpdateForQuestsGO();
player->UpdateForQuestWorldObjects();
}
bool Group::AddInvite(Player *player)
@ -302,7 +302,7 @@ bool Group::AddMember(const uint64 &guid, const char* name)
// quest related GO state dependent from raid memebership
if(isRaidGroup())
player->UpdateForQuestsGO();
player->UpdateForQuestWorldObjects();
}
return true;
@ -319,7 +319,7 @@ uint32 Group::RemoveMember(const uint64 &guid, const uint8 &method)
{
// quest related GO state dependent from raid membership
if(isRaidGroup())
player->UpdateForQuestsGO();
player->UpdateForQuestWorldObjects();
WorldPacket data;
@ -400,7 +400,7 @@ void Group::Disband(bool hideDestroy)
// quest related GO state dependent from raid membership
if(isRaidGroup())
player->UpdateForQuestsGO();
player->UpdateForQuestWorldObjects();
if(!player->GetSession())
continue;

View file

@ -102,6 +102,7 @@ bool ChatHandler::HandleReloadAllNpcCommand(const char* /*args*/)
HandleReloadNpcTrainerCommand("a");
HandleReloadNpcVendorCommand("a");
HandleReloadPointsOfInterestCommand("a");
HandleReloadSpellClickSpellsCommand("a");
return true;
}
@ -429,6 +430,14 @@ bool ChatHandler::HandleReloadPointsOfInterestCommand(const char*)
return true;
}
bool ChatHandler::HandleReloadSpellClickSpellsCommand(const char*)
{
sLog.outString( "Re-Loading `npc_spellclick_spells` Table!" );
objmgr.LoadNPCSpellClickSpells();
SendGlobalSysMessage("DB table `npc_spellclick_spells` reloaded.");
return true;
}
bool ChatHandler::HandleReloadReservedNameCommand(const char*)
{
sLog.outString( "Loading ReservedNames... (`reserved_name`)" );

View file

@ -1624,21 +1624,6 @@ void WorldSession::HandleSetTaxiBenchmarkOpcode( WorldPacket & recv_data )
sLog.outDebug("Client used \"/timetest %d\" command", mode);
}
void WorldSession::HandleSpellClick( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE(recv_data, 8);
uint64 guid;
recv_data >> guid;
Vehicle *vehicle = ObjectAccessor::GetVehicle(guid);
if(!vehicle)
return;
_player->EnterVehicle(vehicle);
}
void WorldSession::HandleInspectAchievements( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE(recv_data, 1);

View file

@ -24,6 +24,7 @@
#include "Corpse.h"
#include "Player.h"
#include "Vehicle.h"
#include "SpellAuras.h"
#include "MapManager.h"
#include "Transports.h"
#include "BattleGround.h"
@ -475,8 +476,8 @@ void WorldSession::HandleDismissControlledVehicle(WorldPacket &recv_data)
// using charm guid, because we don't have vehicle guid...
if(Vehicle *vehicle = ObjectAccessor::GetVehicle(vehicleGUID))
{
_player->ExitVehicle(vehicle);
vehicle->Dismiss();
// Aura::HandleAuraControlVehicle will call Player::ExitVehicle
vehicle->RemoveSpellsCausingAura(SPELL_AURA_CONTROL_VEHICLE);
}
}

View file

@ -600,9 +600,16 @@ void Object::_BuildValuesUpdate(uint8 updatetype, ByteBuffer * data, UpdateMask
{
if( updateMask->GetBit( index ) )
{
// remove custom flag before send
if( index == UNIT_NPC_FLAGS )
*data << uint32(m_uint32Values[ index ] & ~UNIT_NPC_FLAG_GUARD);
{
// remove custom flag before sending
uint32 appendValue = m_uint32Values[ index ] & ~UNIT_NPC_FLAG_GUARD;
if (GetTypeId() == TYPEID_UNIT && !target->canSeeSpellClickOn((Creature*)this))
appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK;
*data << uint32(appendValue);
}
// FIXME: Some values at server stored in float format but must be sent to client in uint32 format
else if(index >= UNIT_FIELD_BASEATTACKTIME && index <= UNIT_FIELD_RANGEDATTACKTIME)
{

View file

@ -5909,6 +5909,76 @@ void ObjectMgr::LoadPointsOfInterest()
sLog.outString(">> Loaded %u Points of Interest definitions", count);
}
void ObjectMgr::LoadNPCSpellClickSpells()
{
uint32 count = 0;
mSpellClickInfoMap.clear();
QueryResult *result = WorldDatabase.Query("SELECT npc_entry, spell_id, quest_id, cast_flags FROM npc_spellclick_spells");
if(!result)
{
barGoLink bar(1);
bar.step();
sLog.outString();
sLog.outErrorDb(">> Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty.");
return;
}
barGoLink bar(result->GetRowCount());
do
{
Field *fields = result->Fetch();
bar.step();
uint32 npc_entry = fields[0].GetUInt32();
CreatureInfo const* cInfo = GetCreatureTemplate(npc_entry);
if (!cInfo)
{
sLog.outErrorDb("Table npc_spellclick_spells references unknown creature_template %u. Skipping entry.", npc_entry);
continue;
}
uint32 spellid = fields[1].GetUInt32();
SpellEntry const *spellinfo = sSpellStore.LookupEntry(spellid);
if (!spellinfo)
{
sLog.outErrorDb("Table npc_spellclick_spells references unknown spellid %u. Skipping entry.", spellid);
continue;
}
uint32 quest = fields[2].GetUInt32();
// quest might be 0 to enable spellclick independent of any quest
if (quest)
{
if(mQuestTemplates.find(quest) == mQuestTemplates.end())
{
sLog.outErrorDb("Table npc_spellclick_spells references unknown quest %u. Skipping entry.", spellid);
continue;
}
}
uint8 castFlags = fields[3].GetUInt8();
SpellClickInfo info;
info.spellId = spellid;
info.questId = quest;
info.castFlags = castFlags;
mSpellClickInfoMap.insert(SpellClickInfoMap::value_type(npc_entry, info));
++count;
} while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u spellclick definitions", count);
}
void ObjectMgr::LoadWeatherZoneChances()
{
uint32 count = 0;

View file

@ -95,6 +95,15 @@ extern ScriptMapMap sSpellScripts;
extern ScriptMapMap sGameObjectScripts;
extern ScriptMapMap sEventScripts;
struct SpellClickInfo
{
uint32 spellId;
uint32 questId;
uint8 castFlags;
};
typedef std::multimap<uint32, SpellClickInfo> SpellClickInfoMap;
struct AreaTrigger
{
uint8 requiredLevel;
@ -533,6 +542,9 @@ class ObjectMgr
void LoadReputationOnKill();
void LoadPointsOfInterest();
SpellClickInfoMap mSpellClickInfoMap;
void LoadNPCSpellClickSpells();
void LoadWeatherZoneChances();
void LoadGameTele();

View file

@ -12284,7 +12284,7 @@ void Player::AddQuest( Quest const *pQuest, Object *questGiver )
CastSpell(this,itr->second->spellId,true);
}
UpdateForQuestsGO();
UpdateForQuestWorldObjects();
}
void Player::CompleteQuest( uint32 quest_id )
@ -13015,7 +13015,7 @@ void Player::SetQuestStatus( uint32 quest_id, QuestStatus status )
if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED;
}
UpdateForQuestsGO();
UpdateForQuestWorldObjects();
}
// not used in MaNGOS, but used in scripting code
@ -13136,7 +13136,7 @@ void Player::ItemAddedQuestCheck( uint32 entry, uint32 count )
}
}
}
UpdateForQuestsGO();
UpdateForQuestWorldObjects();
}
void Player::ItemRemovedQuestCheck( uint32 entry, uint32 count )
@ -13177,7 +13177,7 @@ void Player::ItemRemovedQuestCheck( uint32 entry, uint32 count )
}
}
}
UpdateForQuestsGO();
UpdateForQuestWorldObjects();
}
void Player::KilledMonster( uint32 entry, uint64 guid )
@ -18323,7 +18323,7 @@ bool Player::HasQuestForGO(int32 GOId) const
return false;
}
void Player::UpdateForQuestsGO()
void Player::UpdateForQuestWorldObjects()
{
if(m_clientGUIDs.empty())
return;
@ -18338,6 +18338,24 @@ void Player::UpdateForQuestsGO()
if(obj)
obj->BuildValuesUpdateBlockForPlayer(&udata,this);
}
else if(IS_CREATURE_GUID(*itr) || IS_VEHICLE_GUID(*itr))
{
Creature *obj = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, *itr);
if(!obj)
continue;
// check if this unit requires quest specific flags
SpellClickInfoMap const& map = objmgr.mSpellClickInfoMap;
for(SpellClickInfoMap::const_iterator itr = map.lower_bound(obj->GetEntry()); itr != map.upper_bound(obj->GetEntry()); ++itr)
{
if(itr->second.questId != 0)
{
obj->BuildCreateUpdateBlockForPlayer(&udata,this);
break;
}
}
}
}
udata.BuildPacket(&packet);
GetSession()->SendPacket(&packet);
@ -19204,8 +19222,8 @@ void Player::ExitVehicle(Vehicle *vehicle)
data << uint32(0);
GetSession()->SendPacket(&data);
// only for flyable vehicles?
CastSpell(this, 45472, true); // Parachute
// maybe called at dummy aura remove?
// CastSpell(this, 45472, true); // Parachute
}
bool Player::isTotalImmune()
@ -19849,3 +19867,15 @@ void Player::ResummonPetTemporaryUnSummonedIfAny()
m_temporaryUnsummonedPetNumber = 0;
}
bool Player::canSeeSpellClickOn(Creature const *c) const
{
SpellClickInfoMap const& map = objmgr.mSpellClickInfoMap;
for(SpellClickInfoMap::const_iterator itr = map.lower_bound(c->GetEntry()); itr != map.upper_bound(c->GetEntry()); ++itr)
{
if(itr->second.questId == 0 || GetQuestStatus(itr->second.questId) == QUEST_STATUS_INCOMPLETE)
return true;
}
return false;
}

View file

@ -1182,7 +1182,7 @@ class MANGOS_DLL_SPEC Player : public Unit
void ReputationChanged(FactionEntry const* factionEntry );
bool HasQuestForItem( uint32 itemid ) const;
bool HasQuestForGO(int32 GOId) const;
void UpdateForQuestsGO();
void UpdateForQuestWorldObjects();
bool CanShareQuest(uint32 quest_id) const;
void SendQuestComplete( uint32 quest_id );
@ -2048,6 +2048,7 @@ class MANGOS_DLL_SPEC Player : public Unit
void SetTitle(CharTitlesEntry const* title);
bool isActiveObject() const { return true; }
bool canSeeSpellClickOn(Creature const* creature) const;
protected:
/*********************************************************/

View file

@ -43,6 +43,7 @@
#include "Util.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Vehicle.h"
#include "CellImpl.h"
#define NULL_AURA_SLOT 0xFF
@ -6684,19 +6685,35 @@ void Aura::HandleArenaPreparation(bool apply, bool Real)
m_target->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PREPARATION);
}
void Aura::HandleAuraControlVehicle(bool /*apply*/, bool Real)
/**
* Such auras are applied from a caster(=player) to a vehicle.
* This has been verified using spell #49256
*/
void Aura::HandleAuraControlVehicle(bool apply, bool Real)
{
if(!Real)
return;
if(m_target->GetTypeId() != TYPEID_PLAYER)
Unit *player = GetCaster();
Vehicle *vehicle = dynamic_cast<Vehicle*>(m_target);
if(!player || player->GetTypeId()!=TYPEID_PLAYER || !vehicle)
return;
if(Pet *pet = m_target->GetPet())
pet->Remove(PET_SAVE_AS_CURRENT);
if (apply)
{
if(Pet *pet = player->GetPet())
pet->Remove(PET_SAVE_AS_CURRENT);
((Player*)player)->EnterVehicle(vehicle);
}
else
{
SpellEntry const *spell = GetSpellProto();
WorldPacket data(SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA, 0);
((Player*)m_target)->GetSession()->SendPacket(&data);
// some SPELL_AURA_CONTROL_VEHICLE auras have a dummy effect on the player - remove them
player->RemoveAurasDueToSpell(spell->Id);
((Player*)player)->ExitVehicle(vehicle);
}
}
void Aura::HandleAuraConvertRune(bool apply, bool Real)

View file

@ -1108,6 +1108,19 @@ void Spell::EffectDummy(uint32 i)
m_caster->CastSpell(m_caster, 30452, true, NULL);
return;
}
case 51592: // Pickup Primordial Hatchling
{
if(!unitTarget || unitTarget->GetTypeId() != TYPEID_UNIT)
return;
Creature* creatureTarget = (Creature*)unitTarget;
creatureTarget->setDeathState(JUST_DIED);
creatureTarget->RemoveCorpse();
creatureTarget->SetHealth(0); // just for nice GM-mode view
return;
}
case 52308:
{
switch(i)

View file

@ -489,3 +489,29 @@ void WorldSession::HandleSelfResOpcode( WorldPacket & /*recv_data*/ )
_player->SetUInt32Value(PLAYER_SELF_RES_SPELL, 0);
}
}
void WorldSession::HandleSpellClick( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE(recv_data, 8);
uint64 guid;
recv_data >> guid;
Creature *unit = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid);
if(!unit)
return;
SpellClickInfoMap const& map = objmgr.mSpellClickInfoMap;
for(SpellClickInfoMap::const_iterator itr = map.lower_bound(unit->GetEntry()); itr != map.upper_bound(unit->GetEntry()); ++itr)
{
if(itr->second.questId == 0 || _player->GetQuestStatus(itr->second.questId) == QUEST_STATUS_INCOMPLETE)
{
Unit *caster = (itr->second.castFlags & 0x1) ? (Unit*)_player : (Unit*)unit;
Unit *target = (itr->second.castFlags & 0x2) ? (Unit*)_player : (Unit*)unit;
caster->CastSpell(target, itr->second.spellId, true);
}
}
}

View file

@ -1198,6 +1198,9 @@ void World::SetInitialWorldSettings()
sLog.outString( ">>> Quests Relations loaded" );
sLog.outString();
sLog.outString( "Loading UNIT_NPC_FLAG_SPELLCLICK Data..." );
objmgr.LoadNPCSpellClickSpells();
sLog.outString( "Loading SpellArea Data..." ); // must be after quest load
spellmgr.LoadSpellAreas();

View file

@ -1,4 +1,4 @@
#ifndef __REVISION_NR_H__
#define __REVISION_NR_H__
#define REVISION_NR "7775"
#define REVISION_NR "7776"
#endif // __REVISION_NR_H__