IT | Update to the latest commits

master
Zughy 2021-01-28 00:13:08 +00:00
parent d2252cfd95
commit c455aff265
7 changed files with 140 additions and 85 deletions

View File

@ -94,7 +94,7 @@ if data[idx] == c_pietra then
end
```
Si consiglia di ottenere e salvare (in una variabile locale) gli ID di contenuto al caricare della mod in quanto questi non possono cambiare.
Gli ID di contenuto di un nodo potrebbero cambiare durante la fase di caricamento, quindi è consigliato non tentare di ottenerli durante tale fase.
Le coordinate dei nodi nell'array di un LVM sono salvate in ordine inverso (`z, y, x`), quindi se le si vuole iterare, si tenga presente che si inizierà dalla Z:
@ -148,11 +148,9 @@ Se si passa `false` invece, ci sarà bisogno di ricalcolarla in un secondo tempo
## Esempio
```lua
-- ottiene l'ID di contenuto al caricare della mod, salvandolo in variabili locali
local c_terra = minetest.get_content_id("default:dirt")
local c_erba = minetest.get_content_id("default:dirt_with_grass")
local function da_erba_a_terra(pos1, pos2)
local c_terra = minetest.get_content_id("default:dirt")
local c_erba = minetest.get_content_id("default:dirt_with_grass")
-- legge i dati nella LVM
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)

View File

