Cast Custom spell

Indította kagebunshin, 2010 július 22, 03:26:56 DÉLUTÁN

Előző téma - Következő téma

kagebunshin

Egy érdekes függvényre akadtam miközben ismerkedtem az emu-val. Nem tudom mennyire ismert, mindenesetre itt még nem találkoztam vele.
A 'CastCustomSpell' nevű gyönyörűségről van szó. Néhány régebbi boss is használja, ezért feltételezem a korábbi emukban is benne lehet.

Na de mit is csinál ez?
A neve ugye nagyon árulkodik és bár nagy csodákra nem képes a spellek effect értékét átírhatjuk vele.
Scriptben használni ugyanúgy lehet, mint a sima cast-ot, át kell neki adni a célpontot, magát a spellt, az effect-ek új értékeit és még van néhány paramétere, amibe nem ástam bele magam. A felfedezésnek ezt az örömét meghagyom másoknak.

TrinityCore2 8307 és 8875-ön teszteltem kb 30 különböző spellen és nem okoztak különösebb problémát. De azért csak óvatosan a számokkal.
Kicsit hosszúra sikerült a bevezető, de mindjárt rátérek a lényegre.

A következő módosítások után adatbázis scriptből is használhatjuk a függvényt. Ezen kívül kiegészítettem egy kicsit, hogy a kasztolás ideje is befolyásolható legyen.
Előrebocsátom, hogy nagyon mélyen belenyúltam a dolgokba. Éhgyomorral ne vágjatok bele :).

Patch:
TrinityCore 8848: http://pastebin.com/XUJ8Lhe3

Ha van rá igény más verziókra is elkészítem.

A következő file-ok legyenek kéznél:
IdézGlobals\ObjectMgr.h
Globals\ObjectMgr.cpp
World\World.cpp
AI\EventAI\CreatureEventAI.h
AI\EventAI\CreatureEventAI.cpp
AI\EventAI\CreatureEventAIMgr.cpp
Spells\Spell.h
Spells\Spell.cpp
Entities\Unit\Unit.h
Entities\Unit\Unit.cpp
Chat\Chat.h
Chat\Chat.cpp
Chat\Commands\Level3.cpp

Működési elv röviden:
1. Létrehozunk egy új táblát amiben tároljuk majd a custom spell-eket, majd ezeket beolvastatjuk a core-al.
2. Hozzáadunk egy commandot az eventai scripthez.
3. Létrehozunk egy függvényt mely beállítja nekünk a kasztolási időt.
4. Beszúrunk egy új gm parancsot amivel menet közben is frissíthetjük a táblánkat.

Részletes leírás:
Hozzuk létre a custom spelljeink tárolására szolgáló adatbázis táblát.
Erre én a következő SQL utasítást használtam:
IdézCREATE TABLE spell_custom (
spell_custom_id INT NOT NULL AUTO_INCREMENT,
spell_id INT NOT NULL,
eff1 INT NOT NULL DEFAULT 0,
eff2 INT NOT NULL DEFAULT 0,
eff3 INT NOT NULL DEFAULT 0,
cast_time INT NOT NULL DEFAULT 0,
PRIMARY KEY (spell_custom_id)
) DEFAULT CHARSET = utf8;

Gondolom elég nyilvánvaló melyik mező mit tud, de azért pár szót mindegyikről.
spell_custom_id: A custom spellünk azonosítója. Nem kell megadni.
spell_id: Egy spell id-jét kell megadni. Azt fogja módosítani a megadott értékekkel.
eff1...3: A spell effectjének értékét ennyire állítja.
cast_time: kasztolási idő. Ezredmásodpercben kell megadni. Ennyi ideig tart amíg elsüti a spell-t.

Most hogy már van táblánk illene beolvasnunk is. De előbb létre kell hoznunk neki egy struktúrát.
Nyissuk meg a Globals\ObjectMgr.h file-t és a class ObjectMgr elé valahova szúrjuk be a következőt:
Idézstruct CustomSpell
{
   CustomSpell(uint32 _id, uint32 _spell_id, uint32 _eff1, int32 _eff2, int32 _eff3, int32 _cast_time)
       : id(_id), spell_id(_spell_id), eff1(_eff1), eff2(_eff2), eff3(_eff3), cast_time(_cast_time) {}

   uint32 id;
   uint32 spell_id;
   int32 eff1;
   int32 eff2;
   int32 eff3;
   int32 cast_time;
};

