[Guida] Vuoi scrivere un plugin? Eccoti le basi!

Introduzione
Da un po' di tempo ho notato che ci sono sempre più persone che vogliono iniziare a scrivere plugins e facendo una ricerca per il forum, non ho trovato nessuna guida valida per questo campo di programmazione quindi ho deciso di crearne io una.
Come dal titolo potreste intuire: la guida vi farà conoscere solamente le basi per iniziare a navigare tra l'API(Application Programming Interface) di Spigot/Bukkit.
In questa guida tratterò la creazione di un plugin che avrà:
  1. Un comando
  2. Due eventi
Prerequisiti
Inizialmente avremo bisogno di 3 cose:
  1. Come anche per le mods, così anche per i plugins, vi serviranno delle conoscenze basi del linguaggio Java. La guida che vi consiglio di leggere prima di iniziare a metter in pratica la mia è quella presente sul forum: LINK.
  2. Come potreste aver capito leggendo la guida che ho citato sopra, per programmare avremo bisogno di un IDE, null'altro che un ambiente di sviluppo. Io, personalmente mi trovo abbastanza bene con Eclipse, quindi sarà quest'ultimo che userò anche in questa guida.
  3. Per scrivere plugins avreremo bisogno di una versione preferibilmente aggiornata di Spigot. Per il download di Spigot dovrete usare BuildTools. Ci tengo a precisare che la compatibilita' del plugin e' in base alla versione di Spigot che userete, generalmente se non usate Oggetti/Variabili/Metodi specifichi di una versione il plugin potrebbe funzionare anche con il resto delle versioni.
Svolgimento
Apriamo e settiamo il nostro ambiente di sviluppo
Workspace
Al primo avvio di Eclipse ci si aprirà una schermata in cui dovrete selezionare il percorso del nostro workspace.
Esempio:
Cos'è il workspace?
Il workspace non è altro che il nostro spazio di lavoro, cioè una cartella in cui verranno salvati tutti i nostri progetti.
Package Explorer
Dopo aver selezionato il nostro workspace si aprirà una schermata di benvenuto in cui dobbiamo semplicemente cliccare Workbench in alto a destra.
Esempio:
Adesso visualizzeremo anche il Package Explorer, ossia la zona in cui ci saranno tutti i nostri progetti.
Creiamo un nuovo progetto ed iniziamo a programmare
Preparazione
Progetto
Per creare un nuovo progetto ci sono ben 2 modi:
  • Cliccare su File > New > Java Project
  • Tasto destro nella zona del Package Explorer > New > Java Project
  • Utilizzando la barra degli strumenti
Entrambi i metodi vi porteranno ad aprire una nuova finestra che ci servirà per la creazione del nostro progetto specificandone il nome e se vogliamo la library necessaria per la creazione di un plugin.
Esempio:
Creazione di un progetto
[Immagine: 24c0967c37.png]
Schermata per la creazione di un progetto
[Immagine: 6a5b025570.png]
Library
Per poter creare un plugin abbiamo bisogno di aggiungere al nostro progetto le librerie adatte quindi Spigot che abbiamo scaricato precedentemente.
Per aggiungere una libreria:
  • Testo destro sul progetto > Build Path > Add External Archives...
Così facendo, si aprirà una finestra in cui dobbiamo selezionare il nostro .jar.

Package
Dopo aver creato il nostro progetto, dobbiamo creare il package che dovrà contenere inizialmente la nostra classe principale(Parlerò più tardi di essa).
Per creare un nuovo package ci sono due modi(Quelli più usati secondo me):
  • Tasto destro sul nome del package oppure sulla cartella src > New > Package
  • Utilizzando la barra degli strumenti
Esempio:
Entrambi i metodi ci aprirà una schermata in cui noi potremo scegliere il nome del package.
Il nome del package puo' esser qualunque tranne quelli "vietati", ma spesso viene utilizzato "me.NomeCreatore.NomePlugin" oppure il server per cui si lavora, proprio dominio etc.
I package non devono mai iniziare con una serie di prefissi:
  1. org.bukkit
  2. net.bukkit
  3. com.bukkit
  4. net.minecraft
Esempio:

Classi
Abbiamo creato il progetto, il package, aggiunto Spigot tra le librerie, non ci resta altro che creare la prima classe!
La prima classe sara' quella principale, cioe' il cuore del plugin che chiameremo Main.
Per crearla ci sono vari modi:
  • Tasto destro sul nome del progetto oppure sulla cartella src > New > Class
  • Utilizzando la barra degli strumenti
