Unmantained Tool Quest [RELEASE] Utility per usare mysql nelle quest + guida + esempi

Una guida contrassegnata come Unmantained contiene informazioni su un argomento obsoleto, non più utile o files non aggiornati da parte del creatore.
Stato
Discussione chiusa ad ulteriori risposte.

BlackYuko

Utente Gold
26 Agosto 2010
522
22
459
341
Ultima modifica da un moderatore:
Prima che arrivino i soliti rompi******* (metteteci la parola che volete <.<), dico subito che per fare questa cosa ho preso spunto da una cosa simile che ho visto su epvp, che però non mi piaceva molto nella realizzazione. Quindi ho riprodotto una cosa simile, riscrivendo completamente il codice zero (molto più semplificato anche).
Cos'è? E' un semplicissimo programmino in C++ che vi permette di eseguire query mysql nelle vostre quest. Ah, prima che arrivino i soliti a criticare: sì, avete visto bene, i commenti del programma sono in inglese, sono abituato così dato che all'università seguo i corsi in inglese da due anni. Ora, gli scripter con un po' di pratica mi diranno: "ma per eseguire le query si può già usare l'os.execute per richiamare il comando mysql...". Sì, è vero, ma facendo così:
  • non si può controllare il risultato della query eseguita. Non si può sapere se è andata a buon fine o ci sono stati problemi.
  • eseguire una query SELECT con questo metodo è ben poco pratico, bisogna indirizzare l'output del comando su file e analizzare i risultati dentro il file. Inoltre, se viene fatto il SELECT di più campi, questi vengono messi sulla stessa riga del file separati da spazi e bisogna quindi fare il parsing della riga per dividerli, cosa non proprio semplice per chi non ha un po' di pratica con il linguaggio

Inserimento programma

Purtroppo la compilazione del codice dipende dalla libreria mysql che avete installata sul server, quindi se vi passo l'eseguibile già compilato e voi avete una versione della libreria diversa dalla mia non vi funzionerà. Per questo motivo, nell'archivio troverete direttamente il codice sorgente (almeno potete anche controllare che non faccia cose strane, se non vi fidate) che dovrete compilare sul vostro server, in modo da essere sicuri che funzioni. Parliamoci chiaro: è un programmino talmente semplice che non ho problemi a dare direttamente il sorgente, però almeno fatemi la cortesia di lasciare i crediti all'inizio.
Come compilarlo? E' molto semplice, vi ho preparato un makefile che fa tutto da solo, dovete solo digitare un comando. Quindi:
  • mettete la cartella che trovate nell'archivio in una cartella del server che vi fa comodo, ad esempio quella dove ci sono i file server
  • con putty andate nella cartella appena copiata, e digitate 'make'
  • fatto, se non ci sono errori avete compilato il codice, e dovreste trovare nella cartella un file eseguibile chiamato "mysql_query"

L'output dovrà essere:



Nel caso ci siano errori è possibile che siano dovuti alle cartelle diverse del mysql, quindi aprite il 'makefile' e editate queste due righe mettendo i vostri percorsi:

Codice:
INCLUDE = /usr/local/include
LIBS = /usr/local/lib/mysql

