Make m m .1 work again.
[super-star-trek.git] / sst.py
diff --git a/sst.py b/sst.py
index 3c985160fba4019a27ce677f09b12dd9040f7eb4..7c7d075a8c9527a484e089dcfd070ae77badfade 100755 (executable)
--- a/sst.py
+++ b/sst.py
@@ -21,6 +21,13 @@ try:
 except ImportError:
     pass
 
+# Prevent lossage under Python 3
+try:
+    my_input = raw_input
+except NameError:
+    my_input = input
+
+
 version = "2.1"
 
 docpath        = (".", "doc/", "/usr/share/doc/sst/")
@@ -43,6 +50,9 @@ MINCMDR       = 10            # Minimum number of Klingon commanders
 DOCKFAC                = 0.25          # Repair faster when docked
 PHASEFAC       = 2.0           # Unclear what this is, it was in the C version
 
+ALGERON                = 2311          # Date of the Treaty of Algeron
+
+
 DEFAULT      = -1
 BLACK        = 0
 BLUE         = 1
@@ -225,10 +235,11 @@ OPTION_BLKHOLE    = 0x00000200        # black hole may timewarp you (Stas, 2005)
 OPTION_BASE       = 0x00000400        # bases have good shields (Stas, 2005)
 OPTION_WORLDS     = 0x00000800        # logic for inhabited worlds (ESR, 2006)
 OPTION_AUTOSCAN   = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
+OPTION_CAPTURE    = 0x00002000        # Enable BSD-Trek capture (Almy, 2013).
+OPTION_CLOAK      = 0x80004000        # Enable BSD-Trek capture (Almy, 2013).
 OPTION_PLAIN      = 0x01000000        # user chose plain game
 OPTION_ALMY       = 0x02000000        # user chose Almy variant
 OPTION_COLOR      = 0x04000000        # enable color display (ESR, 2010)
-OPTION_CAPTURE    = 0x80000000        # Enable BSD-Trek capture (Almy, 2013).
 
 # Define devices
 DSRSENS         = 0
@@ -239,15 +250,16 @@ DLIFSUP         = 4
 DWARPEN         = 5
 DIMPULS         = 6
 DSHIELD         = 7
-DRADIO         = 0
-DSHUTTL  = 9
-DCOMPTR  = 10
+DRADIO          = 8
+DSHUTTL         = 9
+DCOMPTR         = 10
 DNAVSYS         = 11
-DTRANSP  = 12
+DTRANSP         = 12
 DSHCTRL         = 13
-DDRAY         = 14
-DDSP         = 15
-NDEVICES = 16        # Number of devices
+DDRAY           = 14
+DDSP            = 15
+DCLOAK          = 16
+NDEVICES        = 17        # Number of devices
 
 SKILL_NONE        = 0
 SKILL_NOVICE        = 1
@@ -398,6 +410,9 @@ class Gamestate:
         self.brigcapacity = 400     # Enterprise brig capacity
         self.brigfree = 400       # How many klingons can we put in the brig?
         self.kcaptured = 0      # Total Klingons captured, for scoring.
+        self.iscloaked = False  # Cloaking device on?
+        self.ncviol = 0         # Algreon treaty violations
+        self.isviolreported = False # We have been warned
     def recompute(self):
         # Stas thinks this should be (C expression):
         # game.state.remkl + len(game.state.kcmdr) > 0 ?
@@ -429,6 +444,7 @@ FDRAY = 18
 FTRIBBLE = 19
 FHOLE = 20
 FCREW = 21
+FCLOAK = 22
 
 def withprob(p):
     return random.random() < p
@@ -906,6 +922,85 @@ def movetholian():
 
 # Code from battle.c begins here
 