Esempio:

Nella classe appena creata troveremo:
Codice:
package me.eduproard.Guida;
/*
*
* Qui ci andranno i vari "import". Per importare qualcosa ci sono due modi:[/font]
*     1) Puntare il cursore del mouse sulla parola sottolineata in rosso per vedere se c'e' da importare qualcosa[/font]
*     2) Su windows: CONTROL + SHIFT + O
*
*/
public class Main {

}
Iniziamo a programmare
Classe principale
Nella classe principale dobbiamo specificare che si tratta di un plugin e non di un normale progetto Java.
Come facciamo a specificarlo?
Semplicissimo, dobbiamo far diventare la nostra classe una subclass, cioe' dipendente da un'altra chiamata JavaPlugin.
Per fare cio' bisogna modificare questo "public class Main" aggiungendo " extends JavaPlugin".
Risultato:
Codice:
package me.eduproard.Guida;

import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {

}

onEnable e onDisable
Nella classe principale ci sono due funzioni molto importanti:
  1. onEnable, esegue le azioni che noi ci scriviamo dentro al blocco codice quando il plugin si sta abilitando. Questa funzione e' necessaria!
  2. onDisable, esegue le azioni che noi ci scriviamo dentro al blocco codice prima che il plugin si disattivi. Questa funzione non e' tanto necessaria quanto la prima perche' generalmente non ci sono cose da eseguire prima della disabilitazione, a meno che non dobbiamo salvare qualcosa dentro a file, etc; quindi possiamo dire che questa funzione e' utile a seconda del plugin che scriviamo.
Queste funzioni, ovviamente, le dobbiamo aggiungere nel blocco codice principale.
Nel nostro caso, utilizzeremo solamente onEnable.
Risultato:
Codice:
package me.eduproard.Guida;

import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {

    public void onEnable() {

    }
}


Creiamo un comando?
Iniziamo col dire che un comando puo' esser creato in 3 modi diversi:
  • Scrivendolo nella classe principale
  • Creando una nuova classe per il comando
  • Scrivendolo direttamente nell'onEnable
Per comodita', in questa guida usero' il secondo metodo.
Quindi, create una nuova classe nel nostro package e chiamatela come volete voi, io la chiamero' "CMD", a questo punto per dire alla classe che si tratta di un comando, dobbiamo implementarle l'interfaccia CommandExecutor che ci fara' utilizzare i propri metodi. Per implementarla dobbiamo modificare "public class CMD" aggiungendo " implements CommandExecutor".
Importando tutto noteremo che nel codice il nome della classe e' sottolineato in rosso, questo perche' dobbiamo aggiungere tutti i metodi dell'interfaccia che abbiamo implementato quindi basta mettere il cursore sopra e cliccare Add unimplemented methods.
Risultato:
Codice:
package me.eduproard.Guida;

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;

public class CMD implements CommandExecutor {

    @Override
    public boolean onCommand(CommandSender arg0, Command arg1, String arg2, String[] arg3) {
        // TODO Auto-generated method stub
        return false;
    }

}

Come possiamo notare, il metodo che ci ha fatto aggiungere contiene 4 argomenti, di cui 3 sono molto importanti ed utilizzati:
  1. CommandSender, verifica chi ha eseguito il comando.
  2. Command, verifica il comando eseguito.
  3. String[], e' un normale array, ma in questo caso verifichera' gli argomenti del comando.
Per comodita' di scrittura del codice, modificheremo questo metodo.
Precedentemente:
Codice:
@Override
    public boolean onCommand(CommandSender arg0, Command arg1, String arg2, String[] arg3) {
        // TODO Auto-generated method stub
        return false;
    }
Successivamente:
Codice:
@Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {

        return false;
    }

Un comando puo' esser costituito da vari argomenti, ma il nostro e' di un solo argomento(/arg0) quindi andremo a scrivere cosa deve accadere se il giocatore esegue il comando con la giusta sintassi(/guida) e quando non lo esegue con la giusta sintassi(/guida arg1 arg2 etc).