typedef std::vector<CustomSpell*> CustomSpellMap;

Ehhez sem fűznék sokat. Szinte egyezik a tábla struktúrájával.

Most a class ObjectMgr-en bellüli részt nézzük meg. Rögtön az elején van egy public: és utána rengeteg változó és függvény. Utána következnek a protected és private elemek. Nem részletezem melyik mit jelent most nem az a fontos.
A lényeg, hogy a public mögé ez kerül:
Idézvoid LoadCustomSpell();

CustomSpell* GetCustomSpell(uint32 entry) const
{
   int cv;
   for(cv=0;cv<m_CustomSpellMap.size();cv++)
       if(m_CustomSpellMap[cv]->id == entry)
           return m_CustomSpellMap[cv];         
   return NULL;
}

A protected mögé pedig ez:
IdézCustomSpellMap      m_CustomSpellMap;

A GetCustomSpell visszaadja majd az általunk keresett custom spell adatait.
LoadCustomSpell pedig betölti őket az adatbázisból. Persze előbb még kell írnunk.
Nyissuk is meg a Globals\ObjectMgr.cpp file-t és szúrjuk a végére a következőt:
Idézvoid ObjectMgr::LoadCustomSpell()
{
   m_CustomSpellMap.clear();

   QueryResult_AutoPtr result = WorldDatabase.Query("SELECT spell_custom_id, spell_id, eff1, eff2, eff3, cast_time FROM spell_custom");

   if (!result)
   {
       barGoLink bar(1);

       bar.step();

       sLog.outString();
       sLog.outErrorDb(">> Loaded ` spell_custom`, table is empty!");
       return;
   }

   barGoLink bar(result->GetRowCount());

   uint32 count = 0;

   do
   {
       bar.step();

       Field* fields = result->Fetch();

       uint32 id = fields[0].GetUInt32();
       uint32 spell_id = fields[1].GetUInt32();
       int32 eff1 = fields[2].GetInt32();
       int32 eff2 = fields[3].GetInt32();
       int32 eff3 = fields[4].GetInt32();
       int32 cast_time = fields[5].GetInt32();

       m_CustomSpellMap.push_back(new CustomSpell(id, spell_id, eff1, eff2, eff3, cast_time));

       ++count;
   }
   while (result->NextRow());

   sLog.outString();
   sLog.outString(">> Loaded %u spell_custom entries", count);
}

Itt más dolgunk nincs is. Ugorhatunk a következőre.
Nyissuk meg a World\World.cpp file-t és keressük meg benne a SetInitialWorldSettings függvényt. Ez olvassa be induláskor a táblákat. Ki is egészítjük a sajátunkkal. Valahova szúrjuk be, nincs különösebb jelentősége. Csak ne legyen nagyon az elején vagy a legvégén. Én a DBC file-ok beolvasása után raktam.
kód:
IdézsLog.outString("Loading Custom spell...");
objmgr.LoadCustomSpell();

Aki nem biztos a dolgában itt egy újraforgatás után tesztelheti, hogy jól dolgozott-e eddig.
Néhány adattal töltse fel a spell_custom táblát majd indítsa el a szervert. Log-ban ezután visszanézheti, hogy tényleg beolvasott-e annyi spell-t amennyi a táblában szerepel.
Ha minden oké, akkor mehetünk is tovább.

Most jön az eventAi módosítása.
Nyissuk meg először az AI\EventAI\CreatureEventAI.h file-t és keressük meg benne az
enum EventAI_ActionType

Válasszunk egy számot a cselekvésünknek. Ügyeljünk rá, hogy az ACTION_T_END értékénél ne legyen nagyobb mert azt betöltéskor visszaállítja 0-ra ezenkívül a creature_ai_scripts tábla action_type mezőjébe fog kerülni ami szintén nem fogad el 255-nél magasabb értéket. Figyeljünk arra is, hogy olyat adjunk meg ami még nincs használva.
Én 51-et adtam meg.
IdézACTION_T_CUSTOM_CAST                = 51,   //Custom spell

