#include #include #include // #include // finally got rid of it! #include #include /* only for memcpy() */ #include #include #include "sounds.h" #ifdef BIGNUM #include "bignum.h" #endif /* define this to use POKEY's RANDOM register instead of cc65's rand(). Disabled for now, maybe forever (pending testing) */ // #define POKEY_RANDOM /**** These defines should be disabled for normal gameplay. Don't leave any of them enabled for a release or a normal test build. */ /* define this for testing sea_battle(). it causes a pirate attack every time you leave port. */ // #define COMBAT_TEST /* define this to show internals of damage calculation */ // #define DAMAGE_TEST /* define this to start the game in the year 1869, with 1000 capacity, 20 guns, and 1 billion cash and bank. */ // #define TIMEWARP /* define this to start the game in a 99% damaged ship */ // #define ALMOST_DEAD /* define this to test the mchenry() routine by entering damage and capacity numbers directly */ // #define MCHENRY_TEST /* define this to test the cprintfancy_big() routine */ // #define BIGNUM_TEST /**** atari-specific stuff */ /* values returned by cgetc() for backspace/enter/delete keys */ #define BKSP 0x7e #define ENTER 0x9b #define DEL 0x9c /* timed_getch() args for seconds, based on jiffy clock of target system. No adjustment made for PAL, sorry. */ #define TMOUT_1S 60 #define TMOUT_3S 180 #define TMOUT_5S 300 #define TMOUT_5M 18000 /* wait up to j jiffies for a keypress. returns -1 if no key pressed */ extern int __fastcall__ timed_getch(unsigned int j); /* custom Atari-aware cgetc() wrapper. returns only non-inverse plain ASCII characters, except EOL and BS. Unlike the real cgetc(), it's an unsigned char, and can't return -1 for failure (but, it will never fail. real cgetc() never fails either, even if user hits Break) */ extern unsigned char agetc(void); /* wrapper for agetc(): lowercases letters */ extern unsigned char lcgetc(void); /* wrapper for agetc(): returns only numbers, a, enter, backspace */ extern unsigned char numgetc(void); /* wrapper for agetc(): returns only y or n. dflt is 'y' or 'n' to set the default answer if the user presses Enter, or 0 for no default (waits until user presses either y or n) */ extern unsigned char __fastcall__ yngetc(char dflt); /* sleep for j jiffies (no PAL adjustment at the moment) */ extern void __fastcall__ jsleep(unsigned int j); #define flushinp() (POKE(764,255)) /* Atari-specific random number functions from rand.s. Non-Atari platforms can probably just: #define initrand() _randomize() #define randi() ((unsigned int)rand()) #define randl() (unsigned long)((randi() << 16) | randi()) */ #ifdef POKEY_RANDOM #define initrand() /* no-op on Atari */ /* random positive int, 0 to 32767 */ extern unsigned int __fastcall__ randi(void); #else #define initrand() _randomize() #define randi() ((unsigned int)rand()) #endif /* random long, 0 to 2**32-1 */ extern unsigned long __fastcall__ randl(void); /* defined in portstat.s, this is the contents of PORTSTAT.DAT. used to quickly redraw the port stats screen. If ever PORTSTAT.DAT needs to be regenerated, use mkportstats.c */ extern const char *port_stat_screen; /* boolean, whether or not port_stats() needs to redraw the static parts of the port stats screen (by copying port_stat_screen into screen RAM) */ char port_stat_dirty = 1; /* asm curses/conio funcs from clrtobot.s. Old C versions moved to oldcurses.c for reference. */ extern void clrtobot(void); extern void clrtoeol(void); /* asm funcs from draw_lorcha.s for drawing/animating enemy ships. used by sea_battle() */ extern void __fastcall__ draw_lorcha(int which); extern void __fastcall__ flash_lorcha(int which); extern void __fastcall__ damage_lorcha(int which); extern void __fastcall__ sink_lorcha(int which); extern void __fastcall__ clear_lorcha(int which); /* used to set the background/text colors here, but now the title screen does it (newtitle.s) */ void atari_text_setup() { jsleep(1); POKE(560, PEEK(212)); // restore the POKE(561, PEEK(213)); // display list jsleep(1); POKE(559, 34); // turn on the screen (normal playfield) jsleep(1); POKE(756, FONT_ADDR / 256); // use our custom font POKE(731, 1); // disable keyclick on XL/XE (does nothing on 400/800) } /* this didn't work out, bummer. */ // extern void __fastcall__ waitvcount(unsigned char c); /**** End of atari-specific stuff. Supposed to be, anyway. */ /* old version of this used to just 'return randl()%clamp'. If clamp were 0, the return value would be the unclamped result from randl() (x % 0 == x, in cc65). If it were 1, the return value would always be 1 (no randomness there). */ unsigned long randclamp(unsigned long clamp) { unsigned long r = randl(); if(clamp == 0) return clamp; if(clamp == 1) return r & 0x01; return r % clamp; } /* print 'count' spaces, but leave the cursor where it was. TODO: rewrite in asm. */ void cblank(unsigned char count) { char oldx = wherex(); char oldy = wherey(); while(count--) cputc(' '); gotoxy(oldx, oldy); } /* conio doesn't back up the cursor if you cputc(BKSP), it prints the graphics character instead. Could use putchar(), but using stdio links a bunch of extra support code. So: */ /* TODO: rewrite in asm */ void backspace() { gotox(wherex()-1); cblank(1); } /* get an inventory item, return its index into items[]. if allow_all is true, allows '*', which is used for 'throw cargo' in sea_battle. */ unsigned char get_item(unsigned char allow_all) { // unsigned char i; for(;;) { /* using a switch makes the code 12 bytes smaller here i = lcgetc(); if(i == 'o') return 0; if(i == 's') return 1; if(i == 'a') return 2; if(i == 'g') return 3; if(allow_all && i == '*') return 4; */ switch(lcgetc()) { case 'o': return 0; case 's': return 1; case 'a': return 2; case 'g': return 3; case '*': if(allow_all) return 4; // else fall thru default: break; } } } /* modified ultoa() with hardcoded radix */ extern char *ultostr(unsigned long value, char* s); #define ultoa(x, y, z) ultostr(x, y) /* if your debt goes above this, Elder Brother Wu has you assassinated, game over. Value is: int((2**31 - 1) / 1.1) */ #define DEBT_MAX 1952257860L char wu_assassin = 0; /* taipan functions (modified as little as possible) */ #define GENERIC 1 #define LI_YUEN 2 /* title screen now a separate xex segment (see Makefile for details) */ // void splash_intro(void); #define get_one() agetc(); unsigned long get_num(void); void name_firm(void); void cash_or_guns(void); void set_prices(void); void port_stats(void); int port_choices(void); void new_ship(void); void new_gun(void); void li_yuen_extortion(void); void elder_brother_wu(void); void good_prices(void); void buy(void); void sell(void); void visit_bank(void); void transfer(void); void quit(void); void overload(void); int sea_battle(int id, int num_ships); void fight_stats(int ships, int orders); void mchenry(void); void retire(void); void final_stats(void); void you_only_have(unsigned char in_bank); void cprintulong(unsigned long ul); void cprintfancy(unsigned long num); void cprintfancy_centered(unsigned long num); void too_much_cash(void); char would_overflow(unsigned long a, unsigned long b); int get_time(void); unsigned char firmpos; /* use page 6 for these buffers */ char *firm = (char *) 0x680; char *num_buf = (char *) 0x600; char *item[] = { "Opium", "Silk", "Arms", "General Cargo" }; char *location[] = { "At sea", "Hong Kong", "Shanghai", "Nagasaki", "Saigon", "Manila", "Singapore", "Batavia" }; char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; char *st[] = { "Critical", " Poor", " Fair", " Good", " Prime", "Perfect" }; #ifdef BIGNUM bignum(bank) = BIG_0; bignum(interest_denom) = BIG_200; bignum(big_max_ulong) = BIG_MAX_ULONG; #else unsigned long bank = 0; #endif unsigned long cash = 0, debt = 0, booty = 0; // ec = 20, // ed = 1; // used to be a float, 0.5 unsigned long price[4]; int base_price[4][8] = { {1000, 11, 16, 15, 14, 12, 10, 13}, {100, 11, 14, 15, 16, 10, 13, 12}, {10, 12, 16, 10, 11, 13, 14, 15}, {1, 10, 11, 12, 13, 14, 15, 16} }; /* hkw_ is the warehouse, hold_ is how much of each item is in your hold. both need to be unsigned (makes no sense to have negative amounts in warehouse or hold). hold_ needs to be a long because it's entirely possible to buy e.g. over 32768 of some item later in the game. You won't be able to leave port, so you'll have to get rid of it on the same turn, but we still have to stop it overflowing a 16-bit int. I'm not convinced hkw_ needs to be long, since it's limited to 10000 of each item. */ unsigned long hkw_[4], hold_[4]; /* this really can go negative (meaning, your ship is overloaded). It needs to be a long though. At some point, it *still* might overflow, e.g. if general cargo drops to 1, you have over 2**31-1 in cash, and you buy that many general cargo... Probably should limit the amount of cargo the player can buy per transaction, but even then, they can do multiple transactions on the same turn. Need a signed version of would_overflow() to do this right. */ long hold = 0; /* these being negative would be a Bad Thing */ unsigned int guns = 0, month = 1, year = 1860, ec = 20, ed = 1; /* ec+=20, ed++ every game-year (12 turns). player would have to play until 15 Jan 5168 to overflow ec. */ unsigned char port = 1, bp = 0, li = 0, wu_warn = 0, wu_bailout = 0; // these need to be longs to avoid int overflow when // displaying ship status. long damage = 0, capacity = 60, newdamage; #ifdef BIGNUM bignum(big1T) = BIG_1T; bignum(big1B) = BIG_1B; bignum(big1M) = BIG_1M; bignum(big1K) = BIG_1K; bignum(big0) = BIG_0; /* what we should have: For Million/Billion, 3 significant digits. range printed as 0..999999 stet 1M..10M-1 1.23 Million 10M..100M-1 10.2 Million, 100 Million 100M..1B-1 100 Million, 999 Billion 1B..10B-1 1.23 Billion 10B..100B-1 10.2 Billion, 100 Billion 100B..1T-1 100 Billion 1T..inf 1 Trillion+! */ void cprintfancy_big(bignump b) { bignum(tmp); unsigned long leftdigits = 0L; unsigned char rightdigits = 0, letter = 'M', leading0 = 0; big_copy(tmp, b); if(big_cmp(tmp, big0) < 0) { cputc('-'); big_negate(tmp); } if(big_cmp(tmp, big1T) >= 0) { revers(1); cputs("1 Trillion+!"); revers(0); return; } /* for >= 1B, divide by 1M */ if(big_cmp(tmp, big1B) >= 0) { big_div(tmp, tmp, big1K); letter = 'B'; } big_to_ulong(tmp, &leftdigits); if(big_cmp(tmp, big1M) < 0) { /* 0..999999 */ letter = 0; } else if(leftdigits < 10000000L) { /* 1M..10M-1 */ leftdigits /= 10000L; rightdigits = (unsigned char)(leftdigits % 100L); leftdigits /= 100L; if(rightdigits < 10) leading0 = 1; } else if(leftdigits < 100000000L) { /* 10M..100M-1 */ leftdigits /= 100000L; rightdigits = (unsigned char)(leftdigits % 10L); leftdigits /= 10L; } else { leftdigits /= 1000000L; } cprintulong(leftdigits); if(rightdigits) { cputc('.'); if(leading0) cputc('0'); cprintulong((unsigned long)rightdigits); } if(letter) { cputc(' '); cputc(letter); cputs("illion"); } } #endif int get_time(void) { return ((year - 1860) * 12) + month; } /* print an int or long as a string, conio-style */ void cprintulong(unsigned long ul) { cputs(ultoa(ul, num_buf, 10)); } void at_sea() { gotoxy(30, 6); cputc(' '); revers(1); cputs(location[0]); revers(0); /* this is 24 bytes smaller: */ cputs(" "); /* than this: cputc(' '); cputc(' '); cputc(' '); */ } /* this bit of code was duplicated a *bunch* of times, making it a function makes the binary 2K smaller. */ void prepare_report(void) { gotoxy(0, 16); clrtobot(); } void compradores_report(void) { prepare_report(); cputs("Comprador's Report\r\n\n"); } void captains_report(void) { prepare_report(); cputs(" Captain's Report\r\n\n"); } void clear_msg_window(void) { gotoxy(0, 18); clrtobot(); } void overload(void) { compradores_report(); cputs("Your ship is overloaded, Taipan!!"); good_joss_sound(); timed_getch(TMOUT_5S); return; } void new_ship(void) { int choice = 0, time; unsigned long amount; // time = ((year - 1860) * 12) + month; time = get_time(); amount = randi()%(1000 * (time + 5) / 6) * (capacity / 50) + 1000; if(cash < amount) { return; } compradores_report(); cputs("Do you wish to trade in your "); if(damage > 0) { revers(1); cputs("damaged"); revers(0); } else { cputs("fine"); } cputs("\r\nship for one with 50 more capacity by\r\n"); cputs("paying an additional "); cprintfancy(amount); cputs(", Taipan? "); choice = yngetc(0); if(choice == 'y') { cash -= amount; hold += 50; capacity += 50; damage = 0; } if ((randi()%2 == 0) && (guns < 1000)) { port_stats(); new_gun(); } port_stats(); return; } void new_gun(void) { int choice = 0, time; unsigned long amount; // time = ((year - 1860) * 12) + month; time = get_time(); amount = randi()%(1000 * (time + 5) / 6) + 500; if((cash < amount) || (hold < 10)) { return; } // fancy_numbers(amount, fancy_num); compradores_report(); cputs("Do you wish to buy a ship's gun\r\nfor "); // cputs(fancy_num); cprintfancy(amount); cputs(", Taipan? "); choice = yngetc(0); if(choice == 'y') { cash -= amount; hold -= 10; guns += 1; } port_stats(); return; } void cprintfancy_centered(unsigned long num) { if(num < 1000000L) { /* 0 to 999999: | 999999 | | 99999 | | 9999 | | 999 | | 99 | | 9 | */ cputs(" "); if(num < 100L) cputc(' '); if(num < 10000L) cputc(' '); revers(1); cprintulong(num); } else { if(num < 10000000L) cputc(' '); revers(1); cprintfancy(num); } revers(0); } /* if BIGNUM, cprintfancy() just converts to bignum and uses cprintfancy_big() to do the work. A bit slower, but fast enough, and avoids duplicate logic. */ #ifdef BIGNUM void cprintfancy(unsigned long num) { bignum(b); ulong_to_big(num, b); cprintfancy_big(b); } #else /* replaces old fancy_numbers. same logic, but stuff is just printed on the screen rather than being kept in a buffer. One minor difference between this and fancy_numbers() is that we print "1.10 Million" rather than "1.1 Million" (extra zero). I don't think anyone's going to complain. */ void cprintfancy(unsigned long num) { unsigned char tmp; if(num >= 100000000L) { /* 100 million and up: |1000 Million| |100 Million | */ cprintulong(num / 1000000L); } else if (num >= 10000000L) { /* 10 million to 99 million: | 10 Million | |10.1 Million|*/ tmp = (num % 1000000L) / 100000L; cprintulong(num / 1000000L); if(tmp) { cputc('.'); cprintulong(tmp); } } else if (num >= 1000000L) { /* 1 million to 9 million: | 1 Million | |1.10 Million| // always has 0, never 1.1 |1.23 Million| */ tmp = (num % 1000000L) / 10000L; cprintulong(num / 1000000L); if(tmp) { cputc('.'); if(tmp < 10L) cputc('0'); cprintulong(tmp); } } else { /* 0 to 999999: | 999999 | | 99999 | | 9999 | | 999 | | 99 | | 9 | */ cprintulong(num); return; } cputs(" Million"); } #endif /* void fancy_numbers(unsigned long num, char *fancy) { static char number[18]; char mil = 0; unsigned int num1, num2; if (num >= 100000000L) { num1 = (num / 1000000L); ultoa(num1, fancy, 10); mil = 1; } else if (num >= 10000000L) { num1 = (num / 1000000L); num2 = ((num % 1000000L) / 100000L); ultoa(num1, fancy, 10); if (num2 > 0) { strcat(fancy, "."); ultoa(num2, number, 10); strcat(fancy, number); } mil = 1; } else if (num >= 1000000L) { num1 = (num / 1000000L); num2 = ((num % 1000000L) / 10000L); ultoa(num1, fancy, 10); if (num2 > 0) { strcat(fancy, "."); ultoa(num2, number, 10); strcat(fancy, number); } mil = 1; } else { ultoa(num, fancy, 10); } if(mil) strcat(fancy, " Million"); } */ void justify_int(unsigned int num) { if(num < 1000) cputc(' '); if(num < 100) cputc(' '); if(num < 10) cputc(' '); cprintulong(num); } void hide_cursor() { gotoxy(0,23); cputc(' '); } void update_guns() { revers(1); gotoxy(31, 1); justify_int(guns); revers(0); hide_cursor(); } void fight_stats(int ships, int orders) { cursor(0); gotoxy(0, 0); justify_int(ships); cputs(" ship"); if(ships != 1) cputc('s'); cputs(" attacking, Taipan! \r\n"); cputs("Your orders are to:"); if(orders == 1) cputs("Fight "); else if(orders == 2) cputs("Run "); else if(orders == 3) cputs("Throw Cargo"); hide_cursor(); } /* print an inverse video plus if there are offscreen ships, or clear it to a space if not. */ void plus_or_space(unsigned char b) { gotoxy(39, 15); cputc(b ? 0xab : ' '); hide_cursor(); } int sea_battle(int id, int num_ships) { /* These locals seem to eat too much stack and cause weird behaviour, so they're static now. */ static int ships_on_screen[10]; static int orders, num_on_screen, time, s0, ok, ik, i, input, status; char choice, flashctr; unsigned long amount, total; port_stat_dirty = 1; orders = 0; num_on_screen = 0; // time = ((year - 1860) * 12) + month; time = get_time(); s0 = num_ships; ok = 0; ik = 1; booty = (time / 4 * 1000 * num_ships) + randi()%1000 + 250; if(would_overflow(cash, booty)) { booty = 0L; } for(i = 0; i <= 9; i++) { ships_on_screen[i] = 0; } clrscr(); cursor(0); flushinp(); /* the static part of "we have N guns" display, gets printed only once per battle. Bloats the code by 30-odd bytes, but updates are smoother-looking. Maybe. */ revers(1); gotoxy(30, 0); cputs(" We have"); gotoxy(30, 1); cputs(" guns"); revers(0); update_guns(); fight_stats(num_ships, orders); while(num_ships > 0) { if(damage >= capacity) return 4; status = 100L - ((damage * 100L / capacity)); /* // I think this is a problem: if(status <= 0) { return 4; } */ flushinp(); gotoxy(0, 3); clrtoeol(); cputs("Current seaworthiness: "); cputs(st[status / 20]); cputs(" ("); cprintulong(status); cputs("%)"); gotoxy(0, 4); for(i = 0; i <= 9; i++) { if (num_ships > num_on_screen) { if (ships_on_screen[i] == 0) { jsleep(5); ships_on_screen[i] = (randi() % ec) + 20; draw_lorcha(i); num_on_screen++; } } } plus_or_space(num_ships > num_on_screen); gotoxy(0, 16); cputs("\r\n"); input = timed_getch(TMOUT_3S); /* using a switch() instead of a chain of if/else actually increases code size by 16 bytes! switch(orders) { case 'f': orders == 1; case 'r': orders == 2; case 't': orders == 3; default: break; } */ if(input == 'f') { orders = 1; } else if(input == 'r') { orders = 2; } else if (input == 't') { orders = 3; } if(orders == 0) { input = timed_getch(TMOUT_3S); if (input == 'f') { orders = 1; } else if(input == 'r') { orders = 2; } else if(input == 't') { orders = 3; } else { gotoxy(0, 3); clrtoeol(); cputs("Taipan, what shall we do??\r\n(Fight, Run, Throw cargo)"); under_attack_sound(); // timeout(-1); while ((input != 'f') && (input != 'r') && (input != 't')) { input = lcgetc(); } gotoxy(0, 3); clrtoeol(); gotoxy(0, 4); clrtoeol(); if(input == 'f') { orders = 1; } else if(input == 'r') { orders = 2; } else { orders = 3; } } } fight_stats(num_ships, orders); if((orders == 1) && (guns > 0)) { static int targeted, sk; sk = 0; ok = 3; ik = 1; gotoxy(0, 3); clrtoeol(); cputs("Aye, we'll fight 'em, Taipan."); timed_getch(TMOUT_3S); gotoxy(0, 3); clrtoeol(); cputs("We're firing on 'em, Taipan!"); timed_getch(TMOUT_1S); for(i = 1; i <= guns; i++) { if ((ships_on_screen[0] == 0) && (ships_on_screen[1] == 0) && (ships_on_screen[2] == 0) && (ships_on_screen[3] == 0) && (ships_on_screen[4] == 0) && (ships_on_screen[5] == 0) && (ships_on_screen[6] == 0) && (ships_on_screen[7] == 0) && (ships_on_screen[8] == 0) && (ships_on_screen[9] == 0)) { static int j; for (j = 0; j <= 9; j++) { if (num_ships > num_on_screen) { if(ships_on_screen[j] == 0) { ships_on_screen[j] = randclamp(ec) + 20; draw_lorcha(j); num_on_screen++; } } } } plus_or_space(num_ships > num_on_screen); gotoxy(0, 16); cputs("\r\n"); do { targeted = randi()%10; } while(ships_on_screen[targeted] == 0); /* flash_lorcha must be called an even number of times to leave the lorcha in an unflashed state after. */ for(flashctr = 0; flashctr < 6; flashctr++) { flash_lorcha(targeted); jsleep(2); } damage_lorcha(targeted); ships_on_screen[targeted] -= randi()%30 + 10; if(ships_on_screen[targeted] <= 0) { num_on_screen--; num_ships--; sk++; ships_on_screen[targeted] = 0; bad_joss_sound(); /* not sure this should be here */ sink_lorcha(targeted); plus_or_space(num_ships > num_on_screen); fight_stats(num_ships, orders); } if(num_ships == 0) { i += guns; } else { jsleep(10); } } gotoxy(0, 3); clrtoeol(); if(sk > 0) { cputs("Sunk "); cprintulong(sk); cputs(" of the buggers, Taipan!"); bad_joss_sound(); } else { cputs("Hit 'em, but didn't sink 'em, Taipan!"); } timed_getch(TMOUT_3S); // if ((randi()%s0 > (num_ships * .6 / id)) && (num_ships > 2)) if((randi()%s0 > ((num_ships / 2) / id)) && (num_ships > 2)) { static int ran; // ran = randi()%(num_ships / 3 / id) + 1; ran = randclamp(num_ships / 3 / id) + 1; num_ships -= ran; fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cprintulong(ran); cputs(" ran away, Taipan!"); bad_joss_sound(); if(num_ships <= 10) { for(i = 9; i >= 0; i--) { if ((num_on_screen > num_ships) && (ships_on_screen[i] > 0)) { ships_on_screen[i] = 0; num_on_screen--; clear_lorcha(i); jsleep(5); } } if(num_ships == num_on_screen) { plus_or_space(0); } } gotoxy(0, 16); timed_getch(TMOUT_3S); if(input == 'f') { orders = 1; } else if(input == 'r') { orders = 2; } else if(input == 't') { orders = 3; } } } else if ((orders == 1) && (guns == 0)) { gotoxy(0, 3); clrtoeol(); cputs("We have no guns, Taipan!!"); timed_getch(TMOUT_3S); } else if (orders == 3) { choice = 0; amount = 0; total = 0; gotoxy(0, 3); cputs("You have the following on board, Taipan:"); gotoxy(4, 4); cputs(item[0]); cputs(": "); cprintulong(hold_[0]); gotoxy(24, 4); cputs(item[1]); cputs(": "); cprintulong(hold_[1]); gotoxy(5, 5); cputs(item[2]); cputs(": "); cprintulong(hold_[2]); gotoxy(21, 5); cputs(item[3]); cputs(": "); cprintulong(hold_[3]); gotoxy(0, 6); clrtoeol(); cputs("What shall I throw overboard, Taipan? "); choice = get_item(1); if(choice < 4) { gotoxy(0, 6); clrtoeol(); cputs("How much, Taipan? "); amount = get_num(); if((hold_[choice] > 0) && ((amount == UINT32_MAX) || (amount > hold_[choice]))) { amount = hold_[choice]; } total = hold_[choice]; } else { total = hold_[0] + hold_[1] + hold_[2] + hold_[3]; } gotoxy(0, 4); clrtoeol(); gotoxy(0, 5); clrtoeol(); gotoxy(0, 6); clrtoeol(); if(total > 0) { gotoxy(0, 3); clrtoeol(); cputs("Let's hope we lose 'em, Taipan!"); bad_joss_sound(); if (choice < 4) { hold_[choice] -= amount; hold += amount; ok += (amount / 10); } else { hold_[0] = 0; hold_[1] = 0; hold_[2] = 0; hold_[3] = 0; hold += total; ok += (total / 10); } // gotoxy(0, 18); // clrtobot(); timed_getch(TMOUT_3S); } else { gotoxy(0, 3); clrtoeol(); cputs("There's nothing there, Taipan!"); good_joss_sound(); timed_getch(TMOUT_3S); } } if((orders == 2) || (orders == 3)) { if(orders == 2) { gotoxy(0, 3); clrtoeol(); cputs("Aye, we'll run, Taipan."); timed_getch(TMOUT_3S); } ok += ik++; if(randi()%ok > randi()%num_ships) { flushinp(); gotoxy(0, 3); clrtoeol(); cputs("We got away from 'em, Taipan!"); good_joss_sound(); timed_getch(TMOUT_3S); num_ships = 0; } else { gotoxy(0, 3); clrtoeol(); cputs("Couldn't lose 'em."); timed_getch(TMOUT_3S); if((num_ships > 2) && (randi()%5 == 0)) { static int lost; lost = (randi()%num_ships / 2) + 1; num_ships -= lost; fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cputs("But we escaped from "); cprintulong(lost); cputs(" of 'em!"); if(num_ships <= 10) { for(i = 9; i >= 0; i--) { if((num_on_screen > num_ships) && (ships_on_screen[i] > 0)) { ships_on_screen[i] = 0; num_on_screen--; clear_lorcha(i); jsleep(5); } } plus_or_space(num_ships > num_on_screen); } gotoxy(0, 16); timed_getch(TMOUT_3S); if(input == 'f') { orders = 1; } else if(input == 'r') { orders = 2; } else if(input == 't') { orders = 3; } } } } if(num_ships > 0) { gotoxy(0, 3); clrtoeol(); cputs("They're firing on us, Taipan!"); timed_getch(TMOUT_3S); flushinp(); /* screen flash doesn't change the hue bit of COLOR2 register, since we now support changing it on the title screen. */ for(i = 0; i < 3; i++) { unsigned char color = PEEK(710) & 0xf0; unsigned char textcolor = PEEK(709); POKE(709,0); POKE(710, color | 0x0c); jsleep(10); POKE(710, color & 0xf0); jsleep(10); POKE(709,textcolor); } fight_stats(num_ships, orders); /* XXX: I don't think this is needed at all! for(i = 0; i <= 9; i++) { if(ships_on_screen[i] > 0) { draw_lorcha(i); } } */ plus_or_space(num_ships > num_on_screen); gotoxy(0, 3); clrtoeol(); cputs("We've been hit, Taipan!!"); under_attack_sound(); timed_getch(TMOUT_3S); i = (num_ships > 15) ? 15 : num_ships; // is this really correct? // if ((guns > 0) && ((randi()%100 < (((float) damage / capacity) * 100)) || // ((((float) damage / capacity) * 100) > 80))) if((guns > 0) && ((randi()%100 < ((damage * 100L) / capacity)) || (((damage * 100L) / capacity)) > 80)) { i = 1; guns--; hold += 10; fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cputs("The buggers hit a gun, Taipan!!"); under_attack_sound(); fight_stats(num_ships, orders); update_guns(); timed_getch(TMOUT_3S); } // damage = damage + ((ed * i * id) * ((float) randi() / RAND_MAX)) + (i / 2); // remember, ed is now scaled by 2 (used to be 0.5, now 1) // broken because sometimes works out to 0 or 1. If it's 0, // randi()%0 is just randi() (ouch)... on a modern platform, // this would trigger a floating point exception or similar. // cc65 runtime can't detect it... // If ((ed * i * id)/2)) works out to 1, anything%1 is 0. // damage = damage + (randi() % ((ed * i * id)/2)) + (i / 2); // The answer is to avoid to % operator if the 2nd arg would be // 0 or 1: the intended result would just be 0 or 1 anyway. newdamage = ((ed * i * id)/2) + (i / 2); if(newdamage <= 0) newdamage = 1; // how the hell could this happen? if(newdamage > 1) newdamage = randi() % newdamage; damage += newdamage; if(damage > capacity) damage = capacity; /* just in case */ /* the above is still somehow broken. When fighting lots of ships, late in the game, we still get ship status over 100% in the fight screen, and mchenry says we're 4 billion percent damaged (and memory gets all kinds of corrupted after that). I do NOT understand what's going on here. It looks like damage is still somehow going negative, but that shouldn't be possible. */ if(damage < 0) damage = capacity; /* band-aid! */ if(damage == capacity) return 4; #ifdef DAMAGE_TEST gotoxy(0, 23); clrtoeol(); cprintulong(ed); cputc(' '); cprintulong(i); cputc(' '); cprintulong(id); cputc(' '); cprintulong(damage); cputc(' '); cprintulong(newdamage); cgetc(); #endif if((id == GENERIC) && (randi()%20 == 0)) { return 2; } } } if(orders == 1) { // clrscr(); fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cputs("We got 'em all, Taipan!"); bad_joss_sound(); timed_getch(TMOUT_3S); return 1; } else { return 3; } } /* TODO: rewrite in asm. Maybe. */ unsigned long get_num(void) { unsigned char count = 0; char input; cursor(1); cblank(1); while((input = numgetc()) != '\n') { if(count >= 10) continue; if(input == BKSP) { if(!count) continue; backspace(); num_buf[count] = '\0'; count--; } else if(input == 'a') { if(!count) return UINT32_MAX; } else if(input == 'k' || input == 'm') { char i; for(i = 0; i < (input == 'k' ? 3 : 6); i++) { cputc('0'); num_buf[count++] = '0'; if(count >= 10) break; } } else if(input == DEL) { while(count) { backspace(); count--; } POKEW(1024, count); } else { cputc(input); num_buf[count++] = input; } } cursor(0); num_buf[count] = '\0'; return strtoul(num_buf, (char **)NULL, 10); } /* TODO: rewrite in asm */ void cash_or_guns(void) { int choice = 0; clrscr(); cputs("Do you want to start . . .\r\n\r\n"); cputs(" 1) With cash (and a debt)\r\n\r\n"); cputs(" -- or --\r\n\r\n"); cputs(" 2) With five guns and no cash\r\n"); cputs(" (But no debt!)\r\n"); while ((choice != '1') && (choice != '2')) { gotoxy(10, 10); cursor(1); cputc('?'); choice = get_one(); cursor(0); } cputc(choice); capacity = 60; damage = 0; if (choice == '1') { cash = 400; debt = 5000; hold = 60; guns = 0; li = 0; bp = 10; } else { #ifdef TIMEWARP year = 1869; cash = 1000000000L; // cash = 4294000000L; // cash = 3500000L; #ifdef BIGNUM big_copy(bank, big1M); // memcpy(bank, big1M, 6); big_mul(bank, bank, big1M); #else bank = 1000000000L; #endif debt = 0; capacity = 1000; hold = 800; guns = 20; li = 1; bp = 7; ed = 9; ec = 90; #else cash = 0; debt = 0; hold = 10; guns = 5; li = 1; bp = 7; #endif #ifdef ALMOST_DEAD damage = capacity - 1; #endif } return; } void set_prices(void) { price[0] = base_price[0][port] / 2 * (randi()%3 + 1) * base_price[0][0]; price[1] = base_price[1][port] / 2 * (randi()%3 + 1) * base_price[1][0]; price[2] = base_price[2][port] / 2 * (randi()%3 + 1) * base_price[2][0]; price[3] = base_price[3][port] / 2 * (randi()%3 + 1) * base_price[3][0]; return; } void port_stats(void) { int i, in_use, status = 100 - ((damage * 100L) / capacity); /* all the static text that used to be in port_stats() has been moved to mkportstats.c, which creates a .xex file which will get prepended to taipan.xex and loaded into a chunk of memory cc65 won't use. When it's time to print it, it'll get memcpy()ed into *SAVMSC. */ #if 0 chlinexy(1, 1, 26); chlinexy(1, 7, 26); chlinexy(1, 13, 26); cvlinexy(0, 2, 5); cvlinexy(27, 2, 5); cvlinexy(0, 8, 5); cvlinexy(27, 8, 5); chlinexy(0, 15, 40); cputcxy(0, 1, 17); // upper left corner cputcxy(0, 7, 1); // |- cputcxy(0, 13, 26); // lower left corner cputcxy(27, 1, 5); // upper right corner cputcxy(27, 7, 4); // -| cputcxy(27, 13, 3); // lower right corner cputsxy(1, 2, "Hong Kong Warehouse"); cputsxy(4, 3, "Opium In use"); cputsxy(4, 4, "Silk "); cputsxy(4, 5, "Arms Vacant"); cputsxy(4, 6, "General "); cputsxy(1, 8, "Hold "); cputsxy(16, 8, "Guns "); cputsxy(4, 9, "Opium "); cputsxy(4, 10, "Silk "); cputsxy(4, 11, "Arms "); cputsxy(4, 12, "General "); cputsxy(32, 2, "Date"); cputsxy(29, 3, "15 "); cputsxy(30, 5, "Location"); cputsxy(32, 8, "Debt"); cputsxy(29, 11, "Ship Status"); cputsxy(0, 14, "Cash: "); cputsxy(20, 14, "Bank: "); #else if(port_stat_dirty) { void *p = (void *)(PEEK(88) + 256 * PEEK(89)); /* don't update the top of the screen while ANTIC is reading from it (prevents tearing)... but it doesn't work :( */ // waitvcount(84); memcpy(p, &port_stat_screen, 640); port_stat_dirty = 0; } #endif /* dynamic stuff: */ // waitvcount(84); cursor(0); gotoxy(firmpos, 0); cputs("Firm: "); cputs(firm); cputs(", Hong Kong"); gotoxy(21, 4); in_use = hkw_[0] + hkw_[1] + hkw_[2] + hkw_[3]; cblank(5); cprintulong(in_use); gotoxy(21, 6); cblank(5); cprintulong(10000 - in_use); gotoxy(12, 3); cblank(5); cprintulong(hkw_[0]); gotoxy(12, 4); cblank(5); cprintulong(hkw_[1]); gotoxy(12, 5); cblank(5); cprintulong(hkw_[2]); gotoxy(12, 6); cblank(5); cprintulong(hkw_[3]); gotoxy(7, 8); if (hold >= 0) { cblank(8); cprintulong(hold); } else { revers(1); cputs("Overload"); revers(0); } gotoxy(22, 8); cblank(5); cprintulong(guns); gotoxy(12, 9); cblank(9); cprintulong(hold_[0]); gotoxy(12, 10); cblank(9); cprintulong(hold_[1]); gotoxy(12, 11); cblank(9); cprintulong(hold_[2]); gotoxy(12, 12); cblank(9); cprintulong(hold_[3]); gotoxy(32, 3); revers(1); cputs(months[month - 1]); revers(0); cputc(' '); cprintulong(year); gotoxy(30, 6); cblank(10); revers(1); if(port == 4 || port == 5) cputc(' '); cputs(location[port]); revers(0); gotoxy(28, 9); // fancy_numbers(debt, fancy_num); // gotox(34 - strlen(fancy_num) / 2); clrtoeol(); // revers(1); cprintfancy_centered(debt); // cputs(fancy_num); // revers(0); gotoxy(29, 12); clrtoeol(); i = status / 20; if (i < 2) { revers(1); } cputs(st[i]); cputc(':'); cprintulong(status); revers(0); gotoxy(6, 14); cblank(14); cprintfancy(cash); gotoxy(26, 14); cblank(13); #ifdef BIGNUM cprintfancy_big(bank); #else cprintfancy(bank); #endif } void mchenry(void) { static int choice; compradores_report(); cputs("Taipan, Mc Henry from the Hong Kong\r\n"); cputs("Shipyards has arrived!! He says, 'I see\r\n"); cputs("ye've a wee bit of damage to yer ship.'\r\n"); cputs("Will ye be wanting repairs? "); choice = yngetc('y'); if(choice == 'y') { static int percent, time; static unsigned long br, repair_price, amount; // int percent = ((float) damage / capacity) * 100, percent = (damage * 100L / capacity); // time = ((year - 1860) * 12) + month; time = get_time(); /* long br = ((((60 * (time + 3) / 4) * (float) randi() / RAND_MAX) + 25 * (time + 3) / 4) * capacity / 50), repair_price = (br * damage) + 1, amount; */ br = ((randclamp(60 * (time + 3) / 4) + 25 * (time + 3) / 4) * capacity / 50); repair_price = (br * damage) + 1; clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Och, 'tis a pity to be "); cprintulong(percent); cputs("% damaged.\r\nWe can fix yer whole ship for "); cprintulong(repair_price); cputs(",\r\nor make partial repairs if you wish.\r\n"); cputs("How much will ye spend? "); for (;;) { gotoxy(24, 21); amount = get_num(); if(amount == UINT32_MAX) { if(cash > repair_price) amount = repair_price; else amount = cash; } if(amount <= cash) { cash -= amount; // damage -= (int)((amount / br) + .5); damage -= (int)(amount / br); damage = (damage < 0) ? 0 : damage; port_stats(); break; } } } return; } void retire_blanks(void) { cputs(" \r\n"); } #ifdef BIGNUM void aire(void) { bignum(networth); bignum(big1T) = BIG_1T; ulong_to_big(cash, networth); big_add(networth, networth, bank); if(big_cmp(networth, big1B) < 0) { cputs(" M I L L I O N A I R E ! \r\n"); } else if(big_cmp(networth, big1T) < 0) { cputs(" B I L L I O N A I R E ! \r\n"); } else { cputs(" T R I L L I O N A I R E ! \r\n"); } } #endif void retire(void) { cursor(0); compradores_report(); revers(1); retire_blanks(); cputs(" Y o u ' r e a \r\n"); retire_blanks(); #ifdef BIGNUM aire(); #else cputs(" M I L L I O N A I R E ! \r\n"); #endif retire_blanks(); revers(0); timed_getch(TMOUT_5S); final_stats(); } void final_stats(void) { int years = year - 1860, // time = ((year - 1860) * 12) + month, time = get_time(), choice = 0; #ifdef BIGNUM long score; bignum(finalcash); bignum(big_100) = BIG_100; bignum(bigscore); bignum(bigtmp); ulong_to_big(cash, finalcash); ulong_to_big(debt, bigtmp); big_add(finalcash, finalcash, bank); big_sub(finalcash, finalcash, bigtmp); big_div(bigscore, finalcash, big_100); ulong_to_big((unsigned long)time, bigtmp); big_div(bigscore, bigscore, bigtmp); if(big_cmp(bigscore, big1M) > 0) { score = 1000000L; } else if(big_cmp(bigscore, big0) < 0) { score = -1; } else { big_to_ulong(bigscore, (unsigned long*)&score); } #else /* TODO: write cprintlong() to print signed value */ long finalcash = cash + bank - debt; long score = finalcash / 100 / time; #endif port_stat_dirty = 1; clrscr(); cputs("Your final status:\r\n\r\n"); cputs("Net cash: "); #ifdef BIGNUM cprintfancy_big(finalcash); #else cprintfancy(finalcash); #endif cputs("\r\nShip size: "); cprintulong(capacity); cputs(" units with "); cprintulong(guns); cputs(" guns\r\n\r\n"); cputs("You traded for "); cprintulong(years); cputs(" year"); if (years != 1) { cputc('s'); } cputs(" and "); cprintulong(month); cputs(" month"); if (month > 1) { cputc('s'); } cputs("\r\n\r\n"); revers(1); cputs("Your score is "); #ifdef BIGNUM cprintfancy_big(bigscore); #else cprintulong(score); #endif cputs(".\r\n"); revers(0); if ((score < 100) && (score >= 0)) { cputs("Have you considered a land based job?\r\n\r\n\r\n"); } else if (score < 0) { cputs("The crew has requested that you stay on\r\n"); cputs("shore for their safety!!\r\n\r\n"); } else { cputs("\r\n\r\n\r\n"); } cputs("Your Rating:\r\n"); cputc(17); // upper left corner chline(31); cputc(5); // upper right corner cputs("\r\n"); cputc('|'); if (score > 49999L) { revers(1); } cputs("Ma Tsu"); revers(0); cputs(" 50,000 and over |\r\n"); cputc('|'); if ((score < 50000L) && (score > 7999L)) { revers(1); } cputs("Master Taipan"); revers(0); cputs(" 8,000 to 49,999|\r\n"); cputc('|'); if ((score < 8000L) && (score > 999L)) { revers(1); } cputs("Taipan"); revers(0); cputs(" 1,000 to 7,999|\r\n"); cputc('|'); if ((score < 1000) && (score > 499)) { revers(1); } cputs("Compradore"); revers(0); cputs(" 500 to 999|\r\n"); cputc('|'); if (score < 500) { revers(1); } cputs("Galley Hand"); revers(0); cputs(" less than 500|\r\n"); cputc(26); // lower left corner chline(31); cputc(3); // lower right corner gotoxy(0, 22); cputs("Play again? "); choice = yngetc(0); if(choice == 'y') { #ifdef BIGNUM big_copy(bank, big0); #else bank = 0; #endif hkw_[0] = 0; hkw_[1] = 0; hkw_[3] = 0; hkw_[4] = 0; hold_[0] = 0; hold_[1] = 0; hold_[2] = 0; hold_[3] = 0; hold = 0; capacity = 60; damage = 0; month = 1; year = 1860; port = 1; name_firm(); cash_or_guns(); set_prices(); return; } /* exit(0) works by itself in DOS 2.0S or 2.5, or any DUP.SYS style DOS that reopens the E: device when entering the menu. However, command-line DOSes (XL and Sparta) don't do this, which results in garbage on screen and wrong colors. So: */ /* restore CHBAS to its original value, generally the ROM font. This is called fontsave in newtitle.s. */ POKE(756, PEEK(0x6fc)); /* restore COLOR1 and COLOR2. These locations are called color1save and color2save in newtitle.s. */ POKE(709, PEEK(0x6fd)); POKE(710, PEEK(0x6fe)); exit(0); } void transfer(void) { /* you might think making i an unsigned char would save a few bytes of code... but it makes the code a little bigger instead! */ int i, in_use; unsigned long amount = 0; if ((hkw_[0] == 0) && (hold_[0] == 0) && (hkw_[1] == 0) && (hold_[1] == 0) && (hkw_[2] == 0) && (hold_[2] == 0) && (hkw_[3] == 0) && (hold_[3] == 0)) { gotoxy(0, 22); clrtobot(); cputs("You have no cargo, Taipan.\r\n"); good_joss_sound(); timed_getch(TMOUT_5S); return; } for (i = 0; i < 4; i++) { if (hold_[i] > 0) { for (;;) { compradores_report(); cputs("How much "); cputs(item[i]); cputs(" shall I move\r\nto the warehouse, Taipan? "); amount = get_num(); if (amount == UINT32_MAX) { amount = hold_[i]; } if (amount <= hold_[i]) { in_use = hkw_[0] + hkw_[1] + hkw_[2] + hkw_[3]; if ((in_use + amount) <= 10000) { hold_[i] -= amount; hkw_[i] += amount; hold += amount; break; } else if (in_use == 10000) { gotoxy(0, 21); cputs("Your warehouse is full, Taipan!"); good_joss_sound(); } else { gotoxy(0, 21); cputs("Your warehouse will only hold an\r\nadditional "); cprintulong(10000 - in_use); cputs(", Taipan!"); good_joss_sound(); timed_getch(TMOUT_5S); } } else { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("You have only "); cprintulong(hold_[i]); cputs(", Taipan.\r\n"); good_joss_sound(); timed_getch(TMOUT_5S); } } port_stats(); } if (hkw_[i] > 0) { for (;;) { compradores_report(); cputs("How much "); cputs(item[i]); cputs(" shall I move\r\naboard ship, Taipan? "); amount = get_num(); if (amount == UINT32_MAX) { amount = hkw_[i]; } if (amount <= hkw_[i]) { hold_[i] += amount; hkw_[i] -= amount; hold -= amount; break; } else { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("You have only "); cprintulong(hkw_[i]); cputs(", Taipan.\r\n"); timed_getch(TMOUT_5S); } } port_stats(); } } return; } void quit(void) { #ifdef BIGNUM bignum(banktmp); #endif unsigned char choice, sunk; int result = 0, damagepct; compradores_report(); cputs("Taipan, do you wish me to go to:\r\n"); cputs("1) Hong Kong, 2) Shanghai, 3) Nagasaki,\r\n"); cputs("4) Saigon, 5) Manila, 6) Singapore, or\r\n"); cputs("7) Batavia ? "); for (;;) { gotoxy(13, 21); clrtobot(); choice = numgetc() - '0'; if (choice == port) { cputs("\r\n\nYou're already here, Taipan."); good_joss_sound(); timed_getch(TMOUT_5S); } else if ((choice >= 1) && (choice <= 7)) { port = choice; break; } } at_sea(); captains_report(); #ifdef COMBAT_TEST if(1) #else if (randi()%bp == 0) #endif { int num_ships = randi()%((capacity / 10) + guns) + 1; if (num_ships > 9999) { num_ships = 9999; } cprintulong(num_ships); cputs(" hostile ship"); if(num_ships != 1) cputc('s'); cputs(" approaching, Taipan!\r\n"); under_attack_sound(); timed_getch(TMOUT_3S); result = sea_battle(GENERIC, num_ships); gotoxy(0,23); /* to avoid disappearing U in "In use" */ } if (result == 2) { port_stats(); at_sea(); captains_report(); cputs("Li Yuen's fleet drove them off!"); timed_getch(TMOUT_3S); } if (((result == 0) && (randi()%(4 + (8 * li))) == 0) || (result == 2)) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Li Yuen's pirates, Taipan!!\r\n\n"); bad_joss_sound(); timed_getch(TMOUT_3S); if (li > 0) { cputs("Good joss!! They let us be!!\r\n"); bad_joss_sound(); timed_getch(TMOUT_3S); return; } else { static int num_ships; num_ships = randi()%((capacity / 5) + guns) + 5; cprintulong(num_ships); cputs(" ships of Li Yuen's pirate\r\n"); cputs("fleet, Taipan!!\r\n"); under_attack_sound(); timed_getch(TMOUT_3S); /* WTF, the original code reads: sea_battle(LI_YUEN, num_ships); ...which seems to mean you die if you succeed in running away from li yuen's pirates... */ result = sea_battle(LI_YUEN, num_ships); gotoxy(0,23); /* to avoid disappearing U in "In use" */ } } if (result > 0) { port_stats(); at_sea(); captains_report(); if (result == 1) { // fancy_numbers(booty, fancy_num); cputs("We captured some booty.\r\n"); cputs("It's worth "); // cputs(fancy_num); cprintfancy(booty); cputc('!'); cash += booty; good_joss_sound(); } else if (result == 3) { cputs("We made it!"); good_joss_sound(); } else { cputs("The buggers got us, Taipan!!!\r\n"); cputs("It's all over, now!!!"); timed_getch(TMOUT_5S); final_stats(); return; } timed_getch(TMOUT_3S); } if (randi()%10 == 0) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Storm, Taipan!!\r\n\n"); bad_joss_sound(); timed_getch(TMOUT_3S); if (randi()%30 == 0) { cputs(" I think we're going down!!\r\n\n"); timed_getch(TMOUT_3S); // if (((damage / capacity * 3) * ((float) randi() / RAND_MAX)) >= 1) // in the float version, damage/capacity*3 is your damage percentage, // scaled 0 (0%) to 3 (100%). So if you're less than 34% damaged, // you have no chance of sinking. If you're 34%-66% damaged, you // have a 1 in 3 chance. If you're over 66%, you have a 2 in // 3 chance. damagepct = damage * 100L / capacity; if(damagepct < 34) sunk = 0; else if(damagepct < 67) sunk = randclamp(3) == 0; else sunk = randclamp(3) != 0; if(sunk) { cputs("We're going down, Taipan!!\r\n"); under_attack_sound(); timed_getch(TMOUT_5S); final_stats(); } } cputs(" We made it!!\r\n\n"); bad_joss_sound(); timed_getch(TMOUT_3S); if (randi()%3 == 0) { int orig = port; while (port == orig) { port = randi()%7 + 1; } clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("We've been blown off course\r\nto "); cputs(location[port]); timed_getch(TMOUT_3S); } } month++; if (month == 13) { month = 1; year++; ec += 10; ed += 1; } /* debt calculation original formula was: debt = debt + (debt * .1); int-based formula is the same, except it would never increase if debt is <= 10, so we fudge it with debt++ in that case. Which means small debts accrue interest *much* faster, but that shouldn't affect gameplay much. There needs to be some overflow detection though... or maybe we let the overflow through, and the player can think of it as Wu forgiving the debt after enough years go by (or, he lost the paperwork?). Most likely though, the player gets his throat cut long before the amount overflows. */ if(debt) { if(debt > 10) debt += (debt / 10); else debt++; } if(debt >= DEBT_MAX) wu_assassin = 1; #ifdef BIGNUM // no good, assumes a bignum can handle a fraction // big_mul(bank, bank, interest_rate); // bank = bank + (bank / 200); big_div(banktmp, bank, interest_denom); big_add(bank, bank, banktmp); #else /* bank calculation original formula was: bank = bank + (bank * .005); int-based formula is the same, except when bank <= 200, it's linear. */ if(bank) { if(bank > 200) bank += (bank / 200); else bank++; } #endif set_prices(); clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Arriving at "); cputs(location[port]); cputs("..."); timed_getch(TMOUT_3S); return; } void li_yuen_extortion(void) { // int time = ((year - 1860) * 12) + month; int time = get_time(); unsigned char choice; /* float i = 1.8, j = 0, amount = 0; */ unsigned long amount = 0; unsigned int i = 2, j = 0; if(time > 12) { j = randi() % (1000 * time) + (1000 * time); i = 1; } // amount = ((cash / i) * ((float) randi() / RAND_MAX)) + j; amount = randclamp((cash >> (i - 1))) + j; /* clear_msg_window(); cputs("DEBUG li_yuen_extortion()\r\n"); cputs("amount time i j\r\n"); cprintulong(amount); cputc(' '); cprintulong(time); cputc(' '); cprintulong(i); cputc(' '); cprintulong(j); agetc(); */ if(!amount) return; /* asking for 0 is dumb */ compradores_report(); cputs("Li Yuen asks "); cprintfancy(amount); cputs(" in donation\r\nto the temple of Tin Hau, the Sea\r\n"); cputs("Goddess. Will you pay? "); choice = yngetc(0); if(choice == 'y') { if(amount <= cash) { cash -= amount; li = 1; } else { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Taipan, you do not have enough cash!!\r\n\r\n"); timed_getch(TMOUT_3S); cputs("Do you want Elder Brother Wu to make up\r\n"); cputs("the difference for you? "); choice = yngetc(0);; clear_msg_window(); // gotoxy(0, 18); // clrtobot(); if(choice == 'y') { amount -= cash; debt += amount; cash = 0; li = 1; cputs("Elder Brother has given Li Yuen the\r\n"); cputs("difference between what he wanted and\r\n"); cputs("your cash on hand and added the same\r\n"); cputs("amount to your debt.\r\n"); } else { cash = 0; cputs("Very well. Elder Brother Wu will not pay\r\n"); cputs("Li Yuen the difference. I would be very\r\n"); cputs("wary of pirates if I were you, Taipan.\r\n"); } timed_getch(TMOUT_5S); } } port_stats(); } #ifdef BIGNUM void you_only_have(unsigned char in_bank) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Taipan, you only have "); if(in_bank) cprintfancy_big(bank); else cprintfancy(cash); cputs("\r\nin "); cputs(in_bank ? "the bank" : "cash"); cputs(".\r\n"); good_joss_sound(); timed_getch(TMOUT_5S); } #else void you_only_have(unsigned char in_bank) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Taipan, you only have "); cprintfancy(in_bank ? bank : cash); cputs("\r\nin "); cputs(in_bank ? "the bank" : "cash"); cputs(".\r\n"); good_joss_sound(); timed_getch(TMOUT_5S); } #endif void elder_brother_wu(void) { int choice = 0; unsigned long wu = 0; compradores_report(); cputs("Do you have business with Elder Brother\r\n"); cputs("Wu, the moneylender? "); for (;;) { gotoxy(21, 19); // choice = get_one(); choice = yngetc('n'); if ((choice == 'n') || choice == 0) { break; } else if (choice == 'y') { if (((int)cash == 0) && #ifdef BIGNUM (bank[0] == '\0') #else ((int)bank == 0) #endif && (guns == 0) && (hold_[0] == 0) && (hkw_[0] == 0) && (hold_[1] == 0) && (hkw_[1] == 0) && (hold_[2] == 0) && (hkw_[2] == 0) && (hold_[3] == 0) && (hkw_[3] == 0)) { int i = randi()%1500 + 500, j; wu_bailout++; j = randi()%2000 * wu_bailout + 1500; for (;;) { compradores_report(); cputs("Elder Brother is aware of your plight,\r\n"); cputs("Taipan. He is willing to loan you an\r\n"); cputs("additional "); cprintulong(i); cputs(" if you will pay back\r\n"); cprintulong(j); cputs(". Are you willing, Taipan? "); choice = get_one(); if (choice == 'n') { compradores_report(); cputs("Very well, Taipan, the game is over!\r\n"); under_attack_sound(); timed_getch(TMOUT_5S); final_stats(); } else if (choice == 'y') { cash += i; debt += j; port_stats(); compradores_report(); cputs("Very well, Taipan. Good joss!!\r\n"); bad_joss_sound(); timed_getch(TMOUT_5S); return; } } } else if ((cash > 0) && (debt != 0)) { for (;;) { compradores_report(); cputs("How much do you wish to repay\r\n"); cputs("him? "); wu = get_num(); if (wu == UINT32_MAX) { wu = cash; } if (wu <= cash) { if(wu > debt) wu = debt; cash -= wu; debt -= wu; /* // currently debt is unsigned so the negative debt // bug (or feature) is unimplemented. if ((wu > debt) && (debt > 0)) { debt -= (wu + 1); } else { debt -= wu; } */ break; } else { you_only_have(0); } } } port_stats(); for (;;) { compradores_report(); cputs("How much do you wish to\r\n"); cputs("borrow? "); wu = get_num(); if(wu == UINT32_MAX) { wu = (cash * 2); } if((wu <= (cash * 2)) && !would_overflow(cash, wu)) { cash += wu; debt += wu; break; } else { cputs("\r\n\r\nHe won't loan you so much, Taipan!"); good_joss_sound(); timed_getch(TMOUT_5S); } } port_stats(); // break; /* do NOT let him steal the money back on the SAME TURN he loans it to you! */ return; } } if ((debt > 20000) && (cash > 0) && (randi()%5 == 0)) { int num = randi()%3 + 1; cash = 0; port_stats(); compradores_report(); cputs("Bad joss!!\r\n"); cprintulong(num); cputs(" of your bodyguards have been killed\r\n"); cputs("by cutthroats and you have been robbed\r\n"); cputs("of all of your cash, Taipan!!\r\n"); under_attack_sound(); timed_getch(TMOUT_5S); } return; } void good_prices(void) { // static char item[14]; unsigned char i = randi()%4; /* int i = randi()%4, j = randi()%2; */ /* if (i == 0) { strcpy(item, "Opium"); } else if (i == 1) { strcpy(item, "Silk"); } else if (i == 2) { strcpy(item, "Arms"); } else { strcpy(item, "General Cargo"); } */ compradores_report(); cputs("Taipan!! The price of "); // cputs(item); cputs(item[i]); cputs("\r\n has "); if(randi()&1) { price[i] *= (randi()%5 + 5); cputs("risen"); } else { price[i] /= 5; /* somehow general cargo dropped to 0 once. stop it. */ if(price[i] < 1) price[i] = 1; cputs("dropped"); } cputs(" to "); /* if (j == 0) { price[i] = price[i] / 5; cputs("dropped"); } else { price[i] = price[i] * (randi()%5 + 5); cputs("risen"); } cputs(" to "); */ cprintulong(price[i]); cputs("!!\r\n"); good_joss_sound(); timed_getch(TMOUT_3S); } int port_choices(void) { int choice = 0; char retire_ok = 0; compradores_report(); cputs("Taipan, present prices per unit here are"); /* NB: exactly 40 cols */ cputs(" Opium: Silk:\r\n"); cputs(" Arms: General:\r\n"); gotoxy(11, 19); cprintulong(price[0]); gotoxy(29, 19); cprintulong(price[1]); gotoxy(11, 20); cprintulong(price[2]); gotoxy(29, 20); cprintulong(price[3]); gotoxy(0, 22); clrtobot(); cursor(0); #ifdef BIGNUM // TODO: make this smaller! if(port == 1) { if(cash > 1000000L) { retire_ok = 1; } else if(big_cmp(bank, big1M) >= 0) { retire_ok = 1; } else { bignum(tmp); ulong_to_big(cash, tmp); big_add(tmp, tmp, bank); if(big_cmp(tmp, big1M) >= 0) retire_ok = 1; } } #else retire_ok = (port == 1 && ((cash + bank) >= 1000000L)); #endif cputs("Shall I Buy, Sell, "); if(port ==1) { cputs("Visit bank, Transfer\r\ncargo, "); } if(!retire_ok) cputs("or "); cputs("Quit trading"); if(retire_ok) cputs(", or Retire"); cursor(1); cputs("? "); for(;;) { choice = lcgetc(); if(choice == 'b' || choice == 's' || choice == 'q') break; if(port == 1) { if(retire_ok && choice == 'r') break; if(choice == 't' || choice == 'v') break; } } cursor(0); return choice; } /* TODO: rewrite in asm, or at least better C */ void name_firm(void) { unsigned char input, firmlen = 0; cursor(0); clrscr(); chlinexy(1, 7, 38); chlinexy(1, 16, 38); cvlinexy(0, 8, 8); cvlinexy(39, 8, 8); cputcxy(0, 7, 17); // upper left corner cputcxy(0, 16, 26); // lower left corner cputcxy(39, 7, 5); // upper right corner cputcxy(39, 16, 3); // lower right corner gotoxy(6, 9); cputs("Taipan,"); gotoxy(2, 11); cputs("What will you name your"); gotoxy(6, 13); cursor(1); cputs("Firm:"); chlinexy(12, 14, 22); gotoxy(12, 13); while((input = agetc()) && (firmlen < 22)) { if(input == ENTER) { if(firmlen) break; else bad_joss_sound(); } else if(input == BKSP) { if(firmlen) { backspace(); firm[firmlen--] = '\0'; } } else { cputc(input); firm[firmlen++] = input; } } cursor(0); firm[firmlen] = '\0'; firmpos = 12 - firmlen / 2; return; } void buy(void) { int choice = 0; unsigned long afford, amount; gotoxy(0, 22); clrtobot(); cputs("What do you wish me to buy, Taipan? "); choice = get_item(0); /* for (;;) { gotoxy(0, 22); clrtobot(); cputs("What do you wish me to buy, Taipan? "); choice = tolower(get_one()); if(choice == 'o') { choice = 0; break; } else if (choice == 's') { choice = 1; break; } else if (choice == 'a') { choice = 2; break; } else if (choice == 'g') { choice = 3; break; } } */ for (;;) { gotoxy(31, 21); clrtobot(); afford = cash / price[choice]; revers(1); cputs(" You can "); revers(0); gotoxy(0, 22); cputs("How much "); cputs(item[choice]); cputs(" shall"); gotoxy(31, 22); revers(1); cputs(" afford "); gotoxy(31, 23); cputs(" "); gotoxy(31, 23); if(afford < 100) cputc(' '); if(afford < 10000) cputc(' '); if(afford < 1000000) cputc(' '); if(afford < 100000000) cputc(' '); cprintulong(afford); revers(0); gotoxy(0, 23); cputs("I buy, Taipan: "); amount = get_num(); if(amount == UINT32_MAX) { amount = afford; } if(amount <= afford) { break; } } cash -= (amount * price[choice]); hold_[choice] += amount; hold -= amount; return; } void sell(void) { int choice = 0; unsigned long amount; gotoxy(0, 22); clrtobot(); cputs("What do you wish me to sell, Taipan? "); choice = get_item(0); /* for (;;) { gotoxy(0, 22); clrtobot(); cputs("What do you wish me to sell, Taipan? "); choice = tolower(get_one()); if(choice == 'o') { choice = 0; break; } else if(choice == 's') { choice = 1; break; } else if(choice == 'a') { choice = 2; break; } else if(choice == 'g') { choice = 3; break; } } */ for (;;) { gotoxy(0, 22); clrtobot(); cputs("How much "); cputs(item[choice]); cputs(" shall\r\n"); cputs("I sell, Taipan: "); amount = get_num(); if (amount == UINT32_MAX) { amount = hold_[choice]; } if(would_overflow(cash, amount * price[choice])) { too_much_cash(); continue; } if (hold_[choice] >= amount) { hold_[choice] -= amount; break; } } cash += (amount * price[choice]); hold += amount; return; } char would_overflow(unsigned long a, unsigned long b) { return ((UINT32_MAX - b) <= a); } void too_much_cash(void) { clear_msg_window(); cputs("\r\nYou cannot carry so much cash, Taipan!"); cputs("\r\nYour ship would sink under the weight"); cputs("\r\nof your riches.\r\n"); bad_joss_sound(); timed_getch(TMOUT_3S); } void visit_bank(void) { unsigned long amount = 0; #ifdef BIGNUM bignum(bigamt); bignum(biglimit); bignum(bigcash); #endif for (;;) { compradores_report(); cputs("How much will you deposit? "); amount = get_num(); if (amount == UINT32_MAX) { amount = cash; } if (amount <= cash) { cash -= amount; #ifdef BIGNUM ulong_to_big(amount, bigamt); big_add(bank, bank, bigamt); #else bank += amount; #endif break; } else { you_only_have(0); } } port_stats(); for (;;) { compradores_report(); cputs("How much will you withdraw? "); amount = get_num(); #ifdef BIGNUM if(amount == UINT32_MAX) { // memcpy(bigamt, bank, 6); big_copy(bigamt, bank); } else { ulong_to_big(amount, bigamt); } ulong_to_big(cash, bigcash); big_sub(biglimit, big_max_ulong, bigcash); /* cputs("\rcash "); cprintfancy_big(bigcash); agetc(); cputs("\ramt "); cprintfancy_big(bigamt); agetc(); cputs("\rlimit "); cprintfancy_big(biglimit); agetc(); */ if(big_cmp(bigamt, biglimit) >= 0) { too_much_cash(); continue; } if(big_cmp(bank, bigamt) < 0) { you_only_have(1); } else { big_sub(bank, bank, bigamt); big_add(bigcash, bigcash, bigamt); big_to_ulong(bigcash, &cash); break; } #else if (amount == UINT32_MAX) { amount = bank; } if (amount <= bank) { cash += amount; bank -= amount; break; } else { you_only_have(1); } #endif } port_stats(); return; } #ifdef BIGNUM_TEST void bignum_test(void) { int i; bignum(n); bignum(o); ulong_to_big(1L, n); ulong_to_big(11L, o); for(i = 0; i < 14; i++) { cprintfancy_big(n); cputc(' '); big_negate(n); cprintfancy_big(n); cputs("\r\n"); big_mul(n, n, o); } agetc(); ulong_to_big(1100000L, n); cprintfancy_big(n); ulong_to_big(1010000L, n); cprintfancy_big(n); ulong_to_big(1001000L, n); cprintfancy_big(n); hangx: goto hangx; } #endif /* N.B. cc65 is perfectly OK with main(void), and it avoids warnings about argv/argc unused. */ int main(void) { int choice; #ifdef MCHENRY_TEST { while(1) { clrscr(); cputs("dmg? "); damage = get_num(); cputs("\r\n"); cputs("cap? "); capacity = get_num(); mchenry(); } } #endif atari_text_setup(); #ifdef BIGNUM_TEST bignum_test(); #endif name_firm(); initrand(); cash_or_guns(); set_prices(); for (;;) { choice = 0; port_stats(); /* clear_msg_window(); cputs("li "); cprintulong(li); agetc(); */ if(wu_assassin) { wu_assassin = 0; compradores_report(); cputs("Taipan, you have been assassinated!"); under_attack_sound(); timed_getch(TMOUT_3S); compradores_report(); cputs("As the masked figure plunges the blade\r\n"); cputs("into your heart, he says:\r\n"); timed_getch(TMOUT_3S); compradores_report(); cputs("Elder Brother Wu regrets to inform you\r\n"); cputs("that your account has been terminated\r\n"); cputs("with extreme prejudice."); timed_getch(TMOUT_3S); final_stats(); } if ((port == 1) && (li == 0) && (cash > 0)) { li_yuen_extortion(); } if ((port == 1) && (damage > 0)) { mchenry(); } if ((port == 1) && (debt >= 10000) && (wu_warn == 0)) { int braves = randi()%100 + 50; compradores_report(); cputs("Elder Brother Wu has sent "); cprintulong(braves); cputs(" braves\r\n"); cputs("to escort you to the Wu mansion, Taipan.\r\n"); timed_getch(TMOUT_3S); clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Elder Brother Wu reminds you of the\r\n"); cputs("Confucian ideal of personal worthiness,\r\n"); cputs("and how this applies to paying one's\r\n"); cputs("debts.\r\n"); timed_getch(TMOUT_3S); clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("He is reminded of a fabled barbarian\r\n"); cputs("who came to a bad end, after not caring\r\n"); cputs("for his obligations.\r\n\r\n"); cputs("He hopes no such fate awaits you, his\r\n"); cputs("friend, Taipan.\r\n"); timed_getch(TMOUT_5S); wu_warn = 1; } if (port == 1) { elder_brother_wu(); } if (randi()%4 == 0) { if (randi()%2 == 0) { new_ship(); } else if (guns < 1000) { new_gun(); } } if ((port != 1) && (randi()%18 == 0) && (hold_[0] > 0)) { // float fine = ((cash / 1.8) * ((float) randi() / RAND_MAX)) + 1; // the 1.8 is now a 2 unsigned long fine = 0; if(cash > 0) fine = randclamp(cash >> 1) + 1; hold += hold_[0]; hold_[0] = 0; cash -= fine; port_stats(); // fancy_numbers(fine, fancy_num); compradores_report(); cputs("Bad Joss!!\r\n"); cputs("The local authorities have seized your\r\n"); cputs("Opium cargo and have also fined you\r\n"); // cputs(fancy_num); cprintfancy(fine); cputs(", Taipan!\r\n"); timed_getch(TMOUT_5S); } if ((randi()%50 == 0) && ((hkw_[0] + hkw_[1] + hkw_[2] + hkw_[3]) > 0)) { int i; for (i = 0; i < 4; i++) { // hkw_[i] = ((hkw_[i] / 1.8) * ((float) randi() / RAND_MAX)); // the 1.8 is now a 2 hkw_[i] = randclamp(hkw_[i] >> 1); } port_stats(); compradores_report(); cputs("Messenger reports large theft\r\n"); cputs("from warehouse, Taipan.\r\n"); timed_getch(TMOUT_5S); } if (randi()%20 == 0) { if (li > 0) { li++; } if (li == 4) { li = 0; } } if ((port != 1) && (li == 0) && (randi()%4 != 0)) { compradores_report(); cputs("Li Yuen has sent a Lieutenant,\r\n"); cputs("Taipan. He says his admiral wishes\r\n"); cputs("to see you in Hong Kong, posthaste!\r\n"); bad_joss_sound(); timed_getch(TMOUT_3S); } if (randi()%9 == 0) { good_prices(); } if ((cash > 25000) && (randi()%20 == 0)) { // float robbed = ((cash / 1.4) * ((float) randi() / RAND_MAX)); // line below changes the 1.4 to 1.5 unsigned long robbed = randclamp((cash >> 2) + (cash >> 1)); cash -= robbed; port_stats(); // fancy_numbers(robbed, fancy_num); compradores_report(); cputs("Bad Joss!!\r\n"); cputs("You've been beaten up and\r\n"); cputs("robbed of "); // cputs(fancy_num); cprintfancy(robbed); cputs(" in cash, Taipan!!\r\n"); under_attack_sound(); timed_getch(TMOUT_5S); } for (;;) { while ((choice != 'Q') && (choice != 'q')) { switch (choice = port_choices()) { case 'B': case 'b': buy(); break; case 'S': case 's': sell(); break; case 'V': case 'v': visit_bank(); break; case 'T': case 't': transfer(); break; case 'R': case 'r': retire(); } port_stats(); } choice = 0; if (hold >= 0) { quit(); break; } else { overload(); } } } cgetc(); POKE(709, 0); hangmain: goto hangmain; return 0; }