Added cloaking.
authorEric S. Raymond <esr@thyrsus.com>
Fri, 24 Feb 2017 21:38:03 +0000 (16:38 -0500)
committerEric S. Raymond <esr@thyrsus.com>
Fri, 24 Feb 2017 21:38:03 +0000 (16:38 -0500)
NEWS
TODO
doc/HACKING
doc/sst-doc.xml
sst.py
test/test8.chk

diff --git a/NEWS b/NEWS
index d7289542ed86804174413ad2885ccb29a4d8968f..c9c70ecbf0906a25e024e43f634b422e83aeee3e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,7 +1,8 @@
                        SST2K project news.
 
 2.2 @
-    Enable GNU readline keys. Added color. Added BSD Trek 'CAPTURE'.
+    Enable GNU readline keys. Added color. Added BSD Trek 'CAPTURE'
+    and 'CLOAK' (from Tom Almy's 2013 changes).
 
 2.1 @ 2012-02-16
     Packaging fixes, pychecker cleanup, cope with Freshmeat name changes.
diff --git a/TODO b/TODO
index dcc8996597bd26b4793838cc7e6a67bbe2ed1d6a..59569612377489833005ed7dafcfdaf6736c7096 100644 (file)
--- a/TODO
+++ b/TODO
@@ -18,10 +18,6 @@ Long-term items:
  
 BSD-Trek features we haven't swiped yet:
 
-* The cloaking device.
-
-* Summoning Klingons to surrender and taking captives.
-
 * Smarter computer, with multiple requests.
 
 * There is a small probility that a nova event will leave a black hole.
index 1739465c1c6e3d60bf1b6f00d12506a26ad64261..cfc811e1e1c0ab15e7a506f9bb08111348aa0a0c 100644 (file)
@@ -210,7 +210,8 @@ and added more:
 the LRSCAN command is no longer needed.  (Controlled by OPTION_AUTOSCAN
 and turned off if game type is "plain" or "almy".)
 
-10. I imported Tom Almy's SCORE command from his 2013 version.
+10. I imported Tom Almy's SCORE, CAPTURE, and CLOAK command from his
+2013 version.
 
 11. I added color.
 
index 03bf78934632521ac4b73a066a480cb7fe5b53e3..a5f73ad9a1599f201aa4190729059a95c814bd3d 100644 (file)
@@ -914,6 +914,29 @@ firing.</para>
 <para>Phasers have no effect on starbases (which are shielded) or on
 stars.</para>
 
+</sect1>
+<sect1><title>Cloak</title>
+
+<literallayout>
+  Mnemonic:  CLOAK
+  Shortest abbreviation:  CLOAK
+  Full commands:  CLOAK ON
+                  CLOAK OFF
+</literallayout>
+
+<para>The cloaking device prevents your ship from being seen by any enemy
+vessels.  When the cloaking device is in use, your subspace radio will
+not receive transmissions, torpedoes will be less accurate, you cannot
+dock, and you cannot use your warp engines. Enemy ships will get a
+chance to attack you when you turn cloaking on.</para>
+
+<para>The Treaty of Algeron with the Romulans in Stardate 2311 prohibits the
+use of cloaking devices. If a Romulan ship observes you cloaking or
+uncloaking after this point in time you will be in violation, which
+will hurt your final score.</para>
+
+<para>The Faerie Queene does not have a cloaking device.</para>
+
 </sect1>
 <sect1><title>Capture</title>
 
@@ -925,11 +948,17 @@ Full commands:  CAPTURE
 
 <para>Ask a Klingon To surrender.</para>
 
-<para>The Subspace Radio is needed to ask a Klingon if he will kindly
-surrender.  The weakest Klingon from the ones in the quadrant is
-chosen. The probability of surrender is a function of that Klingon's
-remaining power, our power, etc.</para>
-
+<para>The capture command provides a more humane way to end a battle than
+just destroying the Klingon battleship with the crew aboard. Assuming
+the subspace radio and transporter are working, and there is room in
+the brig, this command will ask the captain of the weakest Klingon
+ship in the quadrant to surrender.  If the captain agrees, some of the
+crew will transport to your ship and the Klingon ship will be
+destroyed. This command does take time and you will be attacked by any
+other enemy ships if the surrender occurs.</para>
+
+<para>When you dock, any captured Klingons will be transferred to the base
+and you will be credited with the lives you save.</para>
 </sect1>
 <sect1><title>Report</title>
 
diff --git a/sst.py b/sst.py
index 3c985160fba4019a27ce677f09b12dd9040f7eb4..a4ed80e97f5d5b29f23421e7846f9b4fa0816ee2 100755 (executable)
--- a/sst.py
+++ b/sst.py
@@ -43,6 +43,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 +228,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 +243,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 +403,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 +437,7 @@ FDRAY = 18
 FTRIBBLE = 19
 FHOLE = 20
 FCREW = 21
+FCLOAK = 22
 
 def withprob(p):
     return random.random() < p
@@ -906,6 +915,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 damage(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 be 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 +1119,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 +1386,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 +1404,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 +1731,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 +2086,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:
@@ -2016,7 +2112,7 @@ def capture():
     #  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
+    game.optime = 0.05         # This action will take some time
     game.ididit = True #  So any others can strike back
 
     # check out that Klingon
@@ -2211,6 +2307,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 +2362,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.state.iscloaked or \
                 ictbeam or istract or \
                 game.condition == "docked" or game.isatb == 1 or game.iscate:
                 return
@@ -2282,7 +2383,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 +2945,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 +2954,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 +3077,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 +3120,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':
@@ -3120,6 +3230,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:
@@ -3574,8 +3690,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 +3801,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 +3853,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."))
@@ -3987,6 +4112,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)
@@ -5156,6 +5286,8 @@ def sectscan(goodScan, i, j):
                        'R':LIGHTRED,
                        'T':LIGHTRED,
                        }.get(game.quad[i][j], DEFAULT))
