switched to iv.base64; added protection from invalid images
[xreader.git] / eworld.d
blobb21b55c980cfc2c0ad55dfb2a49dcdfeec2e172e
1 /* based on txtelite.c v1.5
2 * Textual version of Elite trading
3 * Converted by Ian Bell from 6502 Elite sources.
4 * Original 6502 Elite by Ian Bell & David Braben.
5 * Edited by Richard Carlsson to compile cleanly under gcc
6 * and to fix a bug in the goat soup algorithm.
8 * Ported to D by Ketmar // Invisible Vector
9 */
10 /* ----------------------------------------------------------------------------
11 * The nature of basic mechanisms used to generate the Elite socio-economic
12 * universe are now widely known. A competant games programmer should be able to
13 * produce equivalent functionality. A competant hacker should be able to lift
14 * the exact system from the object code base of official conversions.
16 * This file may be regarded as defining the Classic Elite universe.
18 * It contains a C implementation of the precise 6502 algorithms used in the
19 * original BBC Micro version of Acornsoft Elite together with a parsed textual
20 * command testbed.
22 * Note that this is not the universe of David Braben's 'Frontier' series.
24 * ICGB 13/10/99
25 * iancgbell@email.com
26 * www.ibell.co.uk
27 * ---------------------------------------------------------------------------- */
28 module eworld;
30 import iv.cmdcon;
33 // ////////////////////////////////////////////////////////////////////////// //
34 public struct Galaxy {
35 // some numbers for galaxy 1
36 enum Lave = 7;
37 enum Zaonce = 129;
38 enum Diso = 147;
39 enum Ried = 46;
41 enum PlanetsInGalaxy = 256;
43 enum Goods : ubyte {
44 Food = 0,
45 Textiles = 1,
46 Radioactives = 2,
47 Slaves = 3,
48 LiquorWines = 4,
49 Luxuries = 5,
50 Narcotics = 6,
51 Computers = 7,
52 Machinery = 8,
53 Alloys = 9,
54 Firearms = 10,
55 Furs = 11,
56 Minerals = 12,
57 Gold = 13,
58 Platinum = 14,
59 GemStones = 15,
60 AlienItems = 16,
63 enum Unit { Tonne, Kg, G }
65 static struct TradeGood {
66 string name;
67 string unitName; // unit name
68 Unit unit;
69 bool alien; // alien items?
70 bool forbidden; // narcotics or alike?
73 static TradeGood good(T : ulong) (T idx) {
74 if (idx > Goods.max) assert(0, "invalid trade good index");
75 TradeGood res;
76 res.name = commodities[idx].name;
77 res.unitName = unitnames[commodities[idx].units];
78 res.unit = cast(Unit)commodities[idx].units;
79 res.alien = (idx == Galaxy.Goods.AlienItems);
80 res.forbidden = (idx == Galaxy.Goods.Slaves || idx == Galaxy.Goods.Narcotics);
81 return res;
84 enum Gov : ubyte {
85 Anarchy,
86 Feudal,
87 MultiGov,
88 Dictatorship,
89 Communist,
90 Confederacy,
91 Democracy,
92 CorporateState,
95 enum Economy : ubyte {
96 RichInd,
97 AverageInd,
98 PoorInd,
99 MainlyInd,
100 MainlyAgri,
101 RichAgri,
102 AverageAgri,
103 PoorAgri,
106 enum MaxTechLevel = 16;
108 string economyName(T : ulong) (T v) const pure nothrow @safe @nogc { return (v >= 0 && v <= Economy.max ? econnames[economy] : "Unknown"); }
109 string govName(T : ulong) (T v) const pure nothrow @safe @nogc { return (v >= 0 && v <= Gov.max ? govnames[govtype] : "Unknown"); }
111 static struct Planet {
112 ubyte number; // in galaxy
113 ubyte x, y;
114 Economy economy; // 0..7
115 Gov govtype; // 0..7
116 ubyte techlev; // 0..16
117 ubyte population;
118 ushort productivity;
119 ushort radius;
120 string name;
122 ushort lastFluct; // fluct used in last genMarket call
124 private FastPRng goatsoupseed;
125 private string desc;
127 // market; please, generate it before using! ;-)
128 ushort[Goods.max+1] quantity;
129 ushort[Goods.max+1] price;
131 string economyName () const pure nothrow @safe @nogc { return econnames[economy]; }
132 string govName () const pure nothrow @safe @nogc { return govnames[govtype]; }
134 /* Prices and availabilities are influenced by the planet's economy type
135 * (0-7) and a random "fluctuation" byte that was kept within the saved
136 * commander position to keep the market prices constant over gamesaves.
137 * Availabilities must be saved with the game since the player alters them
138 * by buying (and selling(?))
140 * Almost all operations are one byte only and overflow "errors" are
141 * extremely frequent and exploited.
143 * Trade Item prices are held internally in a single byte=true value/4.
144 * The decimal point in prices is introduced only when printing them.
145 * Internally, all prices are integers.
146 * The player's cash is held in four bytes.
148 void genMarket (ushort fluct) {
149 lastFluct = fluct;
150 foreach (uint i; 0..Galaxy.Goods.max+1) {
151 int product = economy*commodities[i].gradient;
152 int changing = fluct&commodities[i].maskbyte;
153 int q = commodities[i].basequant+changing-product;
154 q = q&0xFF;
155 if (q&0x80) q = 0; // clip to positive 8-bit
156 quantity[i] = cast(ushort)(q&0x3F); // mask to 6 bits
157 q = commodities[i].baseprice+changing+product;
158 q = q&0xFF;
159 price[i] = cast(ushort)(q*4);
161 quantity[Galaxy.Goods.AlienItems] = 0; // override to force nonavailability
164 // seperation between two planets (4*sqrt(X*X+Y*Y/4))
165 int distanceTo() (in auto ref Planet b) const nothrow @safe @nogc {
166 import std.math : floor, sqrt;
167 return cast(int)floor((4.0*sqrt((this.x-b.x)*(this.x-b.x)+(this.y-b.y)*(this.y-b.y)/4.0))+0.5);
170 // goat soup
171 string description () {
172 if (desc.length == 0) {
173 FastPRng prng = goatsoupseed;
174 desc = goatSoup(prng, "\x8F is \x97.", this);
176 return desc;
180 Planet[PlanetsInGalaxy] planets;
181 private GalaxySeed galseed;
182 bool initialized;
183 Planet currPlanet; // current planet
184 private ulong curGalSeed;
186 this (ubyte galnum) { build(galnum); }
188 @property ulong galSeed () const pure nothrow @nogc { return curGalSeed; }
189 @property void galSeed (ulong v) pure nothrow @nogc { curGalSeed = v; }
191 // generate galaxy
192 void build (ubyte galnum) {
193 galseed = GalaxySeed.galaxy(galnum);
194 curGalSeed = galseed.seed;
195 foreach (immutable idx, ref Planet ps; planets) { makeSystem(ps); ps.number = cast(ubyte)idx; }
196 initialized = true;
197 // generate data for Lave (ignore the fact that we can be in another galaxy)
198 currPlanet = planets[Lave];
199 currPlanet.genMarket(0);
202 // regenerate galaxy
203 void rebuild () {
204 galseed.seed = curGalSeed;
205 foreach (immutable idx, ref Planet ps; planets) { makeSystem(ps); ps.number = cast(ubyte)idx; }
206 initialized = true;
207 currPlanet = planets[currPlanet.number];
208 currPlanet.genMarket(randbyte());
211 void hyperjump () {
212 galseed.seed = curGalSeed;
213 galseed.nextgalaxy();
214 curGalSeed = galseed.seed;
215 foreach (immutable idx, ref Planet ps; planets) { makeSystem(ps); ps.number = cast(ubyte)idx; }
216 initialized = true;
217 // generate data for Lave (ignore the fact that we can be in another galaxy)
218 currPlanet = planets[currPlanet.number];
219 currPlanet.genMarket(randbyte());
222 // move to system i
223 bool jump (int i) {
224 if (i >= 0 && i < planets.length) {
225 currPlanet = planets[i];
226 currPlanet.genMarket(randbyte());
227 return true;
228 } else {
229 return false;
233 // return id of the planet whose name matches passed string closest to currentplanet; if none, return current planet
234 int find (const(char)[] s) {
235 int d = int.max;
236 int res = currPlanet.number;
237 foreach (immutable idx, const ref p; planets) {
238 if (startsWithCI(p.name, s)) {
239 auto nd = currPlanet.distanceTo(p);
240 if (nd < d) {
241 d = nd;
242 res = cast(int)idx;
246 return res;
249 static startsWithCI (const(char)[] s, const(char)[] pat) nothrow @trusted @nogc {
250 import std.ascii : toUpper, toLower;
251 if (pat.length == 0 || pat.length > s.length) return false;
252 foreach (immutable idx, char ch; pat) {
253 if (toLower(s.ptr[idx]) != toLower(ch)) return false;
255 return true;
258 private:
259 // generate system info from seed
260 void makeSystem (out Planet thissys) {
261 alias s = galseed;
262 //Planet thissys;
263 ushort pair1, pair2, pair3, pair4;
264 ushort longnameflag = s.w0&64;
265 const(char)* pairs1 = pairs.ptr+24; // start of pairs used by this routine
267 thissys.x = s.w1>>8;
268 thissys.y = s.w0>>8;
270 thissys.govtype = cast(Galaxy.Gov)((s.w1>>3)&7); // bits 3, 4 & 5 of w1
272 ubyte ec = (s.w0>>8)&7; // bits 8, 9 & A of w0
273 if (thissys.govtype <= 1) ec |= 2;
274 thissys.economy = cast(Galaxy.Economy)ec;
276 thissys.techlev = cast(ubyte)(((s.w1>>8)&3)+(ec^7));
277 thissys.techlev += thissys.govtype>>1;
278 if (thissys.govtype&1) thissys.techlev += 1; // simulation of 6502's LSR then ADC
280 thissys.population = cast(ubyte)(4*thissys.techlev+thissys.economy);
281 thissys.population += thissys.govtype+1;
283 thissys.productivity = cast(ushort)(((thissys.economy^7)+3)*(thissys.govtype+4));
284 thissys.productivity *= thissys.population*8;
286 thissys.radius = cast(ushort)(256*(((s.w2>>8)&15)+11)+thissys.x);
288 thissys.goatsoupseed.a = s.w1&0xFF;
289 thissys.goatsoupseed.b = s.w1>>8;
290 thissys.goatsoupseed.c = s.w2&0xFF;
291 thissys.goatsoupseed.d = s.w2>>8;
293 pair1 = 2*((s.w2>>8)&31); s.tweak;
294 pair2 = 2*((s.w2>>8)&31); s.tweak;
295 pair3 = 2*((s.w2>>8)&31); s.tweak;
296 pair4 = 2*((s.w2>>8)&31); s.tweak;
297 // always four iterations of random number
299 char[12] namebuf;
300 uint nbpos;
302 void putCh (char ch) nothrow @trusted @nogc {
303 import std.ascii : toUpper, toLower;
304 if (ch == '.') return;
305 if (nbpos == 0) ch = toUpper(ch); else ch = toLower(ch);
306 namebuf.ptr[nbpos++] = ch;
309 putCh(pairs1[pair1]);
310 putCh(pairs1[pair1+1]);
311 putCh(pairs1[pair2]);
312 putCh(pairs1[pair2+1]);
313 putCh(pairs1[pair3]);
314 putCh(pairs1[pair3+1]);
316 // bit 6 of ORIGINAL w0 flags a four-pair name
317 if (longnameflag) {
318 putCh(pairs1[pair4]);
319 putCh(pairs1[pair4+1]);
321 thissys.name = namebuf[0..nbpos].idup;
324 private:
325 // prng for planet price fluctuations
326 static uint lastrand = 0x29a;
327 static void mysrand (uint seed) { lastrand = seed-1; }
328 static int myrand () {
329 int r;
330 // As supplied by D McDonnell from SAS Insititute C
331 r = (((((((((((lastrand<<3)-lastrand)<<3)+lastrand)<<1)+lastrand)<<4)-lastrand)<<1)-lastrand)+0xe60)&0x7fffffff;
332 lastrand = r-1;
333 return r;
335 static ubyte randbyte () { return (myrand()&0xFF); }
339 // ////////////////////////////////////////////////////////////////////////// //
340 private:
342 // four byte random number used for planet description
343 struct FastPRng {
344 ubyte a, b, c, d;
346 pure nothrow @safe @nogc:
347 int next () {
348 int a, x;
349 x = (this.a*2)&0xFF;
350 a = x+this.c;
351 if (this.a > 127) ++a;
352 this.a = a&0xFF;
353 this.c = cast(ubyte)x;
354 a /= 256; // a = any carry left from above
355 x = this.b;
356 a = (a+x+this.d)&0xFF;
357 this.b = cast(ubyte)a;
358 this.d = cast(ubyte)x;
359 return a;
364 // six byte random number used as seed for planets
365 // initied with base seed for galaxy 1
366 struct GalaxySeed {
367 ushort w0 = 0x5A4Au;
368 ushort w1 = 0x0248u;
369 ushort w2 = 0xB753u;
371 pure nothrow @safe @nogc:
372 // create seed for the corresponding galaxy
373 this (ubyte galnum) {
374 galnum &= 0x07;
375 foreach (immutable _; 0..galnum) nextgalaxy();
378 static GalaxySeed galaxy (ubyte galnum) { return GalaxySeed(galnum); }
380 @property ulong seed () const {
381 return
382 ((cast(ulong)w0)<<2*16)|
383 ((cast(ulong)w1)<<1*16)|
384 (cast(ulong)w2);
387 @property void seed (ulong v) {
388 w0 = cast(ushort)(v>>(2*16));
389 w1 = cast(ushort)(v>>(1*16));
390 w2 = cast(ushort)(v);
393 private void tweak () {
394 ushort temp = cast(ushort)(cast(ushort)(w0+w1)+w2); // 2 byte aritmetic
395 w0 = w1;
396 w1 = w2;
397 w2 = temp;
400 // apply to base seed; once for galaxy 2, etc.
401 void nextgalaxy () {
402 // rotate 8 bit number leftwards
403 static ushort rotatel() (ushort x) { ushort temp = x&128; return (2*(x&127))+(temp>>7); }
404 static ushort twist() (ushort x) { return cast(ushort)((256*rotatel(x>>8))+rotatel(x&255)); }
405 w0 = twist(w0); // twice for galaxy 3, etc.
406 w1 = twist(w1); // Eighth application gives galaxy 1 again
407 w2 = twist(w2);
412 // ////////////////////////////////////////////////////////////////////////// //
413 struct TradeGoodIntr {
414 // In 6502 version these were:
415 ushort baseprice; // one byte
416 short gradient; // five bits plus sign
417 ushort basequant; // one byte
418 ushort maskbyte; // one byte
419 ushort units; // two bits
420 string name; // longest="Radioactives"
423 static immutable string[8] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship", "Communist", "Confederacy", "Democracy", "Corporate State"];
424 static immutable string[8] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial", "Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"];
425 static immutable string[3] unitnames = ["t", "kg", "g"];
427 /* Data for DB's price/availability generation system */
428 /* Base Grad Base Mask Un Name
429 price ient quant it */
430 static immutable TradeGoodIntr[17] commodities = [
431 TradeGoodIntr(0x13, -0x02, 0x06, 0x01, 0, "Food"),
432 TradeGoodIntr(0x14, -0x01, 0x0A, 0x03, 0, "Textiles"),
433 TradeGoodIntr(0x41, -0x03, 0x02, 0x07, 0, "Radioactives"),
434 TradeGoodIntr(0x28, -0x05, 0xE2, 0x1F, 0, "Slaves"),
435 TradeGoodIntr(0x53, -0x05, 0xFB, 0x0F, 0, "Liquor/Wines"),
436 TradeGoodIntr(0xC4, +0x08, 0x36, 0x03, 0, "Luxuries"),
437 TradeGoodIntr(0xEB, +0x1D, 0x08, 0x78, 0, "Narcotics"),
438 TradeGoodIntr(0x9A, +0x0E, 0x38, 0x03, 0, "Computers"),
439 TradeGoodIntr(0x75, +0x06, 0x28, 0x07, 0, "Machinery"),
440 TradeGoodIntr(0x4E, +0x01, 0x11, 0x1F, 0, "Alloys"),
441 TradeGoodIntr(0x7C, +0x0d, 0x1D, 0x07, 0, "Firearms"),
442 TradeGoodIntr(0xB0, -0x09, 0xDC, 0x3F, 0, "Furs"),
443 TradeGoodIntr(0x20, -0x01, 0x35, 0x03, 0, "Minerals"),
444 TradeGoodIntr(0x61, -0x01, 0x42, 0x07, 1, "Gold"),
445 TradeGoodIntr(0xAB, -0x02, 0x37, 0x1F, 1, "Platinum"),
446 TradeGoodIntr(0x2D, -0x01, 0xFA, 0x0F, 2, "Gem Stones"),
447 TradeGoodIntr(0x35, +0x0F, 0xC0, 0x07, 0, "Alien Items"),
451 // ////////////////////////////////////////////////////////////////////////// //
452 // fixed (R.C): can't assume that two separate array declarations are adjacently located in memory -- unified in a sigle array
453 string pairs = "ABOUSEITILETSTONLONUTHNO..LEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION"; // dots should be nullprint characters
455 /* "Goat Soup" planetary description string code - adapted from Christian Pinder's reverse engineered sources. */
456 static immutable string[5][36] descList = [
457 /* 81 */ ["fabled", "notable", "well known", "famous", "noted"],
458 /* 82 */ ["very", "mildly", "most", "reasonably", ""],
459 /* 83 */ ["ancient", "\x95", "great", "vast", "pink"],
460 /* 84 */ ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"],
461 /* 85 */ ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"],
462 /* 86 */ ["food blenders", "tourists", "poetry", "discos", "\x8E"],
463 /* 87 */ ["talking tree", "crab", "bat", "lobst", "\xB2"],
464 /* 88 */ ["beset", "plagued", "ravaged", "cursed", "scourged"],
465 /* 89 */ ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"],
466 /* 8A */ ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"],
467 /* 8B */ ["juice", "brandy", "water", "brew", "gargle blasters"],
468 /* 8C */ ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"],
469 /* 8D */ ["fabulous", "exotic", "hoopy", "unusual", "exciting"],
470 /* 8E */ ["cuisine", "night life", "casinos", "sit coms", " \xA1 "],
471 /* 8F */ ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"],
472 /* 90 */ ["n unremarkable", " boring", " dull", " tedious", " revolting"],
473 /* 91 */ ["planet", "world", "place", "little planet", "dump"],
474 /* 92 */ ["wasp", "moth", "grub", "ant", "\xB2"],
475 /* 93 */ ["poet", "arts graduate", "yak", "snail", "slug"],
476 /* 94 */ ["tropical", "dense", "rain", "impenetrable", "exuberant"],
477 /* 95 */ ["funny", "wierd", "unusual", "strange", "peculiar"],
478 /* 96 */ ["frequent", "occasional", "unpredictable", "dreadful", "deadly"],
479 /* 97 */ ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"],
480 /* 98 */ ["\x9B", "mountain", "edible", "tree", "spotted"],
481 /* 99 */ ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"],
482 /* 9A */ ["ancient", "exceptional", "eccentric", "ingrained", "\x95"],
483 /* 9B */ ["killer", "deadly", "evil", "lethal", "vicious"],
484 /* 9C */ ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"],
485 /* 9D */ ["plant", "tulip", "banana", "corn", "\xB2weed"],
486 /* 9E */ ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"],
487 /* 9F */ ["shrew", "beast", "bison", "snake", "wolf"],
488 /* A0 */ ["leopard", "cat", "monkey", "goat", "fish"],
489 /* A1 */ ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"],
490 /* A2 */ ["meat", "cutlet", "steak", "burgers", "soup"],
491 /* A3 */ ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"],
492 /* A4 */ ["hockey", "cricket", "karate", "polo", "tennis"]
495 /* B0 = <planet name>
496 B1 = <planet name>ian
497 B2 = <random name>
500 string goatSoup (ref FastPRng prng, string source, in ref Galaxy.Planet psy) {
501 import std.ascii : toLower;
502 string res;
504 void addCh (char ch) {
505 if (!ch) return; // just in case
506 if (ch <= ' ') ch = ' ';
507 if (ch == ' ') {
508 if (res.length == 0 || res[$-1] <= ' ') return;
509 res ~= ch;
510 } else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) {
511 res ~= ch;
512 } else {
513 // punctuation -- remove trailing spaces
514 while (res.length && res[$-1] <= ' ') res = res[0..$-1];
515 res ~= ch;
519 while (source.length) {
520 char c = source[0];
521 source = source[1..$];
522 if (c < 0x80) {
523 addCh(c);
524 } else {
525 if (c <= 0xA4) {
526 int rnd = prng.next;
527 res ~= goatSoup(prng, descList[c-0x81][(rnd >= 0x33)+(rnd >= 0x66)+(rnd >= 0x99)+(rnd >= 0xCC)], psy);
528 } else
529 switch (c) {
530 case 0xB0: // planet name
531 addCh(psy.name[0]);
532 foreach (char ch; psy.name[1..$]) addCh(toLower(ch));
533 break;
534 case 0xB1: // <planet name>ian
535 addCh(psy.name[0]);
536 string t = psy.name[1..$];
537 while (t.length) {
538 if (t.length > 1 || (t[0] != 'e' && t[0] != 'i')) addCh(toLower(t[0]));
539 t = t[1..$];
541 res ~= "ian";
542 break;
543 case 0xB2: // random name
544 int len = prng.next&3;
545 for (int i = 0; i <= len; ++i) {
546 int x = prng.next&0x3e;
547 /* fixed (R.C.): transform chars to lowercase unless
548 first char of first pair, or second char of first
549 pair when first char was not printed */
550 char p1 = pairs[x];
551 char p2 = pairs[x+1];
552 if (p1 != '.') {
553 if (i) p1 = toLower(p1);
554 addCh(p1);
556 if (p2 != '.') {
557 if (i || p1 != '.') p2 = toLower(p2);
558 addCh(p2);
561 break;
562 default: assert(0);
566 return res;
570 // ////////////////////////////////////////////////////////////////////////// //
571 version(elite_world_test) unittest {
572 import std.stdio;
573 auto uni = Galaxy(0);
575 conwriteln("System: ", uni.currPlanet.name);
576 conwriteln("Position: (", uni.currPlanet.x, ",", uni.currPlanet.y, ")");
577 conwriteln("Economy: ", uni.currPlanet.economyName);
578 conwriteln("Government: ", uni.currPlanet.govName);
579 conwriteln("Tech Level: ", uni.currPlanet.techlev+1);
580 conwriteln("Turnover: ", uni.currPlanet.productivity);
581 conwriteln("Radius: ", uni.currPlanet.radius);
582 conwriteln("Population: ", uni.currPlanet.population/10, ".", uni.currPlanet.population%10, " Billion");
583 conwriteln(uni.currPlanet.description);
585 conwriteln("--------------------------------");
586 foreach (immutable idx; 0..Galaxy.Goods.max+1) {
587 auto good = Galaxy.good(idx);
588 conwritefln!"%-12s %3s.%s %2s%s"(good.name, uni.currPlanet.price[idx]/10, uni.currPlanet.price[idx]%10, uni.currPlanet.quantity[idx], good.unitName);