Se aveste letto tutto fin qui, avreste capito che per verificare gli argomenti eseguiti dovremo utilizzare "args"
Risultato:
Codice:
@Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        //Se gli argomenti fossero 0 (I numeri iniziano da 0, quindi stiamo verificando se il comando eseguito e' /guida)
        //manda un messaggio al giocatore(sender) con scritto: "L'autore della guida e' EduProArd"
        if(args.length == 0) {
            sender.sendMessage("L'autore della guida e' EduProArd");
            return false;
        }
        //Se gli argomenti della guida fossero maggiori o uguale ad 1(/guida arg1 arg2 arg3 etc) manda un messaggio
        //al giocatore con scritto: "La sintassi e' errata, prova: /guida!"
        if(args.length >= 1) {
            sender.sendMessage("La sintassi e' errata, prova: /guida!")
            return false;
        }
        return false;
    }
Questo e' il nostro comando bello e funzionante.
Nei vari messaggi non ho utilizzato nessun tipo di colore, per farlo basta utilizzare "§<color-code> messaggio" oppure "ChatColor.<Colore> + "messaggio" ".
Ci tengo a precisare che utilizzando CommandSender verificate anche se chi esegue il comando e' la console, quindi con "sender" usufruirete di azioni che potete fare sia al giocatore che alla console e di conseguenza sono molto limitati. I metodi come ottenere la vita del giocatore sono dell'oggetto Player, per utilizzarlo:
Codice:
//Dichiariamo che d'ora in poi la variabile nomeVariabile dovra' richiamare il giocatore che invia il comando.
//(Player) e' chiamato cast, ci serve per specificare che siamo a conoscenza del fatto che "sender(CommandSender)" non e'//una subclass di Player. Per informazioni piu' precise e complete consultare la documentazione di Java:
//http://docs.oracle.com/javase/7/docs/api/java/lang/ClassCastException.html

Player nomeVariabile = (Player) sender;
Una volta creato il comando, dobbiamo registrarlo sia nell'onEnable che dentro a plugin.yml(Vi parlero' piu' avanti di questo), per farlo bastera' aggiungere dentro al blocco codice di quest'ultimo: "getCommand("NomeDelVostroComando").setExecutor(new ClasseDelVostroComando());"
Risultato:
Codice:
public void onEnable() {
    getCommand("guida").setExecutor(new CMD());
}

Utilizziamo listeners?
I listeners o eventi sono dei metodi che ci consentono di verificare la maggior parte delle cose che accadono nel server.
Come detto all'inizio della guida, il plugin che stiamo creando avra' 1 comando che gia' abbiamo creato e 2 eventi.
Gli eventi che useremo sono:
  1. PlayerJoinEvent, verifica quando entra un giocatore nel server.
  2. PlayerQuitEvent, verifica quando esce un giocatore dal server.
Per utilizzarli ci sono vari metodi:
  • Scrivendoli nella classe principale
  • Creando una classe per essi
  • Scrivendoli direttamente nell'onEnable
Per comodita', come anche per il comando precedente, vi insegnero' il secondo metodo.
Quindi, ci accingiamo a creare una nuova classe che io chiamero' "Events" e implementiamo l'interfaccia Listeners.
Quest'interfaccia, a differenza di quella usata per i comandi, non ci chiede metodi obbligatori.
La mia classe adesso si presentera' cosi':
Codice:
package me.eduproard.Guida;

import org.bukkit.event.Listener;

public class Events implements Listener {

}
Un evento si presenta cosi':
Codice:
@EventHandler //Serve per specificare che precede un evento
public void nome(Evento nomePerRichiamareL'Evento) {

}
Adesso, non ci resta altro che scrivere i due eventi e le azioni che deve fare ciascuno.
Codice:
public class Events implements Listener {
    @EventHandler
    public void on(PlayerJoinEvent e) {
        //Assegnamo e.getPlayer() ad una nuova variabile chiamata "p"
        Player p = e.getPlayer();
        //Se "p(Il giocatore che entra)" non e' mai entrato prima, mandagli un messaggio con scritto:
        //"Benvenuto nel server, NomeGiocatore!" con il colore arancione e rosso.
        if(p.hasPlayedBefore() == false) {
            p.sendMessage("§6Benvenuto nel server, §4" + p.getName() + "!");
        //Altrimenti, se non e' la prima volta che entra nel server, mandagli un messaggio con scritto:
        //Bentornato nel server, NomeGiocatore! con il colore arancione e rosso.
        } else {
            p.sendMessage("§6Bentornato nel server, §4" + p.getName() + "!");
        }
    }

