Tipi di valore TOML
Moongate legge configurazione, file di dati e template da TOML. La maggior parte dei campi sono valori TOML semplici: stringhe, interi, booleani, array e tabelle. Alcuni campi hanno un tipo C# che TOML non conosce, come un punto, un intervallo di tinte o un tipo di account. Un convertitore traduce tra quel tipo e un valore TOML: legge il valore quando viene caricato un file e lo scrive quando il codice salva un file.
Questa pagina elenca ogni convertitore, le forme che accetta, la forma che scrive e gli
errori che produce. I nomi delle chiavi sono sempre snake_case: la proprietà GoLocation è la
chiave go_location.
Come viene applicato un convertitore
Section titled “Come viene applicato un convertitore”Un convertitore raggiunge un campo in uno di tre modi.
Integrato per gli enum. TomlUtils aggiunge sempre EnumTomlConverterFactory alle proprie
opzioni predefinite, quindi ogni enum viene scritto e letto per nome senza registrazione:
vedi Enum. Un convertitore registrato per un tipo enum prevale su di esso.
Registrato per ogni chiamata. TomlUtils in Moongate.Core mantiene un elenco di
convertitori usati da ogni chiamata senza opzioni proprie:
public static void AddTomlConverter(TomlConverter converter);public static bool RemoveTomlConverter<T>() where T : TomlConverter;public static IReadOnlyList<TomlConverter> GetTomlConverters();AddTomlConverter ignora un secondo convertitore dello stesso tipo.
RemoveTomlConverter<T> rimuove ogni convertitore di tipo T e restituisce se ne
ha rimosso uno. GetTomlConverters restituisce l’elenco attuale. Entrambe le modifiche sono
thread-safe: ciascuna costruisce un nuovo elenco e nuove opzioni predefinite sotto un lock e
le pubblica insieme, quindi una chiamata in esecuzione contemporanea vede il vecchio insieme o
quello nuovo, mai una combinazione. L’elenco è globale al processo, quindi registra una volta sola,
all’avvio.
Queste classi registrano convertitori:
| Chi | Convertitori |
|---|---|
MoongateUltimaPlugin.Register |
Serial, Point2D, Point3D, HueSpec, Rectangle2D, DiceSpec, e le factory EnumValueSpec e RangeValueSpec |
| Test | nessuno globalmente; ogni test passa le proprie opzioni con il convertitore che verifica |
Un convertitore registrato copre anche la forma nullable del proprio tipo: go_location in
un file di regione è un Point3D? e usa Point3DTomlConverter.
Dichiarato con un attributo. [TomlConverter(typeof(...))] su una proprietà o su un
tipo applica il convertitore a quella proprietà o a ogni uso di quel tipo, senza
registrazione. Le aree delle regioni lo usano sul proprio tipo:
[TomlConverter(typeof(RegionAreaContentTomlConverter))]public sealed class RegionAreaContentUn convertitore indicato da un attributo deve convertire esattamente il tipo del membro: una proprietà int?
richiede un convertitore per int?, non per int. I convertitori registrati e quello
integrato per gli enum non hanno tale limite: coprono anche la forma nullable, e
un valore nullable non impostato viene omesso dal file.
Le opzioni esplicite non ricevono nulla. Deserialize, Serialize e gli overload per file
accettano un TomlSerializerOptions opzionale. Senza di esso, la chiamata usa le opzioni
predefinite: nomi snake_case, i convertitori registrati e il convertitore enum. Con esso la chiamata usa
le opzioni esattamente come fornite: i convertitori registrati non vengono mai aggiunti.
I convertitori dichiarati con un attributo si applicano comunque, perché appartengono al tipo.
Quando un convertitore rifiuta un valore, il caricamento fallisce con una TomlException. Il messaggio
indica riga e colonna e termina con il motivo del convertitore, per esempio:
(1,10) : error : Exception while trying to convert TOML value to type '...EnumValueSpec`1[...]' using converter '...'.Reason: (1,10) : error : 'not-a-member' is not a valid ItemRarityType value or random_of spec.I motivi citati in questa pagina sono quell’ultima parte.
Riepilogo
Section titled “Riepilogo”| Tipo | Forma TOML | Esempio | Convertitore | Applicato tramite |
|---|---|---|---|---|
Serial |
intero senza virgolette o numero tra virgolette | item_id = 0x0FEF |
SerialTomlConverter |
registrazione |
Point2D |
"(x, y)" tra virgolette |
size = "(7168, 4096)" |
Point2DTomlConverter |
registrazione |
Point3D |
"(x, y, z)" tra virgolette |
location = "(3503, 2574, 14)" |
Point3DTomlConverter |
registrazione |
Rectangle2D |
"(x1, y1)..(x2, y2)" tra virgolette |
bounds = "(44, 65)..(186, 159)" |
Rectangle2DTomlConverter |
registrazione |
RegionAreaContent |
stringa di rettangolo oppure tabella con bounds, z1, z2 |
{ bounds = "(1, 2)..(3, 4)", z1 = 0 } |
RegionAreaContentTomlConverter |
attributo sul tipo |
HueSpec |
intero senza virgolette oppure tinta o intervallo tra virgolette | hue = "0x047E-0x04B0" |
HueSpecTomlConverter |
registrazione |
EnumValueSpec<TEnum> |
nome del membro o specifica random_of tra virgolette |
rarity = "random_of:rare,epic" |
EnumValueSpecTomlConverterFactory |
registrazione |
RangeValueSpec<T> |
numero senza virgolette oppure "min-max" tra virgolette |
amount = "5-10" |
RangeValueSpecTomlConverterFactory |
registrazione |
decimal |
intero o float senza virgolette | weight = 0.02, weight = 7 |
integrato | built in |
DiceSpec |
intero senza virgolette oppure espressione di dadi tra virgolette | strength = "1d25+95" |
DiceSpecTomlConverter |
registrazione |
| Qualsiasi enum | il suo nome snake_case; flag uniti da | |
visibility = "game_master", mode = "standalone" |
EnumTomlConverterFactory |
integrato |
I convertitori del primo gruppo e quello enum sono in
Moongate.Core/Serialization/Toml; RegionAreaContentTomlConverter è in
Moongate.Server.Ultima/Serialization/Toml.
Serial
Section titled “Serial”Un Serial è un’identità UO: un seriale di entità, un id grafico o un numero cliloc.
Forme accettate:
item_id = 0x0FEF # any TOML integer: decimal, hex, octal or binaryitem_id = 4079 # the same valueitem_id = "0x0FEF" # quoted hexitem_id = "4079" # quoted decimalNel testo tra virgolette, il prefisso 0x sceglie la base: "40000001" è quaranta milioni, non
il primo seriale di oggetto "0x40000001". Il testo tra virgolette non accetta segni; gli spazi attorno
vengono ignorati.
Forma scritta: sempre un intero decimale senza virgolette. 0x0FEF viene riscritto come 4079.
Errori:
| Valore | Risultato |
|---|---|
item_id = "not-a-serial" |
'not-a-serial' is not a valid serial. |
item_id = 1.5 |
Expected token Integer but was Float. |
item_id = -1 |
nessun errore: un intero senza virgolette non viene verificato nell’intervallo e viene ridotto a 32 bit, quindi -1 diventa 0xFFFFFFFF |
Usato da: cliloc in starting_cities.toml e item_id nei template di oggetto.
Point2D e Point3D
Section titled “Point2D e Point3D”Un punto viene scritto come il testo tra virgolette prodotto dal suo ToString(), la stessa forma che il
server stampa nei log e nei comandi, quindi un valore copiato da lì può essere incollato in un file.
Forme accettate:
size = "(7168, 4096)" # Point2D: (x, y)location = "(1495, 1629, 10)" # Point3D: (x, y, z)location = "( -5 , 7 , -20 )" # spaces are ignored; negative numbers are allowedLe parentesi sono obbligatorie e ogni coordinata è un intero. Entrambi i convertitori usano la cultura invariabile, quindi un file viene letto allo stesso modo su ogni macchina e un numero negativo viene sempre scritto con un meno ASCII.
Forma scritta: "(x, y)" o "(x, y, z)", con uno spazio dopo ogni virgola.
Errori (mostrati per Point2D; Point3D produce gli stessi messaggi con (x, y, z)):
| Valore | Risultato |
|---|---|
position = 5 |
Expected a "(x, y)" string for a Point2D. |
position = "not-a-point" |
'not-a-point' is not a valid Point2D, expected "(x, y)". |
position = "(1, 2, 3)" |
'(1, 2, 3)' is not a valid Point2D, expected "(x, y)". |
location = "(1, 2)" |
'(1, 2)' is not a valid Point3D, expected "(x, y, z)". |
Usato da: size in maps.toml (Point2D); location in starting_cities.toml,
go_location ed entrance nei file di regione (Point3D).
Rectangle2D
Section titled “Rectangle2D”Un rettangolo consiste di due angoli uniti da ... Il primo è incluso e il
secondo è escluso: il secondo punto è un estremo, non larghezza e altezza.
Forme accettate:
bounds = "(44, 65)..(186, 159)" # X 44 to 185, Y 65 to 158bounds = " (44, 65) .. (186, 159) " # spaces are ignoredbounds = "(44, 65)+(142, 94)" # legacy corner-plus-size form, the same rectangleForma scritta: sempre la forma con gli angoli, "(44, 65)..(186, 159)". Un file che usa la
forma legacy viene riscritto nella forma con gli angoli la prossima volta che il codice lo salva.
Errori:
| Valore | Risultato |
|---|---|
bounds = 5 |
Expected a "(x1, y1)..(x2, y2)" string for a Rectangle2D. |
bounds = "(44, 65)" |
'(44, 65)' is not a valid Rectangle2D, expected "(x1, y1)..(x2, y2)". |
bounds = "(44, 65)-(142, 94)" |
lo stesso messaggio, indicando (44, 65)-(142, 94) |
bounds = "44 65 142 94" |
lo stesso messaggio, indicando 44 65 142 94 |
Il testo che contiene .. ma i cui angoli non possono essere analizzati è sempre un errore; non viene
mai tentato come forma legacy.
Usato da: bounds in containers.toml e dal convertitore delle aree di regione qui sotto.
Aree delle regioni
Section titled “Aree delle regioni”Ogni voce di areas in un file di regione è un RegionAreaContent. Il tipo contiene
[TomlConverter(typeof(RegionAreaContentTomlConverter))], quindi non richiede
registrazione e legge il proprio rettangolo con un suo Rectangle2DTomlConverter.
L’inizio è incluso e la fine è esclusa su ogni asse, anche l’altezza.
Forme accettate, che possono essere mescolate in un array:
areas = [ "(1330, 1991)..(1343, 2004)", # every height { bounds = "(1416, 1498)..(1740, 1777)", z1 = -10, z2 = 128 }, { bounds = "(1, 2)..(3, 4)", z1 = 0 }, # either limit may be left out { x1 = 10, y1 = 20, x2 = 30, y2 = 40, z2 = -80 }, # legacy corners, read only]Una stringa accetta ogni forma di Rectangle2D, compresa la forma legacy +.
Dentro una tabella, le chiavi sono bounds, x1, y1, x2, y2, z1 e z2; z1
è incluso e z2 è escluso.
Forma scritta: una stringa quando l’area non ha limite di altezza, altrimenti una tabella inline
con bounds e i limiti presenti. Le chiavi legacy x1/y1/x2/y2 non vengono mai
scritte.
Errori:
| Valore | Risultato |
|---|---|
42 |
Expected a region area string or a table with bounds and optional z1/z2. |
"(1, 2)..bad" |
il messaggio di Rectangle2D |
{ bounds = "(1, 2)..(3, 4)", x1 = 1 } |
Use bounds or x1/y1/x2/y2 for a region area, not both. |
{ z1 = 0 } oppure { x1 = 1, y1 = 2, x2 = 3 } |
A region area table requires bounds or all of x1/y1/x2/y2. |
{ bounds = "(1, 2)..(3, 4)", z1 = 1.5 } |
Region area coordinates must be integers. |
{ bounds = "(1, 2)..(3, 4)", z2 = 2147483648 } |
Region area coordinates must fit in a 32-bit integer. |
{ bounds = "(1, 2)..(3, 4)", top = 5 } |
Unknown region area field 'top'. |
Usato da: areas in data/regions/<map>.toml. Vedi
Aree delle regioni.
HueSpec
Section titled “HueSpec”Un HueSpec è una singola tinta oppure un intervallo da cui scegliere una nuova tinta ogni volta che viene
risolto, per esempio una volta per oggetto generato. Ogni tinta è tra 0 e 0xFFFF.
Forme accettate:
hue = 1150 # bare integer, decimalhue = 0x047E # bare integer, hex: the same huehue = "0x047E" # a quoted hue, decimal or hexhue = "1150-1200" # a range, both bounds includedhue = "0x047E-0x04B0" # the same range in hexhue = "hue(1150:1200)" # the same range, alternative formIn un intervallo, il primo estremo non deve essere maggiore del secondo. Un intervallo con estremi uguali viene accettato ed è comunque un intervallo.
Forma scritta: una tinta singola come intero decimale senza virgolette (hue = 1150), un intervallo come
intervallo esadecimale tra virgolette (hue = "0x047E-0x04B0").
Errori:
| Valore | Risultato |
|---|---|
hue = 70000 |
70000 is not a hue; a hue must be between 0 and 0xFFFF. |
hue = -1 |
-1 is not a hue; a hue must be between 0 and 0xFFFF. |
hue = "red" |
'red' is not a hue or a hue range. |
hue = "1200-1150" |
'1200-1150' is not a hue or a hue range. |
hue = 1.5 |
Expected a hue number or a hue range string. |
Usato da: skin_hues e hair_hues in races.toml (array di HueSpec, come
["0x03EA-0x0422"]) e hue nei template di oggetto.
EnumValueSpec
Section titled “EnumValueSpec”Un EnumValueSpec<TEnum> è un campo enum fisso oppure scelto casualmente
ogni volta che viene risolto. Una registrazione di EnumValueSpecTomlConverterFactory
copre ogni enum: la factory costruisce un EnumValueSpecTomlConverter<TEnum> per
ogni EnumValueSpec<TEnum> che incontra.
Forme accettate, sempre una stringa tra virgolette:
rarity = "common" # always Commonrarity = "random_of" # any member, picked on each resolverarity = "random_of:rare,epic,legendary" # one of these members, picked on each resolveI nomi dei membri ignorano maiuscole, minuscole e trattini bassi: "Epic" e "epic" sono equivalenti, così
come "north_east", "NorthEast" e "northeast". Gli spazi attorno ai nomi nell’elenco
vengono ignorati. Sono accettati solo nomi, mai numeri.
Forma scritta: snake_case minuscolo, "rare", "north_east" oppure
"random_of:rare,epic"; viene sempre riletto correttamente.
Errori:
| Valore | Risultato |
|---|---|
rarity = "not-a-member" |
'not-a-member' is not a valid ItemRarityType value or random_of spec. |
rarity = "random_of:" |
lo stesso messaggio, indicando random_of: |
rarity = "3" |
'3' is not a valid ItemRarityType value or random_of spec. |
rarity = 1 |
Expected token String but was Integer. |
Usato da: rarity nei template di oggetto. Vedi
Campi risolti casualmente.
RangeValueSpec
Section titled “RangeValueSpec”Un RangeValueSpec<T> è un campo numerico fisso oppure scelto da un intervallo
ogni volta che viene risolto. T è qualsiasi tipo numerico .NET. Una registrazione di
RangeValueSpecTomlConverterFactory copre ogni tipo numerico.
Forme accettate:
amount = 5 # bare integer: fixedamount = 2.5 # bare float: fixedamount = "5" # quoted number: fixedamount = "5-10" # quoted range: a fresh value from 5 to 10 on each resolveamount = "-10--5" # negative bounds: a leading minus is a sign, not the separatorEntrambi gli estremi di un intervallo sono inclusi e il primo non deve essere maggiore del
secondo. Un intervallo sceglie il minimo più un numero intero, quindi un intervallo double
"0.5-2.5" produce 0.5, 1.5 o 2.5. I numeri tra virgolette usano la cultura invariabile.
Forma scritta: un valore intero fisso come intero senza virgolette (amount = 5), una frazione fissa
come float senza virgolette (amount = 2.5), un intervallo come testo tra virgolette (amount = "5-10").
Errori:
| Valore | Risultato |
|---|---|
amount = "not-a-range" |
'not-a-range' is not a valid Int32 value or range. |
amount = "10-5" |
'10-5' is not a valid Int32 value or range. |
amount = true |
Expected a number or a range for Int32. |
small = 300 su un RangeValueSpec<byte> |
Arithmetic operation resulted in an overflow. |
amount = 1.7 su un RangeValueSpec<int> |
nessun errore: un float senza virgolette viene troncato a 1 |
Usato da: amount nei template di bottino. Vedi
Campi risolti in un nuovo numero.
DiceSpec
Section titled “DiceSpec”Un DiceSpec è un campo numerico tirato con la notazione dei dadi oppure una costante. I template dei
mobile lo usano per statistiche, abilità, danni, resistenze, karma, fama e oro.
Forme accettate:
armor = 20 # bare integer: constantkarma = -2500 # negative constants are allowedkarma = "-2500" # the same, quotedstrength = "1d25+95" # one 25-sided die plus 95: 96 to 120damage = "3d4+2" # three 4-sided dice plus 2: 5 to 14hits = "4d6k3" # four 6-sided dice, keep the highest threemana = "(2d6+1)*10" # parentheses, *, /NdM tira N dadi a M facce; +, -, *, /, parentesi e k (conserva i
più alti) li combinano. Un intervallo uniforme da a a b è un dado,
1d(b-a+1)+(a-1). "96-120" non è un intervallo: è 96 meno 120, una costante -24.
Forma scritta: una costante come intero senza virgolette, un’espressione come il testo tra virgolette da cui è stata letta.
Errori:
| Valore | Risultato |
|---|---|
strength = "2d" |
'2d' is not a number or a dice expression. |
strength = true |
Expected a number or a dice expression. |
Usato da: MobileTemplate. Vedi Le strutture dei template.
Ogni enum viene scritto come il proprio nome snake_case e riletto da esso, tramite
EnumTomlConverterFactory, che TomlUtils include sempre nelle opzioni
predefinite. Le regole risiedono in EnumNameUtils, condiviso da EnumValueSpec.
minimum_account_type = "game_master" # AccountType.GameMastermap = "ter_mur" # MapType.TerMurmusic = "mountn_a" # MusicType.Mountn_a- La lettura ignora maiuscole, minuscole e trattini bassi:
"game_master","GameMaster"e"gamemaster"sono equivalenti e"termur"leggeMapType.TerMur. - La scrittura usa il nome snake_case minuscolo, sempre rileggibile.
- I numeri non sono mai accettati, senza virgolette o in una stringa: scrivi il nome. I dati indicizzati dai
numeri del client, come
skills.toml, indicano invece il membro (id = "alchemy"èSkillType.Alchemy, valore 0).
Flag. Un enum [Flags] scrive un valore con nome proprio come quel nome e
ogni altra combinazione come nomi uniti da |; la lettura combina i nomi:
mode = "standalone" # ServerMode.Login | ServerMode.Game has its own namemode = "login|game" # reads the same valueflags = "impassable|surface" # TileFlagType.Impassable | TileFlagType.SurfaceGli spazi attorno ai nomi vengono ignorati. Una combinazione viene divisa nelle parti nominate più
grandi, quindi DirectionType.SouthEast | DirectionType.Running viene scritto come
"south_east|running" (le parti seguono l’ordine dei valori), non come singoli bit. Zero viene scritto come il nome del membro
zero quando ne esiste uno ("none") e altrimenti come stringa vuota; una stringa vuota
viene letta come zero solo per un enum di flag. Un enum semplice rifiuta |.
Errori:
| Valore | Risultato |
|---|---|
minimum_account_type = "gm" |
'gm' is not a AccountType; use one of regular, game_master, administrator. |
minimum_account_type = "1" |
lo stesso messaggio, indicando 1 |
minimum_account_type = 1 |
Expected a AccountType name as a string, such as "regular". |
minimum_account_type = true |
Expected a AccountType name as a string, such as "regular". |
Un valore viene comunque controllato dal codice che lo carica: mode = "none" viene letto, poi la
configurazione rifiuta ServerMode.None.
Usato da: mode e realm_directory.minimum_account_type in moongate.toml,
visibility nei template di oggetto e le chiavi enum dei file di dati dello shard,
come map, season, music e skill.
Scrivere un convertitore
Section titled “Scrivere un convertitore”Per un tuo tipo, deriva da TomlConverter<T> per un tipo chiuso oppure da
TomlConverterFactory quando il tipo è generico. Poi registra un’istanza con
TomlUtils.AddTomlConverter all’avvio, di solito da Register di un plugin, oppure indica
il convertitore in un attributo [TomlConverter(typeof(...))] sulla proprietà o sul tipo.
Alcune regole mantengono coerenti i convertitori:
- In
Read, verificareader.TokenTypee generareader.CreateException(...)per un valore che non accetti; l’eccezione riceve riga e colonna. Indica il testo errato nel messaggio. - In
Write, generaTomlExceptionper un valore che non ha una forma TOML. - Formatta e analizza i numeri con
CultureInfo.InvariantCulture. - Scrivi la forma che una persona digiterebbe e fai sì che
Readaccetti ciò che produceWrite.
Esempio svolto: Serial
Section titled “Esempio svolto: Serial”SerialTomlConverter legge un intero senza virgolette, analizzato nativamente da TOML, oppure lo stesso
valore tra virgolette, e scrive sempre un intero senza virgolette:
public sealed class SerialTomlConverter : TomlConverter<Serial>{ public override Serial Read(TomlReader reader) { if (reader.TokenType == TomlTokenType.String) { var text = reader.GetString();
if (!Serial.TryParse(text, out var parsed)) { throw reader.CreateException($"'{text}' is not a valid serial."); }
return parsed; }
return new((uint)reader.GetInt64()); }
public override void Write(TomlWriter writer, Serial value) { writer.WriteIntegerValue(value.Value); }}Esempio svolto: Point2D e Point3D
Section titled “Esempio svolto: Point2D e Point3D”Un tipo con una propria forma testuale può riusarla. Point2DTomlConverter accetta solo una
stringa, la analizza con Point2D.TryParse e scrive ToString, entrambi con la
cultura invariabile:
public sealed class Point2DTomlConverter : TomlConverter<Point2D>{ public override Point2D Read(TomlReader reader) { if (reader.TokenType != TomlTokenType.String) { throw reader.CreateException("Expected a \"(x, y)\" string for a Point2D."); }
var text = reader.GetString();
if (!Point2D.TryParse(text, CultureInfo.InvariantCulture, out var parsed)) { throw reader.CreateException($"'{text}' is not a valid Point2D, expected \"(x, y)\"."); }
return parsed; }
public override void Write(TomlWriter writer, Point2D value) { writer.WriteStringValue(value.ToString(null, CultureInfo.InvariantCulture)); }}Point3DTomlConverter è identico con tre coordinate.
Tipi generici e tabelle
Section titled “Tipi generici e tabelle”EnumValueSpecTomlConverterFactory mostra la struttura della factory: CanConvert riconosce ogni
EnumValueSpec<> chiuso e CreateConverter costruisce il corrispondente
EnumValueSpecTomlConverter<TEnum> tramite reflection, quindi una registrazione copre ogni
enum.
RegionAreaContentTomlConverter mostra un convertitore che legge sia una stringa sia una
tabella inline: scorre i nomi delle proprietà della tabella fino a EndTable e scrive con
WriteStartInlineTable, WritePropertyName e WriteEndInlineTable.
Vedi anche
Section titled “Vedi anche”- File di dati dello shard: i file sotto
data/e i loro campi. - Caricamento dei template TOML: template di oggetti e bottino e contratto del caricatore.
- Configurazione del server:
moongate.toml.
Creato con ❤️ da Tom