Most jöhet az AI\EventAI\CreatureEventAI.cpp file. Annak is a ProcessAction függvényére lesz szükségünk.
Ugorjunk a függvény végére és az utolsó esemény után illesszük be a sajátunkat:
Idézcase ACTION_T_CUSTOM_CAST:
       {
           CustomSpell const* cSpell = objmgr.GetCustomSpell(action.cast.spellId);

           if(!cSpell)
               return;

           uint32 spell = cSpell->spell_id;

           Unit* target = GetTargetByType(action.cast.target, pActionInvoker);
           Unit* caster = me;

           if (!target)
               return;

           if (action.cast.castFlags & CAST_FORCE_TARGET_SELF)
               caster = target;

           //Allowed to cast only if not casting (unless we interrupt ourself) or if spell is triggered
           bool canCast = !caster->IsNonMeleeSpellCasted(false) || (action.cast.castFlags & (CAST_TRIGGERED | CAST_INTURRUPT_PREVIOUS));

           // If cast flag CAST_AURA_NOT_PRESENT is active, check if target already has aura on them
           if (action.cast.castFlags & CAST_AURA_NOT_PRESENT)
           {
               if (target->HasAura(spell))
                   return;
           }

           if (canCast)
           {
               const SpellEntry* tSpell = GetSpellStore()->LookupEntry(spell);

               //Verify that spell exists
               if (tSpell)
               {
                   //Check if cannot cast spell
                   if (!(action.cast.castFlags & (CAST_FORCE_TARGET_SELF | CAST_FORCE_CAST)) &&
                       !CanCast(target, tSpell, (action.cast.castFlags & CAST_TRIGGERED)))
                   {
                       //Melee current victim if flag not set
                       if (!(action.cast.castFlags & CAST_NO_MELEE_IF_OOM))
                       {
                           if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
                           {
                               AttackDistance = 0.0f;
                               AttackAngle = 0.0f;

                               me->GetMotionMaster()->MoveChase(me->getVictim(), AttackDistance, AttackAngle);
                           }
                       }
                   }
                   else
                   {
                       //Interrupt any previous spell
                       if (caster->IsNonMeleeSpellCasted(false) && action.cast.castFlags & CAST_INTURRUPT_PREVIOUS)
                           caster->InterruptNonMeleeSpells(false);
                  
                       caster->CastCustomSpell(cSpell->id, target, (action.cast.castFlags & CAST_TRIGGERED));
                   }
               }
               else
                   sLog.outErrorDb("CreatureEventAI: event %d creature %d attempt to cast spell that doesn't exist %d", EventId, me->GetEntry(), action.cast.spellId);
           }
           break;
       }

Nem saját kód. A normál cast esemény van átalakítva.
A megadott spell id-nek megfelelő custom spell-t betöltjük majd a spell id-t az ott megadott értékre módosítjuk. A végén pedig cast helyett a saját castcustomspell függvényt használjuk.
A paraméterlistán változtattam kicsit, hogy ne kelljen minden paramétert továbbadni. Csak a custom spell id-jét adjuk tovább, amit majd a következő függvény megint lekér.

A következő módosítás ugyan nem kötelező, mert anélkül is lefordul/működik, viszont a creature_ai_scripts tábla betöltésekor hibaüzenet kapunk, ami nagy mennyiségben lassítja az indulást.
Hogy ezt elkerüljük nyissuk meg a CreatureEventAIMgr.cpp file-t és keressük rá a következőre:
Idézcase ACTION_T_SUMMON_GO:
és szúrjuk be mögé a következőt:
Idézcase ACTION_T_CUSTOM_CAST:

Így már békén fog hagyni.

Most jöhet a Spells\Spell.h. Kényes hely. Vigyázunk, hogy ne rontsunk el semmit.
Keressük meg a class Spell-t és rögtön az elején a public után írjuk be a következőt:
Idézvoid SetSpellValue(int32 id, int32 value) { m_spellValue->EffectBasePoints[id] = value; }
void prepare(uint32 spellId, SpellCastTargets const* targets, AuraEffect const * triggeredByAura = NULL);

