[PyLua]Kereskedőház (with GUI)

Indította masodikbela, 2014-12-02, 21:23:42

2014-12-02, 21:23:42 Utolsó szerkesztés: 2015-06-10, 11:35:13 Szerző: masodikbela
Szép napot!


Még nyáron készítettem ezt a "rövid" kereskedőházat(?), melyet az eddigiekhez eltérően nem csak luában írtam, hanem készítettem hozzá egy pythonos GUI-t is.

Mivel már jó néhány éve írogatok kisebb nagyobb dolgokat metin 2 szerverekhez, és még nem publikáltam semmilyen általam írt munkát, gondoltam bedobok valamit a közösbe, hátha valaki éppen az általam írt kódban talál meg valamilyen problémájára megoldást (gondolok most itt azokra akik hozzám hasonlóan szoktak hobbiból írogatni dolgokat), vagy éppen csak egy hasonló dolgot szeretnének a szerverükbe betenni.

Ugyan akkor gondoltam azokra is, akik ugyan szeretnének ilyet a szerverükre, de szeretnék ha egyedi lenne, a téma második részébe egy kisebb dokumentációt fogok írni, mely segítségével egy kis munkával a GUI egészét szabadon lehet formálni, (tehát a GUI elemeinek részletes leírását, elhelyezkedését, stb) de szükség lesz egy minimális python tudásra és józan logikára.

Mivel eredetileg nem kívántam publikálni, ezért a forráskódban nagyon kevés magyarázat van, illetve nincsenek logikusan tömbökbe szedve a funkciók (pl: most ezen a szakaszon csak és kizárólag 1 fajta működés részhez tartozó funkciók vannak), valamint időrendben lentről felfelé írtam a funkciókat (ennek csupán az az oka, hogy a GUI elemeit tartalmazó funkció a forrás legtetején található, és így sokkal egyszerűbb volt számomra új elemek berakása, illetve a hozzá tartozó funkciók megírása = nem kellett annyit görgetni).
Illetve szintén mivel nem a publikum számára szántam a dolgot, a szórakozottabb óráimban használtam néhány "szép" nevű változót, ezt legyetek szívesek nekem elnézni.

Nah akkor vágjunk is bele.




Hogyan is működik/Mi ez?

Ezen szakaszon a jól átláthatóság érdekében egy-egy képet fogok beilleszteni, illetve alá fogom írni a magyarázatot.

