Roll and draw with Nfty.Ticket¶
Nfty.Ticket decides what an asset is: it reads a Menu, rolls tickets, and tells you what to draw.
It has no dependencies at all, so a server or game engine takes Nfty.Ticket.dll and nothing else.
Everything on this page comes from samples/Nfty.Ticket.Sample, which references Nfty.Ticket only:
building it is the proof that the roll needs nothing more.
Before you start: have a Menu. With the demo:
Reference the library¶
Some namespaces begin with Nfty.Core because those types began in the engine and kept their names
when the roll moved out; they still ship in Nfty.Ticket.dll:
using Nfty.Core.Generation; // SplitMix64Rng, SeedHash (they live in the Ticket assembly)
using Nfty.Core.Imaging; // ColorConvert, ColorSpec, RgbColor
using Nfty.Core.Model; // LayerKind
using Nfty.Ticket; // MenuArchive, Ticketer, AssetTicket, Plating
Open and prepare the Menu¶
MenuArchive.Open reads the manifest and decodes no image. Ticketer.Prepare resolves the draw order
and weight totals once. Keep the result and reuse it for every roll; it is read-only, so several
threads may roll from one.
using var menu = MenuArchive.Open(args[0]); // reads the manifest; decodes no image
PreparedMenu prepared = Ticketer.Prepare(menu.Book); // once; reuse it for every roll
Roll¶
var rng = new SplitMix64Rng(SeedHash.ToUlong(seed)); // same seed, same drops, any machine
AssetTicket? ticket = null;
for (int attempt = 0; attempt < 1000 && ticket is null; attempt++)
ticket = Ticketer.TryRoll(prepared, rng); // null = a rule rejected this roll
if (ticket is null)
throw new InvalidOperationException("The rules rejected every roll.");
string wire = ticket.ToWire(); // store this, or send it
- The seed goes through
SeedHash.ToUlonginto aSplitMix64Rng. The same Menu and seed give the same tickets, in the same order, on any machine. TryRollreturnsnullwhen the Recipe's rules reject the roll. That is not an error: roll again from the same generator, with a bound. nfty's own generator allows 10,000 attempts per asset.- One generator is one stream. Give each worker thread its own
IRng. ToWire()is one line of text, a few hundred bytes. Store it, send it, put it in a token's metadata.
Draw a ticket¶
AssetTicket back = AssetTicket.Parse(wire); // recomputes and checks the DNA
foreach (Draw draw in Plating.Plate(menu, back)) // bottom-first: paint in this order
{
byte[] png = menu.ArtBytes(draw.ImageKey); // atlas mode: the whole atlas PNG
MenuCell cell = draw.Source; // crop this rectangle out of it
if (draw.Kind == LayerKind.Custom)
{
// Full-color art: composite the cropped cell as it is.
Console.WriteLine($" custom {cell.Key} at ({cell.X},{cell.Y}) {cell.Width}x{cell.Height}");
}
else
{
// Gray value-map: every pixel becomes HsvToRgb(hue, sat, gray / 255), alpha untouched.
// (draw.Model says whether the layer is HSV or HSL; this sample only shows the idea.)
RgbColor mid = ColorConvert.HsvToRgb(draw.Hue, draw.Sat, 128 / 255.0);
Console.WriteLine($" tinted {cell.Key} hue {draw.Hue:0.#} sat {draw.Sat:0.##}"
+ $" -> mid-gray becomes {ColorSpec.Format(mid)}");
}
_ = png; // a real renderer decodes this with its own PNG library and crops `cell`
}
AssetTicket.Parserecomputes the DNA and throwsFormatExceptionif it does not match, or if the wire is another version.Plating.Platereturns theDrawlist bottom first, in the Recipe's layer order. A layer left out by its absent chance is not in the list. It takes each layer's kind and color model from the Menu, and refuses a ticket whose quantize steps differ from the Menu's.MenuArchive.ArtBytes(key)gives PNG bytes. In atlas mode that is the whole atlas anddraw.Sourceis the rectangle to crop; in loose mode the rectangle is the whole image, so one cropping routine serves both. Decoding is yours: use whatever PNG library your engine has.- Custom layers composite as they are. Dynamic and Static layers are gray value-maps: each
pixel becomes
HsvToRgb(hue, sat, gray / 255)with alpha untouched (HslToRgbwhendraw.Modelsays HSL).ColorConverthas both. MenuArchive.Depth(key)gives a variant's depth for 3D, ornull.
Run the sample¶
It prints the seed's ticket in wire form and each layer's draw.
See also¶
- Ticket and Menu API and the Nfty.Ticket API pages
- Cook and render with Nfty.Core, to skip writing your own renderer