+def cloak():
+    "Change cloaking-device status."
+    if game.ship == 'F':
+        prout(_("Ye Faerie Queene hath no cloaking device."));
+        return
+
+    key = scanner.nexttok()
+
+    if key == "IHREAL":
+        return
+
+    action = None
+    if key == "IHALPHA":
+        if scanner.sees("on"):
+            if game.iscloaked:
+                prout(_("The cloaking device has already been switched on."))
+                return
+            action = "CLON"
+        elif scanner.sees("off"):
+            if not game.iscloaked:
+                prout(_("The cloaking device has already been switched off."))
+                return
+            action = "CLOFF"
+        else:
+            huh()
+            return
+    else:
+        if not game.iscloaked:
+            proutn(_("Switch cloaking device on? "))
+            if not ja():
+                return
+            action = "CLON"
+        else:
+            proutn(_("Switch cloaking device off? "))
+            if not ja():
+                return
+            action = "CLOFF"
+    if action == None:
+        return;
+
+    if action == "CLOFF":
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("Spock- \"Captain, the Treaty of Algeron is in effect.\n   Are you sure this is wise?\""))
+            if not ja():
+                return;
+        prout("Engineer Scott- \"Aye, Sir.\"");
+        game.iscloaked = False;
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
+            game.ncviol += 1
+            game.isviolreported = True
+
+            #if (neutz and game.state.date >= ALGERON) finish(FCLOAK);
+            return;
+
+    if action == "CLON":
+        if damaged(DCLOAK):
+            prout(_("Engineer Scott- \"The cloaking device is damaged, Sir.\""))
+            return;
+
+        if game.condition == "docked":
+            prout(_("You cannot cloak while docked."))
+
+       if game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("Spock- \"Captain, using the cloaking device is a violation"))
+            prout(_("  of the Treaty of Algeron. Considering the alternatives,"))
+            proutn(_("  are you sure this is wise? "))
+            if not ja():
+                return
+       prout(_("Engineer Scott- \"Cloaking device has engaging, Sir...\""))
+        attack(True)
+       prout(_("Engineer Scott- \"Cloaking device has engaged, Sir.\""))
+       game.iscloaked = True
+
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
+            prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
+            game.ncviol += 1
+            game.isviolreported = True
+
 def doshield(shraise):
     "Change shield status."
     action = "NONE"
@@ -1031,6 +1126,7 @@ def randdevice():
         20,        # DSHCTRL: high-speed shield controller  2.0%
         10,        # DDRAY: death ray                         1.0%
         30,        # DDSP: deep-space probes                 3.0%
+        0,         # DCLOAK: the cloaking device             0.0
     )
     assert(sum(weights) == 1000)
     idx = randrange(1000)
@@ -1297,7 +1393,7 @@ def fry(hit):
         while True:
             j = randdevice()
             # Cheat to prevent shuttle damage unless on ship
-            if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship")):
+            if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship") or (j == DCLOAK and game.ship != 'E' or j == DDRAY)):
                 break
         cdam.append(j)
         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
@@ -1315,10 +1411,15 @@ def fry(hit):
     if damaged(DSHIELD) and game.shldup:
         prout(_("***Shields knocked down."))
         game.shldup = False
+    if damaged(DCLOAK) and game.iscloaked:
+        prout(_("***Cloaking device rendered inoperative."))
+        game.iscloaked = False
 
 def attack(torps_ok):
     # bad guy attacks us
     # torps_ok == False forces use of phasers in an attack
+    if game.iscloaked:
+        return
     # game could be over at this point, check
     if game.alldone:
         return
@@ -1637,7 +1738,9 @@ def torps():
                 prout(_("***Photon tubes damaged by misfire."))
                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
             break
-        if game.shldup or game.condition == "docked":
+        if game.iscloaked:
+            dispersion *= 1.2
+        elif game.shldup or game.condition == "docked":
             dispersion *= 1.0 + 0.0001*game.shield
         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
@@ -1990,7 +2093,7 @@ def phasers():
 
 def capture():
     game.ididit = False # Nothing if we fail
-    Time = 0.0;
+    game.optime = 0.0;
 
     # Make sure there is room in the brig */
     if game.brigfree == 0:
@@ -2015,8 +2118,8 @@ def capture():
     #  Nah, just select the weakest one since it is most likely to
     #  surrender (Tom Almy mod)
     klingons = [e for e in game.enemies if e.type == 'K']
-    weakest = sorted(klingons, key=lambda e: e.power)
-    Time = 0.05                # This action will take some time
+    weakest = sorted(klingons, key=lambda e: e.power)[0]
+    game.optime = 0.05         # This action will take some time
     game.ididit = True #  So any others can strike back
 
     # check out that Klingon
@@ -2024,20 +2127,22 @@ def capture():
     # intelligent design
     # x = 300 + 25*skill;
     x = game.energy / (weakest.power * len(klingons))