Mivel nem vagyok jártas ps-ben, ezért ne várjatok szuperjó desinget, csak olyan elemekkel dolgoztam, melyek minden kliensben megtalálhatók, tehát ebből adódóan a gombok kinézete illetve az ablak kinézete kliensfüggő.



  • Jól láthatóan a bal oldalon találhatóak a menüpontok. Itt a különböző tárgyakat csoportokra osztottam a tárgy type-ja alapján.
  • Az ablak több mint 2/3-át elfoglaló részen pedig láthatóak maguk a tárgyak.
    Minden egyes tárgynál látható annak képe, neve (kliensből olvasva, nem mysql-ből lekérve), tulajdonosa, a tárgy ára SÉ-ben (amennyiben az eladó adott meg árat SÉ-ben) valamint yangban. Jól láthatóan ha az egeret a képre visszük, megjelenik annak leírása egy tooltipben, illetve fegyverek/vértek/fülbevalók stb. esetén a bónuszok, kövek, stb (kőszilánk és üres slot is!), azaz úgy jelenik meg akárcsak a leltárban.
    A tárgyakat nem mysqlban, hanem egy .txt fájlban tárolja a szerver (később erről többet is írok).
  • Az ablak legalján látható a játékos jelenlegi SÉ-je és yangja. A SÉ-t egyértelműen a mysql-ből olvassa, a yangot pedig külön kell feltölteni a kereskedőházba (később erről bővebben), melyet felhasználónként tárol egy txt-ben a rendszer.
    Ezek mellett látható egy frissítés gomb, ezt lenyomva a szerver ismét elküldi a kliens számára a játékos jelenlegi SÉ-jét, yangját, illetve frissíti a kijelölt csoportba feltöltött tárgyak listáját az adott oldalon.
  • Egy oldalon alapvetően 5 darab tárgyat mutat maximum a rendszer. Amennyiben egy adott csoportban több, mint 6 tárgy található, egy gomb jelenik meg a jobb alsó sarokban, mellyel lehet lapozni. (Sajnos a képen nem látszik, mivel csak 4 tárgy van betéve.) Mellékesen szólva szerintem az oldalváltó gomb lett a leggyatrább, majd később lesz róla kép...






  • Megfigyelhettük, hogy lehet kitenni egyberakott tárgyakat. Ilyenkor a vevőnek lehetősége van külön-külön, általa kiválasztott mennyiségben vásárolni.
  • Ha egy tárgyból tehát több van kitéve, mint egy, és rákattintunk valamelyik vásárlásra, ez az ablak fog megjelenni.
    Mint látható, kijelzi a darabárat és a fizetendő összeget. Ezen kívül egy csúszkával láttam el, melyet mozgatva folyamatosan frissül az ár, illetve a mellette lévő rublika, mely kiírja a darabszámot.
  • Természetesen manuálisan is beírható a kívánt mennyiség, ilyenkor a csúszka is automatikusan beáll a helyére, és ezzel együtt változik a fizetendő összeg is.






  • Mint látható ez az eladás menü. Itt adhat el a játékos tárgyakat. A felső részén az oldalnak található 3 slot. Ide lehet behúzni a tárgyat, melyet el szeretnénk adni. (Lényegtelen melyik kockába húzzuk be, érzékeli magától.)
    A behúzást követően a slotok alatt látható szövegesen kiírva a tárgy tulajdonsága (csak fegyvereknél, vérteknél, stb. lejárós tárgyaknál nem!), valamint továbbra is ha rávisszük a tárgyra az egeret mindent mutat.
    A tárgyakat egy jobb kattintással ki lehet venni.
  • Ezek alatt adható meg a tárgy ára. Jobb oldalon a tárgy ára yangban, ezt minden esetben kötelező megadni, valamint bal oldalon a tárgy ára SÉ-ben, ezt nem kötelező megadni. Ezek felett mindkét oldalon pontokkal tagoltan jelenik meg a tárgy ára, ezzel is jól láthatóbbá téve az eladás árát, nehogy a játékos elüssön egy-két 0-át.
  • Itt jön képbe a yangfeltöltés. Ugyebár a mostani metinek világában iszonyatos mennyiségű yangokkal kereskedünk a legalapvetőbb tárgyakkal kapcsolatban is. Ebből adódóan legtöbbször akkora mennyiségű pénzünk van, melyet már nem tud a játék a leltárban kezelni.
    Ezért úgy oldottam meg a rendszert, hogy rögökkel lehessen feltölteni a kereskedőházban lévő yangunkat.
    Tehát ha rögöt húzunk a slotokba, az azonnal eltűnik, és hozzá is adja a rög(ök) értékének megfelelő mennyiségű yangot számlánkhoz.
  • A rendszer nem veszi el tőlünk azonnal a tárgyat, ezzel is elkerülve esetlegesen a tárgy eltűnéseket (pl elmegy a netje játékosunknak).
    Amennyiben átváltunk másik menüre, a rendszer automatikusan kitisztítja a slotokat, és alaphelyzetbe áll.






  • Ezennel el is érkeztünk a keresés menühöz. Egy nagyobb szerveren akár több ezer tárgy lehet kint egyszerre, melyeket a sima csoportos nézetnél kikeresgetni lehetetlen.
  • A keresővel alapvetően kereshetünk csak játékos neve alapján, ilyenkor látjuk az összes általa kirakott tárgyat.
    Kereshetünk tárgy neve alapján is, ezt úgy oldottam meg, hogy nem csak azokat a tárgyakat jeleníti meg, melyek nevével egyezik a beírt szó, hanem azokat is, melyek nevében szerepel a beírt szó. Pl.: A beírt szavunk kard. Ilyenkor kifogja dobni a kard+0-át, és a kard+9-et is, de akár a csatakard+xy-t is kidobhatja, amennyiben van kitéve ilyen tárgy.
    Kereshetünk akár játékos neve, és tárgy neve alapján is egyszerre.
  • Minden keresés esetén legalább egy mezőt kell kitölteni, illetve amennyiben csak tárgy nevére keresünk, legalább két betűt meg kell adnunk a sikeres kereséshez.
  • Jól láthatóan ezen pontnál egy új ablak nyílik meg, tehát logikus, hogy ezt lehet mozgatni. Amint másik menüre váltunk, ez az ablak bezáródik.






  • Itt lehet nyomon követni a kirakott áruinkat. Amennyiben meggondoltuk magunkat, és nem szeretnénk már eladni ezeket, itt tudjuk visszakérni.
  • Nem csak a tárgyainkat, de a pénzünket is itt tudjuk kivenni. A megadott összeget szintén rögökben kapod meg, amennyiben a kivett pénzed + a jelenlegi pénzed összesen több mint 2 mrd.
  • A jobb alsó sarokban látható az a gomb, amellyel lehet oldalt váltani (jelen esetben 6 tárgyam van kint eladáson).




  • A vásárlásaim menüről nem szeretnék képet mutatni, mivel úgy gondolom felesleges, hiszem nagyjából ugyan úgy néz ki, mint bármelyik csoport ahol a tárgyak vannak. A lényege egyébként ennek a fülnek az, hogyha megvettél valamilyen tárgyat, innen tudod átvenni a leltáradba.

    Tehát így első ránézésre felmerülhet egyesekben a gondolat, hogy akkor ennek most mi értelme is van?
    Ennek az lenne a lényege, hogy akkor is kint lehessen a játékos tárgya a piacon, amikor az nem elérhető, illetve így ki tud tenni boltba sokkal értékesebb tárgyakat, mint 2 mrd, valamint akár SÉ-ért is meg tudja venni. Ez utóbbi akár kedvezhet is a szerver bevételének.




    Ezennel eljutottunk ahhoz a ponthoz, mikor az olvasó eldönti, hogy olvassa-e még tovább, vagy sem. Bizonyára most megoszlik a társaság, vannak akik "jóköszi ennyi elégvolt", "lol láttam jobbat is", "ilyeténistudok", "ronda", "nem tetszik az írás stílusa", stb. Illetve most már a társaság másik felének felkeltettem az érdeklődését, tehát akkor akár be is tehetnénk a szerverbe.

    Azonban, mielőtt ebbe belekezdenénk, leszögezném:
    Nem minden gamefileval kompatibilis!

    Hogy ez miért van? Ahhoz, hogy a tárgyaknak le tudja kérni a lua a bónuszait, szükség van vagy iMer item2 libjére, vanilla core-t, vagy forrást kell használnod, utóbbi esetben a harmadik hozzászólásban találod mit kell berakni a questlua_item.cpp-be. (De ha neked spéci gamefiled van, amiben van olyan lua command, amivel le tudod kérni a tárgy tulajdonságait, akkor az is megteszi.) Ha mindez megvan, jöhet a

    Berakás

    A code-ba beillesztett bemásolandó kódok tabulátorhelyesek! Azaz a normális eredeti fájlokat megnyitva notepad++-ban csak be kell másolnod, tehát a tagolással ha jól csinálod nem kell foglalkoznod!

    Nyilvánvalóan már a több éves tapasztalatunk alatt megtanultuk, hogy az ilyen berakásoknál biztonsági mentést csinálunk az átírandó fájlokról, szóval ezt gondolom nem is kell megemlítenem.

    1. Töltsük le a mellékletet. Ebben találunk 2 mappát: szerver, és kliens.

    2. Kezdjük a kliens részével. A kliens mappában 2 további mappát találunk: 40k és 34k(imerLIB). Értelem szerűen abból másoljuk ki az igshop.py-t amelyiket használjuk éppen. Az igshop.py-vel nincs különösebb dolgunk, csomagoljuk ki a rootot, tegyük bele, írjuk hozzá az xml-hez. Még ne csomagoljuk vissza, lesz vele még dolgunk.

    3. Nyissuk meg a game.py-t. A legtetején egy új sorba (mondjuk a legelső sorba) írjuk be a következőt:
    import igshop

    4. Keressünk rá a következőre:
    def __ServerCommand_Build(self):

    Itt a "serverCommandList" array-hoz hozzá kell adnunk néhány új commandot. Ezt úgy tehetjük meg, hogy amíg látjuk, hogy azon a részen ahol egy tabulátorral látjuk, hogy bentebb van, nyomunk az egyik sor után egy entert, és beillesztjük ezeket:
    ##Ingame Shop
    "OpenIGSHOPTEST" : self._openigtest,
    "IGShopqID" : self._IGShopqID,
    "IGSHOP" : self._IGShopCMD,
    "IGShop_ITEMS" : self._IGShopItems,
    "IGShop_BOUGHTITEMS" : self._IGShopBoughtItems,
    "IGShop_MYSALES" : self._IGShopMyItems,
    "IGSHOP_ADDTOTEMPSLOT" : self._AddItemSlot,
    ##End of Ingame Shop
    #InputBlock
    "inputblock" : self.__InputBlock,
    "inputblockend" : self.__InputBlockEnd,
    ##End of InputBlock

    Tehát valahogy így kéne, hogy kinézzen:
    serverCommandList={
    ...sokszemét...,
    ##Ingame Shop
    "OpenIGSHOPTEST" : self._openigtest,
    "IGShopqID" : self._IGShopqID,
    "IGSHOP" : self._IGShopCMD,
    "IGShop_ITEMS" : self._IGShopItems,
    "IGShop_BOUGHTITEMS" : self._IGShopBoughtItems,
    "IGShop_MYSALES" : self._IGShopMyItems,
    "IGSHOP_ADDTOTEMPSLOT" : self._AddItemSlot,
    ##End of Ingame Shop
    #InputBlock
    "inputblock" : self.__InputBlock,
    "inputblockend" : self.__InputBlockEnd,
    ##End of InputBlock
    }


    5. Görgessünk a game.py legaljára, majd illesszük be a következőket:
    #InputBlock

    def __InputBlock(self):
    constInfo.INPUT_IGNORE = 1

    def __InputBlockEnd(self):
    constInfo.INPUT_IGNORE = 0

    ##End of InputBlock

    ## Ingame Shop
    def _IGShopqID(self, qID):
    constInfo.IGShop["qID"] = int(qID)

    def _openigtest(self, se):
    if self.once == 0:
    self.InGameShop = igshop.IngameShop()
    self.InGameShop.Show()
    self.InGameShop.SetSE(se)
    self.once = 1
    else:
    if not self.InGameShop.Board.IsShow():
    self.InGameShop = igshop.IngameShop()
    self.InGameShop.Show()
    self.InGameShop.SetSE(se)

    def _IngameShopQuestCMD(self):
    net.SendQuestInputStringPacket(str(constInfo.IGShop["questCMD"]))
    constInfo.IGShop["questCMD"] = "NULL#"

    def _IGShopCMD(self, command):
    cmd = command.split("/")

    if cmd[0] == "QUESTCMD":
    self._IngameShopQuestCMD()

    elif cmd[0] == "MANAGEPAGES":
    self.InGameShop.ManagePageButtons(int(cmd[1]), int(cmd[2]))

    elif cmd[0] == "SETCASH":
    self.InGameShop.SetSE(cmd[1])

    elif cmd[0] == "SETGOLD":
    self.InGameShop.SetGold(cmd[1])

    elif cmd[0] == "REFRESHAFTARBUY":
    self.InGameShop.Refresh_func()

    elif cmd[0] == "EMPTYINPUTGOLD":
    self.InGameShop.GetMoney.SetText("0")

    def _IGShopItems(self, slot, id, vnum, count, owner_name, price_se, price_yang, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, istate):
    item.SelectItem(int(vnum))
    constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
    constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
    constInfo.IGShop["vnum"+slot] = int(vnum)
    self.InGameShop.AddItem(int(slot), int(id), int(vnum), item.GetItemName(), count, owner_name, price_se, price_yang)

    def _IGShopBoughtItems(self, slot, id, vnum, count, owner_name, price_se, price_yang, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, istate):
    item.SelectItem(int(vnum))
    constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
    constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
    constInfo.IGShop["vnum"+slot] = int(vnum)
    self.InGameShop.AddItemToBoughtItems(int(slot), int(id), int(vnum), item.GetItemName(), count, owner_name)

    def _IGShopMyItems(self, slot, id, vnum, count, owner_name, price_se, price_yang, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6, istate):
    item.SelectItem(int(vnum))
    constInfo.IGShop["slot"+slot]["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
    constInfo.IGShop["slot"+slot]["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
    constInfo.IGShop["vnum"+slot] = int(vnum)
    self.InGameShop.AddItemToMyItems(int(slot), int(id), int(vnum), item.GetItemName(), count, long(istate))

    def _AddItemSlot(self, slot ,itemVnum, count, socket0, socket1, socket2, socket3, socket4, socket5, attrtype0,attrvalue0, attrtype1,attrvalue1, attrtype2, attrvalue2, attrtype3, attrvalue3, attrtype4, attrvalue4, attrtype5, attrvalue5, attrtype6, attrvalue6):
    self.InGameShop.itembs["socket"] = [int(socket0), int(socket1), int(socket2), int(socket3), int(socket4), int(socket5)]
    self.InGameShop.itembs["attr"] = [(int(attrtype0),int(attrvalue0)), (int(attrtype1),int(attrvalue1)), (int(attrtype2), int(attrvalue2)), (int(attrtype3), int(attrvalue3)), (int(attrtype4), int(attrvalue4)), (int(attrtype5), int(attrvalue5)), (int(attrtype6), int(attrvalue6))]
    self.InGameShop.vnumb = int(itemVnum)
    self.InGameShop.countb = int(count)
    self.InGameShop.Slots.ClearSlot(1)
    self.InGameShop.Slots.ClearSlot(2)
    self.InGameShop.Slots.ClearSlot(3)
    self.InGameShop.Slots.SetItemSlot(int(slot), int(itemVnum), int(count))
    self.InGameShop.Slots.RefreshSlot()
    self.InGameShop.ShowForSell()
    self.InGameShop.SetBuypageTexts()

    ## Ingame Shop END


    6. Keressünk rá erre:
    class GameWindow(ui.ScriptWindow):
    Majd közvetlen ez alá illesszük be a következőt:
    once = 0
    hidei = 0


    7. Keressünk rá erre:
    def OpenQuestWindow(self, skin, idx):
    Ezen funkció alá közvetlen illesszük be a következőt:
    if constInfo.INPUT_IGNORE == 1:
    return


    Fontos!, hogy közvetlenül alá, hiszen akkor ha a "self.interface.OpenQuestWindow(skin, idx)" alá illeszted be, semmi értelme az egésznek, és nem fog működni a kliens/szerver kommunikáció!

    8. A game.py-től el is köszönhetünk, mentsük, majd zárjuk be. Nyissuk meg a constinfo.py-t.

    9. Az import rész(ek) után (de akár közvetlen a fájl elejére is be lehet illeszteni) illesszük be a következőket:
    INPUT_IGNORE = 0

    ## Ingame Shop
    IGShop = {
    "qID" : 0,
    "questCMD" : "",
    "slot1" : {"socket" : {}, "attr" : {}},
    "slot2" : {"socket" : {}, "attr" : {}},
    "slot3" : {"socket" : {}, "attr" : {}},
    "slot4" : {"socket" : {}, "attr" : {}},
    "slot5" : {"socket" : {}, "attr" : {}},
    "vnum1" : 0,
    "vnum2" : 0,
    "vnum3" : 0,
    "vnum4" : 0,
    "vnum5" : 0,
    }
    ##End of Ingame Shop


    10. Ezennel készen vagyunk a constinfo-val is, mentsük majd zárjuk be.

    Mivel túllépi a teljes leírás a 20000 karaktert, ezért következő hszben folyt.köv.
    Ha nem látod a válaszom, valamit elrontottál:

    2014-12-02, 21:37:04 #1 Utolsó szerkesztés: 2015-06-10, 10:41:21 Szerző: masodikbela
    11. Nyissuk meg az ui.py-t és keressünk rá a következőre:
    class ListBox(Window):

    Ez alá másoljuk be a következőt:
    def GetSelectedItemText(self):
    return self.textDict.get(self.selectedLine, "")


    12. Az ui-val is megvagyunk, jöhet az uitooltip.py. Keressünk rá a következőre:
    def AddRefineItemData(self, itemVnum, metinSlot, attrSlot = 0):

    Majd ez után a funkció után, tehát nem ebbe a funkcióba! másoljuk be ezt:
    def AddRefineItemData1(self, itemVnum, metinSlot, attrSlot = 0):
    for i in xrange(player.METIN_SOCKET_MAX_NUM):
    metinSlotData=metinSlot[i]

    self.AddItemData(itemVnum, metinSlot, attrSlot)


    13. Rendben van, meg is vagyunk a kliens részével. Indítsuk el a klienst, jelentkezzünk be, és nézzük meg a syserr-t, ír-e valami szokásostól eltérőt. Amennyiben nem, láthatóan nem mindent rontottunk el a beillesztések során. Persze még korántsem biztos, hogy minden rendben lesz a berakás végén.
    Amennyiben már ennél a pontnál valami újat ír a syserr valamit elrontottál (vagy én felejtettem el valamit leírni), és ajánlom kezd előröl az egészet.




    14. Jöhet a szerver része. Kezdetnek az is megteszi, ha felmásoljuk a letöltött fájlból a szerver mappa TARTALMÁT a quest mappánkba.

    15. Eldöntjük melyik módszert akarjuk használni. Azaz vanilla core-t használunk vagy iMer item2 libjét, illetve forrásosok forrást. Ettől függően a szerverünkről letöröljük az ellenkező .questet, és átnevezzük a másikat mb_igshop.quest-re.
    Tehát: Amennyiben vanilla coret használunk, töröljük le az mb_igshop_item2.quest-et és az mb_igshop_source.quest-et és átnevezzük a mb_igshop_vanilla.quest-et mb_igshop.quest-re. Amennyiben item2-t használunk, stb...

    16. Letöltjük és megnyitjuk a questlib.lua fájlunkat, és egy új sorba beírjuk (mondjuk a legelső sorba):
    dofile("locale/hungary/quest/igshop.lua")
    igshop_path = "/usr/game/share/locale/hungary/quest/igshop/"

    Értelemszerűen ha nekünk nem locale/hungary-nk van, akkor átírjuk arra, amink van.

    Amennyiben NEM rendelkezünk questlib.luánkban ATAG mysql kezelőjével, a következőt illesszük be a questlib.lua-ban valahova (mondjuk az aljára):
    db_userw=""
    db_passw=""
    db_hostw="localhost"
    function mysql_select(query,notselect)
        local tmp=number(11111111,99999999)
    os.execute('mysql -h '..db_hostw..' -u '..db_userw..' -p'..db_passw..' -N -e '..string.format("%q",query)..' 2>&1 > /tmp/'..tmp)

    if not notselect then
    local res,i={},1
    local f,e=io.open("/tmp/"..tmp)
    if f then
    local line=f:read("*l")
    while line do
    res[i]={}
    string.gsub(line,"([^\t]+)\t*", function(s)
    table.insert(res[i],s)
    end)
    i=i+1
    line=f:read("*l")
    end
    f:close()
    os.execute("rm /tmp/"..tmp)
    end
    return res
    end
    end

    function mysql_notselect(query)
    return mysql_select(query,true)
    end


    Értelemszerűen kitöltjük a db_userw és db_passw változókat.

    17. Végül utolsó lépésként lefuttatjuk a questet, és a BSD által kiírt funkciókat beírjuk a quest.functions fájlunkba.

    Ezennel sikeresen beraktuk a szerver oldalt is, most belépünk GM karakterünkkel, és nyomunk egy /reload q-t. Ezután RELOGOLUNK! Jelen questnél fontos, mivel loginnál küldi el a quest ID-jét a szerver a kliensnek, és mivel most töltöttük be a questünket a szerverbe, ezért most lép érvénybe a when login... része. Ezután a 9005-ös npc-nél tudjuk megnyitni a kereskedőházat.

    Utóbbit, hogy hol nyíljon meg a kereksedőház átírhatjuk, ha megnyitjuk az mb_igshop.questünket, és rákeresünk erre:
    when 9005.chat."Kereskedőház" begin

    [OPCIONÁLIS] A kereső funkció egy általunk készített txt-ből kiolvassa az összes vnum-ot. Ebből adódóan, amennyiben olyan vnumú itemet keresünk, ami nincs a txtben, nem fogja megtalálni. Megoldás: exportáljuk ki a mysql-es item_proto táblánkból a vnum oszlopot egy itemindex.txt nevű fájlba, majd azt töltsük fel az igshop mappába.

    [spoiler=Hogyan is exportáljunk?]Navicat használata esetében:

  • Csatlakozzunk fel a szerverünkre, menjünk a player adatbázisba, majd ott jobb klikk az item_proto táblára. Válasszuk az export wizard opciót.
  • Export format Text file legyen. Válasszuk ki, nyomjunk a "Next" gombbra.
  • Megjelenik egy szép táblázat, itt az "Export to" oszlopban az item_proto sorában kiválasztjuk hova szeretnénk menteni a txt-t, majd névnek itemindex.txt-t írunk. Mehetünk tovább.
  • Most egy szép lista jelent meg az exportálandó oszlopokról. Kivesszük a pipát az "All fields" dobozból, majd rányomunk az "Unselect all" gombra. Ezután kipipáljuk a "vnum" sorában a dobozt. Ezzel a lépéssel is megvolnánk.
  • Ezen az oldalon csak egy dolgunk van, a "Text Qualifier" fület lenyitjuk, és "None"-ra állítjuk.
  • Végül utolsó lépésként a start gombra rányomunk, és ha mindent jól csináltunk a megadott helyen megjelenik a txt, amit egy lendülettel fel is másolunk a szerverünk quest/igshop mappájába.
  • [/spoiler]




    A mai napba csupán ennyi fért bele, később, mint fentebb írtam mindezt ki fogom egészíteni egy résszel, melyben leírom, hogyan lehet jobban testre szabhatóbbá tenni ezt az egészet ("kisebb dokumentáció"-ként említettem fentebb).

    Így a végére leírnék még néhány (számomra) figyelemreméltó adatot:

    A teljes kódot 8 teljes napon át írtam napi 10-12 órában (összesen kb ~80-96 óra).
    A teljes kód több mint 2000 soros (kb 2300 mindent összenézve).
    A teljes kód több mint 84 000 karaktert tartalmaz (kb 90 000 mindent összenézve).

    Sajnos már többször láttam, hogy mások leírását/munkáját sokan mint sajátjukként kiteszik saját fórumukon/oldalukon. Amennyiben számodra örömöt okoz elmondani azt, hogy ezt te írtad, nyugodtan. Nem helyeztem el egyik fájlban sem a nevemet, hiszen én a sajátomat megismerem, ha esetleg máshol látom legfeljebb csak büszke leszek rá, hogy van annyira jó ez az egész, hogy más is kirakja.

    Köszönöm, amennyiben végigolvastad. Várok minden kulturáltan megfogalmazott véleményt/észrevételt/talált hibát mind a leírással, mind a source-vel kapcsolatban PM-ben.

    Amennyiben valahol elakadtál, nyiss egy témát a segítség részlegen, vagy dobj meg egy üzenettel, és szabadidőmben szívesen segítek. (Teljesen megcsinálni senkinek nem fogom!)

    [spoiler=changelog]<2015. 06. 10.> Súlyos biztonsági rés merült fel a 34k esetén, valamennyire orvosoltam a problémát, ám nem ajánlatos a továbbiakban 34k esetén használni, mivel a fix a kliensoldalon található, amely feltörés esetén könnyedén semlegesíthető.
    Ezen kívül fontos javítás történt 40k-ra is, magasabb játékosszámnál felmerültek problémák a kivett tárgyak bónuszaival kapcsolatban, pl teljesen más bónusz került bele, elbugolt a 6-7 opt, 4 leltár esetében a 3. és 4. leltárban nem adta hozzá a bónuszokat/köveket, stb...
    Ezeken kívül 40k-hoz szükséges funkciók javításra kerültek: kimaradt a leírásból az item.get_attr_type és item.get_attr_value funkció.

    <2015. 05. 07.> Nem működött az expandable_limited_storage funkció normálisan, ez javításra került.

    <2014. 12. 22.> Nagyobb mértékű frissítés történt, a 3. hozzászólásban bővebben olvashattok róla.

    <2014. 12. 08.> Bug javítva: Valószínűleg a tegnapi nap folyamán történt javításból eredő hiba, hogy ha egy darab tárgyat vásárolsz, nem kapod meg.

    <2014. 12. 07.> Bug javítva: Amennyiben vásárolsz x darabot (nem az összeset) egy tárgyból, majd utána ugyan abból még y darabot, majd átveszed az y darabot, az x darabszámú y darabszámúvá cserélődik. Köszönöm az észrevételt kati8411-nek.

    <2014. 12. 05.> Bug javítva: Amennyiben egy másik karakterrel újonnan nyitod meg a kereskedőházat, minden berakott yangot nulláz.

    <2014. 12. 02.> Publikáltam a kereskedőházat[/spoiler]

    ~masodikbela
    Ha nem látod a válaszom, valamit elrontottál:

    2014-12-22, 12:39:35 #2 Utolsó szerkesztés: 2015-06-10, 10:34:44 Szerző: masodikbela
    Szép napot!

    Előételnek mondjuk egy kis rizsával kezdeném...

    Szóval már megint itt vagyunk, eltelt lassan három hét, azóta történt egy s más. Például kiderült, hogy nem is olyan "easy" beüzemelni ezt az egészet, mint elsőre látszik, illetve mint gondoltam (éppen ezért is 1-2 dolgot bővítettem/átalakítottam/kiszedtem). Nyílt kb fél tucat téma, kaptam 100 pm-et, vannak akiknek már megoldottam/tuk a problémá(i)t, vannak akik feladták, vannak akik még most is szenvednek vele, és olyanok is, akiknek meggyőződésük, hogy nem működik.

    Már néhány napja tervezem ezt a nagyobb frissítést, eredetileg úgy akartam, hogy leírom, hogy hogyan lehetne megcsinálni dolgokat, hogyan lehetne mindenkinek saját szája ízére szabni ezt az egészet, aztán ahogy elkezdtem funkciókat irkálni, meg elkezdeni elmagyarázni, ott kötöttem ki, hogy mindent széljelkommenteltem a luában, aztán pedig jött egy-két ötlet, hogy mit lenne jó még beletenni, aztán ez a "kezdeti" lelkesedés abba is maradt, és meggondoltam magam, inkább megírom simán a funkciókat, aztán aki akarja majd beállítja magának.

    Nagyjából miről is lesz szó/miken módosítottam:

  • kliensbeli változások, de semmi komoly dolog, akinek már be van rakva, csak felül kell írnia a py-t, nincs ezúttal semmi ide-oda turkálás... aki most szeretné berakni, annak továbbra is első/második hozzászólásban van útmutató
  • nagyjából 300 sorral bővült a lua
  • txt adatbázisú rendszeremet kissé átnevezgettem = átnevezett txt fájlok
  • forrásosok is próbálták berakni, gondoltam rájuk, kiraktam arra is a szükséges dolgokat

  • Nézzük meg ezeket közelebbről.



    [ÚJ]Quest szűrők/beállítások

    Megpróbálom minél érthetőbben elmagyarázni, mi micsoda, hogy kell beállítani. Eleinte elkezdtem kommentelgetni, aztán meguntam, talán majd ha lesz kedvem befejezem. A CODE-ban található szavak? a funkciók nevei. A .quest fájlban találod őket.

    [FILTER]Maximum berakható tárgyak számának korlátozása:

    Alapvetően háromféle képpen lehet korlátozni. Az első két módszer működik egymással is.

    Első módszer:
    check_itemc()

    Korlátozhatod, hogy egy karakter mennyit tárgyat rakhasson ki egyszerre a kereskedőházba.

    maxitem változóval állítható be, hogy mennyi legyen a limit karakterenként. Amennyiben az értéke 0, nincs limit.

    Második módszer:
    check_accIC()

    Korlátozhatod, hogy egy felhasználón összesen hány tárgy lehet bent a kereskedőházban.

    maxitem változó értékével állítható be, hogy mennyi legyen a limit felhasználónként. Amennyiben az értéke 0, nincs limit.

    Harmadik módszer:
    expandable_limited_storage(s_type)

    Korlátozhatod, hogy egy felhasználón összesen hány tárgy lehet bent a kereskedőházban. Az előzőtől eltérően ez az érték bővíthető. Meg lehet adni egy alap értéket, amennyi a limit, és egy általad meghatározott tárggyal x értékkel megnövelhető ezen érték. Ezt az értéket egy txtben tárolja a rendszer az igshop mappában, a felhasználó idje mellett.

    expandable_limited_storage_enable = 1 amennyiben a funkció be van kapcsolva.
    base_limit alapból berakható itemek száma egyszerre. 0 is lehet az értéke.

    when 50321.use or 50320.use beginEzen résznél adható meg, hogy mely itemek használata után mennyi helyet adjon. Értelemszerűen ezek vnumok, és a minta alapján talán te is át tudod írni. Ezen belül találhatóak a következő változók:

    expandable_limited_storage_value_modifier = 1 amennyiben be van kapcsolva, hogy itemekkel bővíthető legyen a maximum berakható tárgyak száma

    item_values = Ajjajj! Bonyolódnak a dolgok, mivel ez már egy TÁBLA! Kitöltése szemléltetve van két példával, az első érték a vnum, a második pedig hogy hány helyet írjon jóvá. Ide vnumoknak ugyan azt kell írni mint amit ezen when résznél. Figyelmetekbe ajánlom az első rekord utáni , (VESSZŐ) -t. Alapvetően mindegyik ilyen bejegyzés után kell, kivéve az utolsó, de nem haragszik meg, ha ott is van.


    [FILTER]Itemek korlátozása antiflag alapján:
    check_antiflag(vnum, only_table)

    A mysqlben található antiflag alapján korlátozható az itemek berakása, tehát ha egy adott itemet nem lehet eldobni, vagy nem lehet vele kereskedni, vagy berakni raktárba, stb. nem rakható be a kereskedőházba sem.

    check = 1 (vagy legalább is nem 0) amennyiben be van kapcsolva az antiflagos szűrés.
    enable_always_available Ez a funkció arra szolgál, hogy ha van olyan item, ami eladása tiltva lenne antiflag alapján, de te mégis úgy döntesz, hogy el lehessen adni, akkor itt megteheted. Értéke 1, amennyiben ez be van kapcsolva.
    always_available Ismét egy táblázat, itt adhatod meg azoknak az itemeknek a vnumjait, amik akkor is eladhatóak, amikor egyébként az antiflag tiltaná.
    forbiden_antiflags Azok az antiflagok, amiket tilt a szűrő. Ez is táblázat!


    [FILTER]Itemek korlátozása egyedi táblázattal:
    check_sellableItems(vnum)

    Hasonló az antiflagos szűréshez, de itt te adhatod meg, hogy mely itemeket NE lehessen kitenni vnum alapján. Működik az antiflagos szűréssel együtt.

    check = 1 amennyiben a szűrő be van kapcsolva.
    not_sellable_items Ez ismét egy táblázat. A tiltott vnumokat írd ide.


    [FILTER]Itemek korlátozása type és/vagy subtype alapján:
    check_type(item_type, item_sub_type)
    Ez is nagyon hasonló az antiflagos szűréshez, a különbség csak az, hogy type illetve subtype alapján történik a szűrés. Esetleg hasznos lehet mondjuk potik kitiltására.

    enable_type_filtering = 1 amennyiben a type alapú szűrés be van kapcsolva.
    enable_sub_type_filtering = 1 amennyiben a subtype alapú szűrés be van kapcsolva.
    disabled_types tábla, tiltott type-ket tartalmazza.
    disabled_sub_types tábla, tiltott subtype-ket tartalmazza.


    [FILTER]Tárgyberakás korlátozása szint és/vagy játékidő alapján:
    char_filters()

    Tilthatod, hogy egy adott karakterrel ne lehessen tárgyat berakni a kereskedőházba adott játékidő, és/vagy szint alatt.

    enable_char_level_filter = 1 amennyiben a szintszűrő be van kapcsolva.
    enable_char_playtime_filter = 1 amennyiben a játékidő szűrő be van kapcsolva.
    min_level = minimum szükséges karakter szint.
    min_playtime = minimálisan szükséges játékidő percben.


    [SETTINGS]Pénz be- kivételéhez használt tárgy(ak) megadása:
    igshop_gold_manager(what, vnum, count, cell)

    Itt adhatod meg, hogy mely tárgyak mennyi pénzt érjenek yangban. Erre azért van szükség, mert itt lehet megadni, hogy ha az adott vnumú tárgyat húzza a játékos az eladás menüben a slotba, akkor azért x összeget jóváír a kereskedőház az adatbázisába. Ugyan így mikor a pénzt kiveszed a rendszerből, kikalkulálja a meglévő adatok alapján, hogy milyen/mennyi itemet adjon.

    gold_items Ez egy tábla! A kitöltése a következő: első a vnum, második az érték yangban, harmadik maradjon 0 ;) Természetesen lehet többet és kevesebb rekordot is beírni ide.


    [FIX]Egyéb változtatások, javítások:

  • Mivel bonyodalmakat okoznak a rendszerben az ékezetes karakterű fájlok, ezért ezeket átneveztem ékezetmentesre. A dolgotok ezzel kapcsolatban csak annyi, hogy az új txtket a szerver/data mappából fel kell másolni.
  • Kliensben az igshop.py-be bekerült a NumberToMoneyString funkció, ezért a locale/localeinfo.py-t nem kell kiegészíteni a berakásnál.
  • Kliensben a invalid literal for long() with base 10: '' ValueError javítva lett, a kliensemben nem mutatkozott ilyen, de whiteworldnál felmerült ilyen, és felvilágosítottak, hogy elméletben kéne lennie ilyen hibának a syserrben, szóval kijavítottam. Köszönöm az észrevételt whiteworld-nak, valamint a javítást P3NG3R-nek.
  • Forrást használók számára bekerült egy új mb_igshop_source.quest, ezzel különösebb dolog nincsen, csak azt kell használni. A következőkkel kell kiegészítenetek a questlua_item.cpp-t:
  • int item_get_attr_value(lua_State* L)
    {
    CQuestManager& q = CQuestManager::instance();
    LPITEM item = q.GetCurrentItem();

    if (!item)
    {
    sys_err("cannot get current item");
    lua_pushnumber(L, 0);
    return 1;
    }

    if (false == lua_isnumber(L, 1))
    {
    sys_err("index is not a number");
    lua_pushnumber(L, 0);
    return 1;
    }

    int index = lua_tonumber(L, 1);
    const TPlayerItemAttribute& attrItem = item->GetAttribute(index);

    lua_pushnumber(L, attrItem.sValue);

    return 1;
    }

    int item_get_attr_type(lua_State* L)
    {
    CQuestManager& q = CQuestManager::instance();
    LPITEM item = q.GetCurrentItem();

    if (!item)
    {
    sys_err("cannot get current item");
    lua_pushnumber(L, 0);
    return 1;
    }

    if (false == lua_isnumber(L, 1))
    {
    sys_err("index is not a number");
    lua_pushnumber(L, 0);
    return 1;
    }

    int index = lua_tonumber(L, 1);
    const TPlayerItemAttribute& attrItem = item->GetAttribute(index);

    lua_pushnumber(L, attrItem.bType);

    return 1;
    }

    Valamint a luaL_reg item_functions[] tábla végére ezt kell beírni: {"get_attr_type",item_get_attr_type},
    {"get_attr_value",item_get_attr_value},


    Illetve a következőt kell betenni a questlua_pc.cpp-be:
    int pc_give_item_with(lua_State* L)
    {
    if (lua_gettop(L) != 19)
    {
    lua_pushboolean(L, false);
    return 1;
    }

    LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();

    if (!ch)
    {
    sys_err("Char not found (give item with)");
    lua_pushboolean(L, false);
    return 1;
    }

    if (!lua_isstring(L, 1) && !lua_isnumber(L, 1))
    {
    sys_err("QUEST Make item call error : wrong argument");
    lua_pushboolean(L, false);
    return 1;
    }

    DWORD dwVnum;

    if (lua_isnumber(L, 1)) // ąřČŁŔΰćżě ąřČŁ·Î ÁŘ´Ů.
    {
    dwVnum = (int)lua_tonumber(L, 1);
    }
    else if (!ITEM_MANAGER::instance().GetVnum(lua_tostring(L, 1), dwVnum))
    {
    sys_err("QUEST Make item call error : wrong item name : %s", lua_tostring(L, 1));
    lua_pushboolean(L, false);
    return 1;
    }

    int icount = 1;
    if (lua_isnumber(L, 2) && lua_tonumber(L, 2)>0)
    {
    icount = (int)rint(lua_tonumber(L, 2));
    if (icount <= 0)
    {
    sys_err("QUEST Make item call error : wrong item count : %g", lua_tonumber(L, 2));
    lua_pushboolean(L, false);
    return 1;
    }
    }

    LPITEM item = ch->AutoGiveItem(dwVnum, icount);

    if (NULL != item)
    {
    int attr1;
    int value1;
    int attr2;
    int value2;
    int attr3;
    int value3;
    int attr4;
    int value4;
    int attr5;
    int value5;
    int attr6;
    int value6;
    int attr7;
    int value7;
    int socket1;
    int socket2;
    int socket3;

    if (lua_isnumber(L, 3) && lua_isnumber(L, 4) && lua_isnumber(L, 5) && lua_isnumber(L, 6) && lua_isnumber(L, 7) && lua_isnumber(L, 8) && lua_isnumber(L, 9) && lua_isnumber(L, 10) && lua_isnumber(L, 11) && lua_isnumber(L, 12) && lua_isnumber(L, 13) && lua_isnumber(L, 14) && lua_isnumber(L, 15) && lua_isnumber(L, 16) && lua_isnumber(L, 17) && lua_isnumber(L, 18) && lua_isnumber(L, 19))
    {
    attr1 = (int)lua_tonumber(L, 3);
    value1 = (int)lua_tonumber(L, 4);

    attr2 = (int)lua_tonumber(L, 5);
    value2 = (int)lua_tonumber(L, 6);

    attr3 = (int)lua_tonumber(L, 7);
    value3 = (int)lua_tonumber(L, 8);

    attr4 = (int)lua_tonumber(L, 9);
    value4 = (int)lua_tonumber(L, 10);

    attr5 = (int)lua_tonumber(L, 11);
    value5 = (int)lua_tonumber(L, 12);

    attr6 = (int)lua_tonumber(L, 13);
    value6 = (int)lua_tonumber(L, 14);

    attr7 = (int)lua_tonumber(L, 15);
    value7 = (int)lua_tonumber(L, 16);

    socket1 = (int)lua_tonumber(L, 17);
    socket2 = (int)lua_tonumber(L, 18);
    socket3 = (int)lua_tonumber(L, 19);


    item->SetForceAttribute(0, attr1, value1);
    item->SetForceAttribute(1, attr2, value2);
    item->SetForceAttribute(2, attr3, value3);
    item->SetForceAttribute(3, attr4, value4);
    item->SetForceAttribute(4, attr5, value5);
    item->SetForceAttribute(5, attr6, value6);
    item->SetForceAttribute(6, attr7, value7);

    item->SetSocket(0, socket1);
    item->SetSocket(1, socket2);
    item->SetSocket(2, socket3);

    lua_pushboolean(L, true);
    return 1;
    }

    lua_pushboolean(L, false);
    return 1;
    }
    lua_pushboolean(L, false);
    return 1;
    }


    Illetve az előbbihez hasonlóan itt is be kell írni a következőt a luaL_reg item_functions[] tábla végére:
    { "give_item_with", pc_give_item_with },



  • A keresés funkcióban nem érzékelte a lua a "+" jelet, mivel az egy "magic character". Ez is javításra került.
  • További problémák voltak a kereséssel az előző pontban említett mellett.
  • Nem lehetett frissíteni az keresést.
  • Vanilla core használata esetén nem lehetett rendesen megvásárolni a keresésben talált tárgyakat. Ez úton is jelezném, hogy a megoldáshoz a type-t és subtype-t mysqlből kellett lekérnem, tehát akik nem mysqles item_protot használnak, hanem txt-t azok esetében ebből gondok lehetnek, amennyiben ezen sejtésem beigazolódik feltétlenül szóljanak nekem.
  • Első error üzenet is megjelent a rendszerben: syschat("IGSHOP_MYSQL_READING_ERROR!_RET_ZERO") Ez a hiba akkor mutatkozik, ha nem sikerült kiolvasni mysql-ből a játékos SÉ-jét, mely okai a következők lehetnek:

  • Nem lehet csatlakozni a mysql-hez.
  • Nem lehet lekérni az account.account táblából a coins oszlop értékét. (Esetleg nincs olyan mező az adatbázisodban?)
  • Végül a leírást is frissítettem, hogy a változtatások miatt naprakész legyen.


  • GY.I.K.:

  • Kérdés:Kliens syserr a következőt írja: 'GameWindow' object has no attribute 'once'. Ilyenkor mi van?

  • Válasz:Kifelejtetted a game.py-ből a GameWindow class alá a két változót beírni. Lásd: 6. pont.
  • Kérdés:Szerver syserr következőt írja: RunState: LUA_ERROR: [string "mb_igshop"]:106: attempt to index global [akármi******] (a nil value)
  • Válasz:Probléma van az item/item2.set_attr funkcióddal. Oka: Nincs bent a szerveredben semmilyen funkció, amivel le lehet kérni/be lehet állítani a tárgyak optjait, vagy rossz .quest-et használtál.
  • Kérdés:Mikor megvásárlok egy tárgyat, és ki szeretném venni, nem tűnik el a tárgy, és folyamatosan akárhányszor ki tudom venni. Megoldás?
  • Válasz:A probléma az előző kérdéshez hasonló, nem működik a(z) item/item2.set_attr funkció.

  • Jelenleg ezek a problémák ugrottak be, amelyek felmerültek többször, esetleg később még ezeket igény szerint bővítem.

    Azt hiszem, ezzel végére is értünk. Továbbra is várom a kérdéseket, problémákat, felmerülő hibákat, észrevételeket.

    További szép napot,
    ~masodikbela
    [/list][/list]
    Ha nem látod a válaszom, valamit elrontottál: