epigraph width fix
[xreader.git] / eworld.d
blobf0538a7bbb1ea39e8b89a5f0533e3103b1301da2
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;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public struct Galaxy {
33 // some numbers for galaxy 1
34 enum Lave = 7;
35 enum Zaonce = 129;
36 enum Diso = 147;
37 enum Ried = 46;
39 enum PlanetsInGalaxy = 256;
41 enum Goods : ubyte {
42 Food = 0,
43 Textiles = 1,
44 Radioactives = 2,
45 Slaves = 3,
46 LiquorWines = 4,
47 Luxuries = 5,
48 Narcotics = 6,
49 Computers = 7,
50 Machinery = 8,
51 Alloys = 9,
52 Firearms = 10,
53 Furs = 11,
54 Minerals = 12,
55 Gold = 13,
56 Platinum = 14,
57 GemStones = 15,
58 AlienItems = 16,
61 enum Unit { Tonne, Kg, G }
63 static struct TradeGood {
64 string name;
65 string unitName; // unit name
66 Unit unit;
67 bool alien; // alien items?
68 bool forbidden; // narcotics or alike?
71 static TradeGood good(T : ulong) (T idx) {
72 if (idx > Goods.max) assert(0, "invalid trade good index");
73 TradeGood res;
74 res.name = commodities[idx].name;
75 res.unitName = unitnames[commodities[idx].units];
76 res.unit = cast(Unit)commodities[idx].units;
77 res.alien = (idx == Galaxy.Goods.AlienItems);
78 res.forbidden = (idx == Galaxy.Goods.Slaves || idx == Galaxy.Goods.Narcotics);
79 return res;
82 enum Gov : ubyte {
83 Anarchy,
84 Feudal,
85 MultiGov,
86 Dictatorship,
87 Communist,
88 Confederacy,
89 Democracy,
90 CorporateState,
93 enum Economy : ubyte {
94 RichInd,
95 AverageInd,
96 PoorInd,
97 MainlyInd,
98 MainlyAgri,
99 RichAgri,
100 AverageAgri,
101 PoorAgri,
104 enum MaxTechLevel = 16;
106 string economyName(T : ulong) (T v) const pure nothrow @safe @nogc { return (v >= 0 && v <= Economy.max ? econnames[economy] : "Unknown"); }
107 string govName(T : ulong) (T v) const pure nothrow @safe @nogc { return (v >= 0 && v <= Gov.max ? govnames[govtype] : "Unknown"); }
109 static struct Planet {
110 ubyte number; // in galaxy
111 ubyte x, y;
112 Economy economy; // 0..7
113 Gov govtype; // 0..7
114 ubyte techlev; // 0..16
115 ubyte population;
116 ushort productivity;
117 ushort radius;
118 string name;
120 ushort lastFluct; // fluct used in last genMarket call
122 private FastPRng goatsoupseed;
123 private string desc;
125 // market; please, generate it before using! ;-)
126 ushort[Goods.max+1] quantity;
127 ushort[Goods.max+1] price;
129 string economyName () const pure nothrow @safe @nogc { return econnames[economy]; }
130 string govName () const pure nothrow @safe @nogc { return govnames[govtype]; }
132 /* Prices and availabilities are influenced by the planet's economy type
133 * (0-7) and a random "fluctuation" byte that was kept within the saved
134 * commander position to keep the market prices constant over gamesaves.
135 * Availabilities must be saved with the game since the player alters them
136 * by buying (and selling(?))
138 * Almost all operations are one byte only and overflow "errors" are
139 * extremely frequent and exploited.
141 * Trade Item prices are held internally in a single byte=true value/4.
142 * The decimal point in prices is introduced only when printing them.
143 * Internally, all prices are integers.
144 * The player's cash is held in four bytes.
146 void genMarket (ushort fluct) {
147 lastFluct = fluct;
148 foreach (uint i; 0..Galaxy.Goods.max+1) {
149 int product = economy*commodities[i].gradient;
150 int changing = fluct&commodities[i].maskbyte;
151 int q = commodities[i].basequant+changing-product;
152 q = q&0xFF;
153 if (q&0x80) q = 0; // clip to positive 8-bit
154 quantity[i] = cast(ushort)(q&0x3F); // mask to 6 bits
155 q = commodities[i].baseprice+changing+product;
156 q = q&0xFF;
157 price[i] = cast(ushort)(q*4);
159 quantity[Galaxy.Goods.AlienItems] = 0; // override to force nonavailability
162 // seperation between two planets (4*sqrt(X*X+Y*Y/4))
163 int distanceTo() (in auto ref Planet b) const nothrow @safe @nogc {
164 import std.math : floor, sqrt;
165 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);
168 // goat soup
169 string description () {
170 if (desc.length == 0) {
171 FastPRng prng = goatsoupseed;
172 desc = goatSoup(prng, "\x8F is \x97.", this);
174 return desc;
178 Planet[PlanetsInGalaxy] planets;
179 private GalaxySeed galseed;
180 bool initialized;
181 Planet currPlanet; // current planet
182 private ulong curGalSeed;
184 this (ubyte galnum) { build(galnum); }
186 @property ulong galSeed () const pure nothrow @nogc { return curGalSeed; }
187 @property void galSeed (ulong v) pure nothrow @nogc { curGalSeed = v; }
189 // generate galaxy
190 void build (ubyte galnum) {
191 galseed = GalaxySeed.galaxy(galnum);
192 curGalSeed = galseed.seed;
193 foreach (immutable idx, ref Planet ps; planets) { makeSystem(ps); ps.number = cast(ubyte)idx; }
194 initialized = true;
195 // generate data for Lave (ignore the fact that we can be in another galaxy)
196 currPlanet = planets[Lave];
197 currPlanet.genMarket(0);
200 // regenerate galaxy
201 void rebuild () {
202 galseed.seed = curGalSeed;
203 foreach (immutable idx, ref Planet ps; planets) { makeSystem(ps); ps.number = cast(ubyte)idx; }
204 initialized = true;
205 currPlanet = planets[currPlanet.number];
206 currPlanet.genMarket(randbyte());
209 void hyperjump () {
210 galseed.seed = curGalSeed;
211 galseed.nextgalaxy();
212 curGalSeed = galseed.seed;
213 foreach (immutable idx, ref Planet ps; planets) { makeSystem(ps); ps.number = cast(ubyte)idx; }
214 initialized = true;
215 // generate data for Lave (ignore the fact that we can be in another galaxy)
216 currPlanet = planets[currPlanet.number];
217 currPlanet.genMarket(randbyte());
220 // move to system i
221 bool jump (int i) {
222 if (i >= 0 && i < planets.length) {
223 currPlanet = planets[i];
224 currPlanet.genMarket(randbyte());
225 return true;
226 } else {
227 return false;
231 // return id of the planet whose name matches passed string closest to currentplanet; if none, return current planet
232 int find (const(char)[] s) {
233 int d = int.max;
234 int res = currPlanet.number;
235 foreach (immutable idx, const ref p; planets) {
236 if (startsWithCI(p.name, s)) {
237 auto nd = currPlanet.distanceTo(p);
238 if (nd < d) {
239 d = nd;
240 res = cast(int)idx;
244 return res;
247 static startsWithCI (const(char)[] s, const(char)[] pat) nothrow @trusted @nogc {
248 import std.ascii : toUpper, toLower;
249 if (pat.length == 0 || pat.length > s.length) return false;
250 foreach (immutable idx, char ch; pat) {
251 if (toLower(s.ptr[idx]) != toLower(ch)) return false;
253 return true;
256 private:
257 // generate system info from seed
258 void makeSystem (out Planet thissys) {
259 alias s = galseed;
260 //Planet thissys;
261 ushort pair1, pair2, pair3, pair4;
262 ushort longnameflag = s.w0&64;
263 const(char)* pairs1 = pairs.ptr+24; // start of pairs used by this routine
265 thissys.x = s.w1>>8;
266 thissys.y = s.w0>>8;
268 thissys.govtype = cast(Galaxy.Gov)((s.w1>>3)&7); // bits 3, 4 & 5 of w1
270 ubyte ec = (s.w0>>8)&7; // bits 8, 9 & A of w0
271 if (thissys.govtype <= 1) ec |= 2;
272 thissys.economy = cast(Galaxy.Economy)ec;
274 thissys.techlev = cast(ubyte)(((s.w1>>8)&3)+(ec^7));
275 thissys.techlev += thissys.govtype>>1;
276 if (thissys.govtype&1) thissys.techlev += 1; // simulation of 6502's LSR then ADC
278 thissys.population = cast(ubyte)(4*thissys.techlev+thissys.economy);
279 thissys.population += thissys.govtype+1;
281 thissys.productivity = cast(ushort)(((thissys.economy^7)+3)*(thissys.govtype+4));
282 thissys.productivity *= thissys.population*8;
284 thissys.radius = cast(ushort)(256*(((s.w2>>8)&15)+11)+thissys.x);
286 thissys.goatsoupseed.a = s.w1&0xFF;
287 thissys.goatsoupseed.b = s.w1>>8;
288 thissys.goatsoupseed.c = s.w2&0xFF;
289 thissys.goatsoupseed.d = s.w2>>8;
291 pair1 = 2*((s.w2>>8)&31); s.tweak;
292 pair2 = 2*((s.w2>>8)&31); s.tweak;
293 pair3 = 2*((s.w2>>8)&31); s.tweak;
294 pair4 = 2*((s.w2>>8)&31); s.tweak;
295 // always four iterations of random number
297 char[12] namebuf;
298 uint nbpos;
300 void putCh (char ch) nothrow @trusted @nogc {
301 import std.ascii : toUpper, toLower;
302 if (ch == '.') return;
303 if (nbpos == 0) ch = toUpper(ch); else ch = toLower(ch);
304 namebuf.ptr[nbpos++] = ch;
307 putCh(pairs1[pair1]);
308 putCh(pairs1[pair1+1]);
309 putCh(pairs1[pair2]);
310 putCh(pairs1[pair2+1]);
311 putCh(pairs1[pair3]);
312 putCh(pairs1[pair3+1]);
314 // bit 6 of ORIGINAL w0 flags a four-pair name
315 if (longnameflag) {
316 putCh(pairs1[pair4]);
317 putCh(pairs1[pair4+1]);
319 thissys.name = namebuf[0..nbpos].idup;
322 private:
323 // prng for planet price fluctuations
324 static uint lastrand = 0x29a;
325 static void mysrand (uint seed) { lastrand = seed-1; }
326 static int myrand () {
327 int r;
328 // As supplied by D McDonnell from SAS Insititute C
329 r = (((((((((((lastrand<<3)-lastrand)<<3)+lastrand)<<1)+lastrand)<<4)-lastrand)<<1)-lastrand)+0xe60)&0x7fffffff;
330 lastrand = r-1;
331 return r;
333 static ubyte randbyte () { return (myrand()&0xFF); }
337 // ////////////////////////////////////////////////////////////////////////// //
338 private:
340 // four byte random number used for planet description
341 struct FastPRng {
342 ubyte a, b, c, d;
344 pure nothrow @safe @nogc:
345 int next () {
346 int a, x;
347 x = (this.a*2)&0xFF;
348 a = x+this.c;
349 if (this.a > 127) ++a;
350 this.a = a&0xFF;
351 this.c = cast(ubyte)x;
352 a /= 256; // a = any carry left from above
353 x = this.b;
354 a = (a+x+this.d)&0xFF;
355 this.b = cast(ubyte)a;
356 this.d = cast(ubyte)x;
357 return a;
362 // six byte random number used as seed for planets
363 // initied with base seed for galaxy 1
364 struct GalaxySeed {
365 ushort w0 = 0x5A4Au;
366 ushort w1 = 0x0248u;
367 ushort w2 = 0xB753u;
369 pure nothrow @safe @nogc:
370 // create seed for the corresponding galaxy
371 this (ubyte galnum) {
372 galnum &= 0x07;
373 foreach (immutable _; 0..galnum) nextgalaxy();
376 static GalaxySeed galaxy (ubyte galnum) { return GalaxySeed(galnum); }
378 @property ulong seed () const {
379 return
380 ((cast(ulong)w0)<<2*16)|
381 ((cast(ulong)w1)<<1*16)|
382 (cast(ulong)w2);
385 @property void seed (ulong v) {
386 w0 = cast(ushort)(v>>(2*16));
387 w1 = cast(ushort)(v>>(1*16));
388 w2 = cast(ushort)(v);
391 private void tweak () {
392 ushort temp = cast(ushort)(cast(ushort)(w0+w1)+w2); // 2 byte aritmetic
393 w0 = w1;
394 w1 = w2;
395 w2 = temp;
398 // apply to base seed; once for galaxy 2, etc.
399 void nextgalaxy () {
400 // rotate 8 bit number leftwards
401 static ushort rotatel() (ushort x) { ushort temp = x&128; return (2*(x&127))+(temp>>7); }
402 static ushort twist() (ushort x) { return cast(ushort)((256*rotatel(x>>8))+rotatel(x&255)); }
403 w0 = twist(w0); // twice for galaxy 3, etc.
404 w1 = twist(w1); // Eighth application gives galaxy 1 again
405 w2 = twist(w2);
410 // ////////////////////////////////////////////////////////////////////////// //
411 struct TradeGoodIntr {
412 // In 6502 version these were:
413 ushort baseprice; // one byte
414 short gradient; // five bits plus sign
415 ushort basequant; // one byte
416 ushort maskbyte; // one byte
417 ushort units; // two bits
418 string name; // longest="Radioactives"
421 static immutable string[8] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship", "Communist", "Confederacy", "Democracy", "Corporate State"];
422 static immutable string[8] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial", "Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"];
423 static immutable string[3] unitnames = ["t", "kg", "g"];
425 /* Data for DB's price/availability generation system */
426 /* Base Grad Base Mask Un Name
427 price ient quant it */
428 static immutable TradeGoodIntr[17] commodities = [
429 TradeGoodIntr(0x13, -0x02, 0x06, 0x01, 0, "Food"),
430 TradeGoodIntr(0x14, -0x01, 0x0A, 0x03, 0, "Textiles"),
431 TradeGoodIntr(0x41, -0x03, 0x02, 0x07, 0, "Radioactives"),
432 TradeGoodIntr(0x28, -0x05, 0xE2, 0x1F, 0, "Slaves"),
433 TradeGoodIntr(0x53, -0x05, 0xFB, 0x0F, 0, "Liquor/Wines"),
434 TradeGoodIntr(0xC4, +0x08, 0x36, 0x03, 0, "Luxuries"),
435 TradeGoodIntr(0xEB, +0x1D, 0x08, 0x78, 0, "Narcotics"),
436 TradeGoodIntr(0x9A, +0x0E, 0x38, 0x03, 0, "Computers"),
437 TradeGoodIntr(0x75, +0x06, 0x28, 0x07, 0, "Machinery"),
438 TradeGoodIntr(0x4E, +0x01, 0x11, 0x1F, 0, "Alloys"),
439 TradeGoodIntr(0x7C, +0x0d, 0x1D, 0x07, 0, "Firearms"),
440 TradeGoodIntr(0xB0, -0x09, 0xDC, 0x3F, 0, "Furs"),
441 TradeGoodIntr(0x20, -0x01, 0x35, 0x03, 0, "Minerals"),
442 TradeGoodIntr(0x61, -0x01, 0x42, 0x07, 1, "Gold"),
443 TradeGoodIntr(0xAB, -0x02, 0x37, 0x1F, 1, "Platinum"),
444 TradeGoodIntr(0x2D, -0x01, 0xFA, 0x0F, 2, "Gem Stones"),
445 TradeGoodIntr(0x35, +0x0F, 0xC0, 0x07, 0, "Alien Items"),
449 // ////////////////////////////////////////////////////////////////////////// //
450 // fixed (R.C): can't assume that two separate array declarations are adjacently located in memory -- unified in a sigle array
451 string pairs = "ABOUSEITILETSTONLONUTHNO..LEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION"; // dots should be nullprint characters
453 /* "Goat Soup" planetary description string code - adapted from Christian Pinder's reverse engineered sources. */
454 static immutable string[5][36] descList = [
455 /* 81 */ ["fabled", "notable", "well known", "famous", "noted"],
456 /* 82 */ ["very", "mildly", "most", "reasonably", ""],
457 /* 83 */ ["ancient", "\x95", "great", "vast", "pink"],
458 /* 84 */ ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"],
459 /* 85 */ ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"],
460 /* 86 */ ["food blenders", "tourists", "poetry", "discos", "\x8E"],
461 /* 87 */ ["talking tree", "crab", "bat", "lobst", "\xB2"],
462 /* 88 */ ["beset", "plagued", "ravaged", "cursed", "scourged"],
463 /* 89 */ ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"],
464 /* 8A */ ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"],
465 /* 8B */ ["juice", "brandy", "water", "brew", "gargle blasters"],
466 /* 8C */ ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"],
467 /* 8D */ ["fabulous", "exotic", "hoopy", "unusual", "exciting"],
468 /* 8E */ ["cuisine", "night life", "casinos", "sit coms", " \xA1 "],
469 /* 8F */ ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"],
470 /* 90 */ ["n unremarkable", " boring", " dull", " tedious", " revolting"],
471 /* 91 */ ["planet", "world", "place", "little planet", "dump"],
472 /* 92 */ ["wasp", "moth", "grub", "ant", "\xB2"],
473 /* 93 */ ["poet", "arts graduate", "yak", "snail", "slug"],
474 /* 94 */ ["tropical", "dense", "rain", "impenetrable", "exuberant"],
475 /* 95 */ ["funny", "wierd", "unusual", "strange", "peculiar"],
476 /* 96 */ ["frequent", "occasional", "unpredictable", "dreadful", "deadly"],
477 /* 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"],
478 /* 98 */ ["\x9B", "mountain", "edible", "tree", "spotted"],
479 /* 99 */ ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"],
480 /* 9A */ ["ancient", "exceptional", "eccentric", "ingrained", "\x95"],
481 /* 9B */ ["killer", "deadly", "evil", "lethal", "vicious"],
482 /* 9C */ ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"],
483 /* 9D */ ["plant", "tulip", "banana", "corn", "\xB2weed"],
484 /* 9E */ ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"],
485 /* 9F */ ["shrew", "beast", "bison", "snake", "wolf"],
486 /* A0 */ ["leopard", "cat", "monkey", "goat", "fish"],
487 /* A1 */ ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"],
488 /* A2 */ ["meat", "cutlet", "steak", "burgers", "soup"],
489 /* A3 */ ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"],
490 /* A4 */ ["hockey", "cricket", "karate", "polo", "tennis"]
493 /* B0 = <planet name>
494 B1 = <planet name>ian
495 B2 = <random name>
498 string goatSoup (ref FastPRng prng, string source, in ref Galaxy.Planet psy) {
499 import std.ascii : toLower;
500 string res;
502 void addCh (char ch) {
503 if (!ch) return; // just in case
504 if (ch <= ' ') ch = ' ';
505 if (ch == ' ') {
506 if (res.length == 0 || res[$-1] <= ' ') return;
507 res ~= ch;
508 } else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) {
509 res ~= ch;
510 } else {
511 // punctuation -- remove trailing spaces
512 while (res.length && res[$-1] <= ' ') res = res[0..$-1];
513 res ~= ch;
517 while (source.length) {
518 char c = source[0];
519 source = source[1..$];
520 if (c < 0x80) {
521 addCh(c);
522 } else {
523 if (c <= 0xA4) {
524 int rnd = prng.next;
525 res ~= goatSoup(prng, descList[c-0x81][(rnd >= 0x33)+(rnd >= 0x66)+(rnd >= 0x99)+(rnd >= 0xCC)], psy);
526 } else
527 switch (c) {
528 case 0xB0: // planet name
529 addCh(psy.name[0]);
530 foreach (char ch; psy.name[1..$]) addCh(toLower(ch));
531 break;
532 case 0xB1: // <planet name>ian
533 addCh(psy.name[0]);
534 string t = psy.name[1..$];
535 while (t.length) {
536 if (t.length > 1 || (t[0] != 'e' && t[0] != 'i')) addCh(toLower(t[0]));
537 t = t[1..$];
539 res ~= "ian";
540 break;
541 case 0xB2: // random name
542 int len = prng.next&3;
543 for (int i = 0; i <= len; ++i) {
544 int x = prng.next&0x3e;
545 /* fixed (R.C.): transform chars to lowercase unless
546 first char of first pair, or second char of first
547 pair when first char was not printed */
548 char p1 = pairs[x];
549 char p2 = pairs[x+1];
550 if (p1 != '.') {
551 if (i) p1 = toLower(p1);
552 addCh(p1);
554 if (p2 != '.') {
555 if (i || p1 != '.') p2 = toLower(p2);
556 addCh(p2);
559 break;
560 default: assert(0);
564 return res;
568 // ////////////////////////////////////////////////////////////////////////// //
569 version(elite_world_test) unittest {
570 import std.stdio;
571 auto uni = Galaxy(0);
573 writeln("System: ", uni.currPlanet.name);
574 writeln("Position: (", uni.currPlanet.x, ",", uni.currPlanet.y, ")");
575 writeln("Economy: ", uni.currPlanet.economyName);
576 writeln("Government: ", uni.currPlanet.govName);
577 writeln("Tech Level: ", uni.currPlanet.techlev+1);
578 writeln("Turnover: ", uni.currPlanet.productivity);
579 writeln("Radius: ", uni.currPlanet.radius);
580 writeln("Population: ", uni.currPlanet.population/10, ".", uni.currPlanet.population%10, " Billion");
581 writeln(uni.currPlanet.description);
583 writeln("--------------------------------");
584 foreach (immutable idx; 0..Galaxy.Goods.max+1) {
585 auto good = Galaxy.good(idx);
586 writefln("%-12s %3s.%s %2s%s", good.name, uni.currPlanet.price[idx]/10, uni.currPlanet.price[idx]%10, uni.currPlanet.quantity[idx], good.unitName);