    @EventHandler
    public void on(PlayerQuitEvent e) {
        Player p = e.getPlayer();
        //Settiamo il messaggio che devono ricevere tutti i giocatori online quando qualcuno esce dal server.
        e.setQuitMessage(ChatColor.GREEN + "Il giocatore " + ChatColor.RED + p.getName() + ChatColor.GREEN + " e' uscito dal server!");
    }
}
Adesso, abbiamo anche due eventi, non ci resta che registrare anch'essi nell'onEnable, per farlo bastera' aggiungere dentro al blocco codice di quest'ultimo: "getServer().getPluginManager().registerEvents(new ClasseConEventi(), this)"
Risultato finale:
Codice:
public void onEnable() {
    getCommand("guida").setExecutor(new CMD());
    //this serve per specificare la classe del plugin.
    getServer().getPluginManager().registerEvents(new Events(), this);
}
Conclusione
Il file plugin.yml
Qualsiasi plugin, per funzionare deve avere il file plugin.yml che dovra' contenere tutte le informazioni necessarie per la lettura del codice.
Per creare un file ci sono 3 modi:
  • Selezionare il progetto > Cliccare su File > New > File
  • Tasto destro sul nome del progetto > New > File
  • Utilizzando la barra degli strumenti
Questi tre modi ci apriranno una schermata in cui noi dovremo scrivere il nome del file, nel nostro caso dobbiamo scrivere per forza "plugin.yml".
Dopo averlo creato, lo dobbiamo aprire con qualsiasi editor esterno che interno. Io lo apriro' con Notepad++.
Dentro ci dobbiamo scrivere alcune cose obbligatorie ed altre no:
  1. Name, il nome del nostro plugin (Obbligatorio)
  2. Version, la versione del nostro plugin(Per esempio: 1.0) (Obbligatorio)
  3. Main, il percorso del che deve fare per arrivare alla classe principale (Obbligatorio)
  4. Author/s, l'autore del plugin (Non obbligatorio)
  5. Commands, una lista di comandi se esistenti (Non obbligatorio, ma se nel codice sono presenti comandi, essi non funzioneranno se non sono registrati dentro al plugin.yml)
Risultato finale:
Codice:
name: Guida
main: me.eduproard.Guida.Main
author: EduProArd
version: 1.0
commands:
  guida:
    description: L'unico comando esistente!
Export!
Il plugin teoricamente e' finito, ma per utilizzarlo abbiamo bisogno di exportarlo.
Per exportarlo:
  • Tasto destro sul nome del progetto > Export...
Nella schermata che si aprira' dobbiamo semplicemente selezionare "Jar file" cliccare su Next ed infine selezionare la cartella di destinazione.

Guide e JavaDocs
Vi lascio alcuni siti che vi potranno servire per continuare il vostro percorso:
Spero che la guida vi sia stata d'aiuto!

Guida scritta completamente da EduProArd. Qualsiasi pubblicazione deve esser prima autorizzata da EduProArd.
Creative Commons Attribuzione - Non commerciale 4.0 Internazionale (CC BY-NC 4.0)
(Modificato 26/03/2016, 20:24 da EduProArd.)
_____________________________________________________________________________________________________________
GitHub: https://github.com/EduProArd/
Come contattarmi: Skype("EduProArd") oppure per MP("Messaggio privato sul forum")
Hosting: https://www.easyware-hosting.ch/
EasyWay: https://www.minecraft-italia.it/forum/t-...di-sistema
_____________________________________________________________________________________________________________
6 utenti apprezzano questo post
Bella guida per iniziare. Solo una puntualizzazione: in alcuni pezzi di codice c'è ancora il selettore del font (quello del forum). A parte questo, direi ottima.
"Amo molto parlare di niente, è l'unico argomento di cui so tutto" - Oscar Wilde

NON OFFRO ASSISTENZA PRIVATA. NON SCRIVETEMI PER RISOLVERE UN PROBLEMA. CREATE UNA NUOVA DISCUSSIONE.
08/07/2015, 08:57_gjkf_ ha scritto: Bella guida per iniziare. Solo una puntualizzazione: in alcuni pezzi di codice c'è ancora il selettore del font (quello del forum). A parte questo, direi ottima.
Grazie Smile.