@ -20,13 +20,14 @@ In questo capitolo parleremo della programmazione in Lua, degli strumenti necess
- [Operatori logici](#operatori-logici)
- [Programmare](#programmare)
- [Portata locale e globale](#portata-locale-e-globale)
- [Local dovrebbe essere usato il più possibile](#local-dovrebbe-essere-usato-il-piu-possibile)
- [Inclusione di altri script Lua](#inclusione-di-altri-script-lua)
## Editor di codice
Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua.
L'evidenziamento assegna colori diversi a parole e caratteri diversi a seconda del loro significato, permettendo di individuare più facilmente eventuali errori.
L'evidenziamento assegna colori diversi a parole e caratteri diversi, a seconda del loro significato, permettendo quindi di individuare più facilmente eventuali errori e inconsistenze.
Per esempio:
```lua
function ctf.post(team,msg)
@ -44,15 +45,16 @@ function ctf.post(team,msg)
end
```
Per esempio, parole chiave come if, then, end e return sono evidenziate nel passaggio qui sopra.
Lo stesso vale per le funzioni interne di Lua come table.insert.
Nel passaggio qui sopra, le parole chiave `if`, `then`, `end` e `return` sono evidenziate.
E Lo stesso vale per le funzioni interne di Lua come `table.insert`.
Segue una lista di editor noti che si prestano bene per programmare in Lua.
Non sono, ovviamente, gli unici esisteneti.
Tra gli editor più famosi che ben si prestano a lavorare in Lua, troviamo:
* [VSCode](https://code.visualstudio.com/) - software libero (come Code-OSS e VSCodium), rinomato, e che dispone di [estensioni per il modding su Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
* [Notepad++](http://notepad-plus-plus.org/) - Solo per Windows
(ne esistono ovviamente anche altri)
* Windows: [Notepad++](http://notepad-plus-plus.org/), [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
* Linux: Kate, Gedit, [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
* OSX: [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
## Programmare in Lua
@ -65,8 +67,7 @@ Essi possono essere:
* Sequenziali: eseguono un'istruzione dopo l'altra, senza salti.
* Selettivi: saltano alcune sequenze a seconda delle condizioni.
* Iteranti: ripetono ciclicamente. Continuano a eseguire le stesse istruzioni
finché una condizione non è soddisfatta.
* Iteranti: continuano a eseguire le stesse istruzioni finché una condizione non è soddisfatta.
Quindi, come vengono rappresentate le istruzioni in Lua?
@ -78,13 +79,10 @@ a = a + 10
print("La somma è ".. risultato)
```
Whoa, cos'è appena successo?
a, b, e risultato sono *variabili*. Le variabili locali si dichiarano tramite l'uso della parola chiave local (che vedremo tra poco), e assegnando eventualmente loro un valore iniziale.
In quest'esempio, `a`, `b`, e `risultato` sono *variabili*. Le variabili locali si dichiarano tramite l'uso della parola chiave `local` (che vedremo tra poco), e assegnando eventualmente loro un valore iniziale.
Il simbolo `=` significa *assegnazione*, quindi `risultato = a + b` significa impostare "risultato" ad a + b.
Per quanto riguarda i nomi delle variabili, essi possono essere più lunghi di un carattere - al contrario che in matematica - come visto in "risultato", e vale anche la pena notare che Lua è *case-sensitive* (differenzia maiuscole da minuscole);
A è una variabile diversa da a.
Per quanto riguarda i nomi delle variabili, essi possono essere più lunghi di un carattere - al contrario che in matematica - come visto in `risultato`, e vale anche la pena notare che Lua è *case-sensitive* (differenzia maiuscole da minuscole); `A` è una variabile diversa da `a`.
### Tipi di variabili
@ -102,7 +100,7 @@ Una variabile può equivalere solo a uno dei seguenti tipi e può cambiare tipo
### Operatori matematici
Lista non esaustiva, ce ne sono altri
Tra gli operatori di Lua ci sono:
| Simbolo | Scopo | Esempio |
|---------|--------------------|---------------------------|
@ -113,9 +111,11 @@ Lista non esaustiva, ce ne sono altri
| A ^ B | Potenze | 2 ^ 2 = 2<sup>2</sup> = 4 |
| A .. B | Concatena stringhe | "foo" .. "bar" = "foobar" |
Si tenga presente che questa non è comunque una lista esaustiva.
### Selezione
La selezione più basica è il costrutto if.
Il metodo di selezione più basico è il costrutto if.
Si presenta così:
```lua
@ -129,10 +129,11 @@ end
Questo esempio genera un numero casuale tra 1 e 100.
Stampa poi "Woohoo!" se il numero è superiore a 50, altrimenti stampa "No!".
Cos'altro si può usare oltre a '>'?
### Operatori logici
Tra gli operatori logici di Lua ci sono:
| Simbolo | Scopo | Esempio |
|---------|--------------------------------------|-------------------------------------------------------------|
| A == B | Uguale a | 1 == 1 (true), 1 == 2 (false) |
@ -153,9 +154,9 @@ if not A and B then
end
```
Che stampa "Yay!" se A è falso e B vero.
Che stampa "Yay!" se `A` è falso e `B` vero.
Gli operatori logici e matematici funzionano esattamente allo stesso modo; entrambi accettano input e ritornano un valore che può essere immagazzinato.
Gli operatori logici e matematici funzionano esattamente allo stesso modo; entrambi accettano input e ritornano un valore che può essere immagazzinato. Per esempio:
```lua
local A = 5
@ -173,7 +174,7 @@ Insegnarti i processi logici della programmazione non rientra nell'ambito di que
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per imparare come scrivere codice; offre un'esperienza guidata interattiva.
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia dalle basi assolute, imparando le tecniche di problem solving necessarie per la programmazione.\\
Scratch è **ideato per insegnare ai bambini** e non è un linguaggio serio di programmazione.
Scratch è *ideato per insegnare ai bambini* e non è un linguaggio serio di programmazione.
## Portata locale e globale
@ -197,24 +198,6 @@ end
Mentre le variabili globali sono accessibili da qualsiasi script di qualsiasi mod.
```lua
my_global_variable = "ciao"
function one()
my_global_variable = "hey"
end
print(my_global_variable) -- Output: "ciao"
one()
print(my_global_variable) -- Output: "hey"
```
### Local dovrebbe essere usato il più possibile
Lua è globale di default (a differenza di molti altri linguaggi di programmazione).
Le variabili locali devono essere identificate come tali.
```lua
function one()
foo = "bar"
@ -228,14 +211,12 @@ one()
two()
```
dump() è una funzione che può trasformare qualsiasi variabile in una stringa, cosicché il programmatore possa vedere cosa rappresenta.
In questo caso `foo` sarà stampata come "bar", virgolette incluse (che dimostrano che è una stringa).
L'esempio precedente non è tuttavia buona norma e Minetest, infatti, ci avviserà di ciò:
Le variabili locali dovrebbero venire usate il più possibile, con le mod che creano al massimo una globale corrispondente al nome della mod.
Crearne di ulteriori è considerato cattiva programmazione, e Minetest ci avviserà di ciò:
Assignment to undeclared global 'foo' inside function at init.lua:2
Per ovviare, usa "local":
Per ovviare, usa `local`:
```lua
function one()
@ -250,11 +231,10 @@ one()
two()
```
Ricorda che nil significa **non inizializzato**.
Ovvero la variabile non è stata ancora assegnata a un valore, non esiste o è stata deinizializzata (cioè impostata a nil)
Ricorda che `nil` significa **non inizializzato**.
Ovvero la variabile non è stata ancora assegnata a un valore, non esiste o è stata deinizializzata (cioè impostata a `nil`)
La stessa cosa vale per le funzioni.
Le funzioni sono variabili di tipo speciale, e dovrebbero essere dichiarate locali, in quanto altre mod potrebbero sennò avere funzioni con lo stesso nome.
La stessa cosa vale per le funzioni: esse sono variabili di tipo speciale, e dovrebbero essere dichiarate locali, in quanto altre mod potrebbero sennò avere funzioni con lo stesso nome.
```lua
local function foo(bar)
@ -262,7 +242,8 @@ local function foo(bar)
end
```
Le tabelle API dovrebbero essere usate per permettere ad altre mod di chiamare le funzioni, come in:
Per permettere alle mod di richiamare le tue funzioni, dovresti creare una tabella con lo stesso nome della mod e aggiungercele all'interno.
Questa tabella è spesso chiamata una API.
```lua
mymod = {}
@ -294,5 +275,4 @@ local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world!
```
Nei capitoli seguenti si parlerà nel dettaglio di come suddividere il codice di una mod.
Tuttavia, per ora l'approccio semplicistico è di avere file differenti per diversi tipi di cose — nodi.lua, craft.lua, oggetti.lua ecc.
Nei [capitoli seguenti](../quality/clean_arch.html) si parlerà nel dettaglio di come suddividere il codice di una mod.

View File

@ -13,7 +13,8 @@ Saper registrare nuovi nodi, oggetti fabbricabili e conseguenti ricette, è un r
- [Cosa sono i nodi e gli oggetti?](#cosa-sono-i-nodi-e-gli-oggetti)
- [Registrare gli oggetti](#registrare-gli-oggetti)
- [Nomi oggetto e alias](#nomi-oggetto-e-alias)
- [Nomi oggetto](#nomi-oggetto)
- [Alias](#alias)
- [Texture](#texture)
- [Registrare un nodo base](#registrare-un-nodo-base)
- [Azioni e callback](#azioni-e-callback)
@ -50,7 +51,7 @@ minetest.register_craftitem("nomemod:nomeoggetto", {
})
```
### Nomi oggetto e alias
### Nomi oggetto
Ogni oggetto ha un nome usato per riferirsi a esso, che dovrebbe seguire la seguente struttura:
@ -59,13 +60,14 @@ Ogni oggetto ha un nome usato per riferirsi a esso, che dovrebbe seguire la segu
`nomemod` equivale appunto al nome della mod che registra l'oggetto, e `nomeoggetto` è il nome che si vuole assegnare a quest'ultimo.
Esso dovrebbe essere inerente a quello che rappresenta e deve essere unico nella mod.
### Alias
Gli oggetti possono anche avere degli *alias* che puntano al loro nome.
Un *alias* è uno pseudonimo che dice al motore di gioco di trattarlo come se fosse il nome a cui punta.
Ciò è comunemente usato in due casi:
* Rinominare gli oggetti rimossi in qualcos'altro.
Ci potrebbero essere nodi sconosciuti nel mondo e negli inventari se un oggetto viene rimosso da una mod senza nessun codice per gestirlo.
È importante evitare di assegnare come alias nomi di nodi inottenibili già esistenti al nodo rimosso, se quest'ultimo poteva essere ottenuto.
* Aggiungere una scorciatoia.
`/giveme dirt` è più semplice di `/giveme default:dirt`.

View File

@ -67,6 +67,9 @@ Le più frequenti sono quelle per trovare i nodi.
Per esempio, mettiamo che si voglia creare un certo tipo di pianta che cresce più velocemente vicino alla pietra;
si dovrebbe controllare che ogni nodo nei pressi della pianta sia pietra, e modificarne il suo indice di crescita di conseguenza.
`minetest.find_node_near` ritornerà il primo nodo trovato in un dato raggio, combaciante con le informazioni passategli (nomi di nodi o gruppi).
Nell'esempio che segue, andiamo alla ricerca di un nodo di mese nel raggio di 5 nodi:
```lua
local vel_crescita = 1
local pos_nodo = minetest.find_node_near(pos, 5, { "default:stone" })
@ -87,8 +90,9 @@ local lista_pos =
local vel_crescita = 1 + #lista_pos
```
Il codice qui in alto, tuttavia, non fa proprio quello che ci serve, in quanto fa controlli basati su un'area, mentre `find_node_near` li fa su un intervallo.
Per ovviare a ciò, bisogna purtroppo controllare l'intervallo manualmente.
Il codice qui in alto ritorna il numero di nodi in un *volume cuboidale*.
Il che è diverso da usare `find_node_near`, il quale usa la distanza dalla posizione data (cioé una *sfera*).
Per ovviare a ciò, bisogna controllare l'intervallo manualmente.
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
@ -107,7 +111,7 @@ end
Ora il codice aumenterà correttamente `vel_crescita` basandosi su quanti nodi di pietra ci sono in un intervallo.
Notare come si sia comparata la distanza al quadrato dalla posizione, invece che calcolarne la radice quadrata per ottenerne la distanza vera e propria.
Questo perché i computer trovano le radici quadrate computazionalmente pesanti, quindi dovresti evitare di usarle il più possibile.
Questo perché i computer trovano le radici quadrate computazionalmente pesanti, quindi dovrebbero essere evitate il più possibile.
Ci sono altre variazioni delle due funzioni sopracitate, come `find_nodes_with_meta` e `find_nodes_in_area_under_air`, che si comportano in modo simile e sono utili in altre circostanze.
@ -116,7 +120,7 @@ Ci sono altre variazioni delle due funzioni sopracitate, come `find_nodes_with_m
### Scrittura dei nodi
Puoi usare `set_node` per sovrascrivere nodi nella mappa.
Ogni chiamata a set_node ricalcolerà la luce, il ché significa che set_node è alquanto lento quando usato su un elevato numero di nodi.
Ogni chiamata a `set_node` ricalcolerà la luce e richiamerà i suoi callback, il che significa che `set_node` è alquanto lento quando usato su un elevato numero di nodi.
```lua
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
@ -125,7 +129,7 @@ local nodo = minetest.get_node({ x = 1, y = 3, z = 4 })
print(nodo.name) --> default:stone
```
set_node rimuoverà ogni metadato e inventario associato a quel nodo: ciò non è sempre desiderabile, specialmente se si stanno usando
`set_node` rimuoverà ogni metadato e inventario associato a quel nodo: ciò non è sempre desiderabile, specialmente se si stanno usando
più definizioni di nodi per rappresentarne concettualmente uno. Un esempio è il nodo fornace: per quanto lo si immagini come un nodo unico,
sono in verità due.

View File

@ -19,6 +19,10 @@ In questo capitolo imparerai come manipolare gli oggetti e come definirne di tuo
- [Posizione e velocità](#posizione-e-velocità)
- [Proprietà degli oggetti](#proprietà-degli-oggetti)
- [Entità](#entità)
- [Salute e danno](#salute-e-danno)
- [Punti vita (HP)](#punti-vita-hp)
- [Pugni, Gruppi Danno e Gruppi Armatura](#pugni-gruppi-danno-e-gruppi-armatura)
- [Esempi di calcolo del danno](#esempi-di-calcolo-del-danno)
- [Oggetti figli](#oggetti-figli)
- [Il tuo turno](#il-tuo-turno)
@ -203,6 +207,81 @@ Per impostare il messaggio, puoi usare la Tabella di Entità Lua:
oggetto:get_luaentity():imposta_messaggio("ciao!")
```
## Salute e danno
### Punti vita (HP)
Ogni oggetto ha un valore Punti Vita (HP), che rappresenta la salute attuale.
Nei giocatori è inoltre possibile impostare il valore di salute massima tramite la proprietà `hp_max`.
Al raggiungere gli 0 HP, un oggetto muore.
```lua
local hp = oggetto:get_hp()
oggetto:set_hp(hp + 3)
```
### Pugni, Gruppi Danno e Gruppi Armatura
Il danno è la riduzione degli HP di un oggetto.
Quest'ultimo può prendere "a pugni" un altro oggetto per infliggere danno.
"A pugni" perché non si parla necessariamente di un pugno vero e proprio: può essere infatti un'esplosione, un fendente, e via dicendo.
Il danno complessivo è calcolato moltiplicando i Gruppi Danno del pugno con le vulnerabilità dell'obiettivo.
Questo è poi eventualmente ridotto a seconda di quanto recente è stato il colpo precedente.
Vedremo tra poco nel dettaglio quest'aspetto.
Proprio come i [Gruppi Danno dei nodi](../items/nodes_items_crafting.html#strumenti-capacità-e-friabilità), questi gruppi possono prendere qualsiasi nome e non necessitano di essere registrati.
Tuttavia, si è soliti usare gli stessi nomi di quelli dei nodi.
La vulnerabilità di un oggetto a un certo tipo di danno dipende dalla sua [proprietà](#proprietà-degli-oggetti) `armor_groups`.
Al contrario di quello che potrebbe far intendere il nome, `armor_groups` specifica la percentuale di danno subita da specifici Gruppi Danno, e non la resistenza.
Se un Gruppo Danno non è elencato nei Gruppi Armatura di un oggetto, quest'ultimo ne sarà completamente immune.
```lua
obiettivo:set_properties({
armor_groups = { fleshy = 90, crumbly = 50 },
})
```
Nell'esempio qui sopra, l'oggetto subirà il 90% di danno `fleshy` e 50% di quello `crumbly`.
Quando un giocatore prende "a pugni" un oggetto, i Gruppi Danno vengono estrapolati dall'oggetto che ha attualmente il mano.
Negli altri casi, saranno le mod a decidere quali Gruppi Danno usare.
### Esempi di calcolo del danno
Prendiamo a pugni l'oggetto `target`:
```lua
local capacita_oggetto = {
full_punch_interval = 0.8,
damage_groups = { fleshy = 5, choppy = 10 },
-- Questo è usato solo per scavare nodi, ma è comunque richiesto
max_drop_level=1,
groupcaps={
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
},
}
local tempo_da_ultimo_pugno = capacita_oggetto.full_punch_interval
obiettivo:punch(oggetto, tempo_da_ultimo_pugno, capacita_oggetto)
```
Ora, calcoliamo a quanto ammonterà il danno.
I Gruppi Danno del pugno sono `fleshy=5` e `choppy=10`, con l'obiettivo che prenderà 90% di danno da fleshy e 0% da choppy.
Per prima cosa, moltiplichiamo i Gruppi Danno per le vulnerabilità, e ne sommiamo il risultato.
Poi, moltiplichiamo per un numero tra 0 e 1 a seconda di `tempo_da_ultimo_pugno`.
```lua
= (5*90/100 + 10*0/100) * limit(tempo_da_ultimo_pugno / full_punch_interval, 0, 1)
= (5*90/100 + 10*0/100) * 1
= 4.5
```
Dato che HP è un intero, il danno è arrotondato a 5 punti.
## Oggetti figli
Gli oggetti figli (*attachments*) si muovono quando il genitore - l'oggetto al quale sono legati - viene mosso.

View File

@ -36,17 +36,6 @@ local timer = minetest.get_node_timer(pos)
timer:start(10.5) -- in secondi
```
Nell'esempio sottostante controlliamo che un timer sia attivo (`is_started()`), da quanto (`get_elapsed()`), quanto manca (`get_timeout()`) e infine lo fermiamo (`stop()`)
```lua
if timer:is_started() then
print("Il timer sta andando, e gli rimangono " .. timer:get_timeout() .. " secondi!")
print("Sono passati " .. timer:get_elapsed() .. " secondi")
end
timer:stop()
```
Quando un timer raggiunge lo zero, viene eseguito il metodo `on_timer`, che va dichiarato dentro la tabella di definizione del nodo.
`on_timer` richiede un solo parametro, ovvero la posizione del nodo.
@ -59,7 +48,9 @@ minetest.register_node("porteautomatiche:porta_aperta", {
})
```
Ritornando true, il timer ripartirà (con la stessa durata di prima).
Ritornando `true`, il timer ripartirà (con la stessa durata di prima).
È inoltre possibile usare `get_node_timer(pos)` all'interno di `on_timer`, basta assicurarsi di ritornare `false` per evitare conflitti.
Potresti aver tuttavia notato una limitazione: per questioni di ottimizzazione, infatti, è possibile avere uno e un solo timer per tipo di nodo, e solo un timer attivo per nodo.

View File

@ -37,6 +37,8 @@ Puoi adottare la licenza che preferisci; tuttavia, sappi che le mod con licenze
Tieni anche a mente che **la licenza di pubblico dominio non è una licenza valida**, perché la sua definizione varia da stato a stato.
È importante sottolineare che la WTFPL (*do What The Fuck you want to Public License*, la "facci il cazzo che ti pare") è caldamente *s*consigliata, e alcune persone potrebbero decidere di non usare la tua mod se ha questa licenza.
### LGPL e CC-BY-SA
Questa è la combinazione più comune nella comunità di Minetest, nonché quella usata sia da Minetest che da Minetest Game.
@ -49,9 +51,7 @@ Ciò significa che:
### CC0
Queste licenze permettono a chiunque di fare quello che gli va - incluso il non citare l'autore - e possono essere usate sia per il codice che per i contenuti artistici.
È importante sottolineare che la WTFPL (*do What The Fuck you want to Public License*, la "facci il cazzo che ti pare") è caldamente *s*consigliata, e alcune persone potrebbero decidere di non usare la tua mod se ha questa licenza.
Queste licenze possono essere usate sia per il codice che per contenuti artistici, permettendo a chiunque di fare quello che gli va - incluso il non citare l'autore.
### MIT
@ -60,13 +60,14 @@ La differenza con la LGPL è che le copie derivate in questo caso non devono per
## Impacchettare
Ci sono alcuni file che è consigliato includere nella propria mod prima di rilasciarla.
Ci sono alcuni file che è consigliato includere nelle proprie mod e nei propri giochi prima di rilasciarli.
### README.txt
Il README dovrebbe dichiarare:
* Cosa fa la mod;
* Cosa fa la mod/gioco;
* Come si usa;
* Che licenza ha;
* Quali dipendenze richiede;
* Come installare la mod;
@ -75,7 +76,7 @@ Il README dovrebbe dichiarare:
### mod.conf / game.conf
Assicurati di aggiungere una descrizione che spieghi cosa fa la mod, usando la chiave `description`.
Assicurati di aggiungere una descrizione che spieghi cosa fa la mod o il gioco, usando la chiave `description`.
Cerca di essere preciso e coinciso: dovrebbe essere breve perché il contenuto verrà mostrato nell'installer del motore di gioco, che ha uno spazio limitato.
Per esempio, consigliato:
@ -90,7 +91,7 @@ Sconsigliato:
Gli screenshot dovrebbero essere in proporzione 3:2 e avere una grandezza minima di 300x200px.
Lo screen verrà mostrato nel bazar delle mod (sono tutte gratuite).
Lo screen verrà mostrato all'interno di Minetest come anteprima del contenuto.
## Caricare
@ -99,7 +100,7 @@ Ci sono svariati modi per fare ciò quindi usa l'approccio che ritieni più oppo
* **Stabile** - Il sito che conterrà il file non dovrebbe essere propenso a chiudere i battenti da un momento all'altro senza preavviso;
* **Link diretto** - Dovresti essere in grado di cliccare su un link e scaricare il file senza il bisogno di dover passare per altre pagine;
* **Senza virus** - Le mod con contenuti malevoli saranno rimosse dal forum.
* **Senza virus** - Caricamenti su siti sospetti potrebbero contenere materiali non sicuri.
ContentDB soddisfa questi requisiti, richiedendo giusto un file .zip.