Comunque potete contattarmi su Skype in caso non compili. Ora, sempre nella stessa cartella, aprite il file 'conf' e scrivete i dati del vostro DB, in ordine: host (se il DB è sullo stesso server lasciate "localhost" che c'è già), user, password, porta (opzionale, se non mettete niente viene usata quella di default). Esempio file conf:

Codice:
localhost
root
ciaociao
3306

Collegamento alle quest

Bene, ora quello che bisogna fare è "collegare" il nostro programma in modo da poterlo utilizzare dalle quest di Metin2. Per farlo, basta creare una semplice funzione LUA dentro il questlib.lua che vada a richiamare il programma passandogli i parametri corretti e che ne restituisca i risultati. Ovviamente ve l'ho già fatta io:

Codice:
function execute_query(db, query)
    local PROGRAM_PATH = "/game/mysql_query/"
    query = string.gsub(query, "`", "")
    local out_file = PROGRAM_PATH..pc.get_name().."_"..os.time()
    os.execute("cd "..PROGRAM_PATH.." && ./mysql_query "..db.." \""..query.."\" \""..out_file.."\"")
    if io.open(out_file) == nil then
        return -3, "Unable to read query result"
    end
    local _func = loadfile(out_file)
    os.remove(out_file)
    if _func == nil then
        return -2, "Invalid query result"
    end
    local out_array = _func()
    return out_array[1], out_array[2]
end

dentro PROGRAM_PATH scrivete la cartella dove c'è l'eseguibile del programma (ricordatevi di mettere la "/" finale come nel codice). Sul funzionamento non penso ci sia molto da spiegare, è abbastanza elementare, meglio spiegare come usarla. Come vedete, dovete passare due parametri alla funzione: il primo è il nome del database dove eseguire la query (es. common, log, player,...), il secondo è la query. Lo so, avrei potuto anche non mettere il database, dato che può essere anche specificato nella query, ma io lo preferisco così.
Nota sulla query: ovviamente dovete metterla tra virgolette. Quindi, se dovete inserire una stringa dentro la query usate l'apice singolo, ad esempio:

"SELECT id FROM player WHERE name='nome_giocatore' LIMIT 1"

non utilizzate accenti gravi (`), perché tanto vengono eliminati dalla query prima di passarla al programma (o meglio, se volete metterli perché siete abituati metteteli, ma sappiate che poi verranno eliminati), dato che danno problemi.

La funzione vi ritorna due valori:
  • il primo è un numero. Se è zero, la query è andata a buon fine. Se è diverso da zero, c'è stato un errore. I codici di errore che ritorna sono:
    • -3 : non è stato possibile analizzare l'output del programma (può darsi che il percorso del programma nel questlib sia sbagliata, e quindi non riesca ad eseguirlo)
    • -2 : l'output del programma non è valido (questo in teoria non dovrebbe mai succedere)
    • -1 : il file 'conf' non esiste o non contiene tutti i valori richiesti
    • 1 : impossibile inizializzare il modulo mysql (vedere il secondo valore di ritorno per la descrizione specifica dell'errore)
    • 2 : impossibile connettersi al database (vedere il secondo valore di ritorno per la descrizione specifica dell'errore)
    • 3 : impossibile eseguire la query (vedere il secondo valore di ritorno per la descrizione specifica dell'errore)
ovviamente potete anche ignorare il codice di errore, se volete solo controllare che tutto sia andato bene vi basterà controllare che non sia diverso da zero.​

  • il secondo valore dipende dai casi:
    • se c'è stato errore (cioè il precedente numero è diverso da zero), è una stringa che descrive l'errore.
    • se non ci sono stati errori: se la query non è un SELECT, potete ignorare il valore. Se la query eseguita è un SELECT, è un array che contiene i risultati della query. L'array sarà fatto in questo modo:
Codice:
    { {"campo_1","campo_2","campo_3",...,"campo_n"},
      {"campo_1","campo_2","campo_3",...,"campo_n"},
      ...
      {"campo_1","campo_2","campo_3",...,"campo_n"} }

dove i vari campi sono i campi che avete inserito nella query, e le varie righe sono i vari risultati. Tutti i campi saranno stringhe, quindi dovete convertirli in numero con la funzione 'tonumber' se vi serve.
Se siete scripter, dovreste essere capaci a leggere un array di questo tipo senza alcun problema. Se non siete in grado, bé, studiatevi almeno le basi del LUA dio mio. Dai scherzo, oggi mi sento buono <.<, vi faccio degli esempi:

Codice:
-- Esecuzione query (query fatta a caxxo per dare un idea)
local result, query_result = execute_query("player", "SELECT name,level FROM player LIMIT 10")
-- Controllo errori
if result != 0 then
    say("Si e' verificato un errore.")
    say("Descrizione errore:")
    say(query_result)
    return
end
-- Lettura array dei risultati, stampando un risultato per riga
for i = 1,table.getn(query_result) do
    for j = 1,table.getn(query_result[i]) do
        say(query_result[i][j])
    end
end
-- Lettura array dei risultati più carina, stampando tutto il risultato in una riga
for i = 1,table.getn(query_result) do
    say(query_result[i][1]..query_result[i][2])
end
-- Quella prima va bene, ma è troppo legata al tipo di query che abbiamo fatto, dato che leggiamo i singoli campi
-- Meglio una più generica no?
local line
for i = 1,table.getn(query_result) do
    line = ""
    for j = 1,table.getn(query_result[i]) do
        line = line.." "..query_result[i][j]
    end
    say(line)
end

In questo caso, l'array di ritorno sarà:

Codice:
    { {"name_player_1","level_player_1"},
      {"name_player_2","level_player_2"},
      {"name_player_3","level_player_3"},
      ...
      {"name_player_10","level_player_10"} }

Se il SELECT restituirà un solo risultato e un solo campo, l'array sarà del tipo: {{"campo_da_leggere"}}, e dallo script potrete leggerlo con query_result[1][1].
Se il SELECT non trova nulla, vi sarà restituito la stessa struttura precedente ma vuota, cioè: {{""}}
Ad esempio, uno stupido script che legge il player id:

Codice:
-- Lettura nome
say("Inserisci il nome:")
local name = input()
-- Esecuzione query (query fatta a caxxo per dare un idea)
local result, query_result = execute_query("player", "SELECT id FROM player WHERE name='"..name.."' LIMIT 1")
-- Controllo errori
if result != 0 then
    say("Si e' verificato un errore.")
    say("Descrizione errore:")
    say(query_result)
    return
end
-- Lettura del risultato. Dato che è numero, lo convertiamo anche
local player_id = tonumber(query_result[1][1])
-- Se non ha trovato il giocatore, cercherà di convertire in numero una stringa vuota ("") ,
-- e la conversione in numero fallirà, quindi
if player_id == nil then
    say("Il giocatore non esiste")
else
    say("Player id: "..player_id)
end

ovviamente, avremo anche potuto scrivere:

Codice:
if query_result[1][1] == "" then
    say("Il giocatore non esiste")
end

che è equivalente. Ah, ovviamente dovete aggiungere in quest_functions la funzione execute_query per poterla usare nelle quest.

Esempi

Vi ho fatto anche un paio di quest elementari per darvi degli esempi di utilizzo, che troverete anche nell'archivio.

Classifica in game:

Vi ricordate la classifica di Alby? http://www.inforge.net/community/me...3188-[release]classifica-game-lua-alby-d.html. Ecco, richiamando il mysql con l'os.execute veniva un bel casino. Questa è la quest rifatta usando il mio programma:

Codice:
quest classifica begin
    state start begin
        when 20092.chat."Classifica Personaggi" begin
            -- Esecuzione query
            local ret_value, ret_array = execute_query("player", "SELECT level,name,exp FROM player WHERE name not like '[%' order by level desc LIMIT 10")
            if ret_value != 0 then
                say("Si e' verificato un errore.")
                say("Descrizione errore:")
                say_reward(ret_array)
                return
            end
            -- Stampa risultati
            say_title("# | Livello | Nome | Esperienza")
            local line
            for i = 1,table.getn(ret_array) do
                line = i.." - "
                for j = 1,table.getn(ret_array[i]) do
                    line = line.." "..ret_array[i][j]
                end
                say(line)
            end
        end
    end
end

Molto più semplice e leggibile, no?

Screenshot (grazie fanta che mi ha fatto usare il defunto titanius per provare con la versione a 64-bit <.<):




Mini ban-tool in game:

Questo è un semplicissimo ban-tool, solo per i GM, che permette di bannare/sbannare (in modo permanente) l'account di un personaggio inserendo il suo nome, direttamente in game:

Codice:
quest bantool begin
    state start begin
        when letter with pc.is_gm() begin
            send_letter("Ban-tool")
        end
        
        when button or info begin
            -- Tanto per sicurezza...
            if not pc.is_gm() then
                return
            end
            local menu = {"Ban giocatore", "Rimuovi ban", "Chiudi"}
            say_title("Ban-tool")
            say("Seleziona la funzione:[ENTER]")
            local s = select_table(menu)
            if s == table.getn(menu) then
                return
            end
            -- Lettura nome personaggio
            local name
            repeat
                say_title(menu[s])
                say("Inserisci il nome del giocatore:[ENTER]")
                name = input()
                if name == "" then
                    say("Il nome inserito non e' valido.[ENTER]")
                    return
                end
                say_title(menu[s])
                say_reward("Hai inserito: "..name)
                say("Sei sicuro di voler continuare?[ENTER]")
                local a = select("Si", "Ripeti inserimento", "Annulla")
                if a == 3 then
                    return
                elseif a == 1 then
                    break
                end
            until false
            say_title(menu[s])
            -- Lettura ID account
            local result, query_result = execute_query("player", "SELECT account_id FROM player WHERE name='"..name.."' LIMIT 1")
            -- Controllo errori
            if result != 0 then
                say("Si e' verificato un errore.")
                say("Descrizione errore:")
                say_reward(query_result)
                return            
            end
            local AccStatus = {"BLOCK","OK"}
            -- Conversione dell'account in numero
            local AccID = tonumber(query_result[1][1])
            -- Se il select non ha trovato il giocatore, il numero convertito non sarà valido
            if AccID == nil then
                say("Il giocatore inserito non esiste.[ENTER]")
                return
            end            
            -- Ban/sban giocatore
            result, query_result = execute_query("account", "UPDATE account SET status='"..AccStatus[s].."' WHERE id="..AccID.." LIMIT 1")
            -- Controllo errori
            if result != 0 then
                say("Si e' verificato un errore.")
                say("Descrizione errore:")
                say_reward(query_result)
                return
            end    
            say("Operazione completata con successo![ENTER]")
        end
    end
end

Per ora ho compilato e testato il programma sia sulla versione a 32-bit che 64-bit di freebsd, non mi ha dato alcun problema.
So che il 99% degli utenti che leggerà questa cosa non capirà cos'è e non saprà neanche di cosa sto parlando, ma spero che possa essere utile a qualcuno.
Per bug/problemi, contattatemi su Skype.

Download: http://blackyuko.com/download/other/mysql_query.rar
 
Ultima modifica:
Bravo Manu il migliore come sempre ^_^
Anche se non di semplicissimo utilizzo ma (molto importante e funzionale)...Da utilizzare !!!
 
Qualcuno potrebbe uppare da un'altra parte D: il sito di yuko è praticamente off fino per altri 6-7 giorni da quanto ho capito D:
 
Ultima modifica:
ci sono riuscito finalmente vacca miseria!
grazie infinite ultra Yuko per la release, grazie davvero dopo 1 settimana di errori.
 
Yuko mi puoi gentilmente dire dove inserire un
notice_all quando si banna uno
e un
notice all con scritte diverse quando si sbanna uno

perchè al momento l ho fatto e funziona tutto , perfetto, ma purtroppo mi esce sempre un unico notice all
ovvero

... è appena stato bannato.

non so proprio dove farla, me lo fai uno screen veloce, dove li devo mettere, perfavore?

- - - Updated - - -

edit---- risolto da solo, ho sculato alla grande, ho provato tutte le varianti. grazie ancora >Yuko
 
Stato
Discussione chiusa ad ulteriori risposte.