-    x *= 2.5;  # would originally have been equivalent of 1.4,
+    #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
+    #      % (game.energy, weakest.power, len(klingons))) 
+    x *= 2.5  # would originally have been equivalent of 1.4,
                # but we want command to work more often, more humanely */
-    #prout(_("Prob = %d (%.4f)\n", i, x))
+    #prout(_("Prob = %.4f" % x))
     #  x = 100; // For testing, of course!
-    if x > randreal(100):
+    if x < randreal(100):
         # guess what, he surrendered!!! */
         prout(_("Klingon captain at %s surrenders.") % weakest.location)
         i = randreal(200)
         if i > 0:
-            prout(_("%d Klingons commit suicide rather than be taken captive.") % 200 - i)
-        if i > brigfree:
-            prout(_("%d Klingons die because there is no room for them in the brig.") % i-brigfree)
-            i = brigfree
-        brigfree -= i
+            prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
+        if i > game.brigfree:
+            prout(_("%d Klingons die because there is no room for them in the brig.") % (i-brigfree))
+            i = game.brigfree
+        game.brigfree -= i
         prout(_("%d captives taken") % i)
         deadkl(weakest.location, weakest.type, game.sector)
         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
@@ -2045,7 +2150,7 @@ def capture():
         return
 
        # big surprise, he refuses to surrender */
-       prout(_("Fat chance, captain!"))
+    prout(_("Fat chance, captain!"))
 
 # Code from events.c begins here.
 
@@ -2211,6 +2316,11 @@ def events():
                     prout("== Event %d fires" % evcode)
                 datemin = game.future[l].date
         xtime = datemin-game.state.date
+        if game.iscloaked:
+            game.energy -= xtime*500.0
+            if game.energy <= 0:
+                finish(FNRG)
+                return
         game.state.date = datemin
         # Decrement Federation resources and recompute remaining time
         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
@@ -2261,7 +2371,7 @@ def events():
             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
                 return
         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
-            if game.state.nscrem == 0 or \
+            if game.state.nscrem == 0 or game.iscloaked or \
                 ictbeam or istract or \
                 game.condition == "docked" or game.isatb == 1 or game.iscate:
                 return
@@ -2282,7 +2392,7 @@ def events():
                 continue
             i = randrange(len(game.state.kcmdr))
             yank = (game.state.kcmdr[i]-game.quadrant).distance()