+        if game.iscloaked:
+            highvideo()
         proutn("%c " % game.quad[i][j])
         textcolor(DEFAULT)
     else:
@@ -5171,6 +5303,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 +5634,7 @@ device = (
     _("Shield Control"), \
     _("Death Ray"), \
     _("D. S. Probe"), \
+    _("Cloaking Device"), \
 )
 
 def setup():
@@ -5786,7 +5921,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 +5943,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 +5988,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 +6147,7 @@ commands = [
     ("DESTRUCT",         0),
     ("DEATHRAY",         0),
     ("CAPTURE",          OPTION_CAPTURE),
+    ("CLOAK",            OPTION_CLOAK),
     ("DEBUG",            0),
     ("MAYDAY",           0),
     ("SOS",              0),        # Synonym for MAYDAY
@@ -6090,6 +6226,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 +6278,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)
index 42d5abfafcc1676011e9cd10bf1f1c060282ed42..63de67ec1f7530a9ca991e07f2810733319bc852 100644 (file)
@@ -194,9 +194,9 @@ DOCK         DAMAGES      CHART        IMPULSE      REST
 WARP         SENSORS      ORBIT        TRANSPORT    MINE         
 CRYSTALS     SHUTTLE      PLANETS      REPORT       COMPUTER     
 COMMANDS     EMEXIT       PROBE        SAVE         FREEZE       
-ABANDON      DESTRUCT     DEATHRAY     CAPTURE      DEBUG        
-MAYDAY       SOS          CALL         QUIT         HELP         
-SCORE                     
+ABANDON      DESTRUCT     DEATHRAY     CAPTURE      CLOAK        
+DEBUG        MAYDAY       SOS          CALL         QUIT         
+HELP         SCORE                     
 COMMAND> quit
 
 May the Great Bird of the Galaxy roost upon your home planet.