Nem részletezem mire jó. A lényeg, hogy használni fogjuk őket.

Most a Spells\Spell.cpp file-ra lesz szükségünk. Bemásolnám a komplett függvényt, de figyelembe véve nem minden napi méretét inkább csak leírom, hogyan készült.
Másoljuk le a prepare függvényt majd írjuk át az elejét.
Pontosan ilyen legyen különben a fordító nagyon csúnyán fog velünk beszélni:
Idézvoid Spell::prepare(uint32 spellId, SpellCastTargets const* targets, AuraEffect const * triggeredByAura)
{
   CustomSpell const* cSpell = objmgr.GetCustomSpell(spellId);

   if(!cSpell)
   return;

   if (m_CastItem)
       m_castItemGUID = m_CastItem->GetGUID();
   else
       m_castItemGUID = 0;

Továbbá a következő részt:
Idézm_casttime = GetSpellCastTime(m_spellInfo, this);

cseréljük le erre:
Idézm_casttime = cSpell->cast_time;

Ha ezek után még nem köszönt vissza a reggeli, akkor folytassuk.

Hasonló játékot fogunk eljátszani a Entities\Unit\Unit.h-val.
Keressük meg class Unit : public WorldObject és szintén a public után szurjuk be a következőt:
Idézvoid CastCustomSpell(uint32 spellId, Unit* Victim = NULL, bool triggered = true, Item *castItem = NULL, AuraEffect const * triggeredByAura = NULL, uint64 originalCaster = 0);

Az Entities\Unit\Unit.cpp-hez pedig adjuk hozzá a következő függvényt:
Idézvoid Unit::CastCustomSpell(uint32 spellId, Unit* Victim, bool triggered, Item *castItem, AuraEffect const * triggeredByAura, uint64 originalCaster)
{
   CustomSpell const* cSpell = objmgr.GetCustomSpell(spellId);

   if(!cSpell)
       return;

   uint32 Id = cSpell->spell_id;

   SpellEntry const *spellInfo = sSpellStore.LookupEntry(Id);
   if (!spellInfo)
   {
       sLog.outError("CastSpell: unknown spell id %i by caster: %s %u)", Id,(GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"),(GetTypeId() == TYPEID_PLAYER ? GetGUIDLow() : GetEntry()));
       return;
   }

   SpellCastTargets targets;
   targets.setUnitTarget(Victim);

   if (!originalCaster && triggeredByAura)
       originalCaster = triggeredByAura->GetCasterGUID();

   Spell *spell = new Spell(this, spellInfo, triggered, originalCaster);

   if (castItem)
   {
       DEBUG_LOG("WORLD: cast Item spellId - %i", spellInfo->Id);
       spell->m_CastItem = castItem;
   }

   spell->m_currentBasePoints[0]=cSpell-> eff1;
   spell->m_currentBasePoints[1]=cSpell-> eff2;
   spell->m_currentBasePoints[2]=cSpell-> eff3;

   spell->SetSpellValue(0,cSpell-> eff1);
   spell->SetSpellValue(1,cSpell-> eff2);
   spell->SetSpellValue(2,cSpell-> eff3);

   spell->prepare(spellId, &targets, triggeredByAura);
}

Ha eddig mindent jól csináltunk, akkor gyakorlatilag kész is volnánk. Újraforgatás után lehet is tesztelgetni. Nincs más dolgunk, mint feltölteni a spell_custom táblát a custom spelljeinkkel. Majd a creature_ai_scripts táblát megfelelően kitöltve kedvenc kísérleti koboldom máris 35 sec-ig szerencsétlenkedik egy mezei holy light-al. Gondolom aki ilyenbe belevág tisztába van a creature_ai_scripts és a hozzá tartozó táblák működésével.
Aki nem annak itt a saját tesztpéldám SQL parancsokkal:
IdézINSERT spell_custom (spell_id, eff1, cast_time) VALUES (635, 100, 35000);
INSERT creature_ai_scripts (creature_id, event_type, event_chance, event_flags, event_param1, event_param2, event_param3, event_param4, action1_type, action1_param1, action1_param2, action1_param3, COMMENT) VALUES
(6, 0, 100, 1, 3000, 3000, 180000, 180000, 51, 1, 0, 0, 'Kobold Vermin - Cast Holy Light');

Ha szeretnénk, még kiegészíthetjük azzal, hogy új táblánk futás időben is újratölthető legyen.
Nyissuk meg a Chat\Chat.h file-t. Majd keressük meg class ChatHandler és a protected részéhez adjuk hozzá a következőt:
Idézbool HandleReloadCustomSpellCommand(const char* args);

Ha ez megvan, akkor a Chat\Chat.cpp, getCommandTable() függvényét kell megkeresni. Azon belül is a reloadCommandTable[]-t. Valahova szúrjuk be a következőt:
Idéz{ "custom_spell",                SEC_ADMINISTRATOR, true,  &ChatHandler::HandleReloadCustomSpellCommand,             "", NULL },

Én a spell_bonus_data mögé raktam. Ha már abc sorrendben rakták ne rontsuk el.
Most már csak a Chat\Commands\Level3.cpp-hez kell hozzáadni a következő függvényt:
Idézbool ChatHandler::HandleReloadCustomSpellCommand(const char*)
{
   sLog.outString( "Re-Loading `custom_spell` Table!" );
   objmgr.LoadCustomSpell();
   SendGlobalSysMessage("DB table `custom_spell` reloaded.");
   return true;
}

Ismét egy újraforgatás és kész is vagyunk.

Annyit megsúgok még, hogy apró trükkökkel kiügyeskedhető, hogy játékosok is tudják használni ezt a függvényt, ha találok rá valami szép megoldást, akkor azt is közzéteszem.

EDIT:
-Kicsit kiegészítettem a leírást.
-Készítettem hozzá egy patch file-t, hogy könnyebb legyen felhasználni.
"-Egy dolgot viszont elárulhatnál nekem. Ki találta ki, hogy a fák őreinek erejével szálljatok szembe velem, mert nem te ugye? Te nem tudtad!
-Nem... de azért vagyunk többen, hogy valaki tudja."

Bluerák

#1
Azta k... :o :o
Na...ez egy sz

MacBook Pro 13' 2.26 GHZ, 4 GB RAM - White
iPhone 4S 16GB - Black
iPad 3 - Black

NEW PC:
Asus EAH6670 (1GB 128 bit)
AMD FX AM3+ (4x3.8GHZ)
ASRock 970 EXTREME3
8GB RAM

$0undX

Nem rég láttalak felbukkanni ezen a fórumon de a leírásaidat az egyszerűség, és értékesség-gel tudnám jellemezni nem tudom mióta foglalkozol ezzel de nagyon szép leírás lett és tényleg eléggé beletúrtál az emuban és ahogy ezek szerint működik, ahoz szintén csak gratula.  :)

Action

 :o Be kéne csukni de, nem megy. :D

Köszönjük szépen, mást úgy gondolom nem is kell mondanom. :)
A WORD elszáll, az írás megmarad.

kagebunshin

Idézetet írta: $0undX Dátum 2010 július 22, 11:54:12 DÉLUTÁN
Nem rég láttalak felbukkanni ezen a fórumon de a leírásaidat az egyszerűség, és értékesség-gel tudnám jellemezni nem tudom mióta foglalkozol ezzel de nagyon szép leírás lett és tényleg eléggé beletúrtál az emuban és ahogy ezek szerint működik, ahoz szintén csak gratula.  :)

Köszönöm mindenkinek. Igyekszik az ember  :).

Valamikor áprilisban kezdtem el ismerkedni az emulátorokkal és sokat segítettek az itteni leírások.
Intenzíven csak most vizsgaidőszak után ástam bele magam.

Ez a módosítás nagyjából 1 hét volt. Maga a leírás meg durván 6 óra alatt készült el.
"-Egy dolgot viszont elárulhatnál nekem. Ki találta ki, hogy a fák őreinek erejével szálljatok szembe velem, mert nem te ugye? Te nem tudtad!
-Nem... de azért vagyunk többen, hogy valaki tudja."

Powered by EzPortal