-            if istract or game.condition == "docked" or yank == 0:
+            if istract or game.condition == "docked" or game.iscloaked or yank == 0:
                 # Drats! Have to reschedule
                 schedule(FTBEAM,
                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
@@ -2844,7 +2954,8 @@ def badpoints():
             300*game.state.nworldkl + \
             45.0*game.nhelp +\
             100.0*game.state.basekl +\
-            3.0*game.abandoned
+            3.0*game.abandoned +\
+            100*game.ncviol
     if game.ship == 'F':
         badpt += 100.0
     elif game.ship is None:
@@ -2852,7 +2963,7 @@ def badpoints():
     return badpt
 
 def finish(ifin):
-    # end the game, with appropriate notfications
+    # end the game, with appropriate notifications
     igotit = False
     game.alldone = True
     skip(3)
@@ -2975,7 +3086,7 @@ def finish(ifin):
         prout(_("The %s has been cremated by its own phasers.") % crmshp())
     elif ifin == FLOST:
         prout(_("You and your landing party have been"))
-        prout(_("converted to energy, disipating through space."))
+        prout(_("converted to energy, dissipating through space."))
     elif ifin == FMINING:
         prout(_("You are left with your landing party on"))
         prout(_("a wild jungle planet inhabited by primitive cannibals."))
@@ -3018,8 +3129,16 @@ def finish(ifin):
     elif ifin == FHOLE:
         prout(_("Your ship is drawn to the center of the black hole."))
         prout(_("You are crushed into extremely dense matter."))
+    elif ifin == FCLOAK:
+        game.ncviol += 1
+        prout(_("You have violated the Treaty of Algeron."))
+        prout(_("The Romulan Empire can never trust you again."))
     elif ifin == FCREW:
         prout(_("Your last crew member has died."))
+    if ifin != FWON and ifin != FCLOAK and game.iscloaked:
+        prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
+        prout(_("You may have missed some warning messages."))
+        skip(1)
     if game.ship == 'F':
         game.ship = None
     elif game.ship == 'E':
@@ -3046,6 +3165,7 @@ def finish(ifin):
         prout(_("to think about pigeons."))
         game.gamewon = True
     score()
+    scanner.chew()     # Clean up leftovers
 
 def score():
     "Compute player's score."
@@ -3120,6 +3240,12 @@ def score():
     if klship:
         prout(_("%6d ship(s) lost or destroyed          %5d") %
               (klship, -100*klship))
+    if game.ncviol > 0:
+        if ncviol == 1:
+            prout(_("1 Treaty of Algeron violation          -100"))
+        else:
+            prout(_("%6d Treaty of Algeron violations       %5d\n") %
+                  (ncviol, -100*ncviol))
     if not game.alive:
         prout(_("Penalty for getting yourself killed        -200"))
     if game.gamewon:
@@ -3287,7 +3413,7 @@ def pause_game():
         sys.stdout.write('\n')
         proutn(prompt)
         if not replayfp:
-            input()
+            my_input()
         sys.stdout.write('\n' * rows)
         linecount = 0
 
@@ -3359,7 +3485,11 @@ def cgetline():
                 elif linein[0] != "#":
                     break
         else:
-            linein = eval(input()) + "\n"
+            try:
+                linein = my_input() + "\n"
+            except EOFError:
+                prout("")
+                sys.exit(0)
     if logfp:
         logfp.write(linein)
     return linein
@@ -3410,7 +3540,7 @@ def clrscr():
     linecount = 0
 
 def textcolor(color=DEFAULT):
-    if game.options & OPTION_COLOR:
+    if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
         if color == DEFAULT:
             curwnd.attrset(0)
         elif color ==  BLACK:
@@ -3447,7 +3577,7 @@ def textcolor(color=DEFAULT):
             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
 
 def highvideo():
-    if game.options & OPTION_COLOR:
+    if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
         curwnd.attron(curses.A_REVERSE)
 
 #
@@ -3574,8 +3704,8 @@ def imove(icourse=None, noattack=False):
 
     def newquadrant(noattack):
         # Leaving quadrant -- allow final enemy attack
-        # Don't do it if being pushed by Nova
-        if len(game.enemies) != 0 and not noattack:
+        # Don't set up attack if being pushed by nova or cloaked
+        if len(game.enemies) != 0 and not noattack and not game.iscloaked:
             newcnd()
             for enemy in game.enemies:
                 finald = (w - enemy.location).distance()
@@ -3685,10 +3815,16 @@ def imove(icourse=None, noattack=False):
         game.inorbit = False
     # If tractor beam is to occur, don't move full distance
     if game.state.date+game.optime >= scheduled(FTBEAM):
-        trbeam = True
-        game.condition = "red"
-        icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
-        game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
+        if game.iscloaked:
+            # We can't be tractor beamed if cloaked,
+            # so move the event into the future
+            postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
+            pass
+        else:
+            trbeam = True
+            game.condition = "red"
+            icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
+            game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
     # Move out
     game.quad[game.sector.i][game.sector.j] = '.'
     for _m in range(icourse.moves):
@@ -3731,6 +3867,9 @@ def dock(verbose):
     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
         prout(crmshp() + _(" not adjacent to base."))
         return
+    if game.iscloaked:
+        prout(_("You cannot dock while cloaked."))
+        return
     game.condition = "docked"
     if verbose:
         prout(_("Docked."))
@@ -3872,15 +4011,16 @@ def getcourse(isprobe):
             iprompt = True
             key = scanner.nexttok()
         itemp = "verbose"
-        if key != "IHREAL":
+        if key == "IHREAL":
+            delta.j = scanner.real
+        else:
             huh()
             raise TrekError
-        delta.j = scanner.real
         key = scanner.nexttok()
-        if key != "IHREAL":
-            huh()
-            raise TrekError
-        delta.i = scanner.real
+        if key == "IHREAL":
+            delta.i = scanner.real
+        else:
+            delta.i = 0
     # Check for zero movement
     if delta.i == 0 and delta.j == 0:
         scanner.chew()
@@ -3987,6 +4127,11 @@ def warp(wcourse, involuntary):
     blooey = False; twarp = False
     if not involuntary: # Not WARPX entry
         game.ididit = False
+        if game.iscloaked:
+            scanner.chew()
+            skip(1)
+            prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
+            return
         if game.damage[DWARPEN] > 10.0:
             scanner.chew()
             skip(1)
@@ -5143,6 +5288,8 @@ def sectscan(goodScan, i, j):
     "Light up an individual dot in a sector."
     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
         if game.quad[i][j] in ('E', 'F'):
+            if game.iscloaked:
+                highvideo()
             textcolor({"green":GREEN,
                        "yellow":YELLOW,
                        "red":RED,
@@ -5171,6 +5318,8 @@ def status(req=0):
             newcnd()
         prstat(_("Condition"), _("%s, %i DAMAGES") % \
                (game.condition.upper(), sum([x > 0 for x in game.damage])))
+        if game.iscloaked:
+            prout(_(", CLOAKED"))
     if not req or req == 3:
         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
     if not req or req == 4:
@@ -5500,6 +5649,7 @@ device = (
     _("Shield Control"), \
     _("Death Ray"), \
     _("D. S. Probe"), \
+    _("Cloaking Device"), \
 )
 
 def setup():
@@ -5721,9 +5871,9 @@ def choose():
         game.tourn = game.length = 0
         game.thawed = False
         game.skill = SKILL_NONE
-        scanner.chew()
-#        if not scanner.inqueue: # Can start with command line options
-        proutn(_("Would you like a regular, tournament, or saved game? "))
+        # Do not chew here, we want to use command-line tokens
+        if not scanner.inqueue: # Can start with command line options
+            proutn(_("Would you like a regular, tournament, or saved game? "))
         scanner.nexttok()
         if scanner.sees("tournament"):
             while scanner.nexttok() == "IHEOL":
@@ -5786,7 +5936,7 @@ def choose():
         scanner.nexttok()
     if scanner.sees("plain"):
         # Approximates the UT FORTRAN version.
-        game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_CAPTURE)
+        game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_CAPTURE | OPTION_CLOAK)
         game.options |= OPTION_PLAIN
     elif scanner.sees("almy"):
         # Approximates Tom Almy's version.
@@ -5808,7 +5958,7 @@ def choose():
         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
     if game.options & OPTION_WORLDS:
         game.inplan += int(NINHAB)
-    game.state.nromrem = game.inrom = randrange(2 *game.skill)
+    game.state.nromrem = game.inrom = randrange(2 * game.skill)
     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
     game.state.remtime = 7.0 * game.length
     game.intime = game.state.remtime
@@ -5853,7 +6003,7 @@ def newqad():
     game.justin = True
     game.iplnet = None
     game.neutz = game.inorbit = game.landed = False
-    game.ientesc = game.iseenit = False
+    game.ientesc = game.iseenit = game.isviolreported = False
     # Create a blank quadrant
     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
     if game.iscate:
@@ -6012,6 +6162,7 @@ commands = [
     ("DESTRUCT",         0),
     ("DEATHRAY",         0),
     ("CAPTURE",          OPTION_CAPTURE),
+    ("CLOAK",            OPTION_CLOAK),
     ("DEBUG",            0),
     ("MAYDAY",           0),
     ("SOS",              0),        # Synonym for MAYDAY
@@ -6090,6 +6241,11 @@ def helpme():
 
 def makemoves():
     "Command-interpretation loop."
+    def checkviol():
+        if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
+            prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
+            game.ncviol += 1
+            game.isviolreported = True
     while True:         # command loop
         drawmaps(1)
         while True:        # get a command
@@ -6137,10 +6293,12 @@ def makemoves():
         elif cmd == "PHASERS":                # phasers
             phasers()
             if game.ididit:
+                checkviol()
                 hitme = True
         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
             torps()
             if game.ididit:
+                checkviol()
                 hitme = True
         elif cmd == "MOVE":                # move under warp
             warp(wcourse=None, involuntary=False)
@@ -6216,6 +6374,8 @@ def makemoves():
                 hitme = True
         elif cmd == "CAPTURE":
             capture()
+        elif cmd == "CLOAK":
+            cloak()
         elif cmd == "DEBUGCMD":                # What do we want for debug???
             debugme()
         elif cmd == "MAYDAY":                # Call for help