Per quanto riguardano i selettori del font: lo avevo notato anche io ieri sera, ma non avendo tempo non l'ho aggiustato subito. Adesso e' tutto completo e aggiustato.
_____________________________________________________________________________________________________________
GitHub: https://github.com/EduProArd/
Come contattarmi: Skype("EduProArd") oppure per MP("Messaggio privato sul forum")
Hosting: https://www.easyware-hosting.ch/
EasyWay: https://www.minecraft-italia.it/forum/t-...di-sistema
_____________________________________________________________________________________________________________
Hai dimenticato di specificare che il Cast diretto provoca una ClassCastException se il comando è eseguito da console. Quindi, prima del cast è consigliabile fare un if per vedere se il sender è un'istanza di Player se si desidera utilizzare i metodi di quest'ultimo.
Codice:
if (sender instanceof Player) {
  Player player = (Player) sender;
}

Questo ovviamente vale anche per i Listeners che utilizzano le entità (EntityDamageEvent, EntityDamageByEntityEvent, ecc) e l'oggetto del cast è un'entità diversa da un Player (Ad esempio, un Creeper o una freccia) (Modificato 08/07/2015, 10:35 da Samsey.)
[Immagine: 0438196df1.gif]
08/07/2015, 10:31Samsey ha scritto: Hai dimenticato di specificare che il Cast diretto provoca una ClassCastException se il comando è eseguito da console. Quindi, prima del cast è consigliabile fare un if per vedere se il sender è un'istanza di Player se si desidera utilizzare i metodi di quest'ultimo.
Codice:
if (sender instanceof Player) {
 Player player = (Player) sender;
}

Questo ovviamente vale anche per i Listeners che utilizzano le entità (EntityDamageEvent, EntityDamageByEntityEvent, ecc) e l'oggetto del cast è un'entità diversa da un Player (Ad esempio, un Creeper o una freccia)
@Samsey
Mi dispiace contraddirti, ma io non ho dimenticato assolutamente nulla perche' tu da lettore non puoi sapere gli argomenti che volevo trattare nella guida. Penso che chiunque prima di scrivere una guida per bene si prepari gli argomenti da trattare.

Ho chiaramente scritto che CommandSender richiama i metodi eseguibili sia su un giocatore che su una Console quindi sono molto limitati e di conseguenza ho indicato come si faccia a richiamare tutti i metodi dell'oggetto Player. Ti ricordo, inoltre, che la guida e' per dare le basi ai novellini, per chi vuol continuare il suo percorso in questo campo ho indicato nella sezione "Guide e JavaDocs" links utili dove possono trovare di tutto.
Ah, se volevi spiegare te quella parte, mi dispiace dubitare sulla comprensione completa da parte dei giocatori.
PS: e' solamente una risposta completa al tuo commento, nulla di arrogante. (Modificato 08/07/2015, 11:39 da EduProArd.)
_____________________________________________________________________________________________________________
GitHub: https://github.com/EduProArd/
Come contattarmi: Skype("EduProArd") oppure per MP("Messaggio privato sul forum")
Hosting: https://www.easyware-hosting.ch/
EasyWay: https://www.minecraft-italia.it/forum/t-...di-sistema
_____________________________________________________________________________________________________________
08/07/2015, 11:36EduProArd ha scritto:
08/07/2015, 10:31Samsey ha scritto: Hai dimenticato di specificare che il Cast diretto provoca una ClassCastException se il comando è eseguito da console. Quindi, prima del cast è consigliabile fare un if per vedere se il sender è un'istanza di Player se si desidera utilizzare i metodi di quest'ultimo.
Codice:
if (sender instanceof Player) {
 Player player = (Player) sender;
}

Questo ovviamente vale anche per i Listeners che utilizzano le entità (EntityDamageEvent, EntityDamageByEntityEvent, ecc) e l'oggetto del cast è un'entità diversa da un Player (Ad esempio, un Creeper o una freccia)
@Samsey
Mi dispiace contraddirti, ma io non ho dimenticato assolutamente nulla perche' tu da lettore non puoi sapere gli argomenti che volevo trattare nella guida. Penso che chiunque prima di scrivere una guida per bene si prepari gli argomenti da trattare.

Ho chiaramente scritto che CommandSender richiama i metodi eseguibili sia su un giocatore che su una Console quindi sono molto limitati e di conseguenza ho indicato come si faccia a richiamare tutti i metodi dell'oggetto Player. Ti ricordo, inoltre, che la guida e' per dare le basi ai novellini, per chi vuol continuare il suo percorso in questo campo ho indicato nella sezione "Guide e JavaDocs" links utili dove possono trovare di tutto.
Ah, se volevi spiegare te quella parte, mi dispiace dubitare sulla comprensione completa da parte dei giocatori.
PS: e' solamente una risposta completa al tuo commento, nulla di arrogante.

