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 (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_ActionTypeVá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.
Azta k... :o :o
Na...ez egy sz
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. :)
:o Be kéne csukni de, nem megy. :D
Köszönjük szépen, mást úgy gondolom nem is kell mondanom. :)