Már az idejét sem tudom mikor kezdtem el foglalkozni az MMORPG kel, de azóta rengeteget tanultam róluk. Mondhatom szerencsémre.
Jelenlegi írásomban nem a játékokról, hanem a hozzájuk tartozó addonokról szeretnék pár szót szólni. Eléggé sok MMO val volt már dolgom az elmúlt években, ezek közül természetesen mi lehetne az, ami a legtöbb időmet elvette és a mai napig is szép emlékeket társítok hozzá? (nem, nem a Hello Kitty Online) Természetesen a World Of Warcraft. Eme nagyszerű játék volt az első az életembe, Ő volt, aki behúzott az MMO-k, a Raidek, a BG-k világába, és Ő volt az, aki megismertette velem az Addonok-at is.
Azt hiszem mondhatom azt, hogy a WoW volt az alapozó, hisz legyen szó bármilyen MMO ról, az addon rendszer szinte teljesen ugyan az, mint a WoW -é. Ugyan az a scriptnyelv, ugyan azok a függvények, ugyan az a felépítés, így nem lenne csoda, ha az addonok minden ilyen stílusú játékban gomba mód szaporodnának, de ellenben az elvárással, a mai napig a WoW az, aki hatalmas addon adatbázissal rendelkezik, míg a többiek több ezerrel szorulnak mögé!
Egy nagyon hasznos oldal a Curse.com, ami az általam ismert legjobb, legigényesebb, legjobban használható és legnagyobb addon adatbázis, a WoW -nak 5.001, a 2. helyezett Warhammer Online nak pedig 199 addonja van!
A mostani addon fabrikálást egy régi WoW os addonommal fogom bemutatni, amit annak idején még drága barátom Taszi unszolására csináltam a Burning Crusade idején (de régen is volt … :))
Azóta sok víz folyt le a Dunán, de remélem még ma is működik.
Az addon neve: CritKing (WoW link: http://www.curse.com/addons/wow/crtkng, Rift link: http://www.curse.com/addons/rift/riftcritking)
Az alap működése az, hogy számolja a kritikus ütéseket és az egymás utáni győzelmeket egy harcban és annak megfelelő üzenetet ír ki, vagy épp játszik le egy hangot. Jelenleg Runes Of Magic -hez készül a legfrissebb verziója.
Scriptnyelv
Az addonok nyelve a LUA. Erről a nyelvről volt már szó itt a blogomon ( #1 #2 ), én kifejezetten szeretem, hisz nagyon könnyen használható, jól átlátható, és ha az ember sikeresen beintegrálja a C/C++ kódjába (vagy használ valami egyszerű interfacet), akkor már az összeköttetés és oda vissza hívás is egyszerű és kényelmes.
Talán ez az oka annak, hogy a Blizzard is (és utánna mások is) ezt a nyelvet választották.
FrameXML
http://www.wowwiki.com/FrameXML
Na ez nem scriptnyelv, viszont annál nagyobb találmány. A modern MMO-k GUI interface ét nem úgy készítik, hogy egy grafikus megrajzolja az egészet, majd egy texturaként bekerül a játékba és láss csodát. Szerencsénkre! A megoldás a FrameXML, ami nem más, mint egy XML fájl, amiben leírják a GU-t és a hozzá tartozó vezérlő függvényeket, amik LUA ban vannak megvalósítva. Ezáltal az addon fejlesztők is szabadon módosíthatják, alakíthatják a GUI-t, sokszor teljesen átszabva a felületet! Illetve ezzel a módszerrel könnyűszerrel hozhatunk létre új GUI elemeket, növelve ezzel a játék értékét és használhatóságát.
TOC fájl
http://www.wowwiki.com/TOC_format
A TOC fájl egy speciális fájl, ami az addonnal kapcsolatos információkat tárolja. Sok MMO ebben a fájlban kisebb eltérésekkel rendelkezik, de mindegyik ugyan arra használja. Ez a fájl mondja meg a nevet, verziót, betöltendő fájlokat, stb. (Ez a Runes Of Magicre pont nem igaz, mert csak a betöltendő fájlokat tárolja, ott nincs Addon Manager interface)
Megkötések
Általánosságban nehéz bármit is mondani, hogy milyen megkötései vannak az addonoknak, de az biztos, hogy sok minden ki van kötve belőlük, biztonsági okokból. Például egyik sem tud fájlt létrehozni, törölni, módosítani, stb. Nem képesek hálózati kapcsolatot nyitni, vagy alkalmazásokat elindítani. A Blizzrdnál az addonok nem is láttak ki a telepítés könyvtárából (World of Warcraft telepítési könyvtára).
Ezek ugye a LUA moduljainak menedzselése, azaz nem töltik be az OS, IO modulokat, ezáltal a fejlesztők nem férnek hozzá ezekhez a kényes függvényekhez.
Minden más megkötés már nagyon játék függő, attól függően, hogy az adott kiadó milyen megszorításokat ír elő a farmerek és botok ellen, illetve nagyban korlátozza az addonokat, hogy ki mennyi energiát és erőforrást áldoz a belső függvények kivezetésére (a legszerényebb az Allods Online volt, főleg, hogy a dokumentáció is orosz volt :))
Lecsó
Na de vágjunk is akkor bele, mert azt ígértem, hogy itt addonban fogunk taposni! Na azért nem 🙂
CRITKINGPREFIX = "CK" CRITKINGVERSION = "0.1.0" CritKing = {} -- critking class :) CritKing.__index = CritKing CritKingDisplay = "on" -- saved variable, per character CritKingResetOnNormalHit = "on" -- reset crit statistic when hit normal CritKing.VariablesLoaded = false CritKing.PlayerName = "" -- the player's character name CritKing.MaxDamage = 0 -- The maximum damage CritKing.MaxCritNum = 0 -- The maximum crit number CritKing.Damage = 0 -- The actual damage CritKing.CritNum = 0 -- The actual critical number CritKing.KillNum = 0 -- The actual killing blow number CritKing.MaxKillNum = 0 -- The maximum killing blow number CritKing.CritMessages = { "Head shot!" -- 1 , "Oh, Yeah!" -- 2 , "Unstoppable!" -- 3 , "Killing Spree!" -- 4 , "Dominating!" -- 5 , "Ultra Kill!" -- 6 , "Wicked Sick!" -- 7 , "God Like!" -- 8 , "God Like!" -- 9 , "Holy Shit!" -- 10 , "Holy Shit!" -- 11 , "Holy Shit!" -- 12 , "Holy Shit!" -- 13 , "Holy Shit!" -- 14 , "Monster Kill!" -- 15 , "Monster Kill!" -- 16 , "Monster Kill!" -- 17 , "Monster Kill!" -- 18 , "Monster Kill!" -- 19 , "Ludicrous Kill!" -- 20 , "Ludicrous Kill!" -- 21 , "Ownage!" -- 22 } CritKing.CritSounds = { "Interface\\Addons\\CritKing\\sounds\\head_shot.ogg" , "Interface\\Addons\\CritKing\\sounds\\ohyeah.ogg" , "Interface\\Addons\\CritKing\\sounds\\unstoppable.ogg" , "Interface\\Addons\\CritKing\\sounds\\killer.ogg" , "Interface\\Addons\\CritKing\\sounds\\dominating.ogg" , "Interface\\Addons\\CritKing\\sounds\\ultrakill.ogg" , "Interface\\Addons\\CritKing\\sounds\\wickedsick.ogg" , "Interface\\Addons\\CritKing\\sounds\\godlike.ogg" , "Interface\\Addons\\CritKing\\sounds\\godlike.ogg" , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg" , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg" , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg" , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg" , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg" , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg" , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg" , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg" , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg" , "Interface\\Addons\\CritKing\\sounds\\ludicrouskill.ogg" , "Interface\\Addons\\CritKing\\sounds\\ludicrouskill.ogg" , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg" } CritKing.KillingMessages = { "First Blood!" -- 1 , "Double Kill!" -- 2 , "Tripple Kill!" -- 3 , "Multi Kill!" -- 4 , "Ultra Kill!" -- 5 , "Mega Kill!" -- 6 , "Monster Kill!" -- 7 , "Ludicrous Kill!" -- 8 , "Wicked Sick!" -- 9 , "Holy Shit!" -- 10 , "God Like!" -- 11 , "Ownage!" -- 12 , "Ownage!" -- 13 , "Ownage!" -- 14 , "Ownage!" -- 14 , "Ownage!" -- 15 , "I am INVICIBLE!" -- 16 , "I am INVICIBLE!" -- 17 , "I am INVICIBLE!" -- 18 , "I am INVICIBLE!" -- 19 , "YES! I'm a GOD!" -- 20 } CritKing.Killingsounds = { "Interface\\Addons\\CritKing\\sounds\\firstblood.ogg" , "Interface\\Addons\\CritKing\\sounds\\doublekill.ogg" , "Interface\\Addons\\CritKing\\sounds\\tripplekill.ogg" , "Interface\\Addons\\CritKing\\sounds\\multikill.ogg" , "Interface\\Addons\\CritKing\\sounds\\ultrakill.ogg" , "Interface\\Addons\\CritKing\\sounds\\megakill.ogg" , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg" , "Interface\\Addons\\CritKing\\sounds\\ludicrouskill.ogg" , "Interface\\Addons\\CritKing\\sounds\\wickedsick.ogg" , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg" , "Interface\\Addons\\CritKing\\sounds\\godlike.ogg" , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg" , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg" , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg" , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg" , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg" , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg" , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg" , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg" , "Interface\\Addons\\CritKing\\sounds\\god.ogg" } CritKing.MaxCrit = 23 CritKing.MaxKill = 21 -- Simple message function, to the default chat frame function CritKing.SendMsg( msg ) DEFAULT_CHAT_FRAME:AddMessage( CRITKINGPREFIX .. ": " .. msg, 1.0, 0.0, 0.0 ) end -- Command handler function CritKing.OnCommand( args ) if ( ( args ~= nil ) and ( string.len( args ) > 0 ) ) then if ( string.lower( args ) == "help" ) then CritKing.SendMsg( "/ck display on - enable error frame messages" ) CritKing.SendMsg( "/ck display off - disable error frame messages" ) CritKing.SendMsg( "/ck normal on - reset critical statistic when hit normal and end of fight" ) CritKing.SendMsg( "/ck normal off - reset critical statistic just when end of fight" ) CritKing.SendMsg( "/ck help - display this text" ) CritKing.SendMsg( "/ck - display max damage, critical number, killing blow statistic" ) return end if ( string.lower( args ) == "display on" ) then CritKingDisplay = "on" CritKing.SendMsg( "set display on" ) return end if ( string.lower( args ) == "display off" ) then CritKingDisplay = "off" CritKing.SendMsg( "set display off" ) return end if ( string.lower( args ) == "normal on" ) then CritKingResetOnNormalHit = "on" CritKing.SendMsg( "reset critical statistic when hit normal" ) return end if ( string.lower( args ) == "normal off" ) then CritKingResetOnNormalHit = "off" CritKing.SendMsg( "no reset critical statistic when hit normal" ) return end end -- no parameters given, show the infos CritKing.SendMsg( " - Max damage: " .. CritKing.MaxDamage ) CritKing.SendMsg( " - Max crit num: " .. CritKing.MaxCritNum ) CritKing.SendMsg( " - Max kill num: " .. CritKing.MaxKillNum ) end -- Fired when the player hit critical function CritKing.OnCrit() local msg = CritKing.CritMessages[ CritKing.CritNum ] local sound = CritKing.CritSounds[ CritKing.CritNum ] if ( CritKingDisplay == "on" ) then UIErrorsFrame:AddMessage( msg .. " (x" .. CritKing.CritNum .. ") Damage: " .. CritKing.Damage ) end PlaySoundFile( sound ) end -- Fired when the player kill an enemy function CritKing.OnKill() local msg = CritKing.KillingMessages[ CritKing.KillNum ] local sound = CritKing.Killingsounds[ CritKing.KillNum ] if ( CritKingDisplay == "on" ) then UIErrorsFrame:AddMessage( msg .. " (x" .. CritKing.KillNum .. ")" ) end PlaySoundFile( sound ) end -- Init function function CritKing.OnLoad(self) self:RegisterEvent( "COMBAT_LOG_EVENT_UNFILTERED") self:RegisterEvent( "PLAYER_REGEN_DISABLED" ) self:RegisterEvent( "PLAYER_REGEN_ENABLED" ) self:RegisterEvent( "CHAT_MSG_COMBAT_HOSTILE_DEATH" ) self:RegisterEvent( "VARIABLES_LOADED" ) SlashCmdList[ "CRK_CMD" ] = CritKing.OnCommand SLASH_CRK_CMD1 = "/ck" end -- Reset critical statistric function CritKing.ResetCrit() CritKing.CritNum = 0 end -- Reset killing blow statistic function CritKing.ResetKill() CritKing.KillNum = 0 end -- Reset all statistic function CritKing.ResetStat() CritKing.ResetCrit() CritKing.ResetKill() end -- Event handler function CritKing.OnEvent( self, event, ... ) if ( event == "VARIABLES_LOADED" ) -- loading saved variables, and player name then CritKing.VariablesLoaded = true CritKing.PlayerName = UnitName( "player" ) CritKing.SendMsg( "variables loaded! " .. CRITKINGVERSION ) return end if ( CritKing.VariablesLoaded == false ) -- no event handling, while saved variables not loaded ... then return end if ( event == "COMBAT_LOG_EVENT_UNFILTERED" ) -- new event from combatlog then local timestamp, eType, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags = ...; if ( sourceName == nil ) then sourceName = "" end if ( sourceName ~= CritKing.PlayerName ) -- if the sender not equal with the player, just return then return end local fromNum = 9; if ( eType == "SPELL_DAMAGE" ) then fromNum = 12; end local amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing = select(fromNum, ...) if ( critical == nil ) then critical = false end if ( eType == "PARTY_KILL" ) then CritKing.KillNum = CritKing.KillNum + 1 if ( CritKing.MaxKillNum < CritKing.KillNum ) then CritKing.MaxKillNum = CritKing.KillNum end if ( CritKing.KillNum < CritKing.MaxKill ) then CritKing.OnKill() end return end if( critical ) then CritKing.CritNum = CritKing.CritNum + 1 CritKing.Damage = amount if ( amount > CritKing.MaxDamage ) then CritKing.MaxDamage = amount end if ( CritKing.CritNum > CritKing.MaxCritNum ) then CritKing.MaxCritNum = CritKing.CritNum end if ( CritKing.CritNum < CritKing.MaxCrit ) then CritKing.OnCrit() end else if ( CritKingResetOnNormalHit == "on" ) then CritKing.ResetCrit() end end return end if ( ( event == "PLAYER_REGEN_ENABLED" ) -- player enter, or leave from combat or ( event == "PLAYER_REGEN_DISABLED" ) ) then CritKing.ResetStat() return end end
Mondhatnám, hogy ennyi dióhéjban 🙂
De azért nézzük meg közelebbről, ha már ilyen szépen sikerült ide tennem ezt a pár oldalnyi borzalmat.
A kód eleje nem más, csak adat feltöltés, ahol megadom a hangokat és szövegeket, ez eléggé egyszerű. Habár kitérnék arra a kicsit csúnyaságra, hogy kézzel adom meg a tömb elemeinek számát
CritKing.MaxCrit = 23 CritKing.MaxKill = 21
Az oka egyszerű, a table.getn nem akart működni 🙂
Az első függvény, ami fel fog hívódni az OnLoad( self )
Rift ben a CritKing:RegisterEvent( Event.Addon.Load.End, OnLoad ) ot kellett felhívni, ahol az OnLoad az addon nevét kapta meg, míg Runes Of Magic -ben a FrameXML ben kellett megadni, hogy mi az OnLoad függvény, ahol a self helyett this -t kapott.
Apró eltérések, de érdemes feljegyezni Őket, ha valaki portolható addont akar készíteni.
Az OnLoad ezután regisztrálja az eventeket es a Slash commandot (mindjárt kitérek a Slash commandokra). Ez WoWban és ROM ban szinte teljesen ugyan az, míg a Rift ben a
CritKing:RegisterSlash( “crtk”, EvntSlash ) felhívásával értük el ugyan azt. Ez is csak apró különbség.
Alapvetően a Rift annyiban tér el a WoW és ROM tól, hogy nála egy table ban vannak az események, és ehhez adjuk hozzá a mi függvényünket is, míg a másik kettőben az eventeket stringes nevükkel regisztráljuk be.
WoW events: http://www.wowwiki.com/Events_%28API%29
ROM events: http://www.theromwiki.com/List_of_Events
Rift events: http://wiki.riftui.com/Event
A további részek már egyszerűek, csak kezelni kell a beérkező eseményeket. Természetesen a Rift, ROM és WoW teljesen más sorrendben, teljesen másképp közli a számunkra fontos dolgokat, erre is lehet írni egy kellemes interfacet és már el is van fedve.
Egyszer ha igény / energia lesz rá kitérünk erre is 🙂
Slash Command
Ígértem, hogy írok erről is, hogy micsoda. A Slash command, ahogy a neve is mutatja a / es parancsok felsorolása, amit az addonunk kezel. Ez mindenhol megegyezett, hogy így kezelik a chatben a parancsokat, a regisztrációjuk, ahogy már említettem más és más. A lényeg viszont szintén ugyan az, 1 függvény, ami kap 1 bemenő paramétert, ami a string, a parancsunk után. Ezt kell értelmezni és lehet különféle műveleteket végezni.
Addon dokumentációk:
WoW: http://www.wowwiki.com/World_of_Warcraft_API
ROM:
Addon Tutorial: http://www.theromwiki.com/Addon_Tutorial
Eventek: http://www.theromwiki.com/List_of_Events
Függvények: http://www.theromwiki.com/List_of_Functions
Rift: http://wiki.riftui.com/Main_Page
Addon gyűjtemény: http://www.curse.com
Most ennyi lett volna, de ugye megint csak lehet zavarni a kommentekkel, annak, akit érdekel a téma 🙂