Ho semplicemente pensato che parlando di cast fosse doveroso dirlo, visto che qualunque novellino avrebbe fatto l'errore di castare direttamente senza fare alcun tipo di controllo, quindi te l'ho fatto notare.
(Lo so perché anche io facevo quest'errore Asd )

@EduProArd (Modificato 08/07/2015, 12:28 da Samsey.)
[Immagine: 0438196df1.gif]
08/07/2015, 12:15Samsey ha scritto: Ho semplicemente pensato che parlando di cast fosse doveroso dirlo, visto che qualunque novellino avrebbe fatto l'errore di castare direttamente senza fare alcun tipo di controllo, quindi te l'ho fatto notare.
(Lo so perché anche io facevo quest'errore Asd )

@EduProArd
@Samsey
Probabilmente hai iniziato a scrivere plugins senza avere delle basi su Java.
ClassCastException fa parte di Java. (Modificato 08/07/2015, 12:49 da EduProArd.)
_____________________________________________________________________________________________________________
GitHub: https://github.com/EduProArd/
Come contattarmi: Skype("EduProArd") oppure per MP("Messaggio privato sul forum")
Hosting: https://www.easyware-hosting.ch/
EasyWay: https://www.minecraft-italia.it/forum/t-...di-sistema
_____________________________________________________________________________________________________________
Gli spoiler sono vuoti e le immagini che dovrebbero esserci dentro sono fuori.
A parte questo buona guida Wink
My Config:
    MotherBoard: GigaByte 990 FX A-UD3 Ultra Durable
    CPU: AMD FX-8350 (8 core, 4.00GHz)
    Dissipatore: ThermalRight Macho HR-02 Rev.a
    RAM: Corsair Vengeance 8GB 4GBx2
    GPU: nVidia GTX 780 Asus OC
    PSU: Corsair CX 750M
    Case: Cooler Master 690 III Midi
    SSD 256GB
    HHD 1TB
    Masterizzatore
08/07/2015, 21:04SkiFire13 ha scritto: Gli spoiler sono vuoti e le immagini che dovrebbero esserci dentro sono fuori.
A parte questo buona guida Wink
Grazie!
Riguardo agli spoiler: la cosa buffa e' che se vado a modificare la discussione, le immagini si trovano all'interno degli spoiler.
@SkiFire13
_____________________________________________________________________________________________________________
GitHub: https://github.com/EduProArd/
Come contattarmi: Skype("EduProArd") oppure per MP("Messaggio privato sul forum")
Hosting: https://www.easyware-hosting.ch/
EasyWay: https://www.minecraft-italia.it/forum/t-...di-sistema
_____________________________________________________________________________________________________________
08/07/2015, 22:34EduProArd ha scritto:
08/07/2015, 21:04SkiFire13 ha scritto: Gli spoiler sono vuoti e le immagini che dovrebbero esserci dentro sono fuori.
A parte questo buona guida Wink
Grazie!
Riguardo agli spoiler: la cosa buffa e' che se vado a modificare la discussione, le immagini si trovano all'interno degli spoiler.
@SkiFire13
Ora ho provato da tapatalk e si vedono al loro intenrno.
Bho D:
My Config:
    MotherBoard: GigaByte 990 FX A-UD3 Ultra Durable
    CPU: AMD FX-8350 (8 core, 4.00GHz)
    Dissipatore: ThermalRight Macho HR-02 Rev.a
    RAM: Corsair Vengeance 8GB 4GBx2
    GPU: nVidia GTX 780 Asus OC
    PSU: Corsair CX 750M
    Case: Cooler Master 690 III Midi
    SSD 256GB
    HHD 1TB
    Masterizzatore
@HackLover Asd
Se ti sono stato utile pigia quel bel pulsantino REP.

KINGS OF THE HILLS
[Immagine: xwUIc7j.jpg]


[Immagine: Renzi.jpg]
RENZI COMANDERA' IL MONDO UN GIORNO!
Discussioni simili
Risposta di LeoPotterITA
06/12/2018, 16:00
Risposta di astRiKez14
02/12/2018, 08:21
Risposta di ReNext
12/11/2018, 02:40
Risposta di Auties05
31/08/2018, 10:45

Utente(i) che stanno guardando questa discussione: 1 Ospite(i)